I have written the sample code that Hal suggested, along with its data
file.  I attach both to this e-mail message, and they are included as
embedded files with the updated paper, which is at the usual URL:

https://www.systemeyescomputerstore.com/proleptic_UTC.pdf

The data file is preliminary, going back only to just before 1700.
 That is enough to be useful, but it does not cover the whole date
range.  The data also appears in the paper as a new table in section 8:
Extraordinary Days by Julian Day Number.  Completing the data file will
add about 25,000 lines to it, which might make the paper excessively
long if it were included as a table.  Would a shorter table be
acceptable, even after the data file is completed?

Also, any other comments on the paper would be appreciated.
    John Sauter (john_sau...@systemeyescomputerstore.com)
-- 
PGP fingerprint = E24A D25B E5FE 4914 A603  49EC 7030 3EA1
9A0B 511E

#
# Table of Extraordinary Days.
#
# Copyright © 2016 by John Sauter.
# Licensed under the Creative Commons Attribution-ShareAlike 4.0 International
# license.  See https://creativecommons.org/license/by-sa/4.0/.
#
# A program that wishes to measure the time between two dates needs to know
# the length of each day.  Most days are ordinary days, of 86,400 seconds,
# but a few have 86,399 or 86,401 seconds, to keep clocks synchronized with
# the rotation of the Earth.  For dates since 1972, the IERS maintains a
# list of extraordinary days.  For earlier dates, we must imagine that
# current timekeeping rules were in effect and project a list of extraordinary
# days.  See "Extending Coordinated Universal Time to Dates Before 1972."
#
# This table of extraordinary days can be read by any program that needs
# the information.  It uses a simple format with three kinds of lines:
# (1) empty lines, like this one.  Note that any line can end with a comment,
# which starts with "#".  (2) symbol=value lines, which assign a value to
# a symbol.  Certain symbols have meaning, as described below.  (3) data lines,
# formatted as two numbers: the Julian Day Number of the extraordinary day
# followed by the value of DTAI at the end of that day.  Data lines must be
# ascending: earlier dates must come before later ones.
#
# The Julian Day Number is a simple count of days, starting on
# November 24, -4713.  DTAI is the difference between International Atomic
# Time (TAI) and Coordinated Universal Time (UTC).  That value is 0
# on January 1, 1958.  Every extraordinary day changes the value of DTAI
# by +1 for days with 86,401 seconds, or -1 for days with 86,399 seconds.
# Since the data lines must be ordered, each will have a DTAI that differs
# from the preceeding and succeeding line by plus or minus 1.
#
# The symbols used in this file are as follows:
#
# START_DATE the Julian Day Number of the beginning of the data.  There may
# be extraordinary days before this date, but they are not recorded in this
# file.  An attempt to determine the time between two dates, either of which
# is before the START_DATE, should be regarded as an error.
#
# END_DATE is the Julian Day Number of the end of the data.  There may be
# extraordinary days after this date, but they are not recorded in this file.
# An attempt to determine the time between two dates, either of which is
# after the END_DATE, should be regarded as an error.
#
# EXPIRATION_DATE is the Julian Day Number after which this file should have
# been updated.  If you find yourself using this file after its EXPIRATION_DATE,
# look for an updated version of the file.  If necessary you can update it
# yourself, using IERS Circular C.
#
# CHECKSUM is 64 hex digits of the SHA256 hash of this file, excluding the
# CHECKSUM line.  The sample reading software verifies the checksum, to
# defend against accidental corruption.
#
START_DATE=2341607      # 31 Dec 1698
END_DATE=2457751        # 28 Dec 2016
EXPIRATION_DATE=2457751 # 28 Dec 2016
#
# The data extends from just before 1700 through 2015.  I intend to extend
# it to earlier dates, back to -1000.
#
# JD    DTAI Day Month Year
2341607 -22 # 31 Dec 1698
2341788 -23 # 30 Jun 1699
2341972 -24 # 31 Dec 1699
2344163 -23 # 31 Dec 1705
2347815 -22 # 31 Dec 1715
2355120 -21 # 31 Dec 1735
2358773 -20 # 31 Dec 1745
2361695 -19 # 31 Dec 1753
2363156 -18 # 31 Dec 1757
2366078 -17 # 31 Dec 1765
2369730 -16 # 31 Dec 1775
2376305 -17 # 31 Dec 1793
2377035 -18 # 31 Dec 1795
2378131 -19 # 31 Dec 1798
2384339 -20 # 31 Dec 1815
2386531 -21 # 31 Dec 1821
2387261 -22 # 31 Dec 1823
2387992 -23 # 31 Dec 1825
2389088 -24 # 31 Dec 1828
2390914 -25 # 31 Dec 1833
2392375 -26 # 31 Dec 1837
2395297 -25 # 31 Dec 1845
2398949 -24 # 31 Dec 1855
2400776 -25 # 31 Dec 1860
2401506 -26 # 31 Dec 1862
2402237 -27 # 31 Dec 1864
2402602 -28 # 31 Dec 1865
2403332 -29 # 31 Dec 1867
2404063 -30 # 31 Dec 1869
2404428 -31 # 31 Dec 1870
2404793 -32 # 31 Dec 1871
2405524 -33 # 31 Dec 1873
2405889 -34 # 31 Dec 1874
2406620 -35 # 31 Dec 1876
2406985 -36 # 31 Dec 1877
2407350 -37 # 31 Dec 1878
2409907 -38 # 31 Dec 1885
2412829 -37 # 31 Dec 1893
2413925 -36 # 31 Dec 1896
2415020 -35 # 31 Dec 1899
2415385 -34 # 31 Dec 1900
2415750 -33 # 31 Dec 1901
2416115 -32 # 31 Dec 1902
2416296 -31 # 30 Jun 1903
2416480 -30 # 31 Dec 1903
2416846 -29 # 31 Dec 1904
2417211 -28 # 31 Dec 1905
2417392 -27 # 30 Jun 1906
2417576 -26 # 31 Dec 1906
2417941 -25 # 31 Dec 1907
2418307 -24 # 31 Dec 1908
2418488 -23 # 30 Jun 1909
2418672 -22 # 31 Dec 1909
2419037 -21 # 31 Dec 1910
2419402 -20 # 31 Dec 1911
2419768 -19 # 31 Dec 1912
2420133 -18 # 31 Dec 1913
2420498 -17 # 31 Dec 1914
2420679 -16 # 30 Jun 1915
2420863 -15 # 31 Dec 1915
2421229 -14 # 31 Dec 1916
2421594 -13 # 31 Dec 1917
2421959 -12 # 31 Dec 1918
2422324 -11 # 31 Dec 1919
2423785 -10 # 31 Dec 1923
2424881 -9 # 31 Dec 1926
2425977 -8 # 31 Dec 1929
2430360 -7 # 31 Dec 1941
2431090 -6 # 31 Dec 1943
2431821 -5 # 31 Dec 1945
2432551 -4 # 31 Dec 1947
2433282 -3 # 31 Dec 1949
2434378 -2 # 31 Dec 1952
2435473 -1 # 31 Dec 1955
2436204 0 # 31 Dec 1957
2436750 1 # 30 Jun 1959
2437481 2 # 30 Jun 1961
2438211 3 # 30 Jun 1963
2438761 4 # 31 Dec 1964
2439307 5 # 30 Jun 1966
2439672 6 # 30 Jun 1967
2440038 7 # 30 Jun 1968
2440403 8 # 30 Jun 1969
2440768 9 # 30 Jun 1970
2441133 10 # 30 Jun 1971
2441499 11 # 30 Jun 1972
2441683 12 # 31 Dec 1972
2442048 13 # 31 Dec 1973
2442413 14 # 31 Dec 1974
2442778 15 # 31 Dec 1975
2443144 16 # 31 Dec 1976
2443509 17 # 31 Dec 1977
2443874 18 # 31 Dec 1978
2444239 19 # 31 Dec 1979
2444786 20 # 30 Jun 1981
2445151 21 # 30 Jun 1982
2445516 22 # 30 Jun 1983
2446247 23 # 30 Jun 1985
2447161 24 # 31 Dec 1987
2447892 25 # 31 Dec 1989
2448257 26 # 31 Dec 1990
2448804 27 # 30 Jun 1992
2449169 28 # 30 Jun 1993
2449534 29 # 30 Jun 1994
2449899 30 # 30 Jun 1995
2450630 31 # 30 Jun 1997
2451179 32 # 31 Dec 1998
2453736 33 # 31 Dec 2005
2454832 34 # 31 Dec 2008
2456109 35 # 30 Jun 2012
2457204 36 # 30 Jun 2015

