Coverage Report

Created: 2025-11-17 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gpsd/gpsd-3.26.2~dev/gpsd/timebase.c
Line
Count
Source
1
/*****************************************************************************
2
3
All of gpsd's assumptions about time and GPS time reporting live in this file.
4
5
This is a work in progress.  Currently (3.11) GPSD requires that the host
6
system clock be accurate to within one second.  It would be nice to relax
7
this to "accurate within one GPS rollover period" for receivers reporting
8
GPS week+TOW, but this isn't possible in general.
9
10
= Begin Sidebar: Why Leap Seconds =
11
12
Read this carefully, and if there are errors, please correct.  An
13
understanding of the following terms is critical to make sense of the
14
situation, which would be farcical if it were not serious.
15
16
We discuss four timescales:
17
18
 1. TAI, International Atomic Time, which ticks smoothly
19
    at the rate of the SI second.  TAI has no concept of a day, year, etc.
20
    TAI does not define "days" or large units, and is hence difficult
21
    for humans to parse.  Also, TAI is not broadcast or generally available.
22
 2. GPS Time, which ticks at at the rate of TAI, but has a constant offset
23
    from it.  For other GNSS systems, the offset is different.  The
24
    offset is of purely historical interest, being chosen by each
25
    GNSS operator for convenience when the systems were inaugurated.
26
    In other words, only the "epoch" differs between GPS Time and TAI.
27
 3. UT1, a smoothed earth rotation angle, which MUST return to zero
28
    once a day, (why?  Because you want the sun to be overhead *each*
29
    day at the same time on your watch, no?), and ticks SI seconds
30
    (a non-integeral number of seconds will occur in a UT1 day,
31
    obviously).  For those of you who still say "GMT", UT1 is the
32
    closest modern timescale.
33
 4. UTC, Coordinated Universal Time, which ticks SI seconds.  An attempt is
34
    made to keep UTC aligned with the rate of flow of seconds (TAI), and
35
    the rate of flow of days (UT1).
36
37
The reason UTC has to struggle has little to do with the fact that the earth's
38
rotation is slowing down.  Although the length of the day, as measured by UT1,
39
is lengthening in terms of the SI second, this is a very long term slowdown,
40
and since 1980, the earth has actually speeded up.
41
42
The issue simply is that the term "second" is defined in two incompatible ways:
43
44
  Def 1. As a fixed number (9,192,631,770) of cycles of an atomic standard.
45
    We believe this is a constant, and evidence to the contrary may
46
    involve GPSD code review, and Nobel Prizes.  This is the SI second.
47
  Def 2. As 1/86400 of a "day".  The number 86400 arises from
48
    1 day == 24 * 60 * 60 secs.  This is what we learn in school.
49
50
Both of these have been defined separately, and the issue of leap seconds,
51
rubber seconds, Smoothed Leap Seconds, etc, arises because we are
52
unwilling to change the definition of either to be a derived unit of the
53
other.
54
55
At the time the SI second was defined, it was believed that Def 2 was correct,
56
and the number in Def 1 was derived.  Because of ease of measurement, Def 1 was
57
codified, and the problem was ignored for some time.  Prior to 1972,
58
complicated formulae were used to scale the SI second, with the attendant
59
confusion and fear when the formula would be revised.
60
61
Since 1972, the start of UTC, the decision to have leap seconds means that UTC
62
ticks SI seconds.  Every 86400 SI seconds, we declare a new day, and we let the
63
error (UT1 - UTC) build up. This is of the order of a few ms each midnight, not
64
always the same way (think earthquakes that move the earth's crust).
65
66
Once the error has built up substantially, every few years, we (and by
67
"we", I mean M. Daniel Gambis at the IERS) declare that a future
68
day will have 86401 secs.  This is the Leap Second.  Note that this
69
often overcorrects, but if we wait a few months, the error will disappear.
70
71
An animation of this process is available at:
72
https://space-geodesy.nasa.gov/multimedia/videos/EarthOrientationAnimations/UT1/UT1.html
73
74
Clear?
75
76
Two last things:
77
 1. Again, the earth slowing down is NOT the cause of leap seconds,
78
    except very indirectly.  It is the conflict between the two
79
    definitions above that causes leap seconds
80
 2. POSIX declares that there is no conflict, there are always 86400 SI
81
    secs in a day, and hence no leap seconds.  The fact that ostriches
82
    survive in the wild indicates that this is not as mind-crushingly
83
    wrong as it may seem.
84
85
= End Sidebar =
86
87
Date and time in GPS is represented as number of weeks mod 1024 from
88
1980-01-06T00:00.00Z, and number of SI seconds into the week.  GPS
89
time is not leap-second corrected, and has a constant offset from TAI,
90
but not from UTC.
91
92
There are hence two issues with converting GPS Time to UTC:
93
94
1. We need to recover the epoch difference between TAI and GPS Time,
95
   which rolls over to 0 every 1024 weeks (approx 20 years).  Think
96
   of this as analogous to the Y2K problem; we do not know if we are
97
   off by 1024 weeks.  This is the "rollover" issue below.
98
2. Once we have the epoch right, we need to adjust for Leap Seconds
99
   that have been issued.
100
101
(Complicating the issue is that most consumer devices may not apply
102
the corrections when rollover occurs, as this may not be adequately
103
tested.  We hence have to accept the UTC time reported by the device,
104
while checking it on the sly).
105
106
Satellites also broadcast a current leap-second correction which is
107
updated on (theoretically) three-month boundaries according to
108
rotational bulletins issued by the International Earth Rotation and
109
Reference Systems Service (IERS).  Historically all corrections have
110
been made on six-month boundaries.
111
112
The leap-second correction is only included in the satellite subframe
113
broadcast, roughly once ever 20 minutes.  While the satellites do
114
notify GPSes of upcoming leap-seconds, this notification is not
115
necessarily processed correctly on consumer-grade devices, and will
116
not be available at all when a GPS receiver has just
117
cold-booted.  Thus, the time reported from GPS devices, although
118
supposed to be UTC, may be offset by an integer number of seconds
119
between a cold boot or leap second and the following
120
subframe broadcast.
121
122
It might be best not to trust time for 20 minutes after GPSD startup
123
if it is more than 500ms from current system time (that is long enough
124
for an ephemeris to load) but this isn't actually implemented as the
125
divergence will normally be only one second or less.
126
127
GPS date and time are subject to a rollover problem in the 10-bit week
128
number counter, which will re-zero every 1024 weeks (roughly every 20
129
years). The first rollover was 1999-08-22T00:00:00; the most recent
130
was 2019-04-07T00:00:00.  Note that both these time stamps are in GPS
131
Time, not UTC (the recent rollover occurred at 2019-04-06T23:59:42Z).
132
Plans are afoot to upgrade the message format to 13 bits; this
133
will delay the next rollover until 2173.
134
135
For accurate time reporting, therefore, a GPS requires a supplemental
136
time reference sufficient to identify the current rollover period,
137
e.g. accurate to within 512 weeks.  Many GPSes have a wired-in
138
assumption about the UTC time of the last rollover and will thus report
139
incorrect times outside the rollover period they were designed in.
140
141
These conditions leave gpsd in a serious hole.  Actually there are several
142
interrelated problems:
143
144
1) Every device has some assumption about base epoch (date of
145
last rollover) that we don't have access to.  Thus, there's no way to
146
check whether a rollover the device wasn't prepared for has occurred
147
before gpsd startup time (making the reported UTC date invalid)
148
without some other time source.  (Some devices may keep a
149
rollover count in NVRAM and avoid the problem; we can't tell when that's
150
happening, either.)
151
152
2) Many NMEA devices - in fact, all that don't report ZDA - never tell
153
us what century they think it is. Those that do report century are
154
still subject to rollover problems. We need an external time reference
155
for this, too.
156
157
3) Supposing we're looking at a binary protocol that returns week/tow,
158
we can't know which rollover period we're in without an external time
159
source.
160
161
4) Only one external time source, the host system clock, is reliably
162
available, although it may not be accurate.
163
164
5) Another source *may* be available - the GPS leap second count, if we can
165
get the device to report it. The latter is not a given; SiRFs before
166
firmware rev 2.3.2 don't report it unless special subframe data reporting
167
is enabled, which requires 38400bps. Evermore GPSes can't be made to
168
report it at all. Furthermore, before the almanac load the GPS may report
169
a fixed (and possibly out of date) offset.
170
171
Conclusion: if the system clock isn't accurate enough that we can
172
deduce what rollover period we're in, we're utterly
173
hosed. Furthermore, if it's not accurate to within a second and only
174
NMEA devices that don't emit ZDA are reporting, we don't even know
175
what century it is!
176
177
Therefore, we must assume the system clock is reliable to within a second.
178
179
However, none of these caveats affect the usefulness of PPS, which
180
tells us top of second to theoretical 50ns accuracy (actually about 1
181
microsecond over RS232 and roughly one poll interval over USB) and can
182
be made to condition a local NTP instance that does *not* rely on the
183
system clock. The combination of PPS with NTP time should be reliable
184
regardless of what the local system clock gets up to. That is, unless
185
NTP clock skew goes over 1 second, but this is unlikely to ever happen
186
- and if it does the reasons will have nothing to do with GPS
187
idiosyncrasies.
188
189
This file is Copyright 2010 by the GPSD project
190
SPDX-License-Identifier: BSD-2-clause
191
192
*****************************************************************************/
193
194
#include "../include/gpsd_config.h"   // must be before all includes
195
196
#include <ctype.h>
197
#include <stdlib.h>
198
#include <string.h>
199
200
#include "../include/gpsd.h"
201
202
// initialize the GPS context's time fields
203
void gpsd_time_init(struct gps_context_t *context, time_t starttime)
204
0
{
205
    /*
206
     * gpsd can't work with 'right' timezones (leapseconds inserted in
207
     * the timezone offset).  Avoid this and all manner of other local
208
     * time issues by telling the system we want times returned in UTC.
209
     */
210
0
    (void)putenv("TZ=UTC");
211
212
    /*
213
     * Provides a start time for getting the century.  Do this, just
214
     * in case one of our embedded deployments is still in place in
215
     * the year 2.1K.  Still likely to fail if we bring up the daemon
216
     * just before a century mark, but that case is probably doomed
217
     * anyhow because of 2-digit years.
218
     */
219
0
    context->leap_seconds = BUILD_LEAPSECONDS;
220
0
    context->century = BUILD_CENTURY;
221
0
    context->start_time = starttime;
222
223
0
    context->rollovers = (int)((context->start_time - GPS_EPOCH) /
224
0
                               GPS_ROLLOVER);
225
226
0
    if (GPS_EPOCH > context->start_time) {
227
0
        GPSD_LOG(LOG_ERROR, &context->errout,
228
0
                 "system time looks bogus, dates may not be reliable.\n");
229
0
    } else {
230
        // we've forced the UTC timezone, so this is actually UTC
231
0
        struct tm *now = localtime(&context->start_time);
232
0
        char scr[128];
233
0
        timespec_t ts_start_time;
234
235
0
        ts_start_time.tv_sec = context->start_time;
236
0
        ts_start_time.tv_nsec = 0;
237
238
        /*
239
         * This is going to break our regression-test suite once a century.
240
         * I think we can live with that consequence.
241
         */
242
0
        now->tm_year += 1900;
243
0
        context->century = now->tm_year - (now->tm_year % 100);
244
0
        GPSD_LOG(LOG_INF, &context->errout, "startup at %s (%ld)\n",
245
0
                 timespec_to_iso8601(ts_start_time, scr, sizeof(scr)),
246
0
                 (long)context->start_time);
247
0
    }
248
0
}
249
250
/*
251
 * Interpret "#Date: yyyy-mm-dd", setting the session context
252
 * start time.  We do this so the behavior of the
253
 * regression tests won't depend on current system time.
254
 */
