-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathukcalendar.py
More file actions
124 lines (92 loc) · 3.59 KB
/
ukcalendar.py
File metadata and controls
124 lines (92 loc) · 3.59 KB
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
121
122
123
124
#
# Copyright (c) 2023-2025 LateGenXer
#
# SPDX-License-Identifier: AGPL-3.0-or-later
#
import os.path
import datetime
__all__ = [
'is_business_day',
'next_business_day',
'prev_business_day',
'days_in_month',
'ukbankholidays',
'isukbankholiday',
'shift_year',
'shift_month',
]
def is_business_day(date:datetime.date) -> bool:
return date.weekday() < 5 and not isukbankholiday(date)
def next_business_day(date:datetime.date) -> datetime.date:
delta = [1, 1, 1, 1, 3, 2, 1]
while True:
days = delta[date.weekday()]
date = date + datetime.timedelta(days=days)
if not isukbankholiday(date):
return date
def prev_business_day(date:datetime.date) -> datetime.date:
delta = [3, 1, 1, 1, 1, 1, 2]
while True:
days = delta[date.weekday()]
date = date - datetime.timedelta(days=days)
if not isukbankholiday(date):
return date
_days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
def days_in_month(year:int, month:int) -> int:
if month == 2:
return 29 if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) else 28
else:
return _days_in_month[month - 1]
def shift_year(date:datetime.date, years:int=1) -> datetime.date:
assert isinstance(date, datetime.date)
year = date.year + years
month = date.month
day = min(date.day, days_in_month(year, month))
return date.replace(year=year, month=month, day=day)
def shift_month(date:datetime.date, months:int=1) -> datetime.date:
assert isinstance(date, datetime.date)
year_month = date.year*12 + date.month - 1 + months
year = year_month // 12
month = year_month % 12 + 1
day = min(date.day, days_in_month(year, month))
return date.replace(year=year, month=month, day=day)
def isukbankholiday(date:datetime.date) -> bool:
assert isinstance(date, datetime.date)
return (date.year, date.month, date.day) in ukbankholidays
def _read() -> set[tuple[int, int, int]]:
dates = set()
filename = os.path.join(os.path.dirname(__file__), 'ukbankholidays.csv')
with open(filename, 'rt') as stream:
for line in stream:
date = datetime.datetime.strptime(line[:-1], "%Y-%m-%d").date()
dates.add((date.year, date.month, date.day))
return dates
ukbankholidays = _read()
def main() -> None:
"""Generate ukbankholidays.csv."""
import requests
import xlrd # type: ignore[import-untyped]
from download import download
dates:set[datetime.date] = set()
# https://www.dmo.gov.uk/publications/gilt-market/formulae-and-examples/
# XXX Merge https://www.api.gov.uk/gds/bank-holidays/ too
# `curl -s https://www.gov.uk/bank-holidays.json | jq -r '.["england-and-wales"].events[].date'`
url = 'https://www.dmo.gov.uk/media/3lgd2zqc/ukbankholidays-nov23a.xls'
filename = os.path.basename(url)
download(url, filename, ttl=24*3600, content_type='application/vnd.ms-excel')
wb = xlrd.open_workbook('ukbankholidays-nov23a.xls')
sh = wb.sheet_by_index(0)
for rowx in range(1, sh.nrows):
cell = sh.cell(rowx, 0)
assert cell.ctype == xlrd.XL_CELL_DATE
date = datetime.datetime(*xlrd.xldate_as_tuple(cell.value, sh.book.datemode)).date()
dates.add(date)
r = requests.get('https://www.gov.uk/bank-holidays.json')
obj = r.json()
for event in obj['england-and-wales']['events']:
date = datetime.datetime.fromisoformat(event["date"]).date()
dates.add(date)
for date in sorted(dates):
print(f'{date.strftime("%Y-%m-%d")}')
if __name__ == '__main__':
main()