Rewrite mktime in assembly

-415 bytes,
-39% cycles,
Unit test expanded to cover more cases (there was a bug in 2100 before!)
This commit is contained in:
Colin Leroy-Mira 2024-01-10 22:20:43 +01:00
parent 348a9048b7
commit 03d5e5fba0
8 changed files with 575 additions and 283 deletions

View File

@ -1,22 +0,0 @@
/*
** _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

@ -1,23 +0,0 @@
;
; 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

@ -41,7 +41,7 @@ __time_t_to_tm:
ldx #.sizeof(tm)-1
: sta TM,x
dex
bne :-
bpl :-
; Divide t/86400
jsr udiv32

View File

@ -3,7 +3,7 @@
; 2002-10-22, Greg King
;
; This signed-division function returns both the quotient and the remainder,
; in this structure:
; in this structure: (quotient in sreg, remainder in AX)
;
; typedef struct {
; int rem, quot;

View File

@ -1,187 +0,0 @@
/*****************************************************************************/
/* */
/* mktime.c */
/* */
/* Make calendar time from broken down time and cleanup */
/* */
/* */
/* */
/* (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 <limits.h>
#include <stdlib.h>
#include <time.h>
#include "_is_leap_year.h"
/*****************************************************************************/
/* Data */
/*****************************************************************************/
#define JANUARY 0
#define FEBRUARY 1
#define DECEMBER 11
#define JAN_1_1970 4 /* 1/1/1970 is a thursday */
static const unsigned char MonthLength [] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
static const unsigned MonthDays [] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};
/*****************************************************************************/
/* Code */
/*****************************************************************************/
/* use statics for size optimisation (~34 bytes) */
#pragma static-locals(push, on)
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
** values.
*/
{
register div_t D;
int Max;
unsigned DayCount;
/* Check if TM is valid */
if (TM == 0) {
/* Invalid data */
return (time_t) -1L;
}
/* Adjust seconds. */
D = div (TM->tm_sec, 60);
TM->tm_sec = D.rem;
/* Adjust minutes */
TM->tm_min += D.quot;
D = div (TM->tm_min, 60);
TM->tm_min = D.rem;
/* Adjust hours */
TM->tm_hour += D.quot;
D = div (TM->tm_hour, 24);
TM->tm_hour = D.rem;
/* Adjust days */
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.
*/
while (1) {
/* Make sure, month is in the range 0..11 */
D = div (TM->tm_mon, 12);
TM->tm_mon = D.rem;
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)) {
Max = 29;
} else {
Max = MonthLength[TM->tm_mon];
}
if ((unsigned int)TM->tm_mday > Max) {
/* Must correct month and eventually, year */
if (TM->tm_mon == DECEMBER) {
TM->tm_mon = JANUARY;
++TM->tm_year;
} else {
++TM->tm_mon;
}
TM->tm_mday -= Max;
} else {
/* Done */
break;
}
}
/* Ok, all time/date fields are now correct. Calculate the days in this
** year.
*/
TM->tm_yday = MonthDays[TM->tm_mon] + TM->tm_mday - 1;
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 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;
/* No (US) daylight saving (for now) */
TM->tm_isdst = 0;
/* Return seconds since 1970 */
return DayCount * 86400UL +
((unsigned) TM->tm_hour) * 3600UL +
((unsigned) TM->tm_min) * 60U +
((unsigned) TM->tm_sec) -
_tz.timezone;
}
#pragma static-locals(pop)

476
libsrc/common/mktime.s Normal file
View File