CHECKSUM=fdce8130222e064a2aa50d6d3c5fc0a1356318fd55a846d1d1fcdc692c0e3f80
#!/usr/bin/python
# -*- coding: utf-8
#
# read_extraodinary_days_table is a sample program which illustrates how
# to read the table of extraordinary days.

#   Copyright © 2016 by John Sauter <john_sau...@systemeyescomputerstore.com>

#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.

#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.

#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.

#   The author's contact information is as follows:
#     John Sauter
#     System Eyes Computer Store
#     20A Northwest Blvd.  Ste 345
#     Nashua, NH  03063-4066
#     telephone: (603) 424-1188
#     e-mail: john_sau...@systemeyescomputerstore.com

import sys
import re
import hashlib
import datetime
from jdcal import gcal2jd, jd2gcal
import pprint
import argparse

parser = argparse.ArgumentParser (
  formatter_class=argparse.RawDescriptionHelpFormatter,
  description='Read the table of extraordinary days.',
  epilog='Copyright © 2016 by John Sauter' + '\n' +
  'License GPL3+: GNU GPL version 3 or later; ' + '\n' +
  'see <http://gnu.org/licenses/gpl.html> for the full text ' +
  'of the license.' + '\n' +
  'This is free software: you are free to change and redistribute it. ' + '\n' +
  'There is NO WARRANTY, to the extent permitted by law. ' + '\n' + '\n'
  'The input file lists the extraordinary days; ' +
  'the output summarizes the information. ' + '\n')
