Skip to content

Instantly share code, notes, and snippets.

@msmorgan
Last active May 4, 2021 23:30
Show Gist options
  • Save msmorgan/d13bbef214534929b0adc93190f5d1ee to your computer and use it in GitHub Desktop.
Save msmorgan/d13bbef214534929b0adc93190f5d1ee to your computer and use it in GitHub Desktop.
from itertools import accumulate
MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
MONTH_LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
MONTH_STARTS = list(accumulate([0] + MONTH_LENGTHS[:-1])) # [0, 31, 31+28, 31+28+31, ...]
def is_leap_year(year: int) -> bool:
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
def epoch_days_offset(year: int) -> int:
"""
Gets the number of days that `year` is offset from 1970, accounting
for leap years. For example, if `year` is 1971, then the value is 365.
If `year` is 1968, the value is -731 (equal to -(365 + 366), because
1968 was a leap year.)
"""
start = 1970
end = year
negative = start > end # Are we going backwards from 1970?
if negative:
end, start = start, end
days = 0
for year in range(start, end):
days += 365
if is_leap_year(year):
days += 1
if negative:
days *= -1
return days
def to_unix_epoch(date: str) -> int:
"""
Converts a date in the format 'Thu 01 Jan 1970 00:00:00 +0000'
into a `Unix epoch time <https://en.wikipedia.org/wiki/Unix_time>`.
Unlike true epoch, supports values before 1970-01-01.
"""
_, day, month, year, time, timezone = date.split() # First element is weekday so ignore it.
year, day = int(year), int(day) # Convert to integers.
day -= 1 # Number days starting from 0.
month = MONTH_NAMES.index(month)
day_of_year = MONTH_STARTS[month] + day
if month >= 2 and is_leap_year(year):
day_of_year += 1
days_since_epoch = epoch_days_offset(year) + day_of_year # The number of days to add to Jan 1 1970.
timezone = int(timezone) # Parsed as base 10.
timezone_hour = abs(timezone) // 100
timezone_minute = abs(timezone) % 100 # Always returns a positive number.
if timezone < 0:
timezone_hour *= -1
timezone_minute *= -1
hour, minute, second = map(int, time.split(':'))
hour -= timezone_hour
minute -= timezone_minute
seconds_since_midnight = (hour * 60 * 60) + (minute * 60) + second
return (days_since_epoch * 24 * 60 * 60) + seconds_since_midnight
def date_diff(start: str, end: str) -> int:
return abs(to_unix_epoch(end) - to_unix_epoch(start))
# Make sure everything works.
def main():
import datetime as dt
from random import randrange
# Every day from `start` to `end`, choose a random time and timezone and compare
# the above output to the one from datetime.
for year in range(1853, 2025):
for month in range(12):
month_length = MONTH_LENGTHS[month]
if month == 1 and is_leap_year(year):
month_length += 1
for day in range(month_length):
timezone_hour = randrange(-11, 13)
timezone_minute = randrange(0, 60)
hour = randrange(0, 24)
minute = randrange(0, 60)
second = randrange(0, 60)
date_str = 'Day {d:02} {m} {y} {H:02}:{M:02}:{S:02} {tz_h:+03}{tz_m:02}'.format(
d=day + 1,
m=MONTH_NAMES[month],
y=year,
H=hour,
M=minute,
S=second,
tz_h=timezone_hour,
tz_m=timezone_minute,
)
timezone_offset = dt.timedelta(hours=abs(timezone_hour), minutes=timezone_minute)
if timezone_hour < 0:
timezone_offset = -timezone_offset
date_obj = dt.datetime(year, month + 1, day + 1, hour, minute, second,
tzinfo=dt.timezone(timezone_offset))
expected = int(date_obj.timestamp())
actual = to_unix_epoch(date_str)
if expected != actual:
print('Error with date {}: off by {}, expected={}, actual={}'.format(
date_str, actual - expected, expected, actual))
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment