0
\$\begingroup\$

I have written two functions that output a date-time string for any given non-negative integer.

The integer is treated as representing total number of seconds in a given time period, the first function converts that duration to week-day-hour-minute-weekday format (w weeks, d days, HH:mm:ss, dddd), using a mathematically sound mixed radix system: (7, 24, 60, 60, 60), its outputs are factually correct.

The second function is more complex, it outputs a date-time in the following format: MMMM dd, yyyy, HH:mm:ss, dddd, the number is treated as a Gregorian timestamp with the beginning of the Common Era as its epoch, its outputs are factually wrong, however they are practically correct for usage in the 21st century and can be used to correctly calculate date-time.

Specifically, it uses a calendar based on the Gregorian calendar, and the following are assumed:

  1. There was never a Julian calendar, whose ephemeral year is 365.25days.

  2. The start of the Common Era is December 31, 0 CE.

  3. The ephemeral year is always 365.2425days, the Gregorian starts on the epoch of the Common Era, rather than October 15, 1582.

  4. Because October 5 to 14, 1582 exist in this calendar, the number of any given date's weekday is its date expressed as days since epoch modulo seven.

  5. Leap seconds and time zones don't exist.

Having established all above "facts" however, the output of the second function is correct with correct weekday and the second function can be used to calculate the exact second Y2038 occurs.

The code:

WEEKDAYS = [
    'Sunday',
    'Monday',
    'Tuesday',
    'Wednesday',
    'Thursday',
    'Friday',
    'Saturday'
]
MONTHS = (
    {'Month':'January', 'Days':31},
    {'Month':'February', 'Days':28},
    {'Month':'March', 'Days':31},
    {'Month':'April', 'Days':30},
    {'Month':'May', 'Days':31},
    {'Month':'June', 'Days':30},
    {'Month':'July', 'Days':31},
    {'Month':'August', 'Days':31},
    {'Month':'September', 'Days':30},
    {'Month':'October', 'Days':31},
    {'Month':'November', 'Days':30},
    {'Month':'December', 'Days':31}
)

def to_wdhms(n: int) -> str:
    n, seconds = divmod(n, 60)
    n, minutes = divmod(n, 60)
    n, hours = divmod(n, 24)
    weeks, days = divmod(n, 7)
    return '{0} weeks, {1} days, {2:02}:{3:02}:{4:02}, {5}'.format(weeks, days, hours, minutes, seconds, WEEKDAYS[days])

def to_date(n: int) -> str:
    n, seconds = divmod(n, 60)
    n, minutes = divmod(n, 60)
    days, hours = divmod(n, 24)
    sumyears = lambda x: x * 365 + x // 4 - x // 100 + x // 400
    years = 0
    while sumyears(years) < days:
        years += 1
    weekday = days % 7
    days -= sumyears(years - 1)
    def leapyear(n):
        if not n % 4 and n % 100:
            return True
        elif not n % 400:
            return True
        return False
    def summonths(x, y):
        s = sum(i['Days'] for i in MONTHS[0:x])
        if leapyear(y) and x >= 2:
            s += 1
        return s
    months = 1
    while summonths(months, years) < days:
        months += 1
    months -= 1
    days -= summonths(months, years)
    return '{0} {1:02}, {2:04}, {3:02}:{4:02}:{5:02}, {6}'.format(MONTHS[months]['Month'], days, years, hours, minutes, seconds, WEEKDAYS[weekday])

def totaldays(y, m, d):
    ye = y
    if m <= 2: y -= 1
    leaps = y // 4 - y // 100 + y // 400
    M = 0
    for b in range(m - 1): M += MONTHS[b]['Days']
    days = (ye - 1) * 365 + M + d + leaps
    return days

def convert(y, m, d):
    return to_date(totaldays(y, m, d) * 86400)

def y2038():
    epoch = totaldays(1970,1,1) * 86400
    sint32 = 2 ** 31
    return to_date(epoch + sint32)

