Rework time functions a bit

- mktime: Work unsigned as time_t's type implies (shifting Y2K38 bug to 2106)
- mktime: Add unit tests
- gmtime/localtime: factorize
- gmtime/localtime: Add unit tests
- mktime/gmtime/localtime: Size optimisation (-130 bytes wrt master)
- mktime: Speed optimisation (from 23M cycles on the unit test to 2M)
This commit is contained in:
Colin Leroy-Mira 2024-01-07 22:58:45 +01:00
parent 29801a2fde
commit df193c0947
9 changed files with 268 additions and 106 deletions

View File

@ -0,0 +1,22 @@
/*
** _is_leap_year.h
**
** (C) Copyright 2024, Colin Leroy-Mira <colin@colino.net>
**
*/
#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

View File

@ -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

View File

@ -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.

20
libsrc/common/gmtime.s Normal file
View File

@ -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

View File

@ -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 <time.h>
/*****************************************************************************/
/* 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);
}

29
libsrc/common/localtime.s Normal file
View File

@ -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

View File

@ -36,7 +36,7 @@
#include <limits.h>
#include <stdlib.h>
#include <time.h>
#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;
}

View File

@ -0,0 +1,84 @@
#include <stdio.h>
#include <string.h>
#include <time.h>
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(&timestamps[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(&timestamps[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(&timestamps[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;
}

View File

@ -0,0 +1,61 @@
#include <stdio.h>
#include <string.h>
#include <time.h>
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(&timestamps[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;
}