255
void gpsd_set_century(struct gps_device_t *session)
256
0
{
257
0
    int ret;
258
0
    unsigned year = 0;
259
0
    unsigned month = 0;
260
0
    unsigned day = 0;
261
0
    struct gps_context_t *context = session->context;
262
0
    struct tm date = {0};
263
264
0
    ret = sscanf((const char *)session->lexer.outbuffer, "# Date: %u-%u-%u",
265
0
                 &year, &month, &day);
266
0
    if (1 > ret) {
267
        // give up unless we at least got a year
268
0
        return;
269
0
    }
270
271
0
    if (!IN(1968, year, 2200)) {
272
        // bad year
273
0
        return;
274
0
    }
275
276
0
    context->century = year - (year % 100);
277
278
0
    date.tm_year = year - 1900;                // year, 1999..2099
279
0
    if (!IN(1, month, 12)) {
280
0
        month = 1;
281
0
    }
282
0
    date.tm_mon = month - 1;                   // month 0..11
283
0
    if (!IN(1, day, 31)) {
284
0
        day = 1;
285
0
    }
286
0
    date.tm_mday = day;                        // day 1..31
287
0
    context->start_time = mkgmtime(&date);
288
0
    context->rollovers = (int)((context->start_time - GPS_EPOCH) /
289
0
                               GPS_ROLLOVER);
290
291
0
    GPSD_LOG(LOG_PROG, &context->errout,
292
0
             "Setting century: %d rollovers %d %d-%d-%d\n",
293
0
             context->century, context->rollovers, year, month, day);
294
295
0
    gpsd_time_init(context, context->start_time);
296
0
}
297
298
// resolve a UTC date, checking for rollovers
299
timespec_t gpsd_utc_resolve(struct gps_device_t *session)
300
0
{
301
    /*
302
     * We'd like to *correct* for rollover the way we do for GPS week.
303
     * In theory, comparing extracted UTC against present time should
304
     * allow us to compute the device's epoch assumption.  In practice,
305
     * this will be hairy and risky.
306
     */
307
0
    timespec_t t;
308
309
0
    t.tv_sec = (time_t)mkgmtime(&session->nmea.date);
310
0
    t.tv_nsec = session->nmea.subseconds.tv_nsec;
311
0
    session->context->valid &=~ GPS_TIME_VALID;
312
313
    /*
314
     * If the system clock is zero or has a small-integer value,
315
     * Time is from before GPS satellites werelaunched!
316
     *
317
     * Or, start time from regression log.
318
     *
319
     * No further sanity-checking is possible.
320
     */
321
0
    if (GPS_EPOCH > session->context->start_time ||
322
0
        1 == session->regression) {
323
0
        return t;
324
0
    }
325
326
#if 0      // Debug
327
    GPSD_LOG(LOG_IO, &session->context->errout,
328
             "leaps %d GPS_EPOCH %lld >  start_time %lld\n",
329
             session->context->leap_seconds,
330
             (long long)GPS_EPOCH,
331
             (long long)session->context->start_time);
332
#endif
333
334
    /* sanity check unix time against leap second.
335
     * Does not work well with regressions because the leap_sconds
336
     * could be from the receiver, or from BUILD_LEAPSECONDS.
337
     * Leap second 18 at 1 Jan 2017: 1483228800
338
     * (long long) for 32-bit systems */
339
0
    if (17 < session->context->leap_seconds &&
340
0
        1483228800LL > t.tv_sec) {
341
0
        long long old_tv_sec = t.tv_sec;
342
0
        char scr[128];
343
344
0
        t.tv_sec += 619315200LL;                    // fast forward 1024 weeks
345
0
        (void)gmtime_r(&t.tv_sec, &session->nmea.date);   // fix NMEA date
346
0
        (void)timespec_to_iso8601(t, scr, sizeof(scr));
347
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
348
0
                 "WKRO bug: leap second %d inconsistent "
349
0
                 "with %lld, corrected to %lld (%s)\n",
350
0
                 session->context->leap_seconds,
351
0
                 old_tv_sec, (long long)t.tv_sec, scr);
352
0
    }
