Source code for contrib.misc.datetime_tests

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# contrib/misc/datetime_tests.py
#

import os
import sys
import importlib
import datetime as dtime
from unittest.mock import patch

PWD = os.path.dirname(os.path.abspath(__file__))
BASE_DIR = os.path.dirname(os.path.dirname(PWD))
sys.path.append(BASE_DIR)

from _timestamp import TimestampUtils
from badidatetime import (BahaiCalendar, GregorianCalendar, datetime,
                          date as badi_date, timezone, timedelta)
from badidatetime._timedateutils import _td_utils
badidt = importlib.import_module('badidatetime.datetime')


[docs] class DatetimeTests(BahaiCalendar, TimestampUtils): GMT_COORDS = (51.477928, -0.001545, 0.0) BADI_COORDS = BahaiCalendar._BAHAI_LOCATION[:3] LOCAL_COORDS = (35.5894, -78.7792, -5.0) MONTHS = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 0, 19) GREG_BADI_DATES = ( ((1, 3, 19, 18, 14, 32, 553600), (-1842, 1, 1)), ((43, 3, 20, 18, 14, 26, 246400), (-1800, 1, 1)), ((143, 3, 20, 18, 15, 5, 558400), (-1700, 1, 1)), ((243, 3, 20, 18, 16, 31, 8000), (-1600, 1, 1)), ((343, 3, 20, 18, 17, 9, 715200), (-1500, 1, 1)), ((443, 3, 20, 18, 17, 3, 235200), (-1400, 1, 1)), ((543, 3, 20, 18, 17, 42, 115200), (-1300, 1, 1)), ((643, 3, 20, 18, 18, 20, 131200), (-1200, 1, 1)), ((743, 3, 20, 18, 19, 45, 667200), (-1100, 1, 1)), ((843, 3, 20, 18, 19, 37, 27200), (-1000, 1, 1)), ((943, 3, 20, 18, 20, 14, 870400), (-900, 1, 1)), ((1043, 3, 20, 18, 20, 53, 318400), (-800, 1, 1)), ((1143, 3, 20, 18, 22, 17, 817600), (-700, 1, 1)), ((1243, 3, 20, 18, 22, 8, 40000), (-600, 1, 1)), ((1343, 3, 20, 18, 22, 45, 984000), (-500, 1, 1)), ((1443, 3, 20, 18, 23, 22, 790400), (-400, 1, 1)), ((1543, 3, 20, 18, 24, 48, 844800), (-300, 1, 1)), ((1582, 3, 20, 18, 24, 27, 158400), (-261, 1, 1)), ((1582, 4, 7), (-261, 1, 19)), ((1582, 4, 26), (-261, 2, 19)), ((1582, 5, 15), (-261, 3, 19)), ((1582, 6, 3), (-261, 4, 19)), ((1582, 6, 22), (-261, 5, 19)), ((1582, 7, 11), (-261, 6, 19)), ((1582, 7, 30), (-261, 7, 19)), ((1582, 8, 18), (-261, 8, 19)), ((1582, 9, 6), (-261, 9, 19)), ((1582, 9, 25), (-261, 10, 19)), ((1582, 10, 4), (-261, 11, 9)), ((1582, 10, 4, 17, 41, 27, 686400), (-261, 11, 9)), ((1582, 10, 14), (-261, 11, 19)), ((1643, 3, 20, 18, 16, 28, 675200), (-200, 1, 1)), ((1743, 3, 20, 18, 16, 13, 641600), (-100, 1, 1)), ((1752, 9, 2), (-91, 9, 16)), ((1752, 9, 3), (-91, 9, 17)), ((1752, 9, 13), (-91, 10, 8)), ((1752, 9, 14), (-91, 10, 9)), ((1843, 3, 20, 18, 16, 48, 806400), (0, 1, 1)), ((1844, 3, 19, 18, 16, 36, 710400), (1, 1, 1)), ((1943, 3, 20, 18, 16, 32, 563200), (100, 1, 1)), ((2024, 3, 19, 18, 15, 57, 312000), (181, 1, 1)), ((2024, 4, 6), (181, 1, 19)), ((2024, 4, 25), (181, 2, 19)), ((2024, 5, 14), (181, 3, 19)), ((2024, 6, 2), (181, 4, 19)), ((2024, 6, 21), (181, 5, 19)), ((2043, 3, 20, 18, 16, 16, 406400), (200, 1, 1)), ((2143, 3, 20, 18, 15, 59, 731200), (300, 1, 1)), ((2243, 3, 20, 18, 15, 41, 587200), (400, 1, 1)), ((2343, 3, 20, 18, 16, 15, 283200), (500, 1, 1)), ((2443, 3, 20, 18, 15, 56, 707200), (600, 1, 1)), ((2543, 3, 20, 18, 15, 37, 353600), (700, 1, 1)), ((2643, 3, 20, 18, 15, 18, 345600), (800, 1, 1)), ((2743, 3, 20, 18, 15, 50, 54400), (900, 1, 1)), ((2843, 3, 20, 18, 15, 29, 664000), (1000, 1, 1)), ((2943, 3, 20, 18, 15, 9, 100800), (1100, 1, 1)), ((3004, 3, 20, 18, 15, 14, 630400), (1161, 1, 1)), ) """ tuple: Listy of Gregorian and Badí' dates. :meta hide-value: """ TIMESTAMP_DATES = ( # Sunset on day (1, 3, 19), (1844, 3, 19), (1930, 11, 15), (1970, 1, 1), (1970, 1, 2), (2025, 3, 19), (2026, 1, 16), ) """ tuple: List of Gregorian dates. """ def __init__(self): super().__init__() self.gc = GregorianCalendar()
[docs] def analyze_ordinal_error_list(self, options): """ Find the errors between the Badi datetime ordinals and the Python date time ordinals. -a """ data = [] for g_date, b_date in self.GREG_BADI_DATES: g_dt = dtime.datetime(*g_date) g_ord = g_dt.toordinal() b_dt = datetime(*b_date) b_ord = b_dt.toordinal() same = g_ord == b_ord diff = g_ord - b_ord g_jd = self.gc.jd_from_gregorian_date(g_date, exact=True) b_jd = self.jd_from_badi_date(b_date, *self.LOCAL_COORDS) jd_diff = round(g_jd - b_jd, 6) date = self.badi_date_from_jd(b_jd, *self.LOCAL_COORDS, short=True, trim=True, rtd=True) d_diff = self._subtract_tuples(b_date, date) leap = self._is_leap_year(b_date[0]) data.append((g_date, g_ord, b_date, b_ord, same, diff, g_jd, jd_diff, date, d_diff, leap)) return data
[docs] def analyze_ordinal_error_create(self, options): """ Find the errors between conversion between badi dates to JD and vice versa. This tests the BahaiCalendar.badi_date_from_jd() method. Both the jd_from_badi_date() and badi_date_from_jd() methods default to the the time zone in Tehran. -b Also if -S and -E are used they must be used together and refer to Badi years. """ lat, lon, zone = self.GMT_COORDS data = [] start = options.start end = options.end for year in range(start, end): is_leap = self._is_leap_year(year) for month in self.MONTHS: dm = 19 if month != 0 else 4 + is_leap for day in range(1, dm + 1): date = (year, month, day) jd = self.jd_from_badi_date(date, lat, lon, zone) # Get the Badi date from the Julian Period day. b_date = self.badi_date_from_jd( jd, lat, lon, zone, short=True) # Difference of the date converted to a JD then back to # a date again then subtract the converted date from the # original date. They should be the same. diff0 = self._subtract_tuples(b_date, date) # The ordinal date. *** TODO *** Use the new ordinal code. ord = self._ordinal_from_jd(jd) o = datetime.fromordinal(ord, short=True) o_date = (o.year, o.month, o.day) # Difference of the Badi datetime derived from an ordinal # then subtract the derived date original date. They # should be the same. diff1 = self._subtract_tuples(o_date, date) # Get the Gregorian date. g_date = self.gc.gregorian_date_from_jd( jd, hms=True, exact=True) data.append((g_date, jd, ord, date, b_date, o_date, diff0, diff1)) return data
[docs] def analyze_timestamp_errors(self, options): """ Find the errors in timestamp conversions to Badi dates. This test tries to ensures that the badidatetime.date.today() method changes date on sunset based on local time. https://www.unixtimestamp.com -c with -A, -O, -Z, -D, and optional -U (seconds instead of minutes) """ lat = options.latitude lon = options.longitude zone = options.zone # Get min and max minutes or seconds. delta = options.delta // 2 min_delta = - delta max_delta = + delta + 1 step = 1.0 / (86400.0 if options.seconds else 1440.0) data = [] with patch.object(badidt, 'LOCAL_COORD', (lat, lon, zone)): for g_date in self.TIMESTAMP_DATES: hist_jd = self.gc.jd_from_gregorian_date(g_date) hist_ss = self._sun_setting(hist_jd, lat, lon) astro_ss = self._exact_from_meeus(hist_ss) ss_ts = (astro_ss - self._POSIX_EPOCH) * self._SECONDS_PER_DAY ss_items = (g_date, astro_ss, ss_ts) items = [] for delta in range(min_delta, max_delta): #test_jd = astro_ss + (delta / mult) test_jd = astro_ss + (delta * step) ts = (test_jd - self._POSIX_EPOCH) * self._SECONDS_PER_DAY today = badi_date.fromtimestamp(ts, short=True) items.append((delta, test_jd, ts, today)) data.append((ss_items, items)) return data
[docs] def test_sunset_flip_invariant(self, options): lat = options.latitude lon = options.longitude zone = options.zone with patch.object(badidt, "LOCAL_COORD", (lat, lon, zone)): for g_date in self.TIMESTAMP_DATES: jd = self.gc.jd_from_gregorian_date(g_date) sunset_jd = self._sun_setting(jd, lat, lon) sunset_ts = ((sunset_jd - self._POSIX_EPOCH) * self._SECONDS_PER_DAY) before = badi_date.fromtimestamp(sunset_ts - 1, short=True) at = badi_date.fromtimestamp(sunset_ts, short=True) after = badi_date.fromtimestamp(sunset_ts + 1, short=True) assert before != after, ( f"No date flip across sunset for {g_date}" ) assert before == at or at == after, ( f"Unexpected double flip near sunset for {g_date}" )
[docs] def check_dates(self, options): """ Check for correct dates between different methods. -d with -S and -E (Gregorian) -A -O -Z """ data = [] start = options.start end = options.end lat = options.latitude lon = options.longitude zone = options.zone tz = dtime.timezone(dtime.timedelta(hours=zone)) for year in range(start, end): leap = self._gc._is_leap_year(year) for month, days in enumerate(self.gc._MONTHS, start=1): max_days = days + leap for day in range(1, max_days + 1): if year == 1 and (month < 3 or month == 3 and day < 19): continue date = (year, month, day) # Get Badi date from ordinal. g_ord = dtime.datetime(*date, tzinfo=tz).toordinal() dt0 = datetime.fromordinal(g_ord, short=True) b_date0 = self._trim_hms(dt0.b_date + dt0.b_time) # Get Badi date from JD. jd = self.gc.jd_from_gregorian_date(date, exact=True) b_date1 = self.badi_date_from_jd( jd, lat, lon, zone, us=True, short=True) # Get Badi date from the Gregorian date. b_date2 = self.badi_date_from_gregorian_date( date, lat, lon, zone, us=True, short=True) # Get Badi date from timestamp. g_ts = dtime.datetime(*date, tzinfo=tz).timestamp() dt1 = datetime.fromtimestamp(g_ts, short=True) b_date3 = dt1.b_date + dt1.b_time diff0 = [(i, a, b) for i, (a, b) in enumerate( zip(b_date1, b_date2)) if a != b] diff1 = [(i, a, b) for i, (a, b) in enumerate( zip(b_date1, b_date3)) if a != b] data.append((date, b_date0, b_date1, b_date2, b_date3, diff0, diff1)) return data
[docs] def test_ordinals(self, options): """ Check that the ordinals are correct. -o with -S and -E """ data = [] start = options.start end = options.end assert (self.MINYEAR - 1) < start < (self.MAXYEAR + 1), ( f"Start '{start}' must be from {self.MINYEAR} " f"to {self.MAXYEAR}.") assert self.MINYEAR < end < (self.MAXYEAR + 2), ( f"End '{end}' must be from {self.MINYEAR} " f"to {self.MAXYEAR}") prev_ord = 0 for year in range(start, end): is_leap = self._is_leap_year(year) for month in self.MONTHS: dm = 19 if month != 0 else 4 + is_leap for day in range(1, dm + 1): date0 = (year, month, day) ordinal = _td_utils._ymd2ord(*date0) date1 = _td_utils._ord2ymd(ordinal, short=True) if (options.previous and (prev_ord != 0 and (prev_ord + 1) != ordinal)): data.append((date0, prev_ord, date1, ordinal)) elif not options.previous: flag = date0 != date1 if flag: data.append((date0, ordinal, date1, is_leap, flag)) prev_ord = ordinal if not year % 100: print(year, file=sys.stderr) return data
[docs] def analyze_timestamp_for_today(self, options): """ See if we get close to the correct time after sunset so we can prove that sunset can be found with a timestamp. -t -S and -E in Gregorian years """ data = [] start = options.start end = options.end #lat = options.latitude lon = options.longitude if lon: zone = lon / 15 else: zone = options.zone tz0 = dtime.timezone(dtime.timedelta(hours=zone)) tz1 = timezone(timedelta(hours=zone)) for year in range(start, end): g_date = (year, 1, 1) g_ts = dtime.datetime(*g_date, tzinfo=tz0).timestamp() bd = datetime.fromtimestamp(g_ts, tz1) b_date = bd.b_date b_ts = bd.timestamp() diff = b_ts - g_ts data.append((g_date, g_ts, b_date, b_ts, diff)) return data
[docs] def _subtract_tuples(self, t0, t1): return t0[0] - t1[0], t0[1] - t1[1], t0[2] - t1[2]
[docs] def fmt_float(value, left=4, right=4): """ Format one float so that it is visually centered on the decimal point. Parameters ---------- value : float | int | str The number to format. left : int Width to reserve on the left of the decimal (including any minus sign). right : int Number of digits to show after the decimal. """ s = f"{value:.{right}f}" left_part, right_part = s.split(".") return f"{left_part.rjust(left)}.{right_part.ljust(right)}"
[docs] def find_elapse_time(start_time): end_time = time.time() days, hours, minutes, seconds = dt._dhms_from_seconds( end_time - start_time) print(f"\nElapsed time: {hours:02} hours, {minutes:02} minutes, " f"{round(seconds, 6):02.6} seconds.")
if __name__ == "__main__": import time import argparse parser = argparse.ArgumentParser( description=("Test datetime date ranges.")) parser.add_argument( '-a', '--analyze', action='store_true', default=False, dest='analyze', help="Analyze ordinal dates from list.") parser.add_argument( '-b', '--analyze1', action='store_true', default=False, dest='analyze1', help="Analyze Badi and Gregorian dates.") parser.add_argument( '-c', '--analyze2', action='store_true', default=False, dest='analyze2', help="Analyze timestamps relative to sunset.") parser.add_argument( '-d', '--date', action='store_true', default=False, dest='date', help="Check dates by different methods.") parser.add_argument( '-e', '--flip', action='store_true', default=False, dest='flip', help="Check for sunset flip.") parser.add_argument( '-o', '--ordinal', action='store_true', default=False, dest='ordinal', help="Test that _ymd2ord and _ord2ymd produce " "the correct dates.") parser.add_argument( '-t', '--analyze3', action='store_true', default=False, dest='analyze3', help="Analyze timestamps relative to sunset.") parser.add_argument( '-E', '--end', type=int, default=None, dest='end', help="End of year of sequence.") parser.add_argument( '-S', '--start', type=int, default=None, dest='start', help="Start of year of sequence.") parser.add_argument( '-T', '--timezone', type=float, default=0.0, dest='timezone', help="Timezone offset floating point value.") parser.add_argument( '-A', '--latitude', type=float, default=None, dest='latitude', help="Latitude") parser.add_argument( '-O', '--logitude', type=float, default=None, dest='longitude', help="Longitude") parser.add_argument( '-Z', '--zone', type=float, default=None, dest='zone', help="Time zone.") parser.add_argument( '-P', '--previous', action='store_true', default=False, dest='previous', help="Dump the previous and current ordinals.") parser.add_argument( '-D', '--delta', type=int, default=None, dest='delta', help="The spread in minutes or seconds.") parser.add_argument( '-U', '--seconds', action='store_true', default=False, dest='seconds', help="Use seconds instead of minutes.") options = parser.parse_args() dt = DatetimeTests() ret = 0 basename = os.path.basename(__file__) if options.analyze: # -a print(f"./contrib/misc/{basename} -a") print("Gregorian Date Greg ord Badi Date " "Badi ord Same Df | JD JD Diff Badi Date " "Date Diff Leap") print('-'*131) [print(f"{str(g_date):33} " f"{g_ord:>7} " f"{str(b_date):14} " f"{b_ord:>7} " f"{str(same):<5} " f"{diff:02} | " f"{g_jd:<14} " f"{jd_diff:+09} " f"{str(date):14} " f"{str(d_diff)} " f"{str(leap)}" ) for (g_date, g_ord, b_date, b_ord, same, diff, g_jd, jd_diff, date, d_diff, leap) in dt.analyze_ordinal_error_list(options)] elif options.analyze1: # -b if options.start is None or options.end is None: # Set default Gregorian years. options.start = -1842 # Julian year 1 options.end = 1162 # Gregorian year 3005 start_time = time.time() data = dt.analyze_ordinal_error_create(options) underline_length = 149 print(f"./contrib/misc/{basename} -bS {options.start} " f"-E {options.end}") print('-' * underline_length) print(" " * 123, "Orig - Badi Orig - Ord") print("Greg Date", ' ' * 21, "JD", ' ' * 15, "Ordinal", "Orig Date " "Badi Date", ' ' * 22, "Ordinal Date B Date Diff " "O Date Diff") print('-' * underline_length) total_diff0 = total_diff1 = 0 items = [] for g_date, jd, ord, date, b_date, o_date, diff0, diff1 in data: if diff0 != (0, 0, 0): total_diff0 += 1 if diff1 != (0, 0, 0): total_diff1 += 1 [print(f"{str(g_date):31} " f"{jd:<18} " f"{ord:7} " f"{str(date):15} " f"{str(b_date):32} " f"{str(o_date):15} " f"{str(diff0):13} " f"{str(diff1):13} " ) for g_date, jd, ord, date, b_date, o_date, diff0, diff1 in data] print('-' * underline_length) total_errors = total_diff0 + total_diff1 print(f"Analyzing year {options.start} to year {options.end-1}.") print(f"Ordinal Errors: {total_diff1}") print(f" Badi Errors: {total_diff0}") print(f" Total Errors: {total_errors}") errors = [] for g_date, jd, ord, date, b_date, o_date, diff0, diff1 in data: if diff0 != (0, 0, 0): errors.append((date, jd, ord, b_date[:3], diff0, '')) if diff1 != (0, 0, 0): errors.append((date, jd, ord, o_date, '', diff1)) if errors: print("\nDate JD Ordinal " "Offending Date Badi Diff 0 Ordinal Diff 1") print('-' * 86) [print(f"{str(date):15} " f"{jd:<18} " f"{ord:7} " f"{str(offending_date):15} " f"{str(diff0):13}" f"{str(diff1):13}" ) for date, jd, ord, offending_date, diff0, diff1 in errors] print('-' * 86) find_elapse_time(start_time) elif options.analyze2: # -c delta = options.delta seconds = options.seconds assert delta < 119, ("The minutes option cannot be more that 118, " f"found {delta}.") start_time = time.time() underline_length = 117 print(f"./contrib/misc/{basename} -cA {options.latitude} " f"-O {options.longitude} -Z {options.zone} -D {delta} " f"-U {seconds}") print('-' * underline_length) d_type = 'Second' if seconds else 'Minute' print("Gregorian Date Astro Sunset JD Badí' Timestamp " f"{d_type} Offset Astro JD Day Timestamp Today") print('-' * underline_length) data = dt.analyze_timestamp_errors(options) for ss_items, items in data: g_date, astro_ss, ss_ts = ss_items print(f"{str(g_date):14} " f"{fmt_float(astro_ss, 7, 10)} " f"{fmt_float(ss_ts, 12, 4)} ", end='') items_len = len(items) for idx, (delta, test_jd, ts, today) in enumerate(items): print(f"{fmt_float(delta, 3, 1)} " f"{fmt_float(test_jd, 7, 10)} " f"{fmt_float(ts, 12, 4)} " f"{str(today):>11} " ) if idx < items_len - 1: print(" " * 52, end='') print('-' * underline_length) find_elapse_time(start_time) elif options.date: # -d if None in (options.start, options.end): print("If option -d is used, -S and -E must also be used.", file=sys.stderr) ret = 1 elif None in (options.latitude, options.longitude, options.zone): print("If option -d is used, -A, -O and -Z must also be used.", file=sys.stderr) ret = 1 else: start_time = time.time() data = dt.check_dates(options) print(f"./contrib/misc/{basename} -dS {options.start} " f"-E {options.end} -A {options.latitude} " f"-O {options.longitude} -Z {options.zone}") underline_length = 215 print('-' * underline_length) print("Gregorian Date|Badi Date from |" "Badi Date from JD", ' ' * 16, "|Badi Date from Gregorian", ' ' * 9, "|Badi Date from Timestamp", ' ' * 9, "|Tuples are:") print("at Midnight |Ordinal |", ' ' * 33, "|", ' ' * 33, "|", ' ' * 33, "|(idx, diff val 0, diff val 1)") print('-' * underline_length) [print(f"{str(date):14} " f"{str(b_date0):15} " f"{str(b_date1):35} " f"{str(b_date2):35} " f"{str(b_date3):35} " f"{diff0} " f"{diff1}" ) for (date, b_date0, b_date1, b_date2, b_date3, diff0, diff1) in data] print('-' * underline_length) find_elapse_time(start_time) elif options.flip: # -e dt.test_sunset_flip_invariant(options) elif options.ordinal: # -o if options.start is None or options.end is None: print("If option -o is used, -S and -E must also be used.", file=sys.stderr) ret = 1 elif options.previous: # with -P start_time = time.time() data = dt.test_ordinals(options) print(f"./contrib/misc/{basename} -oPS{options.start} " f"-E{options.end}") print("Start Date Prev Ord Result Date Cur Ord") underline_length = 48 print('-' * underline_length) [print(f"{str(date0):15} " f"{prev_ord:>7} " f"{str(date1):15} " f"{ordinal:>7} " ) for date0, prev_ord, date1, ordinal in data] print('-' * underline_length) end_time = time.time() days, hours, minutes, seconds = dt._dhms_from_seconds( end_time - start_time) print(f"\nElapsed time: {hours:02} hours, {minutes:02} minutes, " f"{round(seconds, 6):02.6} seconds.") else: start_time = time.time() data = dt.test_ordinals(options) print(f"./contrib/misc/{basename} -oS{options.start} " f"-E{options.end}") print("Start Date Ordinal Result Date Leap Error") underline_length = 51 print('-' * underline_length) [print(f"{str(date0):15} " f"{ordinal:>7} " f"{str(date1):15} " f"{str(leap):5} " f"{str(flag):5}" ) for date0, ordinal, date1, leap, flag in data] print('-' * underline_length) print(f" Total Years Tested: {options.end - options.start}") errors = [l[4] is True for l in data].count(True) print(f"Total Number of Errors: {errors}") find_elapse_time(start_time) elif options.analyze3: # -t if options.start is None or options.end is None: # Set default Gregorian years. options.start = 2 # Year 1 would be before that oldest Badi date. options.end = 3005 start_time = time.time() underline_length = 76 lon = options.longitude zone = lon / 14 if options.zone is None else options.zone lon_text = "" if lon is None else f" -O {lon}" print(f"./contrib/misc/{basename} -tS {options.start} " f"-E {options.end}{lon_text} -Z {zone}") print("Gregorian DT Greg TS Badi Date Badi Timestamp " "Diff") print('-' * underline_length) data = dt.analyze_timestamp_for_today(options) [print(f"{str(g_date):12} " f"{fmt_float(g_ts, 12, 1)} " f"{str(b_date):14} " f"{fmt_float(b_ts, 12, 6)} " f"{fmt_float(diff, 6, 6)}" ) for g_date, g_ts, b_date, b_ts, diff in data] print('-' * underline_length) deviation = {'n': 0, 'p': 0, 'max_n': 0, 'max_p': 0, 'total': 0} for items in data: diff = items[-1] if diff < 0: deviation['n'] += 1 elif diff > 0: deviation['p'] += 1 if diff < deviation['max_n']: deviation['max_n'] = diff elif diff > deviation['max_p']: deviation['max_p'] = diff deviation['total'] += diff total_years = options.end - options.start mean_deviation = deviation['total'] / total_years print(f"Analyzing year {options.start} to year {options.end-1} " "Gregorian.") print(f"Total years: {total_years}") print(f"Total Timestamp negative deviations: {deviation['n']}") print(f"Maximum negative deviation (seconds): {deviation['max_n']}") print(f"Total Timestamp positive deviations: {deviation['p']}") print(f"Maximum positive deviation (seconds): {deviation['max_p']}") print(f"Mean deviation (seconds): {mean_deviation}") find_elapse_time(start_time) else: parser.print_help() sys.exit(ret)