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:
parent
97c57ca604
commit
9033153c47
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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.
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
add_leap_seconds.3
|
|
@ -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 ,
|
||||
|
|
Loading…
Reference in New Issue