1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
"""
Provides date conversion functions, HistDate, and date scales.
"""
# For conversion between calendars and Julian day numbers. Algorithms were obtained from
# https://en.wikipedia.org/wiki/Julian_day#Converting_Gregorian_calendar_date_to_Julian_Day_Number.
def gregorianToJdn(year: int, month: int, day: int) -> int:
"""
Converts a Gregorian calendar date to a Julian day number,
denoting the noon-to-noon 'Julian day' that starts within the input day.
A year of 1 means 1 AD, and -1 means 1 BC (0 is treated like -1).
A month of 1 means January. Can use a month of 13 and a day of 0.
Valid for dates from 24th Nov 4714 BC onwards.
"""
if year < 0:
year += 1
x = int((month - 14) / 12)
jdn = int(1461 * (year + 4800 + x) / 4)
jdn += int((367 * (month - 2 - 12 * x)) / 12)
jdn -= int((3 * int((year + 4900 + x) / 100)) / 4)
jdn += day - 32075
return jdn
def julianToJdn(year: int, month: int, day: int) -> int:
"""
Like gregorianToJdn(), but converts a Julian calendar date.
Valid for dates from 1st Jan 4713 BC onwards.
"""
if year < 0:
year += 1
jdn = 367 * year
jdn -= int(7 * (year + 5001 + int((month - 9) / 7)) / 4)
jdn += int(275 * month / 9)
jdn += day + 1729777
return jdn
def jdnToGregorian(jdn: int) -> tuple[int, int, int]:
"""
Converts a Julian day number to a Gregorian calendar date, denoting the
day in which the given noon-to-noon 'Julian day' begins.
Valid for non-negative input.
"""
f = jdn + 1401 + (((4 * jdn + 274277) // 146097) * 3) // 4 - 38
e = 4 * f + 3
g = (e % 1461) // 4
h = 5 * g + 2
D = (h % 153) // 5 + 1
M = (h // 153 + 2) % 12 + 1
Y = (e // 1461) - 4716 + (12 + 2 - M) // 12
if Y <= 0:
Y -= 1
return Y, M, D
def jdnToJulian(jdn: int) -> tuple[int, int, int]:
""" Like jdnToGregorian(), but converts to a Julian calendar date """
f = jdn + 1401
e = 4 * f + 3
g = (e % 1461) // 4
h = 5 * g + 2
D = (h % 153) // 5 + 1
M = (h // 153 + 2) % 12 + 1
Y = (e // 1461) - 4716 + (12 + 2 - M) // 12
if Y <= 0:
Y -= 1
return Y, M, D
def julianToGregorian(year: int, month: int, day: int) -> tuple[int, int, int]:
return jdnToGregorian(julianToJdn(year, month, day))
def gregorianToJulian(year: int, month: int, day: int) -> tuple[int, int, int]:
return jdnToJulian(gregorianToJdn(year, month, day))
# For date representation
MIN_CAL_YEAR = -4713 # Year before which JDNs are not usable
MONTH_SCALE = -1;
DAY_SCALE = -2;
SCALES: list[int] = [int(s) for s in [1e9, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 100, 10, 1, MONTH_SCALE, DAY_SCALE]];
class HistDate:
"""
Represents a historical date
- 'year' may be negative (-1 means 1 BC)
- 'month' and 'day' are at least 1, if given
- 'gcal' may be:
- True: Indicates a Gregorian calendar date
- False: Means the date should, for display, be converted to a Julian calendar date
- None: 'month' and 'day' are 1 (required for dates before MIN_CAL_YEAR)
"""
def __init__(self, gcal: bool | None, year: int, month=1, day=1):
self.gcal = gcal
self.year = year
self.month = month
self.day = day
# Used in unit testing
def __eq__(self, other):
return isinstance(other, HistDate) and \
(self.gcal, self.year, self.month, self.day) == (other.gcal, other.year, other.month, other.day)
def __repr__(self):
return str(self.__dict__)
def dbDateToHistDate(n: int, fmt: int, end=False) -> HistDate:
""" Converts a start/start_upper/etc and fmt value in the 'events' db table, into a HistDate """
if fmt == 0: # year
if n >= MIN_CAL_YEAR:
return HistDate(True, n, 1, 1)
else:
return HistDate(None, n)
elif fmt == 1 or fmt == 3 and end: # jdn for gregorian calendar
return HistDate(True, *jdnToGregorian(n))
else: # fmt == 2 or fmt == 3 and not end
return HistDate(False, *jdnToJulian(n))
def dateToUnit(date: HistDate, scale: int) -> int:
""" Converts a date to an int representing a unit on a scale """
if scale >= 1:
return date.year // scale
elif scale == MONTH_SCALE:
if date.gcal == False:
return julianToJdn(date.year, date.month, 1)
else: # True or None
return gregorianToJdn(date.year, date.month, 1)
else: # scale == DAY_SCALE
if date.gcal == False:
return julianToJdn(date.year, date.month, date.day)
else:
return gregorianToJdn(date.year, date.month, date.day)
|