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:
There was never a Julian calendar, whose ephemeral year is 365.25days.
The start of the Common Era is December 31, 0 CE.
The ephemeral year is always 365.2425days, the Gregorian starts on the epoch of the Common Era, rather than October 15, 1582.
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.
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?
-
\$\begingroup\$ That calendar is normally described as a proleptic Gregorian calendar, as used by ISO 8601. \$\endgroup\$Toby Speight– Toby Speight2021年09月21日 16:19:14 +00:00Commented Sep 21, 2021 at 16:19
2 Answers 2
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}')
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)
Explore related questions
See similar questions with these tags.