def test():
    assert convert(1776,7,4) == 'July 04, 1776, 00:00:00, Thursday'
    assert convert(1999,4,10) == 'April 10, 1999, 00:00:00, Saturday'
    assert convert(2000,2,29) == 'February 29, 2000, 00:00:00, Tuesday'
    assert convert(2000,3,1) == 'March 01, 2000, 00:00:00, Wednesday'
    assert convert(2000,3,31) == 'March 31, 2000, 00:00:00, Friday'
    assert convert(2000,12,31) == 'December 31, 2000, 00:00:00, Sunday'
    assert convert(2008,8,1) == 'August 01, 2008, 00:00:00, Friday'
    assert convert(2012,12,21) == 'December 21, 2012, 00:00:00, Friday'
    assert convert(2020,9,21) == 'September 21, 2020, 00:00:00, Monday'
    assert convert(2021,9,21) == 'September 21, 2021, 00:00:00, Tuesday'
    assert convert(2021,12,25) == 'December 25, 2021, 00:00:00, Saturday'
    assert convert(2021,12,31) == 'December 31, 2021, 00:00:00, Friday'
    assert to_wdhms(86400) == '0 weeks, 1 days, 00:00:00, Monday'
    assert to_wdhms(86399) == '0 weeks, 0 days, 23:59:59, Sunday'
    assert to_wdhms(0) == '0 weeks, 0 days, 00:00:00, Sunday'
    assert to_wdhms(43200) == '0 weeks, 0 days, 12:00:00, Sunday'
    assert to_wdhms(21600) == '0 weeks, 0 days, 06:00:00, Sunday'
    assert to_wdhms(3600) == '0 weeks, 0 days, 01:00:00, Sunday'
    assert to_wdhms(1799) == '0 weeks, 0 days, 00:29:59, Sunday'
    assert to_wdhms(172799) == '0 weeks, 1 days, 23:59:59, Monday'
    assert to_wdhms(215199) == '0 weeks, 2 days, 11:46:39, Tuesday'
    assert to_wdhms(259199) == '0 weeks, 2 days, 23:59:59, Tuesday'
    assert to_wdhms(345599) == '0 weeks, 3 days, 23:59:59, Wednesday'
    assert to_wdhms(3628799) == '5 weeks, 6 days, 23:59:59, Saturday'
    assert y2038() == 'January 19, 2038, 03:14:08, Tuesday'

if __name__ == '__main__':
    test()

How well is my code formatted? How is my code in terms of performance? What improvements can be made? Is there any problem I have overlooked?

\$\endgroup\$
1

2 Answers 2

2
\$\begingroup\$

This runs in 6ms on my system. Therefore any efficiency changes are likely to have minimal effect. Having said that, your use of the MONTHS tuple is more complex than it needs to be. I have made several changes but important ones to note are the change to MONTHS and the introduction of DIYSF (days in year so far) that latter meaning that one of your original loops can be omitted. Here's the full code:-

WEEKDAYS = [
    'Sunday',
    'Monday',
    'Tuesday',
    'Wednesday',
    'Thursday',
    'Friday',
    'Saturday'
]
DIYSF = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]
MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December']



def to_wdhms(n: int) -> str:
    n, seconds = divmod(n, 60)
    n, minutes = divmod(n, 60)
    n, hours = divmod(n, 24)
    weeks, days = divmod(n, 7)
    return '{0} weeks, {1} days, {2:02}:{3:02}:{4:02}, {5}'.format(weeks, days, hours, minutes, seconds, WEEKDAYS[days])


def to_date(n: int) -> str:
    n, seconds = divmod(n, 60)
    n, minutes = divmod(n, 60)
    days, hours = divmod(n, 24)
    def sumyears(x):
        return x * 365 + x // 4 - x // 100 + x // 400
    years = 1
    while sumyears(years) < days:
        years += 1
    weekday = days % 7
    days -= sumyears(years - 1)

    def leapyear(n):
        return n % 400 == 0 or (n % 4 == 0 and n % 100 != 0)
  
    def summonths(x, y):
        s = DIYSF[x]
        if leapyear(y) and x >= 2:
            s += 1
        return s
    months = 0
    while summonths(months+1, years) < days:
        months += 1
    days -= summonths(months, years)
    return '{0} {1:02}, {2:04}, {3:02}:{4:02}:{5:02}, {6}'.format(MONTHS[months], days, years, hours, minutes, seconds, WEEKDAYS[weekday])


