Re: [tz] [PROPOSED] Improve leapseconds support

The following has been kicking around my "Handling_Leapseconds directory for more than a year. It is a complete replacement for the current "leapseconds.awk". It uses the NTP timestamp *data* rather than the *comments* at the end of each leap-second datum from the leap-seconds.list file. I found it again today and finished the changes needed to use Dennis' sstamp_to_ymdhMs() AWK function. I've incorporated some AT&T UNIX programming style changes (which I learned back in the dark ages), too. I'm sure Paul will re-arrange it whichever way he wants before (or even if) it makes it into the TZ distribution. # -=*< This file is in the public domain !! >*=- # Generate zic LEAP commands from an NIST format 'leap-seconds.list'. BEGIN { print "# This file is in the public domain\n" print "# Allowance for leap-seconds added to each time zone file.\n" print "# This script generates zic \"Leap\" directives from public-domain data" print "# in the NIST format leap-seconds.list file (originally created for the" print "# Network Time Protocol (NTP) daemon by Judah Levine). Find it at:\n" print "# <ftp://ftp.nist.gov/pub/time/leap-seconds.list>" print "# -or- <ftp://ftp.boulder.nist.gov/pub/time/leap-seconds.list>\n" print "# For more info about the leap-seconds.list file, please see:\n" print "# The NTP Timescale and Leap Seconds by Prof. David L. Mills." print "# <https://www.eecis.udel.edu/~mills/leap.html>.\n" print "# The rules for leap-seconds are specified in Annex 1 (Time scales) of:\n" print "# \"Standard-frequency and time-signal emissions\"" print "# International Telecommunication Union -" print "# Radiocommunication Sector Recommendation (ITU-R) TF.460-6 (02/2002)\n" print "# <https://www.itu.int/rec/R-REC-TF.460-6-200202-I/>\n" print "# which is incorporated by reference in the [ITU] Radio Regulations.\n" print "# The International Earth Rotation and Reference Systems Service (IERS)" print "# periodically uses leap seconds to keep UTC to within 0.9 s of UT1" print "# (which is a proxy for the earth's angular orientation in space)" print "# and publishes leap-second data in a copyrighted file" print "# <https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat>." print "# USNO also has a comprehensive uncopyrighted list of leap-seconds at:" print "# <ftp://199.211.133.23/ser7/leapsec.dat> (AKA maia.usno.navy.mil)" print "# Also see: Coordinated Universal Time and the leap second by Judah Levine" print "# URSI Radio Sci Bull. 2016;89(4):30-6. doi:10.23919/URSIRSB.2016.7909995" print "# <https://ieeexplore.ieee.org/document/7909995>.\n" print "# There were no leap-seconds before 1972, as no official mechanism existed" print "# accounting for the discrepancy between atomic time (TAI) and UT1." print "# All leap-seconds are Stationary (S) at the given UTC time.\n" print "# The correction (+ or -) is made at the given time, so lines" print "# will typically look like:\n" print "# Leap YEAR MON DAY 23:59:60 + S" print "# - or -" print "# Leap YEAR MON DAY 23:59:59 - S\n" monthabbr[ 1] = "Jan" ; monthabbr[ 2] = "Feb" ; monthabbr[ 3] = "Mar" monthabbr[ 4] = "Apr" ; monthabbr[ 5] = "May" ; monthabbr[ 6] = "Jun" monthabbr[ 7] = "Jul" ; monthabbr[ 8] = "Aug" ; monthabbr[ 9] = "Sep" monthabbr[10] = "Oct" ; monthabbr[11] = "Nov" ; monthabbr[12] = "Dec" # In case the input has CRLF form a la NIST RS = "\r?\n" old_TAI_minus_UTC = int( 0 ) # This variable should be initialize !! sstamp_init() # Init function } # Note: maintaining case, spacing or punctuation can # not be depended upon when humans are involved... /^#[ \t]*[Uu]pdated through[ \t:]*/ || /^#[ \t]*[Ff]ile expires on[ \t:]*/ { last_lines = last_lines $0 "\n" } /^#[$][ \t]/ { updated = strtonum( $2 ) } # Save update NTP timestamp /^#[@][ \t]/ { expires = strtonum( $2 ) } # Save expiry NTP timestamp /^[ \t]*#/ { next } { NTP_timestamp = int( $1 ) # NTP epoch timestamp TAI_minus_UTC = int( $2 ) # delta T if ( old_TAI_minus_UTC ) { if ( old_TAI_minus_UTC < TAI_minus_UTC ) { sign = "23:59:60\t+" } else { sign = "23:59:59\t-" } sstamp_to_ymdhMs( NTP_timestamp - 1, ss_NTP ) # Note minus one !! printf "Leap\t%d\t%s\t%d\t%s\tS\n", ss_y, monthabbr[ ss_m ], ss_d, sign } old_TAI_minus_UTC = TAI_minus_UTC } END { # The difference between the NTP and POSIX epochs is 70 years # (including 17 leap days), each 24 hours of 60 minutes of 60 # seconds each. epoch_minus_NTP = ((1970 - 1900) * 365 + 17) * 24 * 60 * 60 print "" print "# POSIX timestamps for the data in this file:" sstamp_to_ymdhMs( updated, ss_NTP ) printf "# Updated: %s (%02d %s %d)\n", ( updated - epoch_minus_NTP ), ss_d, monthabbr[ ss_m ], ss_y sstamp_to_ymdhMs( expires, ss_NTP ) printf "# Expires: %s (%02d %s %d)\n", ( expires - epoch_minus_NTP ), ss_d, monthabbr[ ss_m ], ss_y printf "\n%s", last_lines } # Contributed by # # Dennis Ferguson <dennis.c.ferguson@gmail.com> on Sun, 14 Oct 2018 21:56:57 -0700 # # sstamp_to_ymdhMs() - convert (NTP, POSIX) seconds stamp to date and time # # Call as: # # sstamp_to_ymdhMs( sstamp, epoch_days ) # # where: # # sstamp - is a seconds timestamp, e.g. POSIX or NTP # epoch_days - is the timestamp epoch in (proleptic Gregorian) days since # 1 Mar 1600. ss_NTP is appropriate for a NTP sstamp, ss_POSIX # for a Unix sstamp. # # On return the following variables are set based on sstamp: # # ss_y - calendar year for the sstamp (e.g. 2016) # ss_m - month of the year (1-January to 12-December) # ss_d - day of the month # ss_h - hour (0-23) # ss_M - minute (0-59) # ss_s - second (0-59) # ss_w - day of week (0-Sunday to 6-Saturday) # # The function # # sstamp_init() # # should be called prior to using sstamp_to_ymdhMs(). # # The calendar computation part of this starts with a count of days since # March 1, 1600 (in a proleptic Gregorian calendar) and computes a ymd # date with days of the month numbered 0-30, months (March-February) numbered # 0-11 and years which run from March-February. The last four lines of code # in the function convert the date to a conventional day of month (1-31), # month (1-12, January-December) and Gregorian year. # # In the alternate calendar a year starts at March 1 and ends after the last # day of February. A quad-year starts on March 1 of a year evenly divisible # by 4 and ends after the last day of February 4 years later. A century # starts on and ends before March 1 in a year evenly divisible by 100 # (i.e. 1600, 1900, 2100). A quad-century starts on and ends before March 1 # in years divisible by 400 (1600, 2000, 2400). Given this the magic constants # used here are: # # days per year = 365 # days per quad-year = 1461 = (365 * 4) + 1 # days per century = 36524 = (1461 * 25) - 1 # days per quad-century = 146097 = (36524 * 4) + 1 # weekday (0-Sunday 6-Saturday) of first day of quad-century = 3 (Wednesday) # # Note that while the number of days in a quad-century is a constant the # number of days in each of the other time periods can vary by 1. In all # these cases, however, the variation is in the last day of the time period # (there might or might not be a February 29) where it is easy to deal with. # # The three standard day_epochs are: # # ss_MJD = 94493 # ss_NTP = 109513 # ss_POSIX = 135080 function sstamp_init(){ ss_mon_days[ 1] = 31 ss_mon_days[ 2] = 61 ss_mon_days[ 3] = 92 ss_mon_days[ 4] = 122 ss_mon_days[ 5] = 153 ss_mon_days[ 6] = 184 ss_mon_days[ 7] = 214 ss_mon_days[ 8] = 245 ss_mon_days[ 9] = 275 ss_mon_days[10] = 306 ss_mon_days[11] = 337 ss_NTP = 109513 ss_POSIX = 135080 } function sstamp_to_ymdhMs( sstamp, epoch_days, ss_qcent, ss_cent, ss_qyr, ss_yr ){ ss_s = sstamp % 86400 # isolate hms ss_h = int( ss_s / 3600 ) ss_s %= 3600 ss_M = int( ss_s / 60 ) ss_s %= 60 ss_d = int( sstamp / 86400 ) + epoch_days # select epoch ss_qcent = int( ss_d / 146097 ) ss_y = 1600 + ss_qcent * 400 ss_d -= ss_qcent * 146097 ss_w = ( ss_d + 3 ) % 7 ss_cent = int( ss_d / 36524 ) if (ss_cent == 4) { ss_cent = 3 } ss_d -= ss_cent * 36524 ss_y += ss_cent * 100 ss_qyr = int( ss_d / 1461 ) ss_y += ss_qyr * 4 ss_d -= ss_qyr * 1461 ss_yr = int(ss_d / 365) if ( ss_yr == 4 ) { ss_yr = 3 } ss_d -= ss_yr * 365 ss_y += ss_yr for ( ss_m = 11 ; ss_m > 0 ; ss_m-- ){ if( ss_d >= ss_mon_days[ss_m] ){ ss_d -= ss_mon_days[ss_m] ; break ; } } # convert to conventional calendar ss_d += 1; if (ss_m > 9) { ss_m -= 9 ; ss_y++ } else { ss_m += 3 } }

Thanks. I revamped that quite a bit, to try to make it clearer and to match the existing tzdb style better, and installed the attached proposed patch into the development tzdb version on GitHub.

On Sep 10, 2019, at 21:41, Paul Eggert <eggert@cs.ucla.edu> wrote:
Thanks. I revamped that quite a bit, to try to make it clearer and to match the existing tzdb style better, and installed the attached proposed patch into the development tzdb version on GitHub.
That looks very pretty to me, but I think there may be a typo in this bit of the patch: + for (month = 0; month < 12; month++) { + if (day < ss_mon_days[month]) + break + day -= ss_mon_days[month] + } I think you probably want to stop at 11 rather than 12. Dennis Ferguson

On 9/10/19 3:34 PM, Dennis Ferguson wrote:
I think you probably want to stop at 11 rather than 12.
Thanks for catching that typo; it would have caused a failure if we ever had an leap second announcement or expiration date (or leap second itself!) in February. I installed the attached patch.
participants (3)
-
Chris Woodbury
-
Dennis Ferguson
-
Paul Eggert