Add sub_leap_seconds(3) and add_leap_seconds(3).

Advertise leap seconds being counted via CLOCK_REALTIME_HAS_LEAP_SECONDS.
This commit is contained in:
Jonas 'Sortie' Termansen 2023-03-26 23:56:15 +02:00
parent 97c57ca604
commit 9033153c47
7 changed files with 240 additions and 55 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <stdint.h>
#include <time.h>
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;
}
}
}

View File

@ -0,0 +1 @@
add_leap_seconds.3

View File

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