@ -0,0 +1,476 @@
;
; Colin Leroy-Mira, 2024
;
; time_t __fastcall__ mktime (register struct tm* TM)
;
; Converts a struct tm to a time_t timestamp, making sure
; day, month, year, hour, minute and seconds are in the
; correct range.
;
.export _mktime
.import __tz
.import pushax, pusha0, pusheax
.import shrax2, _div, tosumulax, tosumodax, tossubeax, tosaddeax, tosumuleax
.importzp ptr2, tmp3, sreg
.include "time.inc"
; ------------------------------------------------------------------------
; Special values
FEBRUARY = 1
MARCH = 2
JAN_1_1970 = 4
N_SEC = 60
N_MIN = 60
N_HOUR = 24
N_MON = 12
N_DAY_YEAR = 365
; ------------------------------------------------------------------------
; Helpers
; Helper to shift overflows from one field to the next
; Current field in Y, divisor in A
; Keeps remainder in current field, and adds the quotient
; to the next one
adjust_field:
pha ; Push divisor
iny ; Point to high byte of current field
lda (ptr2),y
tax
dey
sty tmp3 ; Store current field (_div will mess with
lda (ptr2),y ; tmp1 and tmp2)
jsr pushax
pla ; Load divisor
ldx #$00
jsr _div
ldy tmp3 ; Store remainder in current field
sta (ptr2),y
iny
txa
sta (ptr2),y
lda sreg ; Add quotient to next field
iny
clc
adc (ptr2),y
sta (ptr2),y
iny
lda sreg+1
adc (ptr2),y
sta (ptr2),y
rts
; Returns 1 in A if the given year is a leap year. Expects a year
; from 0 to 206, without 1900 added.
is_leap_year:
cmp #$00 ; Y 0 (1900) is not a leap year
beq not_leap
cmp #$C8 ; Y 200 (2100) is not a leap year
beq not_leap
and #$03 ; Year % 4 == 0 means leap year
bne not_leap
lda #$01 ; Return 1
rts
not_leap:
lda #$00 ; Return 0
rts
; Returns the number of days in the current month/year in A
get_days_in_month:
ldy #tm::tm_mon
lda (ptr2),y
tax
lda months_len,x
cpx #FEBRUARY
beq :+
rts
: tax
ldy #tm::tm_year ; Adjust for leap years
lda (ptr2),y
jsr is_leap_year
beq :+
inx
: txa
rts
; Add AX to counter
addaxcounter:
clc
adc Counter
sta Counter ; Store in Counter
txa
adc Counter+1
sta Counter+1
rts
; Helpers for long chain of arithmetic on day counter.
; Reload Counter and push it on the stack
load_and_push_counter:
lda Counter+3
sta sreg+1
lda Counter+2
sta sreg
lda Counter
ldx Counter+1
jsr pusheax
rts
; Store result in AX:sreg to Counter
store_counter:
sta Counter
stx Counter+1
lda sreg
sta Counter+2
lda sreg+1
sta Counter+3
rts
; ------------------------------------------------------------------------
; Code
_mktime:
sta ptr2 ; Store struct to ptr2, which arithmetic
stx ptr2+1 ; functions won't touch
; Check pointer validity
ora ptr2+1
bne :+
lda #$FF
tax
sta sreg
sta sreg+1
rts
; Adjust seconds
: ldy #tm::tm_sec
lda #N_SEC
jsr adjust_field
; Adjust minutes
ldy #tm::tm_min
lda #N_MIN
jsr adjust_field
; Adjust hours
ldy #tm::tm_hour
lda #N_HOUR
jsr adjust_field
;Shift one year as long as tm_mday is more than a year
ldy #tm::tm_year
lda (ptr2),y
dec_by_year:
jsr is_leap_year ; Compute max numbers of days in year
clc
adc #<N_DAY_YEAR ; No care about carry,
sta Max ; 365+1 doesn't overflow low byte
ldy #tm::tm_mday+1 ; Do we have more days in store?
lda (ptr2),y
cmp #>N_DAY_YEAR
beq :+ ; High byte equal, check low byte
bcs do_year_dec ; High byte greater, decrement
bcc dec_by_month ; Low byte lower, we're done
: dey
lda (ptr2),y
cmp Max
bcc dec_by_month
beq dec_by_month
do_year_dec:
; Decrement days
ldy #tm::tm_mday
lda (ptr2),y
sbc Max ; Carry already set
sta (ptr2),y
iny
lda (ptr2),y
sbc #>N_DAY_YEAR
sta (ptr2),y
; Increment year
ldy #tm::tm_year
lda (ptr2),y
clc
adc #1
sta (ptr2),y ; No carry possible here either
bcc dec_by_year ; bra, go check next year
dec_by_month:
; We're done decrementing days by full years, now do it
; month per month.
ldy #tm::tm_mon
lda #N_MON
jsr adjust_field
; Get max day for this month
jsr get_days_in_month
sta Max
; So, do we have more days than this month?
ldy #tm::tm_mday+1
lda (ptr2),y
bne do_month_dec ; High byte not zero, sure we do
dey
lda (ptr2),y
cmp Max
bcc calc_tm_yday ; No
beq calc_tm_yday
do_month_dec:
; Decrement days
ldy #tm::tm_mday
lda (ptr2),y
sec
sbc Max
sta (ptr2),y
iny
lda (ptr2),y
sbc #$00
sta (ptr2),y
; Increment month
ldy #tm::tm_mon
lda (ptr2),y
clc
adc #1
sta (ptr2),y
bne dec_by_month ; Check next month
calc_tm_yday:
; We finished decrementing tm_mday and have put it in the correct
; year/month range. Now compute the day of the year.
ldy #tm::tm_mday ; Get current day of month
lda (ptr2),y
sta Counter ; Store it in Counter
lda #$00 ; Init counter high bytes
sta Counter+1
sta Counter+2
sta Counter+3
ldy #tm::tm_mon ; Get current month
lda (ptr2),y
asl
tax
clc
lda yday_by_month,x ; Get yday for this month's start
adc Counter ; Add it to counter
sta Counter
inx
lda yday_by_month,x
adc Counter+1
sta Counter+1
ldy #tm::tm_year ; Adjust for leap years (if after feb)
lda (ptr2),y
jsr is_leap_year
beq dec_counter
ldy #tm::tm_mon ; Leap year, get current month
lda (ptr2),y
cmp #MARCH
bcs store_yday
dec_counter:
lda Counter ; Decrease counter by one (yday starts at 0),
bne :+ ; unless we're after february in a leap year
dec Counter+1
: dec Counter
store_yday:
ldy #tm::tm_yday ; Store tm_yday
lda Counter
sta (ptr2),y
iny
lda Counter+1
sta (ptr2),y
; Now calculate total day count since epoch with the formula:
; ((unsigned) (TM->tm_year-70)) * 365U + (number of days per year since 1970)
; (((unsigned) (TM->tm_year-(68+1))) / 4) + (one extra day per leap year since 1970)
; TM->tm_yday (number of days in this year)
ldy #tm::tm_year ; Get full years
lda (ptr2),y
sec
sbc #70
ldx #0
jsr pushax
lda #<N_DAY_YEAR
ldx #>N_DAY_YEAR
jsr tosumulax
jsr addaxcounter
; Add one day per leap year
ldy #tm::tm_year ; Get full years
lda (ptr2),y
sec
sbc #69
ldx #0
jsr shrax2 ; Divide by 4
jsr addaxcounter
; Handle the 2100 exception (which was considered leap by "Add one day
; per leap year" just before)
ldy #tm::tm_year ; Get full years
lda (ptr2),y
cmp #201
bcc finish_calc ; <= 200, nothing to do
lda Counter
bne :+
dec Counter+1
: dec Counter
finish_calc:
; Now we can compute the weekday.
lda Counter
clc
adc #JAN_1_1970
pha
lda Counter+1
adc #0
tax
pla
jsr pushax
lda #7 ; Modulo 7
ldx #0
jsr tosumodax
ldy #tm::tm_wday ; Store tm_wday
sta (ptr2),y
iny
txa
sta (ptr2),y
; DST
lda #$00 ; Store tm_isdst
ldy #tm::tm_isdst
sta (ptr2),y
iny
sta (ptr2),y
; Our struct tm is all fixed and every field calculated.
; We can finally count seconds according to this formula:
; seconds = (full days since epoch) * 86400UL +
; ((unsigned) TM->tm_hour) * 3600UL +
; ((unsigned) TM->tm_min) * 60U +
; ((unsigned) TM->tm_sec) -
; _tz.timezone;
; We already have the number of days since epoch in our counter,
; from just before when we computed tm_wday. Reuse it.
jsr load_and_push_counter
lda #$00 ; Multiply by 86400
sta sreg+1
lda #$01
sta sreg
lda #$80
ldx #$51
jsr tosumuleax
jsr store_counter ; Store into counter
; Push counter to add 3600 * hours to it
jsr load_and_push_counter
ldx #$00 ; Load hours
stx sreg
stx sreg+1
ldy #tm::tm_hour
lda (ptr2),y
jsr pusheax ; Push
ldx #$00 ; Load 3600
stx sreg
stx sreg+1
lda #<3600
ldx #>3600
jsr tosumuleax ; Multiply (pops the pushed hours)
jsr tosaddeax ; Add to counter (pops the pushed counter)
jsr store_counter ; Store counter
; Push counter to add 60 * min to it
jsr load_and_push_counter
ldy #tm::tm_min ; Load minutes
lda (ptr2),y
jsr pusha0 ; Push
lda #N_MIN
ldx #0
stx sreg
stx sreg+1
jsr tosumulax ; Multiply
jsr tosaddeax ; Add to pushed counter
jsr store_counter ; Store
; Add seconds
jsr load_and_push_counter
ldy #tm::tm_sec ; Load seconds
lda (ptr2),y
ldx #0
stx sreg
stx sreg+1
jsr tosaddeax ; Simple addition there
; No need to store/load/push the counter here, simply to push it
; for the last substraction
jsr pusheax
; Substract timezone
lda __tz+1+3
sta sreg+1
lda __tz+1+2
sta sreg
ldx __tz+1+1
lda __tz+1
jsr tossubeax
; And we're done!
rts
.data
months_len:
.byte 31
.byte 28
.byte 31
.byte 30
.byte 31
.byte 30
.byte 31
.byte 31
.byte 30
.byte 31
.byte 30
.byte 31
yday_by_month:
.word 0
.word 31
.word 59
.word 90
.word 120
.word 151
.word 181
.word 212
.word 243
.word 273
.word 304
.word 334
.bss
Max: .res 1 ; We won't need a high byte
Counter:
.res 4

View File

@ -6,6 +6,9 @@ int fails = 0;
time_t timestamps[] = {
0,
0x41eb00,
0x1e7cb00,
0x21c8700,
0x2FFFFFFF,
0x6FFFFFFF,
0xF48656FF,
@ -19,6 +22,9 @@ time_t timestamps[] = {
/* Values checked against glibc 2.37's implementation of ctime() */
const char *dates_gmt[] = {
"Thu Jan 1 00:00:00 1970\n",
"Fri Feb 20 00:00:00 1970\n",
"Wed Jan 6 00:00:00 1971\n",
"Mon Feb 15 00:00:00 1971\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",
@ -32,6 +38,9 @@ const char *dates_gmt[] = {
const char *dates_gmt_plus_one[] = {
"Thu Jan 1 01:00:00 1970\n",
"Fri Feb 20 01:00:00 1970\n",
"Wed Jan 6 01:00:00 1971\n",
"Mon Feb 15 01:00:00 1971\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",
@ -70,15 +79,15 @@ int main (void)
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);
}
// /* 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

@ -4,58 +4,97 @@
int fails = 0;
time_t timestamps[] = {
0,
0x2FFFFFFF,
0x6FFFFFFF,
0xF48656FF,
0xF4865700,
0xFC5A3EFF,
0x6D6739FF,
0x6D673A00,
0xFFFFFFFF,
typedef struct _test_data {
time_t t;
struct tm tm;
char *str;
} test_data;
/* Test data generated using glibc 2.37 */
test_data data[] = {
/* First year */
{0x00000000, {0, 0, 0, 1, 0, 70, 0, 4}, "Thu Jan 1 00:00:00 1970\n"},
{0x004e7970, {56, 34, 12, 1, 2, 70, 59, 0}, "Sun Mar 1 12:34:56 1970\n"},
{0x01e1337f, {59, 59, 23, 31, 11, 70, 364, 4}, "Thu Dec 31 23:59:59 1970\n"},
/* First leap year */
{0x03c26700, {0, 0, 0, 1, 0, 72, 0, 6}, "Sat Jan 1 00:00:00 1972\n"},
{0x03c8fe7f, {59, 59, 23, 5, 0, 72, 4, 3}, "Wed Jan 5 23:59:59 1972\n"},
{0x041180ff, {59, 59, 23, 29, 1, 72, 59, 2}, "Tue Feb 29 23:59:59 1972\n"},
{0x04118100, {0, 0, 0, 1, 2, 72, 60, 3}, "Wed Mar 1 00:00:00 1972\n"},
{0x05a4ebff, {59, 59, 23, 31, 11, 72, 365, 0}, "Sun Dec 31 23:59:59 1972\n"},
/* A non-leap year */
{0x63b0cd00, {0, 0, 0, 1, 0, 123, 0, 0}, "Sun Jan 1 00:00:00 2023\n"},
{0x63fe957f, {59, 59, 23, 28, 1, 123, 58, 2}, "Tue Feb 28 23:59:59 2023\n"},
{0x63fe9580, {0, 0, 0, 1, 2, 123, 59, 3}, "Wed Mar 1 00:00:00 2023\n"},
{0x656d4ec0, {0, 0, 4, 4, 11, 123, 337, 1}, "Mon Dec 4 04:00:00 2023\n"},
{0x6592007f, {59, 59, 23, 31, 11, 123, 364, 0}, "Sun Dec 31 23:59:59 2023\n"},
/* Another leap year */
{0x65920080, {0, 0, 0, 1, 0, 124, 0, 1}, "Mon Jan 1 00:00:00 2024\n"},
{0x65e11a7f, {59, 59, 23, 29, 1, 124, 59, 4}, "Thu Feb 29 23:59:59 2024\n"},
{0x65e11a80, {0, 0, 0, 1, 2, 124, 60, 5}, "Fri Mar 1 00:00:00 2024\n"},
{0x6774857f, {59, 59, 23, 31, 11, 124, 365, 2}, "Tue Dec 31 23:59:59 2024\n"},
/* End of century */
{0xf48656ff, {59, 59, 23, 31, 11, 199, 364, 4}, "Thu Dec 31 23:59:59 2099\n"},
/* A non-leap year for exceptional reasons */
{0xf4865700, {0, 0, 0, 1, 0, 200, 0, 5}, "Fri Jan 1 00:00:00 2100\n"},
{0xf4d41f7f, {59, 59, 23, 28, 1, 200, 58, 0}, "Sun Feb 28 23:59:59 2100\n"},
{0xf4d41f80, {0, 0, 0, 1, 2, 200, 59, 1}, "Mon Mar 1 00:00:00 2100\n"},
{0xf4fceff0, {0, 0, 23, 31, 2, 200, 89, 3}, "Wed Mar 31 23:00:00 2100\n"},
{0xf6678a7f, {59, 59, 23, 31, 11, 200, 364, 5}, "Fri Dec 31 23:59:59 2100\n"},
/* First post-2100 leap year */
{0xfc0b2500, {0, 0, 0, 1, 0, 204, 0, 2}, "Tue Jan 1 00:00:00 2104\n"},
{0xfc5a3eff, {59, 59, 23, 29, 1, 204, 59, 5}, "Fri Feb 29 23:59:59 2104\n"},
{0xfc5a3f00, {0, 0, 0, 1, 2, 204, 60, 6}, "Sat Mar 1 00:00:00 2104\n"},
{0xfcaa9c70, {0, 0, 23, 30, 3, 204, 120, 3}, "Wed Apr 30 23:00:00 2104\n"},
/* End of epoch */
{0xfdedaa00, {0, 0, 0, 1, 0, 205, 0, 4}, "Thu Jan 1 00:00:00 2105\n"},
{0xffffffff, {15, 28, 6, 7, 1, 206, 37, 0}, "Sun Feb 7 06:28:15 2106\n"}
};
/* 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
};
static int compare_tm(time_t t, struct tm *tm, struct tm *ref) {
if (memcmp(tm, ref, sizeof(tm))) {
printf("0x%lx: unexpected tm from gmtime: "
"expected {%u, %u, %u, %u, %u, %u, %u, %u}, "
"got {%u, %u, %u, %u, %u, %u, %u, %u}\n",
t,
ref->tm_sec, ref->tm_min, ref->tm_hour, ref->tm_mday, ref->tm_mon, ref->tm_year, ref->tm_yday, ref->tm_wday,
tm->tm_sec, tm->tm_min, tm->tm_hour, tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_yday, tm->tm_wday);
return 1;
}
return 0;
}
int main (void)
{
struct tm tm;
time_t t;
int i;
/* Verify conversion both ways */
for (t = 0x0FFFFFFF; ; t += 0x10000000) {
for (i = 0; ; i++) {
time_t t = data[i].t;
time_t r;
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;
}
}
r = mktime(tm);
for (i = 0; dates[i] != NULL; i++) {
char *str = ctime(&timestamps[i]);
if (strcmp(str, dates[i])) {
if (t != r) {
printf("unexpected timestamp from mktime: expected 0x%lx, got 0x%lx\n", t, r);
fails++;
printf("Unexpected result for t %lx: Expected \"%s\", got \"%s\"\n",
timestamps[i], dates[i], str);
}
if (compare_tm(t, tm, &data[i].tm)) {
fails++;
}
if (strcmp(data[i].str, ctime(&t))) {
printf("0x%lx: unexpected ctime result: expected %s, got %s", t, data[i].str, ctime(&t));
}
if (t == 0xFFFFFFFF)
break;
}
return fails;
}