parser.add_argument ('input_file',
                     help='the table of extraordinary days')
parser.add_argument ('--version', action='version', 
                     version='read_extraordinary_days_table 1.0 2016-04-27',
                     help='print the version number and exit')
parser.add_argument ('--trace', metavar='trace_file',
                     help='write trace output to the specified file')
parser.add_argument ('--latex-output', metavar='latex_output_file',
                     help='write data as a LaTeX longtable')
parser.add_argument ('--verbose', type=int, metavar='verbosity level',
                     help='control the amount of output from the program: ' +
                     '1 is normal, 0 suppresses summary messages')

do_trace = 0
tracefile = ""
do_latex_output = 0;
latex_output_file = ""
verbosity_level = 1
error_counter = 0

# Subroutine to convert a Julian Day Number to its equivalent Gregorian date.
month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
               "Aug", "Sep", "Oct", "Nov", "Dec"] 
def greg (jdn):
  ymdf = jd2gcal (float(jdn), 0.0)
  year_no = ymdf [0]
  month_no = ymdf [1]
  day_no = ymdf [2]
  month_name = month_names [month_no-1]
  return (str(day_no) + " " + month_name + " " + str(year_no))

# The data is kept in dictionary extraordinary_days, indexed by Julian
# Day number.  The symbols are kept in dictionary symbol_values, indexed
# by symbol name.  The length of each extraordinary day is kept in
# dictionary day_length, indexed by Julian Day Number.
extraordinary_days = {}
symbol_values = {}
day_length = {}

# Parse the command line.
arguments = parser.parse_args ()
arguments = vars(arguments)

if (arguments ['trace'] != None):
  do_trace = 1
  trace_file_name = arguments ['trace']
  tracefile = open (trace_file_name, 'wt')

if (arguments ['latex_output'] != None):
  do_latex_output = 1
  latex_output_file_name = arguments ['latex_output']
    
if (arguments ['verbose'] != None):
  verbosity_level = arguments ['verbose']

