emailler/apps/date65.c

480 lines
11 KiB
C
Raw Permalink Normal View History

///////////////////////////////////////////
// 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)
{
2020-04-15 12:52:59 +00:00
printf("- %s\n", ip65_strerror(ip65_error));
2018-07-27 15:07:31 +00:00
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;
}
2020-06-21 06:54:41 +00:00
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[])
{
Removed Ethernet driver I/O base. So far the base address of the Ethernet chip was a general property of all Ethernet drivers. It served two purposes: 1. Allowing to use a single Ethernet driver for a certain Ethernet chip, no matter what machine was connected to the chip. 2. Allowing use an Ethernet card in all Apple II slots. However, we now use customized Ethernet drivers for the individual machines so 1.) isn't relevant anymore. In fact one wants to omit the overhead of a runtime-adjustable base address where it isn't needed. So only the Apple II slots are left. But this should rather be a driver-internal approach then. We should just hand the driver the slot number the user wants to use and have the driver do its thing. Independently from the aspect if the driver parameter is a base address or a slot number the parameter handling was changed too. For asm programs there was so far a specific init function to be called prior to the main init function if it was desired to chnage the parameter default. This was done to keep the main init function backward compatible. But now that the parameter (now the slot number) is only used on the Apple II anyhow it seems reasonable to drop the specific init function again and just provide the parameter to the main init function. All C64-only user code can stay as-is. Only Apple II user code needs to by adjusted. Please note that this change only affects asm programs, C programs always used a single init function with the Apple II slot number as parameter.
2019-05-02 12:44:24 +00:00
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)
{
Removed Ethernet driver I/O base. So far the base address of the Ethernet chip was a general property of all Ethernet drivers. It served two purposes: 1. Allowing to use a single Ethernet driver for a certain Ethernet chip, no matter what machine was connected to the chip. 2. Allowing use an Ethernet card in all Apple II slots. However, we now use customized Ethernet drivers for the individual machines so 1.) isn't relevant anymore. In fact one wants to omit the overhead of a runtime-adjustable base address where it isn't needed. So only the Apple II slots are left. But this should rather be a driver-internal approach then. We should just hand the driver the slot number the user wants to use and have the driver do its thing. Independently from the aspect if the driver parameter is a base address or a slot number the parameter handling was changed too. For asm programs there was so far a specific init function to be called prior to the main init function if it was desired to chnage the parameter default. This was done to keep the main init function backward compatible. But now that the parameter (now the slot number) is only used on the Apple II anyhow it seems reasonable to drop the specific init function again and just provide the parameter to the main init function. All C64-only user code can stay as-is. Only Apple II user code needs to by adjusted. Please note that this change only affects asm programs, C programs always used a single init function with the Apple II slot number as parameter.
2019-05-02 12:44:24 +00:00
read(file, &eth_init, 1);
close(file);
Removed Ethernet driver I/O base. So far the base address of the Ethernet chip was a general property of all Ethernet drivers. It served two purposes: 1. Allowing to use a single Ethernet driver for a certain Ethernet chip, no matter what machine was connected to the chip. 2. Allowing use an Ethernet card in all Apple II slots. However, we now use customized Ethernet drivers for the individual machines so 1.) isn't relevant anymore. In fact one wants to omit the overhead of a runtime-adjustable base address where it isn't needed. So only the Apple II slots are left. But this should rather be a driver-internal approach then. We should just hand the driver the slot number the user wants to use and have the driver do its thing. Independently from the aspect if the driver parameter is a base address or a slot number the parameter handling was changed too. For asm programs there was so far a specific init function to be called prior to the main init function if it was desired to chnage the parameter default. This was done to keep the main init function backward compatible. But now that the parameter (now the slot number) is only used on the Apple II anyhow it seems reasonable to drop the specific init function again and just provide the parameter to the main init function. All C64-only user code can stay as-is. Only Apple II user code needs to by adjusted. Please note that this change only affects asm programs, C programs always used a single init function with the Apple II slot number as parameter.
2019-05-02 12:44:24 +00:00
eth_init &= ~'0';
}
printf(" %d\n", eth_init);
}
#endif
printf("Initializing -");
Removed Ethernet driver I/O base. So far the base address of the Ethernet chip was a general property of all Ethernet drivers. It served two purposes: 1. Allowing to use a single Ethernet driver for a certain Ethernet chip, no matter what machine was connected to the chip. 2. Allowing use an Ethernet card in all Apple II slots. However, we now use customized Ethernet drivers for the individual machines so 1.) isn't relevant anymore. In fact one wants to omit the overhead of a runtime-adjustable base address where it isn't needed. So only the Apple II slots are left. But this should rather be a driver-internal approach then. We should just hand the driver the slot number the user wants to use and have the driver do its thing. Independently from the aspect if the driver parameter is a base address or a slot number the parameter handling was changed too. For asm programs there was so far a specific init function to be called prior to the main init function if it was desired to chnage the parameter default. This was done to keep the main init function backward compatible. But now that the parameter (now the slot number) is only used on the Apple II anyhow it seems reasonable to drop the specific init function again and just provide the parameter to the main init function. All C64-only user code can stay as-is. Only Apple II user code needs to by adjusted. Please note that this change only affects asm programs, C programs always used a single init function with the Apple II slot number as parameter.
2019-05-02 12:44:24 +00:00
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;
2020-06-21 06:27:11 +00:00
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
2020-09-05 05:11:15 +00:00
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);
2020-04-15 19:25:23 +00:00
return EXIT_SUCCESS;
}