Utoljára aktív 10 months ago

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

timmy gist felülvizsgálása 10 months ago. Revízióhoz ugrás

1 file changed, 206 insertions

date_utils.py(fájl létrehozva)

@@ -0,0 +1,206 @@
1 + import calendar # 匯入 calendar 模組
2 + import math
3 + from datetime import date, datetime, time
4 +
5 + import pytz
6 + from dateutil.relativedelta import relativedelta
7 +
8 +
9 + def date_type(value):
10 + return (datetime if isinstance(value, datetime) else date) # 根據值的類型返回 datetime 或 date # fmt: skip
11 +
12 +
13 + def 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 +
19 + def get_quarter_number(date):
20 + return math.ceil(date.month / 3) # 回傳日期對應的季度數字,向上取整
21 +
22 +
23 + def 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 +
34 + def 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 +
45 + def 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 +
77 + def 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 +
106 + def add(value, *args, **kwargs):
107 + return value + relativedelta(*args, **kwargs) # 將 value 增加指定的相對時間
108 +
109 +
110 + def subtract(value, *args, **kwargs):
111 + return value - relativedelta(*args, **kwargs) # 將 value 減去指定的相對時間
112 +
113 +
114 + def 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 + # 檢查當前程式是否被作為主程式執行
151 + if __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 +
Újabb Régebbi