diff --git a/lstab.c b/lstab.c new file mode 100644 index 0000000..5fede16 --- /dev/null +++ b/lstab.c @@ -0,0 +1,206 @@ +/** + * @file lstab.c + * @note Copyright (C) 2012 Richard Cochran + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#include +#include +#include + +#include "lstab.h" + +/* + * Keep a history of the TAI - UTC offset in a lookup table. + * + * Each entry gives the NTP time when a new TAI offset came into + * effect. This is always the second immediately after a leap second. + * + * The size of the table is the number of entries from the NIST table, + * plus room for two hundred more entries to be added at run time. + * Since there can be at most two leap seconds per year, this allows + * for at least one hundred years. + * + * The table data are available from + * + * https://www.ietf.org/timezones/data/leap-seconds.list + * + * ftp://ftp.nist.gov/pub/time/leap-seconds.list + * + * When updating this table, do not forget to set N_HISTORICAL_LEAPS + * and the expiration date. + */ + +#define BASE_TAI_OFFSET 10 +#define N_HISTORICAL_LEAPS 28 +#define N_LEAPS (N_HISTORICAL_LEAPS + 200) +#define NTP_UTC_OFFSET 2208988800ULL + +struct epoch_marker { + int offset; /* TAI - UTC offset of epoch */ + uint64_t ntp; /* NTP time of epoch */ + uint64_t tai; /* TAI time of epoch */ + uint64_t utc; /* UTC time of epoch */ +}; + +struct lstab { + struct epoch_marker lstab[N_LEAPS]; + uint64_t expiration_utc; + int length; +}; + +static const uint64_t expiration_date_ntp = 3802291200ULL; /* 28 June 2020 */ + +static const uint64_t offset_table[N_LEAPS * 2] = { + 2272060800ULL, 10, /* 1 Jan 1972 */ + 2287785600ULL, 11, /* 1 Jul 1972 */ + 2303683200ULL, 12, /* 1 Jan 1973 */ + 2335219200ULL, 13, /* 1 Jan 1974 */ + 2366755200ULL, 14, /* 1 Jan 1975 */ + 2398291200ULL, 15, /* 1 Jan 1976 */ + 2429913600ULL, 16, /* 1 Jan 1977 */ + 2461449600ULL, 17, /* 1 Jan 1978 */ + 2492985600ULL, 18, /* 1 Jan 1979 */ + 2524521600ULL, 19, /* 1 Jan 1980 */ + 2571782400ULL, 20, /* 1 Jul 1981 */ + 2603318400ULL, 21, /* 1 Jul 1982 */ + 2634854400ULL, 22, /* 1 Jul 1983 */ + 2698012800ULL, 23, /* 1 Jul 1985 */ + 2776982400ULL, 24, /* 1 Jan 1988 */ + 2840140800ULL, 25, /* 1 Jan 1990 */ + 2871676800ULL, 26, /* 1 Jan 1991 */ + 2918937600ULL, 27, /* 1 Jul 1992 */ + 2950473600ULL, 28, /* 1 Jul 1993 */ + 2982009600ULL, 29, /* 1 Jul 1994 */ + 3029443200ULL, 30, /* 1 Jan 1996 */ + 3076704000ULL, 31, /* 1 Jul 1997 */ + 3124137600ULL, 32, /* 1 Jan 1999 */ + 3345062400ULL, 33, /* 1 Jan 2006 */ + 3439756800ULL, 34, /* 1 Jan 2009 */ + 3550089600ULL, 35, /* 1 Jul 2012 */ + 3644697600ULL, 36, /* 1 Jul 2015 */ + 3692217600ULL, 37, /* 1 Jan 2017 */ +}; + +static void epoch_marker_init(struct epoch_marker *ls, uint64_t val, int offset) +{ + ls->ntp = val; + ls->utc = val - NTP_UTC_OFFSET; + ls->tai = val - NTP_UTC_OFFSET + offset; + ls->offset = offset; +} + +static void lstab_init(struct lstab *lstab) +{ + struct epoch_marker *ls; + uint64_t offset, val; + int i; + + for (i = 0; i < N_HISTORICAL_LEAPS; i++) { + ls = lstab->lstab + i; + val = offset_table[2 * i]; + offset = offset_table[2 * i + 1]; + epoch_marker_init(ls, val, offset); + } + lstab->expiration_utc = expiration_date_ntp - NTP_UTC_OFFSET; + lstab->length = i; +} + +void lstab_print(struct lstab *lstab, FILE *fp) +{ + int i, len = lstab->length; + + fprintf(fp, "%3s%12s%12s%12s%4s\n", "idx", "NTP", "TAI", "UTC", "OFF"); + for (i = 0; i < len; i++) { + fprintf(fp, "%3d" "%12" PRIu64 "%12" PRIu64 "%12" PRIu64 "%4d\n", i, + lstab->lstab[i].ntp, lstab->lstab[i].tai, + lstab->lstab[i].utc, lstab->lstab[i].offset); + } +} + +static int lstab_read(struct lstab *lstab, const char *name) +{ + uint64_t expiration, val; + struct epoch_marker *ls; + int index = 0, offset; + char buf[1024]; + FILE *fp; + + fp = fopen(name, "r"); + if (!fp) { + fprintf(stderr, "failed to open '%s' for reading: %m\n", name); + return -1; + } + while (1) { + if (!fgets(buf, sizeof(buf), fp)) { + break; + } + if (1 == sscanf(buf, "#@ %" PRIu64, &expiration)) { + lstab->expiration_utc = expiration - NTP_UTC_OFFSET; + continue; + } + if (2 == sscanf(buf, "%" PRIu64 " %d", &val, &offset)) { + ls = lstab->lstab + index; + epoch_marker_init(ls, val, offset); + index++; + } + } + if (!lstab->expiration_utc) { + fprintf(stderr, "missing expiration date in '%s'\n", name); + return -1; + } + lstab->length = index; + + return 0; +} + +struct lstab *lstab_create(const char *filename) +{ + struct lstab *lstab = calloc(1, sizeof(*lstab)); + + if (!lstab) { + return NULL; + } + if (filename && filename[0]) { + if (lstab_read(lstab, filename)) { + free(lstab); + return NULL; + } + } else { + lstab_init(lstab); + } + return lstab; +} + +void lstab_destroy(struct lstab *lstab) +{ + free(lstab); +} + +enum lstab_result lstab_utc2tai(struct lstab *lstab, uint64_t utctime, + int *tai_offset) +{ + int epoch = -1, index, next; + + if (utctime > lstab->expiration_utc) { + return LSTAB_UNKNOWN; + } + + for (index = lstab->length - 1; index > -1; index--) { + if (utctime >= lstab->lstab[index].utc) { + epoch = index; + break; + } + } + + if (epoch == -1) { + return LSTAB_UNKNOWN; + } + + *tai_offset = lstab->lstab[epoch].offset; + next = epoch + 1; + + if (next < lstab->length && utctime == lstab->lstab[next].utc - 1) { + return LSTAB_AMBIGUOUS; + } + return LSTAB_OK; +} diff --git a/lstab.h b/lstab.h new file mode 100644 index 0000000..7c5d918 --- /dev/null +++ b/lstab.h @@ -0,0 +1,64 @@ +/** + * @file lstab.h + * @note Copyright (C) 2012 Richard Cochran + * @note SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef HAVE_LEAP_SECONDS_H +#define HAVE_LEAP_SECONDS_H + +#include + +/** Opaque type */ +struct lstab; + +/** + * Creates an instance of a leap second table. + * @param filename File from which to initialize the table. If NULL or empty, + * the hard coded default table will be used. + * @return A pointer to a leap second table on success, NULL otherwise. + */ +struct lstab *lstab_create(const char *filename); + +/** + * Destroys a leap second table instance. + * @param lstab A pointer obtained via lstab_create(). + */ +void lstab_destroy(struct lstab *lstab); + +/** + * Enumerates the possible result code for the lstab_utc2tai() method. + */ +enum lstab_result { + /** + * The given UTC value was found in the table, and the + * corresponding TAI time is utctime + tai_offset. + */ + LSTAB_OK, + + /** + * The given UTC value is out of the range of the table, and + * the tai_offset return value is not set. + */ + LSTAB_UNKNOWN, + + /** + * The given UTC value is ambiguous. The corresponding TAI time is either + * + * utctime + tai_offset + * or + * utctime + tai_offset + 1. + */ + LSTAB_AMBIGUOUS, +}; + +/** + * Returns the TAI - UTC offset for a given UTC time value. + * @param lstab A pointer obtained via lstab_create(). + * @param utctime The UTC time value of interest. + * @param tai_offset Pointer to a buffer to hold the result. + * @return One of the lstab_result enumeration values. + */ +enum lstab_result lstab_utc2tai(struct lstab *lstab, uint64_t utctime, + int *tai_offset); + +#endif