353
354
    /*
355
     * If the GPS is reporting a time from before the daemon started,
356
     * maybe we've had a rollover event while the daemon was running.
357
     */
358
359
0
    return t;
360
0
}
361
362
void gpsd_century_update(struct gps_device_t *session, int century)
363
0
{
364
0
    session->context->valid |= CENTURY_VALID;
365
0
    if (century > session->context->century) {
366
        /*
367
         * This mismatch is almost certainly not due to a GPS week
368
         * rollover, because that would throw the ZDA report backward
369
         * into the last rollover period instead of forward.  Almost
370
         * certainly it means that a century mark has passed while
371
         * gpsd was running, and we should trust the new ZDA year.
372
         */
373
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
374
0
                 "century rollover detected.\n");
375
0
        session->context->century = century;
376
0
    } else if (GPS_EPOCH <= session->context->start_time &&
377
0
               century < session->context->century) {
378
        /*
379
         * This looks like a GPS week-counter rollover.
380
         */
381
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
382
0
                 "ZDA year less than clock year, "
383
0
                 "probable GPS week rollover lossage\n");
384
0
        session->context->valid &=~ CENTURY_VALID;
385
0
    }
386
0
}
387
388
/* gpsd_gpstime() convert GPS week/tow to "GPS time"
389
 * "GPS time" as defined by RINEX:  GPS time + leap seconds == UTC
390
 *
391
 * Use for time that is not "time now in UTC".
392
 * Do not touch: gps_week, gps_tow, GPS_VALID, rollovers in context.
393
 */
