aboutsummaryrefslogtreecommitdiff
path: root/backend/hist_data/cal.py
blob: 3b65205570cc1b5d7891fe5d6333835e96e62810 (plain)
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
119
120
"""
Provides date conversion functions, HistDate, and date scales.
Algorithms for converting between calendars and Julian day number values 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 CE, 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))

MIN_CAL_YEAR = -4713 # Disallow within-year dates before this year
MONTH_SCALE = -1;
DAY_SCALE = -2;
SCALES: list[int] = [int(x) for x 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 BCE)
	- '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 (used for dates before the Julian period starting year 4713 BCE)
	"""
	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:
	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 not end: # jdn for julian calendar
		return HistDate(False, *jdnToJulian(n))
	else: # fmt == 2 or fmt == 3 and end
		return HistDate(True, *jdnToGregorian(n))
def dateToUnit(date: HistDate, scale: int) -> int:
	if scale >= 1:
		return date.year // scale
	elif scale == MONTH_SCALE:
		if date.gcal == False:
			return julianToJdn(date.year, date.month, 1)
		else:
			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)