diff --git a/libsrc/common/_is_leap_year.h b/libsrc/common/_is_leap_year.h new file mode 100644 index 000000000..378c462ff --- /dev/null +++ b/libsrc/common/_is_leap_year.h @@ -0,0 +1,22 @@ +/* +** _is_leap_year.h +** +** (C) Copyright 2024, Colin Leroy-Mira +** +*/ + + + +#ifndef __IS_LEAP_YEAR_H +#define __IS_LEAP_YEAR_H + + + +unsigned char __fastcall__ IsLeapYear (unsigned char Year); +/* Returns 1 if the given year is a leap year. Expects a year from 0 to 206, + * without 1900 added */ + + + +/* End of _is_leap_year.h */ +#endif diff --git a/libsrc/common/_is_leap_year.s b/libsrc/common/_is_leap_year.s new file mode 100644 index 000000000..d3136c1c8 --- /dev/null +++ b/libsrc/common/_is_leap_year.s @@ -0,0 +1,23 @@ +; +; Colin Leroy-Mira, 2024 +; +; unsigned char __fastcall__ IsLeapYear (unsigned char Year) +; Returns 1 in A if the given year is a leap year. Expects a year from 0 to 206, +; without 1900 added. +; + + .export _IsLeapYear + +_IsLeapYear: + ldx #$00 ; Prepare X for rts + cmp #$00 ; Y 0 (1900) is not a leap year + beq NotLeap + cmp #$C8 ; Y 200 (2100) is not a leap year + beq NotLeap + and #$03 ; Year % 4 == 0 means leap year + bne NotLeap + lda #$01 ; Return 1 + rts +NotLeap: + lda #$00 ; Return 0 + rts diff --git a/libsrc/common/gmtime.c b/libsrc/common/_time_t_to_tm.c similarity index 94% rename from libsrc/common/gmtime.c rename to libsrc/common/_time_t_to_tm.c index 85e9de3d0..684cff752 100644 --- a/libsrc/common/gmtime.c +++ b/libsrc/common/_time_t_to_tm.c @@ -42,18 +42,9 @@ /*****************************************************************************/ - -struct tm* __fastcall__ gmtime (const time_t* timep) +struct tm* __fastcall__ _time_t_to_tm (const time_t t) { static struct tm timebuf; - time_t t; - - /* Check the argument */ - if (timep == 0 || (long) (t = *timep) < 0) { - /* Invalid arg */ - return 0; - } - /* Since our ints are just 16 bits, split the given time into seconds, ** hours and days. Each of the values will fit in a 16 bit variable. ** The mktime routine will then do the rest. diff --git a/libsrc/common/gmtime.s b/libsrc/common/gmtime.s new file mode 100644 index 000000000..288b285eb --- /dev/null +++ b/libsrc/common/gmtime.s @@ -0,0 +1,20 @@ +; +; Colin Leroy-Mira, 2024 +; +; struct tm* __fastcall__ gmtime (const time_t* timep); +; + + .export _gmtime + .import __time_t_to_tm + .import ldeaxi + +_gmtime: + cpx #$00 ; Check for null pointer + bne :+ + cmp #$00 + beq no_pointer +: jsr ldeaxi ; Load value from pointer + jmp __time_t_to_tm ; Convert it + +no_pointer: + rts ; A/X already set diff --git a/libsrc/common/localtime.c b/libsrc/common/localtime.c deleted file mode 100644 index 48931ea62..000000000 --- a/libsrc/common/localtime.c +++ /dev/null @@ -1,60 +0,0 @@ -/*****************************************************************************/ -/* */ -/* localtime.c */ -/* */ -/* Convert calendar time into broken down local time */ -/* */ -/* */ -/* */ -/* (C) 2002 Ullrich von Bassewitz */ -/* Wacholderweg 14 */ -/* D-70597 Stuttgart */ -/* EMail: uz@musoftware.de */ -/* */ -/* */ -/* This software is provided 'as-is', without any expressed or implied */ -/* warranty. In no event will the authors be held liable for any damages */ -/* arising from the use of this software. */ -/* */ -/* Permission is granted to anyone to use this software for any purpose, */ -/* including commercial applications, and to alter it and redistribute it */ -/* freely, subject to the following restrictions: */ -/* */ -/* 1. The origin of this software must not be misrepresented; you must not */ -/* claim that you wrote the original software. If you use this software */ -/* in a product, an acknowledgment in the product documentation would be */ -/* appreciated but is not required. */ -/* 2. Altered source versions must be plainly marked as such, and must not */ -/* be misrepresented as being the original software. */ -/* 3. This notice may not be removed or altered from any source */ -/* distribution. */ -/* */ -/*****************************************************************************/ - - - -#include - - - -/*****************************************************************************/ -/* Code */ -/*****************************************************************************/ - - - -struct tm* __fastcall__ localtime (const time_t* timep) -{ - time_t t; - - /* Check for a valid time spec */ - if (timep == 0) { - return 0; - } - - /* Get the time and correct for the time zone offset */ - t = *timep + _tz.timezone; - - /* Use gmtime for conversion */ - return gmtime (&t); -} diff --git a/libsrc/common/localtime.s b/libsrc/common/localtime.s new file mode 100644 index 000000000..279442c9d --- /dev/null +++ b/libsrc/common/localtime.s @@ -0,0 +1,29 @@ +; +; Colin Leroy-Mira, 2024 +; +; struct tm* __fastcall__ localtime (const time_t* timep); +; + + .export _localtime + .import __time_t_to_tm, __tz + .import ldeaxi, tosaddeax, pusheax + .importzp sreg + +_localtime: + cpx #$00 ; Check for null pointer + bne :+ + cmp #$00 + beq no_pointer +: jsr ldeaxi ; Load value + jsr pusheax ; Push it + lda __tz+1+3 + sta sreg+1 + lda __tz+1+2 + sta sreg + ldx __tz+1+1 + lda __tz+1 + jsr tosaddeax ; Add _tz.timezone + jmp __time_t_to_tm ; Convert to struct tm + +no_pointer: + rts ; A/X already set diff --git a/libsrc/common/mktime.c b/libsrc/common/mktime.c index 275589dbb..c9ac1652c 100644 --- a/libsrc/common/mktime.c +++ b/libsrc/common/mktime.c @@ -36,7 +36,7 @@ #include #include #include - +#include "_is_leap_year.h" /*****************************************************************************/ @@ -67,14 +67,6 @@ static const unsigned MonthDays [] = { -static unsigned char __fastcall__ IsLeapYear (unsigned Year) -/* Returns 1 if the given year is a leap year */ -{ - return (((Year % 4) == 0) && ((Year % 100) != 0 || (Year % 400) == 0)); -} - - - time_t __fastcall__ mktime (register struct tm* TM) /* Make a time in seconds since 1/1/1970 from the broken down time in TM. ** A call to mktime does also correct the time in TM to contain correct @@ -82,13 +74,13 @@ time_t __fastcall__ mktime (register struct tm* TM) */ { register div_t D; - int Max; - unsigned DayCount; + static int Max; + static unsigned DayCount; /* Check if TM is valid */ if (TM == 0) { /* Invalid data */ - goto Error; + return (time_t) -1L; } /* Adjust seconds. */ @@ -96,27 +88,29 @@ time_t __fastcall__ mktime (register struct tm* TM) TM->tm_sec = D.rem; /* Adjust minutes */ - if (TM->tm_min + D.quot < 0) { - goto Error; - } TM->tm_min += D.quot; D = div (TM->tm_min, 60); TM->tm_min = D.rem; /* Adjust hours */ - if (TM->tm_hour + D.quot < 0) { - goto Error; - } TM->tm_hour += D.quot; D = div (TM->tm_hour, 24); TM->tm_hour = D.rem; /* Adjust days */ - if (TM->tm_mday + D.quot < 0) { - goto Error; - } TM->tm_mday += D.quot; + /* Adjust year */ + while (1) { + Max = 365UL + IsLeapYear (TM->tm_year); + if ((unsigned int)TM->tm_mday > Max) { + ++TM->tm_year; + TM->tm_mday -= Max; + } else { + break; + } + } + /* Adjust month and year. This is an iterative process, since changing ** the month will change the allowed days for this month. */ @@ -125,20 +119,17 @@ time_t __fastcall__ mktime (register struct tm* TM) /* Make sure, month is in the range 0..11 */ D = div (TM->tm_mon, 12); TM->tm_mon = D.rem; - if (TM->tm_year + D.quot < 0) { - goto Error; - } TM->tm_year += D.quot; /* Now check if mday is in the correct range, if not, correct month ** and eventually year and repeat the process. */ - if (TM->tm_mon == FEBRUARY && IsLeapYear (TM->tm_year + 1900)) { + if (TM->tm_mon == FEBRUARY && IsLeapYear (TM->tm_year)) { Max = 29; } else { Max = MonthLength[TM->tm_mon]; } - if (TM->tm_mday > Max) { + if ((unsigned int)TM->tm_mday > Max) { /* Must correct month and eventually, year */ if (TM->tm_mon == DECEMBER) { TM->tm_mon = JANUARY; @@ -157,19 +148,27 @@ time_t __fastcall__ mktime (register struct tm* TM) ** year. */ TM->tm_yday = MonthDays[TM->tm_mon] + TM->tm_mday - 1; - if (TM->tm_mon > FEBRUARY && IsLeapYear (TM->tm_year + 1900)) { + if (TM->tm_mon > FEBRUARY && IsLeapYear (TM->tm_year)) { ++TM->tm_yday; } /* Calculate days since 1/1/1970. In the complete epoch (1/1/1970 to - ** somewhere in 2038) all years dividable by 4 are leap years, so - ** dividing by 4 gives the days that must be added cause of leap years. + ** somewhere in 2106) all years dividable by 4 are leap years(1), + ** so dividing by 4 gives the days that must be added because of leap years. ** (and the last leap year before 1970 was 1968) + ** (1): Exception on 2100, which is not leap, and handled just after. */ DayCount = ((unsigned) (TM->tm_year-70)) * 365U + (((unsigned) (TM->tm_year-(68+1))) / 4) + TM->tm_yday; + /* Handle the 2100 exception */ + if (TM->tm_year == 200 && TM->tm_mon > FEBRUARY) { + DayCount--; + } else if (TM->tm_year > 200) { + DayCount--; + } + /* Calculate the weekday */ TM->tm_wday = (JAN_1_1970 + DayCount) % 7; @@ -182,11 +181,4 @@ time_t __fastcall__ mktime (register struct tm* TM) ((unsigned) TM->tm_min) * 60U + ((unsigned) TM->tm_sec) - _tz.timezone; - -Error: - /* Error exit */ - return (time_t) -1L; } - - - diff --git a/test/val/lib_common_gmtime_localtime.c b/test/val/lib_common_gmtime_localtime.c new file mode 100644 index 000000000..143d15831 --- /dev/null +++ b/test/val/lib_common_gmtime_localtime.c @@ -0,0 +1,84 @@ +#include +#include +#include + +int fails = 0; + +time_t timestamps[] = { + 0, + 0x2FFFFFFF, + 0x6FFFFFFF, + 0xF48656FF, + 0xF4865700, + 0xFC5A3EFF, + 0x6D6739FF, + 0x6D673A00, + 0xFFFFFFFF, +}; + +/* Values checked against glibc 2.37's implementation of ctime() */ +const char *dates_gmt[] = { + "Thu Jan 1 00:00:00 1970\n", + "Sun Jul 9 16:12:47 1995\n", + "Wed Jul 18 05:49:51 2029\n", + "Thu Dec 31 23:59:59 2099\n", + "Fri Jan 1 00:00:00 2100\n", + "Fri Feb 29 23:59:59 2104\n", + "Tue Feb 29 23:59:59 2028\n", + "Wed Mar 1 00:00:00 2028\n", + "Sun Feb 7 06:28:15 2106\n", + NULL +}; + +const char *dates_gmt_plus_one[] = { + "Thu Jan 1 01:00:00 1970\n", + "Sun Jul 9 17:12:47 1995\n", + "Wed Jul 18 06:49:51 2029\n", + "Fri Jan 1 00:59:59 2100\n", + "Fri Jan 1 01:00:00 2100\n", + "Sat Mar 1 00:59:59 2104\n", + "Wed Mar 1 00:59:59 2028\n", + "Wed Mar 1 01:00:00 2028\n", + "Thu Jan 1 00:59:59 1970\n", + NULL +}; + +int main (void) +{ + int i; + + for (i = 0; dates_gmt[i] != NULL; i++) { + struct tm *tm; + char *str; + + /* Check gmtime */ + tm = gmtime(×tamps[i]); + str = asctime(tm); + if (strcmp(str, dates_gmt[i])) { + fails++; + printf("gmtime: Unexpected result for t %lx: Expected \"%s\", got \"%s\"\n", + timestamps[i], dates_gmt[i], str); + } + + /* Check localtime with UTC timezone */ + _tz.timezone = 0; + tm = localtime(×tamps[i]); + str = asctime(tm); + if (strcmp(str, dates_gmt[i])) { + fails++; + printf("localtime: Unexpected result for t %lx: Expected \"%s\", got \"%s\"\n", + timestamps[i], dates_gmt[i], str); + } + + /* Check localtime at UTC+1 */ + _tz.timezone = 3600; + tm = localtime(×tamps[i]); + str = asctime(tm); + if (strcmp(str, dates_gmt_plus_one[i])) { + fails++; + printf("localtime: Unexpected result for t %lx: Expected \"%s\", got \"%s\"\n", + timestamps[i], dates_gmt_plus_one[i], str); + } + } + return fails; +} diff --git a/test/val/lib_common_mktime.c b/test/val/lib_common_mktime.c new file mode 100644 index 000000000..832ce3834 --- /dev/null +++ b/test/val/lib_common_mktime.c @@ -0,0 +1,61 @@ +#include +#include +#include + +int fails = 0; + +time_t timestamps[] = { + 0, + 0x2FFFFFFF, + 0x6FFFFFFF, + 0xF48656FF, + 0xF4865700, + 0xFC5A3EFF, + 0x6D6739FF, + 0x6D673A00, + 0xFFFFFFFF, +}; + +/* Values checked against glibc 2.37's implementation of ctime() */ +const char *dates[] = { + "Thu Jan 1 00:00:00 1970\n", + "Sun Jul 9 16:12:47 1995\n", + "Wed Jul 18 05:49:51 2029\n", + "Thu Dec 31 23:59:59 2099\n", + "Fri Jan 1 00:00:00 2100\n", + "Fri Feb 29 23:59:59 2104\n", + "Tue Feb 29 23:59:59 2028\n", + "Wed Mar 1 00:00:00 2028\n", + "Sun Feb 7 06:28:15 2106\n", + NULL +}; + +int main (void) +{ + struct tm tm; + time_t t; + int i; + + /* Verify conversion both ways */ + for (t = 0x0FFFFFFF; ; t += 0x10000000) { + struct tm *tm = gmtime(&t); + time_t r = mktime(tm); + if (t != r) { + fails++; + printf("Unexpected result for t %lx: %lx\n", t, r); + } + if (t == 0xFFFFFFFF) { + break; + } + } + + for (i = 0; dates[i] != NULL; i++) { + char *str = ctime(×tamps[i]); + if (strcmp(str, dates[i])) { + fails++; + printf("Unexpected result for t %lx: Expected \"%s\", got \"%s\"\n", + timestamps[i], dates[i], str); + } + } + return fails; +}