import calendar # 匯入 calendar 模組 import math from datetime import date, datetime, time import pytz from dateutil.relativedelta import relativedelta def date_type(value): return (datetime if isinstance(value, datetime) else date) # 根據值的類型返回 datetime 或 date # fmt: skip def get_month(date): date_from = date_type(date)(date.year, date.month, 1) # 建立日期起始值為當月第一天 date_to = date_type(date)(date.year, date.month, calendar.monthrange(date.year, date.month)[1]) # 建立日期結束值為當月最後一天 # fmt: skip return date_from, date_to # 返回起始日期和結束日期 def get_quarter_number(date): return math.ceil(date.month / 3) # 回傳日期對應的季度數字,向上取整 def get_quarter(date): quarter_number = get_quarter_number(date) # 獲取日期對應的季度數字 month_from = ((quarter_number - 1) * 3) + 1 # 計算季度的起始月份 date_from = date_type(date)(date.year, month_from, 1) # 建立季度的起始日期 date_to = date_from + relativedelta(months=2) # 結束日期為起始日期加兩個月 date_to = date_to.replace( day=calendar.monthrange(date_to.year, date_to.month)[1] ) # 調整結束日期為該月的最後一天 return date_from, date_to # 返回季度的起始日期和結束日期 def get_timedelta(qty, granularity): switch = { "hour": relativedelta(hours=qty), "day": relativedelta(days=qty), "week": relativedelta(weeks=qty), "month": relativedelta(months=qty), "year": relativedelta(years=qty), } return switch[granularity] def start_of(value, granularity): is_datetime = isinstance(value, datetime) if granularity == "year": result = value.replace(month=1, day=1) elif granularity == "quarter": # Q1 = Jan 1st # Q2 = Apr 1st # Q3 = Jul 1st # Q4 = Oct 1st result = get_quarter(value)[0] elif granularity == "month": result = value.replace(day=1) elif granularity == "week": # by default MONDAY is the first day of the week and SUNDAY is the last. result = value - relativedelta( days=calendar.weekday(value.year, value.month, value.day) ) elif granularity == "day": result = value elif granularity == "hour" and is_datetime: return datetime.combine(value, time.min).replace(hour=value.hour) elif is_datetime: raise ValueError("Granularity must be year, quarter, month, week, day or hour for value %s" % value) # fmt: skip # raise ValueError("請設定精確度為年、季、月、週、日或小時,目前設定值為 %s" % value) else: raise ValueError("Granularity must be year, quarter, month, week or day for value %s" % value) # fmt: skip # raise ValueError("精確度應為年、季、月、週或日,但收到的值為 %s" % value) return datetime.combine(result, time.min) if is_datetime else result def end_of(value, granularity): is_datetime = isinstance(value, datetime) if granularity == "year": result = value.replace(month=12, day=31) elif granularity == "quarter": # Q1 = Mar 31st # Q2 = Jun 30th # Q3 = Sep 30th # Q4 = Dec 31st result = get_quarter(value)[1] elif granularity == "month": result = value + relativedelta(day=1, months=1, days=-1) elif granularity == "week": result = value + relativedelta(days=6 - calendar.weekday(value.year, value.month, value.day)) # fmt: skip elif granularity == "day": result = value elif granularity == "hour" and is_datetime: return datetime.combine(value, time.max).replace(hour=value.hour) elif is_datetime: raise ValueError("Granularity must be year, quarter, month, week, day or hour for value %s" % value) # fmt: skip # raise ValueError("請設定精確度為年、季、月、週、日或小時,目前設定值為 %s" % value) else: raise ValueError("Granularity must be year, quarter, month, week or day for value %s" % value) # fmt: skip # raise ValueError("精確度應為年、季、月、週或日,但收到的值為 %s" % value) return datetime.combine(result, time.max) if is_datetime else result def add(value, *args, **kwargs): return value + relativedelta(*args, **kwargs) # 將 value 增加指定的相對時間 def subtract(value, *args, **kwargs): return value - relativedelta(*args, **kwargs) # 將 value 減去指定的相對時間 def date_range(start, end, step=relativedelta(months=1)): if isinstance(start, datetime) and isinstance(end, datetime): are_naive = start.tzinfo is None and end.tzinfo is None are_utc = start.tzinfo == pytz.utc and end.tzinfo == pytz.utc # Cases with miscellaneous time zones are more complex due to DST. are_others = start.tzinfo and end.tzinfo and not are_utc if are_others and start.tzinfo.zone != end.tzinfo.zone: raise ValueError("Timezones of start argument and end argument seem inconsistent") # fmt: skip if not are_naive and not are_utc and not are_others: raise ValueError("Timezones of start argument and end argument mismatch") dt = start.replace(tzinfo=None) end_dt = end.replace(tzinfo=None) post_process = start.tzinfo.localize if start.tzinfo else lambda dt: dt elif isinstance(start, date) and isinstance(end, date): dt, end_dt = start, end post_process = lambda dt: dt else: raise ValueError("start/end should be both date or both datetime type") if start > end: raise ValueError("start > end, start date must be before end") if start == start + step: raise ValueError("Looks like step is null") while dt <= end_dt: yield post_process(dt) dt = dt + step # 檢查當前程式是否被作為主程式執行 if __name__ == "__main__": # 建立一個日期物件 input_date = date(2023, 12, 15) # 使用 get_month 函數獲取該日期所在月份的起始日期和結束日期 month_start_date, month_end_date = get_month(input_date) # 輸出結果 print("Month Start Date:", month_start_date) print("Month End Date:", month_end_date) # 使用 get_quarter_number 函數獲取該日期對應的季度數字 quarter_num = get_quarter_number(input_date) # 使用 get_quarter 函數獲取該日期所在季度的起始日期和結束日期 quarter_start_date, quarter_end_date = get_quarter(input_date) # 輸出結果 print("Quarter Number:", quarter_num) print("Quarter Start Date:", quarter_start_date) print("Quarter End Date:", quarter_end_date) # 呼叫 get_timedelta 函數,設定數量為 2,時間間隔為 'month' time_delta = get_timedelta(2, "month") # 輸出結果 print("Time Delta:", time_delta) input_datetime = datetime(2024, 3, 22, 15, 30, 0) # 使用 start_of 函數獲取以週為精確度的日期起始時間 start_of_week = start_of(input_datetime, "week") print("Start of Week:", start_of_week) # 使用 end_of 函數獲取以週為精確度的日期結束時間 end_of_week = end_of(input_datetime, "week") print("End of Week:", end_of_week) # 呼叫 add 函數,將 value 增加 2 個月 result_add = add(datetime.now(), months=2) print("Result of add function:", result_add) # 呼叫 subtract 函數,將 value 減去 1 年 result_subtract = subtract(datetime.now(), years=1) print("Result of subtract function:", result_subtract) # 定義起始日期和結束日期 start_date = datetime(2024, 1, 1) end_date = datetime(2024, 3, 1) # 呼叫date_range函數並迭代生成的日期序列 for date in date_range(start_date, end_date, step=relativedelta(months=1)): print(date)