Última atividade 10 months ago

此程式提供一系列函數,方便處理日期與時間,包括取得月份、季度範圍、時間增減與計算日期範圍等,適用於財務分析、報表生成與時間管理應用。

date_utils.py Bruto
1import calendar # 匯入 calendar 模組
2import math
3from datetime import date, datetime, time
4
5import pytz
6from dateutil.relativedelta import relativedelta
7
8
9def date_type(value):
10 return (datetime if isinstance(value, datetime) else date) # 根據值的類型返回 datetime 或 date # fmt: skip
11
12
13def get_month(date):
14 date_from = date_type(date)(date.year, date.month, 1) # 建立日期起始值為當月第一天
15 date_to = date_type(date)(date.year, date.month, calendar.monthrange(date.year, date.month)[1]) # 建立日期結束值為當月最後一天 # fmt: skip
16 return date_from, date_to # 返回起始日期和結束日期
17
18
19def get_quarter_number(date):
20 return math.ceil(date.month / 3) # 回傳日期對應的季度數字,向上取整
21
22
23def get_quarter(date):
24 quarter_number = get_quarter_number(date) # 獲取日期對應的季度數字
25 month_from = ((quarter_number - 1) * 3) + 1 # 計算季度的起始月份
26 date_from = date_type(date)(date.year, month_from, 1) # 建立季度的起始日期
27 date_to = date_from + relativedelta(months=2) # 結束日期為起始日期加兩個月
28 date_to = date_to.replace(
29 day=calendar.monthrange(date_to.year, date_to.month)[1]
30 ) # 調整結束日期為該月的最後一天
31 return date_from, date_to # 返回季度的起始日期和結束日期
32
33
34def get_timedelta(qty, granularity):
35 switch = {
36 "hour": relativedelta(hours=qty),
37 "day": relativedelta(days=qty),
38 "week": relativedelta(weeks=qty),
39 "month": relativedelta(months=qty),
40 "year": relativedelta(years=qty),
41 }
42 return switch[granularity]
43
44
45def start_of(value, granularity):
46 is_datetime = isinstance(value, datetime)
47 if granularity == "year":
48 result = value.replace(month=1, day=1)
49 elif granularity == "quarter":
50 # Q1 = Jan 1st
51 # Q2 = Apr 1st
52 # Q3 = Jul 1st
53 # Q4 = Oct 1st
54 result = get_quarter(value)[0]
55 elif granularity == "month":
56 result = value.replace(day=1)
57 elif granularity == "week":
58 # by default MONDAY is the first day of the week and SUNDAY is the last.
59 result = value - relativedelta(
60 days=calendar.weekday(value.year, value.month, value.day)
61 )
62 elif granularity == "day":
63 result = value
64 elif granularity == "hour" and is_datetime:
65 return datetime.combine(value, time.min).replace(hour=value.hour)
66 elif is_datetime:
67 raise ValueError("Granularity must be year, quarter, month, week, day or hour for value %s" % value) # fmt: skip
68 # raise ValueError("請設定精確度為年、季、月、週、日或小時,目前設定值為 %s" % value)
69
70 else:
71 raise ValueError("Granularity must be year, quarter, month, week or day for value %s" % value) # fmt: skip
72 # raise ValueError("精確度應為年、季、月、週或日,但收到的值為 %s" % value)
73
74 return datetime.combine(result, time.min) if is_datetime else result
75
76
77def end_of(value, granularity):
78 is_datetime = isinstance(value, datetime)
79 if granularity == "year":
80 result = value.replace(month=12, day=31)
81 elif granularity == "quarter":
82 # Q1 = Mar 31st
83 # Q2 = Jun 30th
84 # Q3 = Sep 30th
85 # Q4 = Dec 31st
86 result = get_quarter(value)[1]
87 elif granularity == "month":
88 result = value + relativedelta(day=1, months=1, days=-1)
89 elif granularity == "week":
90 result = value + relativedelta(days=6 - calendar.weekday(value.year, value.month, value.day)) # fmt: skip
91
92 elif granularity == "day":
93 result = value
94 elif granularity == "hour" and is_datetime:
95 return datetime.combine(value, time.max).replace(hour=value.hour)
96 elif is_datetime:
97 raise ValueError("Granularity must be year, quarter, month, week, day or hour for value %s" % value) # fmt: skip
98 # raise ValueError("請設定精確度為年、季、月、週、日或小時,目前設定值為 %s" % value)
99 else:
100 raise ValueError("Granularity must be year, quarter, month, week or day for value %s" % value) # fmt: skip
101 # raise ValueError("精確度應為年、季、月、週或日,但收到的值為 %s" % value)
102
103 return datetime.combine(result, time.max) if is_datetime else result
104
105
106def add(value, *args, **kwargs):
107 return value + relativedelta(*args, **kwargs) # 將 value 增加指定的相對時間
108
109
110def subtract(value, *args, **kwargs):
111 return value - relativedelta(*args, **kwargs) # 將 value 減去指定的相對時間
112
113
114def date_range(start, end, step=relativedelta(months=1)):
115 if isinstance(start, datetime) and isinstance(end, datetime):
116 are_naive = start.tzinfo is None and end.tzinfo is None
117 are_utc = start.tzinfo == pytz.utc and end.tzinfo == pytz.utc
118
119 # Cases with miscellaneous time zones are more complex due to DST.
120 are_others = start.tzinfo and end.tzinfo and not are_utc
121
122 if are_others and start.tzinfo.zone != end.tzinfo.zone:
123 raise ValueError("Timezones of start argument and end argument seem inconsistent") # fmt: skip
124
125 if not are_naive and not are_utc and not are_others:
126 raise ValueError("Timezones of start argument and end argument mismatch")
127
128 dt = start.replace(tzinfo=None)
129 end_dt = end.replace(tzinfo=None)
130 post_process = start.tzinfo.localize if start.tzinfo else lambda dt: dt
131
132 elif isinstance(start, date) and isinstance(end, date):
133 dt, end_dt = start, end
134 post_process = lambda dt: dt
135
136 else:
137 raise ValueError("start/end should be both date or both datetime type")
138
139 if start > end:
140 raise ValueError("start > end, start date must be before end")
141
142 if start == start + step:
143 raise ValueError("Looks like step is null")
144
145 while dt <= end_dt:
146 yield post_process(dt)
147 dt = dt + step
148
149
150# 檢查當前程式是否被作為主程式執行
151if __name__ == "__main__":
152 # 建立一個日期物件
153 input_date = date(2023, 12, 15)
154
155 # 使用 get_month 函數獲取該日期所在月份的起始日期和結束日期
156 month_start_date, month_end_date = get_month(input_date)
157
158 # 輸出結果
159 print("Month Start Date:", month_start_date)
160 print("Month End Date:", month_end_date)
161
162 # 使用 get_quarter_number 函數獲取該日期對應的季度數字
163 quarter_num = get_quarter_number(input_date)
164
165 # 使用 get_quarter 函數獲取該日期所在季度的起始日期和結束日期
166 quarter_start_date, quarter_end_date = get_quarter(input_date)
167
168 # 輸出結果
169 print("Quarter Number:", quarter_num)
170 print("Quarter Start Date:", quarter_start_date)
171 print("Quarter End Date:", quarter_end_date)
172
173 # 呼叫 get_timedelta 函數,設定數量為 2,時間間隔為 'month'
174 time_delta = get_timedelta(2, "month")
175
176 # 輸出結果
177 print("Time Delta:", time_delta)
178
179 input_datetime = datetime(2024, 3, 22, 15, 30, 0)
180
181 # 使用 start_of 函數獲取以週為精確度的日期起始時間
182 start_of_week = start_of(input_datetime, "week")
183 print("Start of Week:", start_of_week)
184
185 # 使用 end_of 函數獲取以週為精確度的日期結束時間
186 end_of_week = end_of(input_datetime, "week")
187 print("End of Week:", end_of_week)
188
189 # 呼叫 add 函數,將 value 增加 2 個月
190 result_add = add(datetime.now(), months=2)
191
192 print("Result of add function:", result_add)
193
194 # 呼叫 subtract 函數,將 value 減去 1 年
195 result_subtract = subtract(datetime.now(), years=1)
196
197 print("Result of subtract function:", result_subtract)
198
199 # 定義起始日期和結束日期
200 start_date = datetime(2024, 1, 1)
201 end_date = datetime(2024, 3, 1)
202
203 # 呼叫date_range函數並迭代生成的日期序列
204 for date in date_range(start_date, end_date, step=relativedelta(months=1)):
205 print(date)
206
207