def totaldays(y, m, d):
    ye = y
    if m <= 2:
        y -= 1
    leaps = y // 4 - y // 100 + y // 400
    return (ye - 1) * 365 + DIYSF[m-1] + d + leaps


def convert(y, m, d):
    return to_date(totaldays(y, m, d) * 86400)


def y2038():
    return to_date(719163 * 86400 + 2**31)


def test():
    assert convert(1776, 7, 4) == 'July 04, 1776, 00:00:00, Thursday'
    assert convert(1999, 4, 10) == 'April 10, 1999, 00:00:00, Saturday'
    assert convert(2000, 2, 29) == 'February 29, 2000, 00:00:00, Tuesday'
    assert convert(2000, 3, 1) == 'March 01, 2000, 00:00:00, Wednesday'
    assert convert(2000, 3, 31) == 'March 31, 2000, 00:00:00, Friday'
    assert convert(2000, 12, 31) == 'December 31, 2000, 00:00:00, Sunday'
    assert convert(2008, 8, 1) == 'August 01, 2008, 00:00:00, Friday'
    assert convert(2012, 12, 21) == 'December 21, 2012, 00:00:00, Friday'
    assert convert(2020, 9, 21) == 'September 21, 2020, 00:00:00, Monday'
    assert convert(2021, 9, 21) == 'September 21, 2021, 00:00:00, Tuesday'
    assert convert(2021, 12, 25) == 'December 25, 2021, 00:00:00, Saturday'
    assert convert(2021, 12, 31) == 'December 31, 2021, 00:00:00, Friday'
    assert to_wdhms(86400) == '0 weeks, 1 days, 00:00:00, Monday'
    assert to_wdhms(86399) == '0 weeks, 0 days, 23:59:59, Sunday'
    assert to_wdhms(0) == '0 weeks, 0 days, 00:00:00, Sunday'
    assert to_wdhms(43200) == '0 weeks, 0 days, 12:00:00, Sunday'
    assert to_wdhms(21600) == '0 weeks, 0 days, 06:00:00, Sunday'
    assert to_wdhms(3600) == '0 weeks, 0 days, 01:00:00, Sunday'
    assert to_wdhms(1799) == '0 weeks, 0 days, 00:29:59, Sunday'
    assert to_wdhms(172799) == '0 weeks, 1 days, 23:59:59, Monday'
    assert to_wdhms(215199) == '0 weeks, 2 days, 11:46:39, Tuesday'
    assert to_wdhms(259199) == '0 weeks, 2 days, 23:59:59, Tuesday'
    assert to_wdhms(345599) == '0 weeks, 3 days, 23:59:59, Wednesday'
    assert to_wdhms(3628799) == '5 weeks, 6 days, 23:59:59, Saturday'
    assert y2038() == 'January 19, 2038, 03:14:08, Tuesday'

import time
if __name__ == '__main__':
    start = time.perf_counter()
    test()
    end = time.perf_counter()
    print(f'Duration={end-start:.4f}')
\$\endgroup\$
0
\$\begingroup\$

I have made a major improvement in the logic, by changing just one line in the code, the performance would be significantly boosted.

Specifically, by changing the initial assignment of the years variable inside to_date (years = 0) to years = int(days // 365.2425 - 1), instead of incrementing from 0, by incrementing from a calculated approximate value using the ephemeral year, the while loop that counts years can be tremendously shortened.

After this change the program is able to calculate the (2 ^ 64)th second in Common Era in 10.5 microseconds.

In [1183]: to_date(2**64)
Out[1183]: 'November 08, 584554049254, 07:00:16, Sunday'

In [1184]: %timeit to_date(2**64)
10.5 µs ± 20.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.