[tz] [PROPOSED] Improve leapseconds support

Chris Woodbury tzocd at yahoo.com
Tue Sep 10 01:14:13 UTC 2019


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 at 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 }
}
-------------- next part --------------
A non-text attachment was scrubbed...
Name: DF_nleapseconds.awk.gz
Type: application/x-gzip
Size: 3386 bytes
Desc: not available
URL: <http://mm.icann.org/pipermail/tz/attachments/20190910/b16bb494/DF_nleapseconds.awk-0001.gz>


More information about the tz mailing list