diff --git a/kernel/include/sortix/clock.h b/kernel/include/sortix/clock.h index c2f4b181..ee64cf26 100644 --- a/kernel/include/sortix/clock.h +++ b/kernel/include/sortix/clock.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Jonas 'Sortie' Termansen. + * Copyright (c) 2013, 2023 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -37,6 +37,8 @@ extern "C" { #define CLOCK_THREAD_CPUTIME_ID 8 #define CLOCK_THREAD_SYSTIME_ID 9 +#define CLOCK_REALTIME_HAS_LEAP_SECONDS 1 + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/libc/Makefile b/libc/Makefile index 76b75862..3f22ba07 100644 --- a/libc/Makefile +++ b/libc/Makefile @@ -780,6 +780,10 @@ scram/scram.2 \ sys/dnsconfig/getdnsconfig.2 \ sys/dnsconfig/setdnsconfig.2 \ +MANPAGES3=\ +time/add_leap_seconds.3 \ +time/sub_leap_seconds.3 \ + HEADERS:=$(shell find include -type f) LIBK_OBJS:=$(FREEOBJS:.o=.libk.o) @@ -906,3 +910,5 @@ install-libs-kernel: $(INSTALLLIBSKERNEL) install-man: mkdir -p $(DESTDIR)$(MANDIR)/man2 cp $(MANPAGES2) $(DESTDIR)$(MANDIR)/man2 + mkdir -p $(DESTDIR)$(MANDIR)/man3 + cp $(MANPAGES3) $(DESTDIR)$(MANDIR)/man3 diff --git a/libc/include/time.h b/libc/include/time.h index e1815caf..053fede6 100644 --- a/libc/include/time.h +++ b/libc/include/time.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2012, 2013, 2014 Jonas 'Sortie' Termansen. + * Copyright (c) 2011, 2012, 2013, 2014, 2023 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -163,7 +163,9 @@ time_t timegm(struct tm*); #if __USE_SORTIX int clock_gettimeres(clockid_t, struct timespec*, struct timespec*); int clock_settimeres(clockid_t, const struct timespec*, const struct timespec*); -int timens(struct tmns* tmns); +int timens(struct tmns*); +int sub_leap_seconds(time_t*); +int add_leap_seconds(time_t*); #endif extern char* tzname[2]; diff --git a/libc/time/add_leap_seconds.3 b/libc/time/add_leap_seconds.3 new file mode 100644 index 00000000..9a05a69c --- /dev/null +++ b/libc/time/add_leap_seconds.3 @@ -0,0 +1,104 @@ +.Dd March 26, 2023 +.Dt ADD_LEAP_SECONDS 2 +.Os +.Sh NAME +.Nm add_leap_seconds , +.Nm sub_leap_seconds +.Nd convert between utc and tai-10 timestamps +.Sh SYNOPSIS +.In time.h +.Ft int +.Fn add_leap_seconds "time_t *timestamp" +.Ft int +.Fn sub_leap_seconds "time_t *timestamp" +.Sh DESCRIPTION +.Fn add_leap_seconds +adds leap seconds to the UTC +.Fa timestamp +to give the corresponding TAI-10 timestamp. +.Pp +.Fn sub_leap_seconds +subtracts leap seconds from the TAI-10 +.Fa timestamp +to give the corresponding UTC timestamp. +.Pp +Leap seconds are announced usually six months in advanced by the +International Earth Rotation and Reference Systems Service (IERS) in Bulletin C. +Leap seconds can be added or removed at the end of the June or December (UTC) +where the last minute can be 61 or 59 seconds. +Leap seconds correct the difference between solar time and civil time when +planetary rotation varies. +Leap seconds have never been removed as of mid 2023. +.Pp +.Dv CLOCK_REALTIME +on this system is in the TAI-10 format which measures the number of actual +seconds including leap seconds that has happened since the Unix epoch of +1970-01-01 00:00:00 UTC. +The inclusion of leap seconds is advertised by the +.Dv CLOCK_REALTIME_HAS_LEAP_SECONDS +definition. +This violates POSIX's requirement that +.Dv CLOCK_REALTIME +is in UTC, which pretends leap seconds don't happen. +The +.Fn add_leap_seconds +and +.Fn sub_leap_seconds +functions are useful when exchanging timestamps with other operating systems. +.Pp +TAI-10 is International Atomic Time (TAI) subtracted by 10 seconds so the epoch +is the same moment in TAI-10 and UTC. +.Pp +TAI-10 has the advantage that every actual past moment in time has an unique and +unambiguous and continuous representation. +The time difference between two TAI-10 timestamps can be calculated as a simple +subtraction. +Durations can be simply added to TAI-10 timestamps to produce the the timestamp +changed by that many seconds. +However it's not possible to compute which TAI10 timestamp corresponds to a +calendar date and time more than 6 months in the future, as the leap seconds +have not been announced yet. +The system leap second table needs to be up to date in order to perform properly +between TAI-10 and calendar time. +TAI-10 is the most useful format when dealing with relative times, as it's +guaranteed a certain amount of time has elapsed. +.Pp +UTC has the disadvantage that two different seconds can have the same timestamp +when leap seconds are removed, and UTC can be discontinuous if a leap second +removed where a timestamp could correspond to no actual moment in time. +The actual time between two UTC timestamps cannot be computed by a simple +subtraction (a leap second table is required) and durations cannot simply be +added to wait for a particular amount of time. +UTC is the most useful format when dealing with calendar times, as it's always +possible to losslessly convert UTC to and from calendar time. +.Sh RETURN VALUES +On success 1 is returned. +.Pp +0 is returned when the input timestamp has no representation in the output +format or is ambigous. +The output timestamp will be the best approximation. +This can happen in +.Fn sub_leap_seconds +when converting from TAI-10 to UTC and a leap second has been +inserted, or in +.Fn add_leap_seconds +when converting from UTC to TAI-10 and a leap second has been removed (which has +never happened so far). +.Pp +-1 is returned when the input timestamp is too far in the future and it is +unknown whether a leap second will occur. +The output timestamp will be the best approximation. +The accurate result can be obtained when the system leap second table has been +updated. +.Sh SEE ALSO +.Xr clock_gettime 2 , +.Xr gmtime_r 3 , +.Xr localtime_r 3 , +.Xr mktime 3 , +.Xr timegm 3 +.Sh HISTORY +The +.Fn add_leap_seconds +and +.Fn sub_leap_seconds +functions originally appeared in Sortix 1.1. diff --git a/libc/time/gmtime_r.c b/libc/time/gmtime_r.c index 405dcfae..8024b94b 100644 --- a/libc/time/gmtime_r.c +++ b/libc/time/gmtime_r.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Jonas 'Sortie' Termansen. + * Copyright (c) 2013, 2023 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -23,23 +23,25 @@ #include #include -static const int DAYS_JANUARY = 31; -static const int DAYS_FEBRUARY = 28; -static const int DAYS_MARCH = 31; -static const int DAYS_APRIL = 30; -static const int DAYS_MAY = 31; -static const int DAYS_JUNE = 30; -static const int DAYS_JULY = 31; -static const int DAYS_AUGUST = 31; -static const int DAYS_SEPTEMBER = 30; -static const int DAYS_OCTOBER = 31; -static const int DAYS_NOVEMBER = 30; -static const int DAYS_DECEMBER = 31; +#define DAYS_JANUARY 31 +#define DAYS_FEBRUARY 28 +#define DAYS_MARCH 31 +#define DAYS_APRIL 30 +#define DAYS_MAY 31 +#define DAYS_JUNE 30 +#define DAYS_JULY 31 +#define DAYS_AUGUST 31 +#define DAYS_SEPTEMBER 30 +#define DAYS_OCTOBER 31 +#define DAYS_NOVEMBER 30 +#define DAYS_DECEMBER 31 + +#define UNKNOWN 127 #define DECL_LEAP_SECOND(year, jun, dec) \ {0, 0, 0, 0, 0, jun, 0, 0, 0, 0, 0, dec} -static int8_t leap_seconds[][12] = +static const char leap_seconds[][12] = { DECL_LEAP_SECOND(1970, 0, 0), DECL_LEAP_SECOND(1971, 0, 0), @@ -88,18 +90,47 @@ static int8_t leap_seconds[][12] = DECL_LEAP_SECOND(2014, 0, 0), DECL_LEAP_SECOND(2015, 1, 0), DECL_LEAP_SECOND(2016, 0, 1), + DECL_LEAP_SECOND(2017, 0, 0), + DECL_LEAP_SECOND(2018, 0, 0), + DECL_LEAP_SECOND(2019, 0, 0), + DECL_LEAP_SECOND(2020, 0, 0), + DECL_LEAP_SECOND(2021, 0, 0), + DECL_LEAP_SECOND(2022, 0, 0), + DECL_LEAP_SECOND(2023, 0, UNKNOWN), }; -static time_t get_leap_second(int year, int month) +static const char month_days_list[12] = +{ + DAYS_JANUARY, + DAYS_FEBRUARY, + DAYS_MARCH, + DAYS_APRIL, + DAYS_MAY, + DAYS_JUNE, + DAYS_JULY, + DAYS_AUGUST, + DAYS_SEPTEMBER, + DAYS_OCTOBER, + DAYS_NOVEMBER, + DAYS_DECEMBER, +}; + +static time_t get_leap_second_maybe(int year, int month) { const time_t num_years = sizeof(leap_seconds) / sizeof(leap_seconds[0]); if ( year < 1970 ) return 0; if ( num_years <= year-1970 ) - return 0; + return UNKNOWN; return leap_seconds[year-1970][month]; } +static time_t get_leap_second(int year, int month) +{ + time_t result = get_leap_second_maybe(year, month); + return result == UNKNOWN ? 0 : result; +} + static time_t leap_seconds_in_year(int year) { time_t ret = 0; @@ -129,6 +160,11 @@ static time_t days_in_year(int year) DAYS_DECEMBER; } +static int days_in_month(int year, int month) +{ + return month_days_list[month] + (month == 1 && is_leap_year(year)); +} + struct tm* gmtime_r(const time_t* time_ptr, struct tm* ret) { time_t left = *time_ptr; @@ -165,29 +201,13 @@ struct tm* gmtime_r(const time_t* time_ptr, struct tm* ret) ret->tm_wday = (ret->tm_wday - year_days + 7*7*7*7) % 7; } - int month_days_list[12] = - { - DAYS_JANUARY, - DAYS_FEBRUARY + (is_leap_year(ret->tm_year) ? 1 : 0), - DAYS_MARCH, - DAYS_APRIL, - DAYS_MAY, - DAYS_JUNE, - DAYS_JULY, - DAYS_AUGUST, - DAYS_SEPTEMBER, - DAYS_OCTOBER, - DAYS_NOVEMBER, - DAYS_DECEMBER, - }; - // Figure out the correct month. ret->tm_mon = 0; ret->tm_yday = 0; while ( true ) { int month_leaps = get_leap_second(ret->tm_year, ret->tm_mon); - int month_days = month_days_list[ret->tm_mon]; + int month_days = days_in_month(ret->tm_year, ret->tm_mon); int month_seconds = month_days * 24 * 60 * 60 + month_leaps; if ( month_seconds <= left ) { @@ -204,7 +224,7 @@ struct tm* gmtime_r(const time_t* time_ptr, struct tm* ret) left = left % (24 * 60 * 60); // If this is a regular timestamp. - if ( ret->tm_mday < month_days_list[ret->tm_mon] ) + if ( ret->tm_mday < days_in_month(ret->tm_year, ret->tm_mon) ) { ret->tm_yday += ret->tm_mday; @@ -257,26 +277,10 @@ time_t timegm(struct tm* tm) ret += year_seconds; } - int month_days_list[12] = - { - DAYS_JANUARY, - DAYS_FEBRUARY + (is_leap_year(year) ? 1 : 0), - DAYS_MARCH, - DAYS_APRIL, - DAYS_MAY, - DAYS_JUNE, - DAYS_JULY, - DAYS_AUGUST, - DAYS_SEPTEMBER, - DAYS_OCTOBER, - DAYS_NOVEMBER, - DAYS_DECEMBER, - }; - - for ( uint8_t m = 0; m < month; m++ ) + for ( int m = 0; m < month; m++ ) { int month_leaps = get_leap_second(year, m); - int month_days = month_days_list[m]; + int month_days = days_in_month(year, m); int month_seconds = month_days * 24 * 60 * 60 + month_leaps; ret += month_seconds; } @@ -290,3 +294,58 @@ time_t timegm(struct tm* tm) return ret; } + +int sub_leap_seconds(time_t* ptr) +{ + time_t t = *ptr; + time_t next = 0; + time_t offset = 0; + for ( int year = 1970; true; year++ ) + { + for ( int month = 0; month < 12; month++ ) + { + time_t seconds = days_in_month(year, month) * 24 * 60 * 60; + time_t leap = get_leap_second_maybe(year, month); + next += seconds; + if ( leap == UNKNOWN ) + { + *ptr = t - offset; + return t < next - 1 ? 1 : -1; + } + next += leap; + if ( t < next ) + { + *ptr = t - offset; + return 0 < leap && t + 1 == next ? 0 : 1; + } + offset += leap; + } + } +} + +int add_leap_seconds(time_t* ptr) +{ + time_t t = *ptr; + time_t next = 0; + time_t offset = 0; + for ( int year = 1970; true; year++ ) + { + for ( int month = 0; month < 12; month++ ) + { + time_t seconds = days_in_month(year, month) * 24 * 60 * 60; + time_t leap = get_leap_second_maybe(year, month); + next += seconds; + if ( leap == UNKNOWN ) + { + *ptr = t + offset; + return t < next - 1 ? 1 : -1; + } + if ( t < next ) + { + *ptr = t + offset; + return leap < 0 && t + 1 == next ? 0 : 1; + } + offset += leap; + } + } +} diff --git a/libc/time/sub_leap_seconds.3 b/libc/time/sub_leap_seconds.3 new file mode 120000 index 00000000..2e628cbd --- /dev/null +++ b/libc/time/sub_leap_seconds.3 @@ -0,0 +1 @@ +add_leap_seconds.3 \ No newline at end of file diff --git a/share/man/man7/portability.7 b/share/man/man7/portability.7 index aa6d5cee..af4c9726 100644 --- a/share/man/man7/portability.7 +++ b/share/man/man7/portability.7 @@ -271,6 +271,17 @@ and such. .Dv CLOCK_REALTIME counts the number of seconds since the epoch including leap seconds, unlike other operating systems and in violation of POSIX. +.Dv CLOCK_REALTIME_HAS_LEAP_SECONDS +definition advertises +.Dv CLOCK_REALTIME +contains leap seconds since the epoch in the TAI-10 format. +.Pp +.Xr sub_leap_seconds 3 +converts timestamps from TAI-10 to UTC by subtracting the leap seconds, while +.Xr add_leap_seconds 3 +converts timestamps from UTC TO TAI-10 by adding the leap seconds. +These functions are useful when communicating with other operating systems +either via the network or exchanged data files. .Ss u_char, u_short, u_int, u_long .Vt unsigned char , .Vt unsigned short ,