394
timespec_t gpsd_gpstime(struct gps_device_t *session,
395
                        unsigned week, timespec_t tow)
396
0
{
397
0
    timespec_t t;
398
399
0
    if (1024 <= week) {
400
        // we have a full, not truncated, week
401
0
    } else {
402
        // truncated week, add in the roll overs
403
0
        week += session->context->rollovers * 1024;
404
0
    }
405
406
    // gcc needs the (time_t)week to not overflow. clang got it right.
407
    // if time_t is 32-bits, then still 2038 issues
408
    // no leap seconds
409
0
    t.tv_sec = GPS_EPOCH + ((time_t)week * SECS_PER_WEEK) + tow.tv_sec;
410
0
    t.tv_nsec = tow.tv_nsec;
411
412
0
#if 4 < SIZEOF_TIME_T
413
    // 2038 rollover hack for unsigned 32-bit time, assuming today is < 2038
414
0
    if (0 > t.tv_sec) {
415
        // recompute for previous EPOCH
416
0
        week -= 1024;
417
0
        t.tv_sec = GPS_EPOCH + ((time_t)week * SECS_PER_WEEK) + tow.tv_sec;
418
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
419
0
                 "gpsd_gpstime(): 2038 rollover. Adjusting to %lld week %u\n",
420
0
                 (long long)t.tv_sec, week);
421
0
    }
