1*2723c7ffSPhilip Paeps# Generate zic format 'leapseconds' from NIST/IERS format 'leap-seconds.list'. 2ba2b2efdSGlen Barber 3ba2b2efdSGlen Barber# This file is in the public domain. 4ba2b2efdSGlen Barber 5798c0c0bSPhilip Paeps# This program uses awk arithmetic. POSIX requires awk to support 6798c0c0bSPhilip Paeps# exact integer arithmetic only through 10**10, which means for NTP 7798c0c0bSPhilip Paeps# timestamps this program works only to the year 2216, which is the 8798c0c0bSPhilip Paeps# year 1900 plus 10**10 seconds. However, in practice 9798c0c0bSPhilip Paeps# POSIX-conforming awk implementations invariably use IEEE-754 double 10798c0c0bSPhilip Paeps# and so support exact integers through 2**53. By the year 2216, 11798c0c0bSPhilip Paeps# POSIX will almost surely require at least 2**53 for awk, so for NTP 12798c0c0bSPhilip Paeps# timestamps this program should be good until the year 285,428,681 13798c0c0bSPhilip Paeps# (the year 1900 plus 2**53 seconds). By then leap seconds will be 14798c0c0bSPhilip Paeps# long obsolete, as the Earth will likely slow down so much that 15798c0c0bSPhilip Paeps# there will be more than 25 hours per day and so some other scheme 16798c0c0bSPhilip Paeps# will be needed. 17798c0c0bSPhilip Paeps 18ba2b2efdSGlen BarberBEGIN { 19ba2b2efdSGlen Barber print "# Allowance for leap seconds added to each time zone file." 20ba2b2efdSGlen Barber print "" 21ba2b2efdSGlen Barber print "# This file is in the public domain." 22ba2b2efdSGlen Barber print "" 23ba2b2efdSGlen Barber print "# This file is generated automatically from the data in the public-domain" 24*2723c7ffSPhilip Paeps print "# NIST/IERS format leap-seconds.list file, which can be copied from" 25dc505d53SPhilip Paeps print "# <https://hpiers.obspm.fr/iers/bul/bulc/ntp/leap-seconds.list>" 26*2723c7ffSPhilip Paeps print "# or, in a variant with different comments, from" 27*2723c7ffSPhilip Paeps print "# <ftp://ftp.boulder.nist.gov/pub/time/leap-seconds.list>." 28ba2b2efdSGlen Barber print "# For more about leap-seconds.list, please see" 29ba2b2efdSGlen Barber print "# The NTP Timescale and Leap Seconds" 3089abb9f8SPhilip Paeps print "# <https://www.eecis.udel.edu/~mills/leap.html>." 31ba2b2efdSGlen Barber print "" 32798c0c0bSPhilip Paeps print "# The rules for leap seconds are specified in Annex 1 (Time scales) of:" 33798c0c0bSPhilip Paeps print "# Standard-frequency and time-signal emissions." 34798c0c0bSPhilip Paeps print "# International Telecommunication Union - Radiocommunication Sector" 35798c0c0bSPhilip Paeps print "# (ITU-R) Recommendation TF.460-6 (02/2002)" 36798c0c0bSPhilip Paeps print "# <https://www.itu.int/rec/R-REC-TF.460-6-200202-I/>." 37798c0c0bSPhilip Paeps print "# The International Earth Rotation and Reference Systems Service (IERS)" 38ba2b2efdSGlen Barber print "# periodically uses leap seconds to keep UTC to within 0.9 s of UT1" 39798c0c0bSPhilip Paeps print "# (a proxy for Earth's angle in space as measured by astronomers)" 4089abb9f8SPhilip Paeps print "# and publishes leap second data in a copyrighted file" 4189abb9f8SPhilip Paeps print "# <https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat>." 4289abb9f8SPhilip Paeps print "# See: Levine J. Coordinated Universal Time and the leap second." 438d7edd17SPhilip Paeps print "# URSI Radio Sci Bull. 2016;89(4):30-6. doi:10.23919/URSIRSB.2016.7909995" 4489abb9f8SPhilip Paeps print "# <https://ieeexplore.ieee.org/document/7909995>." 452c5e84ccSPhilip Paeps print "" 46798c0c0bSPhilip Paeps print "# There were no leap seconds before 1972, as no official mechanism" 47798c0c0bSPhilip Paeps print "# accounted for the discrepancy between atomic time (TAI) and the earth's" 48798c0c0bSPhilip Paeps print "# rotation. The first (\"1 Jan 1972\") data line in leap-seconds.list" 492c5e84ccSPhilip Paeps print "# does not denote a leap second; it denotes the start of the current definition" 502c5e84ccSPhilip Paeps print "# of UTC." 51ba2b2efdSGlen Barber print "" 52798c0c0bSPhilip Paeps print "# All leap-seconds are Stationary (S) at the given UTC time." 53798c0c0bSPhilip Paeps print "# The correction (+ or -) is made at the given time, so in the unlikely" 54798c0c0bSPhilip Paeps print "# event of a negative leap second, a line would look like this:" 55798c0c0bSPhilip Paeps print "# Leap YEAR MON DAY 23:59:59 - S" 56798c0c0bSPhilip Paeps print "# Typical lines look like this:" 57798c0c0bSPhilip Paeps print "# Leap YEAR MON DAY 23:59:60 + S" 58ba2b2efdSGlen Barber 5989abb9f8SPhilip Paeps monthabbr[ 1] = "Jan" 6089abb9f8SPhilip Paeps monthabbr[ 2] = "Feb" 6189abb9f8SPhilip Paeps monthabbr[ 3] = "Mar" 6289abb9f8SPhilip Paeps monthabbr[ 4] = "Apr" 6389abb9f8SPhilip Paeps monthabbr[ 5] = "May" 6489abb9f8SPhilip Paeps monthabbr[ 6] = "Jun" 6589abb9f8SPhilip Paeps monthabbr[ 7] = "Jul" 6689abb9f8SPhilip Paeps monthabbr[ 8] = "Aug" 6789abb9f8SPhilip Paeps monthabbr[ 9] = "Sep" 6889abb9f8SPhilip Paeps monthabbr[10] = "Oct" 6989abb9f8SPhilip Paeps monthabbr[11] = "Nov" 7089abb9f8SPhilip Paeps monthabbr[12] = "Dec" 71798c0c0bSPhilip Paeps 72798c0c0bSPhilip Paeps sstamp_init() 7389abb9f8SPhilip Paeps} 74ba2b2efdSGlen Barber 75dd5f96c4SPhilip Paeps# In case the input has CRLF form a la NIST. 76dd5f96c4SPhilip Paeps{ sub(/\r$/, "") } 77dd5f96c4SPhilip Paeps 78798c0c0bSPhilip Paeps/^#[ \t]*[Uu]pdated through/ || /^#[ \t]*[Ff]ile expires on/ { 79ba2b2efdSGlen Barber last_lines = last_lines $0 "\n" 80ba2b2efdSGlen Barber} 81ba2b2efdSGlen Barber 8289abb9f8SPhilip Paeps/^#[$][ \t]/ { updated = $2 } 8389abb9f8SPhilip Paeps/^#[@][ \t]/ { expires = $2 } 8489abb9f8SPhilip Paeps 85798c0c0bSPhilip Paeps/^[ \t]*#/ { next } 86ba2b2efdSGlen Barber 87ba2b2efdSGlen Barber{ 88ba2b2efdSGlen Barber NTP_timestamp = $1 89ba2b2efdSGlen Barber TAI_minus_UTC = $2 90ba2b2efdSGlen Barber if (old_TAI_minus_UTC) { 91ba2b2efdSGlen Barber if (old_TAI_minus_UTC < TAI_minus_UTC) { 92ba2b2efdSGlen Barber sign = "23:59:60\t+" 93ba2b2efdSGlen Barber } else { 94ba2b2efdSGlen Barber sign = "23:59:59\t-" 95ba2b2efdSGlen Barber } 96798c0c0bSPhilip Paeps sstamp_to_ymdhMs(NTP_timestamp - 1, ss_NTP) 97798c0c0bSPhilip Paeps printf "Leap\t%d\t%s\t%d\t%s\tS\n", \ 98798c0c0bSPhilip Paeps ss_year, monthabbr[ss_month], ss_mday, sign 99ba2b2efdSGlen Barber } 100ba2b2efdSGlen Barber old_TAI_minus_UTC = TAI_minus_UTC 101ba2b2efdSGlen Barber} 102ba2b2efdSGlen Barber 103ba2b2efdSGlen BarberEND { 104259e2ad7SPhilip Paeps print "" 105259e2ad7SPhilip Paeps 106259e2ad7SPhilip Paeps if (expires) { 107dd5f96c4SPhilip Paeps sstamp_to_ymdhMs(expires, ss_NTP) 108dd5f96c4SPhilip Paeps 109dd5f96c4SPhilip Paeps print "# UTC timestamp when this leap second list expires." 110dd5f96c4SPhilip Paeps print "# Any additional leap seconds will come after this." 11112a899b6SPhilip Paeps if (! EXPIRES_LINE) { 112dd5f96c4SPhilip Paeps print "# This Expires line is commented out for now," 113dd5f96c4SPhilip Paeps print "# so that pre-2020a zic implementations do not reject this file." 11412a899b6SPhilip Paeps } 115dd5f96c4SPhilip Paeps printf "%sExpires %.4d\t%s\t%.2d\t%.2d:%.2d:%.2d\n", \ 116dd5f96c4SPhilip Paeps EXPIRES_LINE ? "" : "#", \ 117dd5f96c4SPhilip Paeps ss_year, monthabbr[ss_month], ss_mday, ss_hour, ss_min, ss_sec 118259e2ad7SPhilip Paeps } else { 119259e2ad7SPhilip Paeps print "# (No Expires line, since the expires time is unknown.)" 120259e2ad7SPhilip Paeps } 121dd5f96c4SPhilip Paeps 12289abb9f8SPhilip Paeps # The difference between the NTP and POSIX epochs is 70 years 12389abb9f8SPhilip Paeps # (including 17 leap days), each 24 hours of 60 minutes of 60 12489abb9f8SPhilip Paeps # seconds each. 12589abb9f8SPhilip Paeps epoch_minus_NTP = ((1970 - 1900) * 365 + 17) * 24 * 60 * 60 12689abb9f8SPhilip Paeps 12789abb9f8SPhilip Paeps print "" 12889abb9f8SPhilip Paeps print "# POSIX timestamps for the data in this file:" 129259e2ad7SPhilip Paeps if (updated) { 130798c0c0bSPhilip Paeps sstamp_to_ymdhMs(updated, ss_NTP) 131798c0c0bSPhilip Paeps printf "#updated %d (%.4d-%.2d-%.2d %.2d:%.2d:%.2d UTC)\n", \ 132798c0c0bSPhilip Paeps updated - epoch_minus_NTP, \ 133798c0c0bSPhilip Paeps ss_year, ss_month, ss_mday, ss_hour, ss_min, ss_sec 134259e2ad7SPhilip Paeps } else { 135259e2ad7SPhilip Paeps print "#(updated time unknown)" 136259e2ad7SPhilip Paeps } 137259e2ad7SPhilip Paeps if (expires) { 138798c0c0bSPhilip Paeps sstamp_to_ymdhMs(expires, ss_NTP) 139798c0c0bSPhilip Paeps printf "#expires %d (%.4d-%.2d-%.2d %.2d:%.2d:%.2d UTC)\n", \ 140798c0c0bSPhilip Paeps expires - epoch_minus_NTP, \ 141798c0c0bSPhilip Paeps ss_year, ss_month, ss_mday, ss_hour, ss_min, ss_sec 142259e2ad7SPhilip Paeps } else { 143259e2ad7SPhilip Paeps print "#(expires time unknown)" 144259e2ad7SPhilip Paeps } 145ba2b2efdSGlen Barber printf "\n%s", last_lines 146ba2b2efdSGlen Barber} 147798c0c0bSPhilip Paeps 148798c0c0bSPhilip Paeps# sstamp_to_ymdhMs - convert seconds timestamp to date and time 149798c0c0bSPhilip Paeps# 150798c0c0bSPhilip Paeps# Call as: 151798c0c0bSPhilip Paeps# 152798c0c0bSPhilip Paeps# sstamp_to_ymdhMs(sstamp, epoch_days) 153798c0c0bSPhilip Paeps# 154798c0c0bSPhilip Paeps# where: 155798c0c0bSPhilip Paeps# 156798c0c0bSPhilip Paeps# sstamp - is the seconds timestamp. 157798c0c0bSPhilip Paeps# epoch_days - is the timestamp epoch in Gregorian days since 1600-03-01. 158798c0c0bSPhilip Paeps# ss_NTP is appropriate for an NTP sstamp. 159798c0c0bSPhilip Paeps# 160798c0c0bSPhilip Paeps# Both arguments should be nonnegative integers. 161798c0c0bSPhilip Paeps# On return, the following variables are set based on sstamp: 162798c0c0bSPhilip Paeps# 163798c0c0bSPhilip Paeps# ss_year - Gregorian calendar year 164798c0c0bSPhilip Paeps# ss_month - month of the year (1-January to 12-December) 165798c0c0bSPhilip Paeps# ss_mday - day of the month (1-31) 166798c0c0bSPhilip Paeps# ss_hour - hour (0-23) 167798c0c0bSPhilip Paeps# ss_min - minute (0-59) 168798c0c0bSPhilip Paeps# ss_sec - second (0-59) 169798c0c0bSPhilip Paeps# ss_wday - day of week (0-Sunday to 6-Saturday) 170798c0c0bSPhilip Paeps# 171798c0c0bSPhilip Paeps# The function sstamp_init should be called prior to using sstamp_to_ymdhMs. 172798c0c0bSPhilip Paeps 173798c0c0bSPhilip Paepsfunction sstamp_init() 174798c0c0bSPhilip Paeps{ 175798c0c0bSPhilip Paeps # Days in month N, where March is month 0 and January month 10. 176798c0c0bSPhilip Paeps ss_mon_days[ 0] = 31 177798c0c0bSPhilip Paeps ss_mon_days[ 1] = 30 178798c0c0bSPhilip Paeps ss_mon_days[ 2] = 31 179798c0c0bSPhilip Paeps ss_mon_days[ 3] = 30 180798c0c0bSPhilip Paeps ss_mon_days[ 4] = 31 181798c0c0bSPhilip Paeps ss_mon_days[ 5] = 31 182798c0c0bSPhilip Paeps ss_mon_days[ 6] = 30 183798c0c0bSPhilip Paeps ss_mon_days[ 7] = 31 184798c0c0bSPhilip Paeps ss_mon_days[ 8] = 30 185798c0c0bSPhilip Paeps ss_mon_days[ 9] = 31 186798c0c0bSPhilip Paeps ss_mon_days[10] = 31 187798c0c0bSPhilip Paeps 188798c0c0bSPhilip Paeps # Counts of days in a Gregorian year, quad-year, century, and quad-century. 189798c0c0bSPhilip Paeps ss_year_days = 365 190798c0c0bSPhilip Paeps ss_quadyear_days = ss_year_days * 4 + 1 191798c0c0bSPhilip Paeps ss_century_days = ss_quadyear_days * 25 - 1 192798c0c0bSPhilip Paeps ss_quadcentury_days = ss_century_days * 4 + 1 193798c0c0bSPhilip Paeps 194798c0c0bSPhilip Paeps # Standard day epochs, suitable for epoch_days. 195798c0c0bSPhilip Paeps # ss_MJD = 94493 196798c0c0bSPhilip Paeps # ss_POSIX = 135080 197798c0c0bSPhilip Paeps ss_NTP = 109513 198798c0c0bSPhilip Paeps} 199798c0c0bSPhilip Paeps 200798c0c0bSPhilip Paepsfunction sstamp_to_ymdhMs(sstamp, epoch_days, \ 201798c0c0bSPhilip Paeps quadcentury, century, quadyear, year, month, day) 202798c0c0bSPhilip Paeps{ 203798c0c0bSPhilip Paeps ss_hour = int(sstamp / 3600) % 24 204798c0c0bSPhilip Paeps ss_min = int(sstamp / 60) % 60 205798c0c0bSPhilip Paeps ss_sec = sstamp % 60 206798c0c0bSPhilip Paeps 207798c0c0bSPhilip Paeps # Start with a count of days since 1600-03-01 Gregorian. 208798c0c0bSPhilip Paeps day = epoch_days + int(sstamp / (24 * 60 * 60)) 209798c0c0bSPhilip Paeps 210798c0c0bSPhilip Paeps # Compute a year-month-day date with days of the month numbered 211798c0c0bSPhilip Paeps # 0-30, months (March-February) numbered 0-11, and years that start 212798c0c0bSPhilip Paeps # start March 1 and end after the last day of February. A quad-year 213798c0c0bSPhilip Paeps # starts on March 1 of a year evenly divisible by 4 and ends after 214798c0c0bSPhilip Paeps # the last day of February 4 years later. A century starts on and 215798c0c0bSPhilip Paeps # ends before March 1 in years evenly divisible by 100. 216798c0c0bSPhilip Paeps # A quad-century starts on and ends before March 1 in years divisible 217798c0c0bSPhilip Paeps # by 400. While the number of days in a quad-century is a constant, 218798c0c0bSPhilip Paeps # the number of days in each other time period can vary by 1. 219798c0c0bSPhilip Paeps # Any variation is in the last day of the time period (there might 220798c0c0bSPhilip Paeps # or might not be a February 29) where it is easy to deal with. 221798c0c0bSPhilip Paeps 222798c0c0bSPhilip Paeps quadcentury = int(day / ss_quadcentury_days) 223798c0c0bSPhilip Paeps day -= quadcentury * ss_quadcentury_days 224798c0c0bSPhilip Paeps ss_wday = (day + 3) % 7 225798c0c0bSPhilip Paeps century = int(day / ss_century_days) 226798c0c0bSPhilip Paeps century -= century == 4 227798c0c0bSPhilip Paeps day -= century * ss_century_days 228798c0c0bSPhilip Paeps quadyear = int(day / ss_quadyear_days) 229798c0c0bSPhilip Paeps day -= quadyear * ss_quadyear_days 230798c0c0bSPhilip Paeps year = int(day / ss_year_days) 231798c0c0bSPhilip Paeps year -= year == 4 232798c0c0bSPhilip Paeps day -= year * ss_year_days 233798c0c0bSPhilip Paeps for (month = 0; month < 11; month++) { 234798c0c0bSPhilip Paeps if (day < ss_mon_days[month]) 235798c0c0bSPhilip Paeps break 236798c0c0bSPhilip Paeps day -= ss_mon_days[month] 237798c0c0bSPhilip Paeps } 238798c0c0bSPhilip Paeps 239798c0c0bSPhilip Paeps # Convert the date to a conventional day of month (1-31), 240798c0c0bSPhilip Paeps # month (1-12, January-December) and Gregorian year. 241798c0c0bSPhilip Paeps ss_mday = day + 1 242798c0c0bSPhilip Paeps if (month <= 9) { 243798c0c0bSPhilip Paeps ss_month = month + 3 244798c0c0bSPhilip Paeps } else { 245798c0c0bSPhilip Paeps ss_month = month - 9 246798c0c0bSPhilip Paeps year++ 247798c0c0bSPhilip Paeps } 248798c0c0bSPhilip Paeps ss_year = 1600 + quadcentury * 400 + century * 100 + quadyear * 4 + year 249798c0c0bSPhilip Paeps} 250