emailler/apps/date65.c

480 lines
11 KiB
C

///////////////////////////////////////////
// https://www.epochconverter.com/timezones
#define NTP_SERVER "pool.ntp.org"
///////////////////////////////////////////
#include <cc65.h>
#include <time.h>
#include <fcntl.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include "email_common.h"
#include "../inc/ip65.h"
/*
* Represents a date and time
*/
struct datetime {
unsigned int year;
unsigned char month;
unsigned char day;
unsigned char hour;
unsigned char minute;
unsigned char ispd25format;
unsigned char nodatetime;
};
unsigned char months[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 30, 31};
char nondst_tz_code[4];
int nondst_tz_secs;
char dst_tz_code[4];
int dst_tz_secs;
uint8_t exec_email_on_exit = 0;
/*
* Add an hour
*/
void add_hour(struct datetime *dt) {
++dt->hour;
if (dt->hour == 24) {
dt->hour = 0;
++dt->day;
if (dt->day > months[dt->month]) {
dt->day = 1;
++dt->month;
if (dt->month == 13) {
dt->month = 1;
++dt->year;
}
}
}
}
/*
* Is year a leap year?
*/
unsigned char isleap(unsigned int year) {
if ((year % 4) == 0)
if ((year % 100) == 0)
if ((year % 400) == 0)
return 1;
else
return 0;
else
return 1;
else
return 0;
}
/*
* Return day of year
*/
unsigned int dayofyear(struct datetime *dt) {
unsigned char m;
unsigned int count = 0;
months[2] = isleap(dt->year) ? 29 : 28;
for (m = 1; m < dt->month; ++m)
count += months[m];
count += dt->day;
return count;
}
/*
* Is DST in effect?
* dt is the date and time, dow is day of week (0 = Mon, 6 = Sun)
* DST starts at 02:00 on the second Sunday in March
* and sends at 02:00 on the first Sunday in November
* NOTE: The datetime passed in is in non-DST time.
*/
unsigned char isDST(struct datetime *dt, unsigned char dow) {
unsigned int today_doy = dayofyear(dt);
unsigned char dow_jan1 = (dow + 7 - (today_doy - 1) % 7) % 7;
unsigned char dow_mar1 = (dow_jan1 + months[1] + months[2]) % 7;
unsigned char date_mar = (6 - dow_mar1) + 8;
unsigned char dow_nov1 =
(dow_jan1 + months[1] + months[2] + months[3] + months[4] +
months[5] + months[6] + months[7] + months[8] + months[9] +
months[10]) % 7;
unsigned char date_nov = (6 - dow_nov1) + 1;
#if 0
printf("today_doy %u\n", today_doy);
printf("dow_jan1 %u\n", dow_jan1);
printf("dow_mar1 %u\n", dow_mar1);
printf("date_mar %u\n", date_mar);
printf("dow_nov1 %u\n", dow_nov1);
printf("date_nov %u\n", date_nov);
#endif
printf(" DST begins on Mar %u, %u\n and ends on Nov %u, %u\n\n",
date_mar, dt->year, date_nov, dt->year);
switch(dt->month) {
case 12:
case 1:
case 2:
return 0; // No DST in the winter
case 3:
if (dt->day < date_mar)
return 0;
if (dt->day > date_mar)
return 1;
if (dt->day == date_mar)
return (dt->hour < 2) ? 0 : 1;
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
return 1; // DST in the summer
case 11:
if (dt->day < date_nov)
return 1;
if (dt->day > date_nov)
return 0;
if (dt->day == date_nov)
return (dt->hour < 3) ? 1 : 0; // 3 because we use non-DST time
}
}
/*
* Parse mtime or ctime fields and populate the fields of the datetime struct
* Supports the legacy ProDOS date/time format as used by ProDOS 1.0->2.4.0
* and also the new format introduced with ProDOS 2.5.
*/
void readdatetime(unsigned char time[4], struct datetime *dt) {
unsigned int d = time[0] + 256U * time[1];
unsigned int t = time[2] + 256U * time[3];
if ((d == 0) && (t == 0)) {
dt->nodatetime = 1;
return;
}
dt->nodatetime = 0;
if (!(t & 0xe000)) {
/* ProDOS 1.0 to 2.4.2 date format */
dt->year = (d & 0xfe00) >> 9;
dt->month = (d & 0x01e0) >> 5;
dt->day = d & 0x001f;
dt->hour = (t & 0x1f00) >> 8;
dt->minute = t & 0x003f;
dt->ispd25format = 0;
if (dt->year < 40) /* See ProDOS-8 Tech Note 48 */
dt->year += 2000;
else
dt->year += 1900;
} else {
/* ProDOS 2.5.0+ */
dt->year = t & 0x0fff;
dt->month = ((t & 0xf000) >> 12) - 1;
dt->day = (d & 0xf800) >> 11;
dt->hour = (d & 0x07c0) >> 6;
dt->minute = d & 0x003f;
dt->ispd25format = 1;
}
}
/*
* Write the date and time stored in struct datetime in ProDOS on disk format,
* storing the bytes in array time[]. Supports both legacy format
* (ProDOS 1.0-2.4.2) and the new date and time format introduced
* with ProDOS 2.5
*/
void writedatetime(struct datetime *dt, unsigned char time[4]) {
unsigned int d, t;
if (dt->nodatetime == 1) {
time[0] = time[1] = time[2] = time[3] = 0;
return;
}
if (dt->ispd25format == 0) {
/* ProDOS 1.0 to 2.4.2 date format */
unsigned int year = dt->year;
if (year > 2039) /* 2039 is last year */
year = 2039;
if (year < 1940) /* 1940 is first year */
year = 1940;
if (year >= 2000)
year -= 2000;
if (year >= 1900)
year -= 1900;
d = (year << 9) | (dt->month << 5) | dt->day;
t = (dt->hour << 8) | dt->minute;
} else {
/* ProDOS 2.5.0+ */
t = ((dt->month + 1) << 12) | dt->year;
d = (dt->day << 11) | (dt->hour << 6) | dt->minute;
}
time[0] = d & 0xff;
time[1] = (d >> 8) & 0xff;
time[2] = t & 0xff;
time[3] = (t >> 8) & 0xff;
}
/*
* Print date/time value for directory listing
*/
void printdatetime(struct datetime *dt) {
if (dt->nodatetime)
fputs("---------- --:--", stderr);
else {
if (dt->ispd25format)
revers(1);
printf("%04d-%02d-%02d %02d:%02d",
dt->year, dt->month, dt->day, dt->hour, dt->minute);
revers(0);
}
}
void error_exit(void)
{
printf("- %s\n", ip65_strerror(ip65_error));
exit(EXIT_FAILURE);
}
void confirm_exit(void)
{
printf("\n[Press Any Key]");
cgetc();
if (exec_email_on_exit) {
exec("EMAIL.SYSTEM", NULL); // Assuming it is in current working dir
}
}
void printsystemdate(void)
{
unsigned char *p;
unsigned char timebytes[4];
struct datetime dt;
p = (unsigned char*)0xbf90;
timebytes[0] = *p;
p = (unsigned char*)0xbf91;
timebytes[1] = *p;
p = (unsigned char*)0xbf92;
timebytes[2] = *p;
p = (unsigned char*)0xbf93;
timebytes[3] = *p;
readdatetime(timebytes, &dt);
printf("ProDOS date & time: ");
printdatetime(&dt);
}
void readtimezonefile(void) {
FILE *fp = fopen("TZONE.TXT", "r");
if (!fp) {
strcpy(nondst_tz_code, "EST");
nondst_tz_secs = -18000;
strcpy(dst_tz_code, "EDT");
dst_tz_secs = -14400;
}
fscanf(fp, "%d,%s", &nondst_tz_secs, nondst_tz_code);
fscanf(fp, "%d,%s", &dst_tz_secs, dst_tz_code);
nondst_tz_code[3] = dst_tz_code[3] = 0;
fclose(fp);
}
int main(int argc, char *argv[])
{
uint8_t eth_init = ETH_INIT_DEFAULT;
uint32_t server;
struct timespec time;
struct datetime dt;
unsigned char timebytes[4];
char datestr[30]; // Should be long enough
unsigned char *p;
unsigned char dow, dst;
if ((argc == 2) && (strcmp(argv[1], "EMAIL") == 0))
exec_email_on_exit = 1;
if (exec_email_on_exit) {
videomode(VIDEOMODE_80COL);
printf("%c%s NTP%c\n\n", 0x0f, PROGNAME, 0x0e);
}
printsystemdate();
if (doesclrscrafterexit())
{
atexit(confirm_exit);
}
printf("\n\nRead timezone from TZONE.TXT -");
readtimezonefile();
printf(" Ok");
#ifdef __APPLE2__
{
int file;
printf("\nSetting slot -");
file = open("ethernet.slot", O_RDONLY);
if (file != -1)
{
read(file, &eth_init, 1);
close(file);
eth_init &= ~'0';
}
printf(" %d\n", eth_init);
}
#endif
printf("Initializing -");
if (ip65_init(eth_init))
{
error_exit();
}
printf(" Ok\nObtaining IP address -");
if (dhcp_init())
{
error_exit();
}
printf(" Ok\nResolving %s -", NTP_SERVER);
server = dns_resolve(NTP_SERVER);
if (!server)
{
error_exit();
}
// Assume non-DST TZ initially
strncpy(_tz.tzname, nondst_tz_code, sizeof(_tz.tzname) - 1);
_tz.timezone = nondst_tz_secs;
printf(" Ok\nGetting date and time -");
time.tv_sec = sntp_get_time(server);
if (!time.tv_sec)
{
error_exit();
}
printf(" Ok\n\n");
// Convert time from seconds since 1900 to
// seconds since 1970 according to RFC 868
time.tv_sec -= 2208988800UL;
strcpy(datestr, ctime(&time.tv_sec));
// Format is:
// 012345678901234567890123456789
// Sun Jun 21 00:44:54 2020
dt.year = atoi(datestr+20);
datestr[16] = 0;
dt.minute = atoi(datestr+14);
datestr[13] = 0;
dt.hour = atoi(datestr+11);
datestr[10] = 0;
dt.day = atoi(datestr+7);
datestr[7] = 0;
if (!strcmp(datestr+4, "Jan"))
dt.month = 1;
else if (!strcmp(datestr+4, "Feb"))
dt.month = 2;
else if (!strcmp(datestr+4, "Mar"))
dt.month = 3;
else if (!strcmp(datestr+4, "Apr"))
dt.month = 4;
else if (!strcmp(datestr+4, "May"))
dt.month = 5;
else if (!strcmp(datestr+4, "Jun"))
dt.month = 6;
else if (!strcmp(datestr+4, "Jul"))
dt.month = 7;
else if (!strcmp(datestr+4, "Aug"))
dt.month = 8;
else if (!strcmp(datestr+4, "Sep"))
dt.month = 9;
else if (!strcmp(datestr+4, "Oct"))
dt.month = 10;
else if (!strcmp(datestr+4, "Nov"))
dt.month = 11;
else if (!strcmp(datestr+4, "Dec"))
dt.month = 12;
else {
printf("\nWhat kind of month is %s?\n", datestr+4);
error_exit();
}
datestr[3] = 0;
if (!strcmp(datestr, "Mon"))
dow = 0;
if (!strcmp(datestr, "Tue"))
dow = 1;
if (!strcmp(datestr, "Wed"))
dow = 2;
if (!strcmp(datestr, "Thu"))
dow = 3;
if (!strcmp(datestr, "Fri"))
dow = 4;
if (!strcmp(datestr, "Sat"))
dow = 5;
if (!strcmp(datestr, "Sun"))
dow = 6;
p = (unsigned char*)0xbfff;
if (*p == 0x25)
dt.ispd25format = 1;
else
dt.ispd25format = 0;
dt.nodatetime = 0;
dst = isDST(&dt, dow);
#if 0
printdatetime(&dt);
#endif
// Now we know if it is DST or not, we can set the timezone
strncpy(_tz.tzname,
dst ? dst_tz_code : nondst_tz_code,
sizeof(_tz.tzname) - 1);
_tz.timezone = dst ? dst_tz_secs : nondst_tz_secs;
strcpy(datestr, ctime(&time.tv_sec));
datestr[24] = 0; // Remove carriage return
printf("%s (%s)", datestr, _tz.tzname);
if (dst)
add_hour(&dt); // Spring forward!
p = (unsigned char*)0xbf98;
if (*p & 0x01) {
puts("\n\nPlease update your real-time clock.");
puts("It will overwrite this date/time!!");
}
writedatetime(&dt, timebytes);
// Write the date/time info into $bf90-$bf93
p = (unsigned char*)0xbf8e;
*p = 0;
p = (unsigned char*)0xbf8f;
*p = 0;
p = (unsigned char*)0xbf90;
*p = timebytes[0];
p = (unsigned char*)0xbf91;
*p = timebytes[1];
p = (unsigned char*)0xbf92;
*p = timebytes[2];
p = (unsigned char*)0xbf93;
*p = timebytes[3];
putchar('\n');
printsystemdate();
putchar('\n');
//time.tv_nsec = 0;
//clock_settime(CLOCK_REALTIME, &time);
return EXIT_SUCCESS;
}