422
0
#endif   // SIZEOF_TIME_T
423
424
0
    return t;
425
0
}
426
427
/* gpsd_gpstime_resolv() convert GPS week/tow to UTC as a timespec
428
 * using current leap second.
429
 *
430
 * Sets gps_week, gps_tow, GPS_VALID, rollovers, in context.
431
 * FIXME: this prolly should call gpsd_resolv() instead of duplicating it.
432
 */
433
timespec_t gpsd_gpstime_resolv(struct gps_device_t *session,
434
                               unsigned week, timespec_t tow)
435
0
{
436
0
    timespec_t t;
437
438
    /*
439
     * This code detects and compensates for week counter rollovers that
440
     * happen while gpsd is running. It will not save you if there was a
441
     * rollover that confused the receiver before gpsd booted up.  It *will*
442
     * work even when Block IIF satellites increase the week counter width
443
     * to 13 bits.
444
     */
445
0
    if ((int)week < (session->context->gps_week & 0x3ff)) {
446
0
        ++session->context->rollovers;
447
0
        GPSD_LOG(LOG_INF, &session->context->errout,
448
0
                 "GPS week %u 10-bit rollover detected. rollovers %d\n",
449
0
                 week, session->context->rollovers);
450
0
    }
451
452
    /*
453
     * This guard copes with both conventional GPS weeks and the "extended"
454
     * 15-or-16-bit version with no wraparound that appears in Zodiac
455
     * chips and is supposed to appear in the Geodetic Navigation
456
     * Information (0x29) packet of SiRF chips.  Some SiRF firmware versions
457
     * (notably 231) actually ship the wrapped 10-bit week, despite what
458
     * the protocol reference claims.
459
     */
460
0
    if (1024 > week) {
461
0
        week += session->context->rollovers * 1024;
462
0
    }
463
464
    /* This used to sanity check week number, GPS epoch, against leap
465
     * seconds.  Did not work well with regressions because the leap_sconds
466
     * could be from the receiver, or from BUILD_LEAPSECONDS.
467
     * Maybe if the regressions files provided BUILD_LEAPSECONDS this
468
     * could be tried again.
469
     */
470
471
    // gcc needs the (time_t)week to not overflow. clang got it right.
472
    // if time_t is 32-bits, then still 2038 issues
473
0
    t.tv_sec = GPS_EPOCH + ((time_t)week * SECS_PER_WEEK) + tow.tv_sec;
474
0
    t.tv_sec -= session->context->leap_seconds;
475
0
    t.tv_nsec = tow.tv_nsec;
476
477
0
#if 4 < SIZEOF_TIME_T
478
    // 2038 rollover hack for unsigned 32-bit time, assuming today is < 2038
479
0
    if (0 > t.tv_sec) {
480
        // recompute for previous EPOCH
481
0
        week -= 1024;
482
0
        t.tv_sec = GPS_EPOCH + ((time_t)week * SECS_PER_WEEK) + tow.tv_sec;
483
0
        t.tv_sec -= session->context->leap_seconds;
484
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
485
0
                 "2038 rollover. Adjusting to %lld. week %u leap %d\n",
486
0
                 (long long)t.tv_sec, week,
487
0
                 session->context->leap_seconds);
488
0
    }
489
0
#endif   // SIZEOF_TIME_T
490
491
0
    session->context->gps_week = week;
492
0
    session->context->gps_tow = tow;
493
0
    session->context->valid |= GPS_TIME_VALID;
494
495
0
    return t;
496
0
}
497
498
// vim: set expandtab shiftwidth=4