# Read the data file into memory.
file_name = arguments ['input_file']
# We use rU rather than rt to be liberal in recognizing line terminators,
# but always use the NL character to represent a line termnator in memory,
# so the checksum will not depend on the operating system's line termination
# convention.
infile = open (file_name, 'rU')
# Read the file into memory as a list of byte strings.
file_data = infile.readlines()
infile.close()

# Classify each line as either an empty line, a symbol=value line, or a
# data line.  Remember the values of the symbols and the data.
line_number = 0
previous_jdn = 0
previous_DTAI = 0
first_data_line = 1

for byte_string in file_data:
# The file data is assumed to be coded as utf-8.  Decode it into Unicode.
  line = byte_string.decode ('utf-8')
  line_number = line_number + 1
  if (re.match ("^\s*(#.*)?\n$", line)):
    continue;                   #  ignore empty lines.
  matchc = re.match ("^\s*(?P<keyword>(\w)+)\s*=\s*(?P<value>(\w)+)\s*(#.*)?\n$", line)
  if (matchc):
    # This line has the form keyword = value
    keyword = matchc.groupdict () ['keyword']
    value = matchc.groupdict() ['value']
    if keyword in symbol_values:
      print "Keyword " + keyword + " seen more than once."
      error_counter = error_counter + 1
    symbol_values[keyword] = value;
    if (do_trace == 1):
      tracefile.write ("Keyword " + keyword + "=" + value + "\n")
    continue

  matchd = re.match ("^\s*(?P<jdn>(\d)+)\s+(?P<DTAI>-?(\d)+)\s*(#.*)?\n$", line)
  if (matchd):
    # This line has the form julian_day_number DTAI
    jdn = matchd.groupdict () ['jdn']
    DTAI = matchd.groupdict () ['DTAI']
    if jdn in extraordinary_days:
      print "Julian Day Number " + str(jdn) + " seen more than once."
      error_counter = error_counter + 1
    if jdn < previous_jdn:
      print "Julian Day Number " + str(jdn) + " out of order."
      error_counter = error_counter + 1

    if (first_data_line == 0):
      if (abs(int(DTAI) - int(previous_DTAI)) != 1):
        print ("At Julian Day Number " + str(jdn) + ", DTAI of " + str(DTAI) +
               " does not differ from the previous DTAI of " +
               str(previous_DTAI) + " by plus or minus 1." + "\n")
        error_counter = error_counter + 1
      day_length[int(jdn)] = 86400 + (int(DTAI) - int(previous_DTAI))
    else:
      day_length[int(jdn)] = 86399
    extraordinary_days[int(jdn)] = int(DTAI)
    previous_jdn = jdn
    previous_DTAI = DTAI
    first_data_line = 0
    if (do_trace == 1):
      tracefile.write ("At Julian Day Number " + str(jdn) +
                       " DTAI was " + str(DTAI) + "." + "\n")
    continue

  # This line is not recognized
  print "Line " + str(line_number) + " is not recognized."
  print line
  error_counter = error_counter + 1

# Verify that the start, end and expiration dates are specified.
# IF the checksum is missing we will print the correct value and
# ask that it be added.
if ("START_DATE" not in symbol_values):
  print ("Start date is missing.")
  error_counter = error_counter + 1
else:
  start_date = symbol_values ["START_DATE"]
  if (verbosity_level > 0):
    print ("Start date is " + str(start_date) + " = " + greg (start_date))
if ("END_DATE" not in symbol_values):
  print ("End date is missing.")
  error_counter = error_counter + 1
else:
  end_date = symbol_values ["END_DATE"]
  if (verbosity_level > 0):
    print ("End date is " + str(end_date) + " = " + greg (end_date))
if ("EXPIRATION_DATE" not in symbol_values):
  print ("Expiration date is missing.")
  error_counter = error_counter + 1
