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."
signature.asc
Description: This is a digitally signed message part
_______________________________________________ LEAPSECS mailing list LEAPSECS@leapsecond.com https://pairlist6.pair.net/mailman/listinfo/leapsecs