32
/* Seconds since the UTC Epoch */
34
/* This is a disaster, thanks to the mind-boggling brain damage of
35
POSIX. At any time t, the POSIX time P(t) is the number of SI
36
seconds, S(t), since the UTC epoch (1972-01-01T00:00:00Z), plus
37
63072000 (= 2*365*86400, to adjust for the Unix epoch, which would
38
be `1970-01-01T00:00:00Z' if modern UTC hadn't begun only in 1972),
39
*minus* the number L(t) of those seconds that were leap seconds in
40
UTC. That is, P(t) = S(t) + 63072000 - L(t).
42
Problem (in the sense of problem set): Find S given P.
43
Problem (in the sense of disaster): POSIX doesn't tell us S or L.
45
So what do we do? Fortunately, many popular Unix systems set their
46
clocks using the Network Time Protocol with the NTP Project's ntpd.
47
In order to support this, they provide a couple of extra-POSIX
48
routines, ntp_gettime and ntp_adjtime, giving the TAI - UTC offset.
50
What if we don't have ntp_gettime or ntp_adjtime, or if the kernel
51
doesn't know the TAI - UTC offset? We're screwed. We could guess
52
that there have been 24 leap seconds, which is true at the time of
53
writing (2010-12-09). But this is wrong if the clock is set to TAI,
54
and this causes the Scheme clock to misbehave if the kernel assumes
55
the TAI - UTC offset to be zero and then increments it when an NTP
56
server tells it of a new leap second: at this point Scheme would
57
switch from assuming a TAI - UTC offset of 24 seconds to assuming a
58
TAI - UTC offset of 1 second, and rewind the clock by 23 seconds --
59
it would behave even worse than a POSIX clock.
61
So instead, if the kernel reports a TAI - UTC offset of under ten,
62
we take that to be the number of leap seconds, so that at least when
63
it is incremented and the system clock is rewound by a second, we
64
can show a smoothly advancing clock. If the kernel reports a TAI -
65
UTC offset of at least ten, we subtract ten from it to compute the
66
number of leap seconds (because 1972-01-01T00:00:00Z is 1972-01-01
69
Why a threshold of ten? Since modern UTC began, the TAI - UTC
70
offset has never been under ten. So if you represent times during
71
the entire existence of modern UTC so far, this heuristic will
74
/* Summary of scenarios:
76
(a) If your system lacks ntp_gettime/ntp_adjtime, and its clock is
77
set to POSIX time, then you lose exactly as badly as any POSIX
78
program does. (The clock is wrong by 24 at the time of writing,
79
and it misbehaves during a leap second.)
81
(b) If your system lacks ntp_gettime/ntp_adjtime, and its clock is
82
set to TAI, then you win. (The clock is correct and behaves
85
(c) If your system has ntp_gettime/ntp_adjtime, its clock is set to
86
POSIX time, and its TAI - UTC offset is initialized to 0 but is
87
incremented at the same time the POSIX clock is rewound, then
89
. you will have time stamps that are off by 24 seconds at the
92
. your clock will behave well,
94
at least for the next net of nine positive leap seconds during
95
continuous operation of your system, which should cover a good
98
(d) If your system has ntp_gettime/ntp_adjtime, and its clock is set
99
to POSIX time with the correct TAI - UTC offset, then you win,
100
at least for the next net of fourteen negative leap seconds, but
101
there never has been a negative leap second and probably never
104
(e) If your system has ntp_gettime/ntp_adjtime, and its clock is set
105
to TAI with a constant TAI - UTC offset of 0, then you win.
107
Scenario (c) is the case for most modern Unix systems that I know.
108
Scenario (d) is easily configured for most such Unix systems.
109
Scenarios (b) and (e) are the case for any system administered by
110
users of djbware (see <http://cr.yp.to/proto/utctai.html>).
112
What about time zones? I will think about them some other time. */
114
static intmax_t utc_epoch_minus_unix_epoch = 63072000L;
117
guess_n_leap_seconds (long tai)
119
return ((tai < 10) ? tai : (tai - 10));
123
guess_time_from_posix (intmax_t posix, long tai)
125
/* This is to be used only for querying the current clock -- it
126
assumes tai is the current TAI - UTC offset. To find the (best
127
approximation of) the seconds since an epoch from POSIX time
128
requires consulting a leap second table. */
129
/* FIXME: There is a minor danger of arithmetic overflow here -- but
130
only on broken operating systems with wildly bogus values for
131
POSIX time and the TAI - UTC offset, or near 2038 on archaic
132
systems with no 64-bit integer type. */
133
return (posix - utc_epoch_minus_unix_epoch + (guess_n_leap_seconds (tai)));
136
/* The following routines may lose information -- namely, the
137
information that the system's clock is bogus. But if this is so,
138
you'll probably notice the fact anyway, and this paranoia prevents
139
bad values from making Scheme crash. These are macros because the
140
signedness of the types defined in struct timeval and struct
141
timespec is pretty random. */
143
#define SANITIZE_NSEC(NSEC) \
145
: (((uintmax_t) (NSEC)) < 1000000000UL) ? ((uint32_t) (NSEC)) \
148
#define SANITIZE_USEC(USEC) \
150
: (((uintmax_t) (USEC)) < 1000000UL) ? (1000UL * ((uint32_t) (USEC))) \
153
#if defined(HAVE_BSD_NTP)
156
OS_nanotime_since_utc_epoch (struct scheme_nanotime *t)
158
struct ntptimeval ntv;
159
STD_VOID_SYSTEM_CALL (syscall_ntp_gettime, (UX_ntp_gettime (&ntv)));
161
= (guess_time_from_posix (((intmax_t) (ntv.time.tv_sec)), (ntv.tai)));
162
(t->nanoseconds) = (SANITIZE_NSEC (ntv.time.tv_nsec));
165
#elif defined(HAVE_LINUX_NTP)
168
OS_nanotime_since_utc_epoch (struct scheme_nanotime *t)
170
static const struct timex zero_tx;
171
struct timex tx = zero_tx;
172
/* This doesn't actually adjust the time, because we have set
173
tx.modes to zero, meaning no modifications. It does, however,
174
return some useful information in tx, which Linux's ntp_gettime
175
failed to return until recent versions. */
176
STD_VOID_SYSTEM_CALL (syscall_ntp_adjtime, (UX_ntp_adjtime (&tx)));
178
= (guess_time_from_posix (((intmax_t) (tx.time.tv_sec)), (tx.tai)));
179
(t->nanoseconds) = (SANITIZE_USEC (tx.time.tv_usec));
182
#elif defined(HAVE_CLOCK_GETTIME)
185
OS_nanotime_since_utc_epoch (struct scheme_nanotime t)
189
(syscall_clock_gettime, (UX_clock_gettime (CLOCK_REALTIME, (&ts))));
190
(t->seconds) = (guess_time_from_posix (((intmax_t) (ts.tv_sec)), 0));
191
(t->nanoseconds) = (SANITIZE_NSEC (ts.tv_nsec));
194
#elif defined(HAVE_GETTIMEOFDAY)
197
OS_nanotime_since_utc_epoch (struct scheme_nanotime *t)
200
STD_VOID_SYSTEM_CALL (syscall_gettimeofday, (UX_gettimeofday ((&tv), 0)));
201
(t->seconds) = (guess_time_from_posix (((intmax_t) (tv.tv_sec)), 0));
202
(t->nanoseconds) = (SANITIZE_USEC (tv.tv_usec));
205
#else /* You are a sad, strange little Unix. */
208
OS_nanotime_since_utc_epoch (struct scheme_nanotime *t)
211
STD_UINT_SYSTEM_CALL (syscall_time, posix_time, (UX_time (0)));
212
(t->seconds) = (guess_time_from_posix (posix_time, 0));
213
(t->nanoseconds) = 0;
32
219
OS_encoded_time (void)