else:
  expiration_date = symbol_values ["EXPIRATION_DATE"]
  if (verbosity_level > 0):
    print ("Expiration date is " + str(expiration_date) + " = " + 
           greg (expiration_date))
  today = datetime.datetime.now()
  today_jdn_pair = gcal2jd (today.year, today.month, today.day)
  today_jdn = int(today_jdn_pair [0] + today_jdn_pair [1] + 0.5)
  if (verbosity_level > 1):
    print ("Today, " + greg (today_jdn) + ", " +
           " expressed as a Julian Day Number, is " + str(today_jdn))
  if (today_jdn > expiration_date):
    print ("This file has expired.  You should find a later version," +
           " or update it yourself" + "\n" +
           "using leap second information from the IERS.")
    error_counter = error_counter + 1

 # If no errors have been detected yet, look for out-of-range data values.
if (error_counter == 0):
  for extraordinary_day in extraordinary_days:
    if (extraordinary_day < int(symbol_values["START_DATE"])):
      print ("Julian Day Number " + str(extraordinary_day) +
             " is before the start date of " + symbol_values["START_DATE"])
      error_counter = error_counter + 1
      if (extraordinary_day > int(symbol_values["END_DATE"])):
        print ("Julian Day NUmber " + str(extraordinary_day) +
               " is after the end date of " + symbol_values["END_DATE"])
        error_counter = error_counter + 1

# If there are still no errors, compute the checksum.
if (error_counter == 0):
  hash_function = hashlib.new('sha256')
  for byte_string in file_data:
    # Don't include the checksum line.
    omit_checksum = 0
    line = byte_string.decode ('utf-8')
    matchc = re.match ("^\s*(?P<keyword>(\w)+)\s*=\s*(?P<value>(\w)+)\s*(#.*)?\n$", line)
    if (matchc):
      # This line has the form keyword = value
      keyword = matchc.groupdict () ['keyword']
      value = matchc.groupdict() ['value']
      if (keyword == "CHECKSUM"):
        omit_checksum = 1
    if (omit_checksum == 0):
      hash_function.update (byte_string)
  computed_checksum = hash_function.hexdigest()
  if ("CHECKSUM" in symbol_values):
    if (symbol_values ["CHECKSUM"] != computed_checksum):
      print ("Checksum is incorrect.\n" +
             "Value in file is " + symbol_values ["CHECKSUM"] + ", " + "\n" +
             "but computed value is " + computed_checksum + "\n")
    else:
      if (verbosity_level > 0):
        print ("Checksum is correct.")
  else:
    print ("No checksum value in file.  Please add this line:\n" +
           "CHECKSUM=" + computed_checksum )

#
# Now do something useful with the data, as an example.
# Here we output the list of extraordinary days and the value of DTAI at the
# end of the day as LaTeX source, suitable for making a table.
#
if ((do_latex_output == 1) & (error_counter == 0)):
  latex_output_file = open (latex_output_file_name, 'wt')
  latex_output_file.write ("\\begin{longtable}{|c|c|c|c|}" + "\n")
  latex_output_file.write ("\\caption{Extraordinary days ")
  latex_output_file.write ("from " + greg (start_date) + " to " +
                           greg (end_date) + "} \\\\" + "\n")
  latex_output_file.write ("\\hline Julian Day Number & length in seconds &" +
                           " DTAI & ")
  latex_output_file.write ("Day Month Year \\endhead \\hline " + "\n")
  latex_output_file.write ("\\label{JDN_DTAI}" + "\n")
                               
  for extraordinary_day in sorted(extraordinary_days.keys()):
    DTAI = extraordinary_days [extraordinary_day]
    if extraordinary_day in day_length:
      jdn_length = day_length [extraordinary_day]
    else:
      jdn_length = 86400
    latex_output_file.write ("\\num{" + str(extraordinary_day) + "}&" +
                             "\\num{" + str(jdn_length) + "}&" +
                             "\\num{" + str(DTAI) + "}&" +
                             "\\# " + greg (extraordinary_day) +
                             "\\\\\\hline" + "\n")
  latex_output_file.write ("\\end{longtable}" + "\n")
  latex_output_file.close()

if (do_trace == 1):
  tracefile.close()

if (error_counter > 0):
  print "Encountered " + str(error_counter) + " errors."

Attachment: signature.asc
Description: This is a digitally signed message part

_______________________________________________
LEAPSECS mailing list
LEAPSECS@leapsecond.com
https://pairlist6.pair.net/mailman/listinfo/leapsecs

Reply via email to