From d9a9b13f4329aef8f027eadd061f2c475861825a Mon Sep 17 00:00:00 2001 From: kinegratii Date: Mon, 12 Feb 2024 20:03:29 +0800 Subject: [PATCH 01/10] :sparkles: add freq string for SolarFestival&LunarFestival --- borax/calendars/festivals2.py | 17 ++++++++++++++--- docs/changelog.md | 4 ++++ docs/guides/festivals2.md | 28 +++++++++++++++++----------- tests/test_festival2_list.py | 13 +++++++++++++ 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/borax/calendars/festivals2.py b/borax/calendars/festivals2.py index e402ab5..49f0ccd 100644 --- a/borax/calendars/festivals2.py +++ b/borax/calendars/festivals2.py @@ -30,6 +30,8 @@ class FreqConst: YEARLY = 0 MONTHLY = 1 + LABEL2VAL = {'monthly': 1, 'yearly': 0, 'm': 1, 'y': 0} + class FestivalCatalog: basic = 'basic' @@ -460,12 +462,17 @@ class SolarFestival(Festival): """ date_class = date - def __init__(self, *, day: int, freq: int = FreqConst.YEARLY, month: int = 0, name: str = None): + def __init__(self, *, day: int, freq: Union[int, Literal['yearly', 'monthly', 'y', 'm']] = FreqConst.YEARLY, + month: int = 0, name: str = None): if day < 0: day = -day reverse = 1 else: reverse = 0 + if isinstance(freq, str): + freq = FreqConst.LABEL2VAL.get(freq, -1) + if freq == -1: + raise ValueError('Invalid freq string.') super().__init__(name=name, freq=freq, month=month, day=day, reverse=reverse, schema=FestivalSchema.SOLAR) def _get_description(self) -> str: @@ -680,13 +687,17 @@ class LunarFestival(Festival): """ date_class = LunarDate - def __init__(self, *, day: int, freq: int = FreqConst.YEARLY, month: int = 0, leap: int = _IGNORE_LEAP_MONTH, - name: str = None): + def __init__(self, *, day: int, freq: Union[int, Literal['yearly', 'monthly', 'y', 'm']] = FreqConst.YEARLY, + month: int = 0, leap: int = _IGNORE_LEAP_MONTH, name: str = None): if day < 0: day = -day reverse = 1 else: reverse = 0 + if isinstance(freq, str): + freq = FreqConst.LABEL2VAL.get(freq, -1) + if freq == -1: + raise ValueError('Invalid freq string.') super().__init__(freq=freq, name=name, month=month, day=day, leap=leap, reverse=reverse, schema=FestivalSchema.LUNAR) diff --git a/docs/changelog.md b/docs/changelog.md index 9c4aa6d..0de0594 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,9 @@ # 更新日志 +## v4.1.1 + +- `SolarFestival` 和`LunarFestival` 初始化函数 `freq` 参数支持字符串设置( [ #56](https://github.com/kinegratii/borax/issues/56) ) + ## v4.1.0 (20240131) > Borax最低python版本要求为python3.9 diff --git a/docs/guides/festivals2.md b/docs/guides/festivals2.md index 0d5aa2c..a6fedac 100644 --- a/docs/guides/festivals2.md +++ b/docs/guides/festivals2.md @@ -2,6 +2,8 @@ > 模块: `borax.calendars.festivals2` +> Updated in 4.1.1: SolarFestival和LunarFestival的freq参数支持字符串形式。 + > Updated in 4.1.0:新增 Festival.code属性。 > Updated in 3.5.6: 星期型节日(WeekFestival)类支持倒数序号。如:“国际麻风节(1月最后一个星期天)” @@ -161,11 +163,13 @@ class SolarFestival(*, day: int, freq: int = FreqConst.YEARLY, month: int = 0, n 参数定义 -| 参数 | 描述 | 取值 | -| ----- | ------------------------------------------------------------ | -------------- | -| freq | 节日频率,“每年”或“每月”,默认“每年”。 | 0:每年;1:每月 | -| month | 月份。 | 0,1-12 | -| day | 日期序号。当month取值0时,表示一年的第几天;否则表示该月的第几天。允许取负值,表示一年/一个月的倒数 第几天。 | | +| 参数 | 描述 | 取值 | +| ----- | ------------------------------------------------------------ | ---------------------------------------------- | +| freq | 节日频率,“每年”或“每月”,默认“每年”。 | 0/yearly/y:每年;1/monthly/m:每月 1 | +| month | 月份。 | 0,1-12 | +| day | 日期序号。当month取值0时,表示一年的第几天;否则表示该月的第几天。允许取负值,表示一年/一个月的倒数 第几天。 | | + +1. v4.1.1 新增字符串形式。 6种形式定义 @@ -186,12 +190,14 @@ class LunarFestival(*, day:int, freq:int=FreqConst.YEARLY, month:int=0, leap:int 参数定义 -| 参数 | 描述 | 取值 | -| ----- | ------------------------------------------------------------ | --------------- | -| freq | 节日频率,“每年”或“每月”,默认“每年”。 | 0:每年;1:每月 | -| month | 月份。 | 0,1-12 | -| leap | 闰月标记。取值参见 `LeapConst`。 | LeapConst.MIXED | -| day | 日期序号。当month未设置时,表示一年的第几天;否则表示该月的第几天。允许取负值,表示一年/一个月的倒数 第几天。 | | +| 参数 | 描述 | 取值 | +| ----- | ------------------------------------------------------------ | ---------------------------------------------- | +| freq | 节日频率,“每年”或“每月”,默认“每年”。 | 0/yearly/y:每年;1/monthly/m:每月 1 | +| month | 月份。 | 0,1-12 | +| leap | 闰月标记。取值参见 `LeapConst`。 | LeapConst.MIXED | +| day | 日期序号。当month未设置时,表示一年的第几天;否则表示该月的第几天。允许取负值,表示一年/一个月的倒数 第几天。 | | + +1. v4.1.1 新增字符串形式。 ### WeekFestival diff --git a/tests/test_festival2_list.py b/tests/test_festival2_list.py index 421ec86..463881c 100644 --- a/tests/test_festival2_list.py +++ b/tests/test_festival2_list.py @@ -47,6 +47,13 @@ def test_reverse(self): self.assertEqual(date(2021, 2, 28), sf2.at(year=2021, month=2)) self.assertEqual(date(2020, 2, 29), sf2.at(year=2020, month=2)) + def test_freq_string(self): + sf = SolarFestival(freq='monthly', day=1) + self.assertTrue(sf.is_(date(2024, 2, 1))) + + with self.assertRaises(ValueError): + sf2 = SolarFestival(freq='33', day=1) + class WeekFestivalTestCase(unittest.TestCase): def test_basic_logic(self): @@ -136,6 +143,12 @@ def test_monthly(self): with self.assertRaises(FestivalError): lf.at(year=2021) + def test_freq_string(self): + lf = LunarFestival(freq='m', day=1) + self.assertTrue(lf.is_(LunarDate(2024, 1, 1))) + with self.assertRaises(ValueError): + LunarFestival(freq='xxx', day=1) + class CheckFestivalTestCase(unittest.TestCase): def test_all_days(self): From 447dfe41a9bd51242466e5ee3c8cc9dfff09e156 Mon Sep 17 00:00:00 2001 From: kinegratii Date: Sun, 18 Feb 2024 21:10:12 +0800 Subject: [PATCH 02/10] :sparkles: Update freq string. #56 --- docs/guides/festivals2.md | 4 ++-- tests/test_festival2_list.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guides/festivals2.md b/docs/guides/festivals2.md index a6fedac..951dc33 100644 --- a/docs/guides/festivals2.md +++ b/docs/guides/festivals2.md @@ -392,7 +392,7 @@ Festival.list_days_in_future(end_date=None, reverse: bool = False, count: int = Festival.list_days_in_past(end_date=None, reverse: bool = False, count: int = -1) -> List[WrappedDate] ``` -返回今后时间([today, end_date])之间(含起止日期)匹配本 Festival 的日期列表。 +返回过去时间([today, end_date])之间(含起止日期)匹配本 Festival 的日期列表。 ### get_one_day @@ -693,7 +693,7 @@ pprint.pprint(days) FestivalLibrary.monthdaycalendar(year: int, month: int, firstweekday: int = 0) ``` -返回二维列表,每一行表示一个星期。逻辑同iter_month_daytuples。 +返回二维列表,每一行表示一个星期。逻辑同`iter_month_daytuples` 。 ### to_csv diff --git a/tests/test_festival2_list.py b/tests/test_festival2_list.py index 463881c..5d46530 100644 --- a/tests/test_festival2_list.py +++ b/tests/test_festival2_list.py @@ -52,7 +52,7 @@ def test_freq_string(self): self.assertTrue(sf.is_(date(2024, 2, 1))) with self.assertRaises(ValueError): - sf2 = SolarFestival(freq='33', day=1) + SolarFestival(freq='33', day=1) class WeekFestivalTestCase(unittest.TestCase): From d2520ad841c8ecacdfd367272522c961a27a7e11 Mon Sep 17 00:00:00 2001 From: kinegratii Date: Tue, 20 Feb 2024 23:39:55 +0800 Subject: [PATCH 03/10] :recycle: Refactor creator ui layouts --- borax/capp/festival_creator.py | 113 +++++++++++++++++---------------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/borax/capp/festival_creator.py b/borax/capp/festival_creator.py index e728f30..1e7ebb4 100644 --- a/borax/capp/festival_creator.py +++ b/borax/capp/festival_creator.py @@ -139,8 +139,7 @@ def __init__(self, master=None, **kwargs): self._vm = VarModel() - n_row, s_row, l_row, w_row, t_row, btn_row, msg_row = 0, 2, 4, 6, 8, 10, 11 - empty_rows = (1, 3, 5, 7, 9) + n_row, s_row, l_row, w_row, t_row, btn_row, msg_row = 0, 0, 0, 0, 0, 0, 0 ccb_w = 10 freq_choices = ((FreqConst.YEARLY, '每年'), (FreqConst.MONTHLY, '每月')) @@ -154,83 +153,87 @@ def __init__(self, master=None, **kwargs): delta_choices = [(0, '当日'), (-1, '之前'), (1, '之后')] gz_day_choices = list(TextUtils.BRANCHES + TextUtils.STEMS) + # main_frame -> + frame = ttk.Frame(main_frame) frame.pack(side='left', expand=True, fill=tk.BOTH, padx=10, pady=10) - ttk.Label(frame, text='名称').grid(row=n_row, column=0) - ttk.Entry(frame, textvariable=self._vm.vars['name']).grid(row=n_row, column=1, columnspan=3, sticky='we') - ttk.Label(frame, text='分类').grid(row=n_row, column=4) - ttk.Entry(frame, textvariable=self._vm.vars['catalog']).grid(row=n_row, column=5, columnspan=3, sticky='we') + + name_ui = ttk.Frame(frame) + name_ui.pack(side='top', fill='x') + ttk.Label(name_ui, text='名称').grid(row=n_row, column=0) + ttk.Entry(name_ui, textvariable=self._vm.vars['name']).grid(row=n_row, column=1, columnspan=3, sticky='we') + ttk.Label(name_ui, text='分类').grid(row=n_row, column=4) + ttk.Entry(name_ui, textvariable=self._vm.vars['catalog']).grid(row=n_row, column=5, columnspan=3, sticky='we') # Solar Festival - ttk.Radiobutton(frame, text='公历型', value=FestivalSchema.SOLAR.value, variable=self._vm.vars['schema']).grid( - row=s_row, column=0) - ChoicesCombobox(frame, choices=freq_choices, val_variable=self._vm.vars['s_freq'], width=ccb_w).grid(row=s_row, - column=1) - ChoicesCombobox(frame, choices=month_choices, val_variable=self._vm.vars['s_month'], width=ccb_w).grid( + + s_rb = ttk.Radiobutton(frame, text='公历型', value=FestivalSchema.SOLAR.value, variable=self._vm.vars['schema']) + s_ui = ttk.LabelFrame(frame, labelwidget=s_rb, padding=5) + s_ui.pack(side='top', fill='x', expand=True, pady=10) + ChoicesCombobox(s_ui, choices=freq_choices, val_variable=self._vm.vars['s_freq'], width=ccb_w).grid(row=s_row, + column=1) + ChoicesCombobox(s_ui, choices=month_choices, val_variable=self._vm.vars['s_month'], width=ccb_w).grid( row=s_row, column=3) - ttk.Label(frame, text='月').grid(row=s_row, column=4) - ChoicesCombobox(frame, choices=day_reverse_choices, val_variable=self._vm.vars['s_reverse'], width=ccb_w).grid( + ttk.Label(s_ui, text='月').grid(row=s_row, column=4) + ChoicesCombobox(s_ui, choices=day_reverse_choices, val_variable=self._vm.vars['s_reverse'], width=ccb_w).grid( row=s_row, column=5) - ttk.Combobox(frame, values=list(range(1, 32)), textvariable=self._vm.vars['s_day'], width=ccb_w).grid(row=s_row, - column=6) - ttk.Label(frame, text='日/天').grid(row=s_row, column=7) + ttk.Combobox(s_ui, values=list(range(1, 32)), textvariable=self._vm.vars['s_day'], width=ccb_w).grid(row=s_row, + column=6) + ttk.Label(s_ui, text='日/天').grid(row=s_row, column=7) # Lunar Festival - ttk.Radiobutton(frame, text='农历型', value=FestivalSchema.LUNAR.value, variable=self._vm.vars['schema']).grid( - row=l_row, - column=0) - ChoicesCombobox(frame, choices=freq_choices, val_variable=self._vm.vars['l_freq'], width=ccb_w).grid(row=l_row, - column=1) - ChoicesCombobox(frame, choices=leap_choices, val_variable=self._vm.vars['l_leap'], width=ccb_w).grid(row=l_row, - column=2) - ChoicesCombobox(frame, choices=month_choices, val_variable=self._vm.vars['l_month'], width=ccb_w).grid( + l_rb = ttk.Radiobutton(frame, text='农历型', value=FestivalSchema.LUNAR.value, variable=self._vm.vars['schema']) + l_ui = ttk.LabelFrame(frame, labelwidget=l_rb, padding=5) + l_ui.pack(side='top', fill='x', expand=True, pady=10) + ChoicesCombobox(l_ui, choices=freq_choices, val_variable=self._vm.vars['l_freq'], width=ccb_w).grid(row=l_row, + column=1) + ChoicesCombobox(l_ui, choices=leap_choices, val_variable=self._vm.vars['l_leap'], width=ccb_w).grid(row=l_row, + column=2) + ChoicesCombobox(l_ui, choices=month_choices, val_variable=self._vm.vars['l_month'], width=ccb_w).grid( row=l_row, column=3) - ttk.Label(frame, text='月').grid(row=l_row, column=4) - ChoicesCombobox(frame, choices=day_reverse_choices, val_variable=self._vm.vars['l_reverse'], width=ccb_w).grid( + ttk.Label(l_ui, text='月').grid(row=l_row, column=4) + ChoicesCombobox(l_ui, choices=day_reverse_choices, val_variable=self._vm.vars['l_reverse'], width=ccb_w).grid( row=l_row, column=5) - ttk.Combobox(frame, values=list(range(1, 31)), textvariable=self._vm.vars['l_day'], width=ccb_w).grid(row=l_row, - column=6) - ttk.Label(frame, text='日/天').grid(row=l_row, column=7) + ttk.Combobox(l_ui, values=list(range(1, 31)), textvariable=self._vm.vars['l_day'], width=ccb_w).grid(row=l_row, + column=6) + ttk.Label(l_ui, text='日/天').grid(row=l_row, column=7) # Week Festival - ttk.Radiobutton(frame, text='星期型', value=FestivalSchema.WEEK.value, variable=self._vm.vars['schema']).grid( - row=w_row, column=0) - ttk.Label(frame, text='每年').grid(row=w_row, column=1) - ChoicesCombobox(frame, choices=month_choices, val_variable=self._vm.vars['w_month'], width=ccb_w).grid( + w_rb = ttk.Radiobutton(frame, text='星期型', value=FestivalSchema.WEEK.value, variable=self._vm.vars['schema']) + w_ui = ttk.LabelFrame(frame, labelwidget=w_rb, padding=5) + w_ui.pack(side='top', fill='x', expand=True, pady=10) + + ttk.Label(w_ui, text='每年').grid(row=w_row, column=1) + ChoicesCombobox(w_ui, choices=month_choices, val_variable=self._vm.vars['w_month'], width=ccb_w).grid( row=w_row, column=3) - ttk.Label(frame, text='月').grid(row=w_row, column=4) - ChoicesCombobox(frame, choices=index2_choices, val_variable=self._vm.vars['w_index'], width=ccb_w).grid( + ttk.Label(w_ui, text='月').grid(row=w_row, column=4) + ChoicesCombobox(w_ui, choices=index2_choices, val_variable=self._vm.vars['w_index'], width=ccb_w).grid( row=w_row, column=5, columnspan=1) - ttk.Label(frame, text='星期').grid(row=w_row, column=6, columnspan=1) - ChoicesCombobox(frame, choices=week_choices, val_variable=self._vm.vars['w_week'], width=ccb_w).grid( + ttk.Label(w_ui, text='星期').grid(row=w_row, column=6, columnspan=1) + ChoicesCombobox(w_ui, choices=week_choices, val_variable=self._vm.vars['w_week'], width=ccb_w).grid( row=w_row, column=7, columnspan=1) # Term Festival - ttk.Radiobutton(frame, text='节气型', value=FestivalSchema.TERM.value, variable=self._vm.vars['schema']).grid( - row=t_row, - column=0) - ttk.Label(frame, text='每年').grid(row=t_row, column=1) - ChoicesCombobox(frame, choices=term_choices, val_variable=self._vm.vars['t_term'], width=ccb_w).grid( + t_rb = ttk.Radiobutton(frame, text='节气型', value=FestivalSchema.TERM.value, variable=self._vm.vars['schema']) + t_ui = ttk.LabelFrame(frame, labelwidget=t_rb, padding=5) + t_ui.pack(side='top', fill='x', expand=True, pady=10) + + ttk.Label(t_ui, text='每年').grid(row=t_row, column=1) + ChoicesCombobox(t_ui, choices=term_choices, val_variable=self._vm.vars['t_term'], width=ccb_w).grid( row=t_row, column=2, columnspan=1) - ttk.Label(frame, text='节气').grid(row=t_row, column=3) - ChoicesCombobox(frame, choices=delta_choices, val_variable=self._vm.vars['t_delta'], width=ccb_w).grid( + ttk.Label(t_ui, text='节气').grid(row=t_row, column=3) + ChoicesCombobox(t_ui, choices=delta_choices, val_variable=self._vm.vars['t_delta'], width=ccb_w).grid( row=t_row, column=4, columnspan=1) - ChoicesCombobox(frame, choices=index_choices, val_variable=self._vm.vars['t_index'], empty_value=0, + ChoicesCombobox(t_ui, choices=index_choices, val_variable=self._vm.vars['t_index'], empty_value=0, width=ccb_w).grid(row=t_row, column=5, columnspan=1) - ttk.Combobox(frame, values=gz_day_choices, textvariable=self._vm.vars['t_day_gz'], state='readonly', + ttk.Combobox(t_ui, values=gz_day_choices, textvariable=self._vm.vars['t_day_gz'], state='readonly', width=ccb_w).grid( row=t_row, column=6, columnspan=1) - ttk.Label(frame, text='日').grid(row=t_row, column=7) + ttk.Label(t_ui, text='日').grid(row=t_row, column=7) - # Toolbar 8 cols - ttk.Button(frame, text='创建节日', command=self._create).grid(row=btn_row, column=0, columnspan=8, - sticky='we') + ttk.Button(frame, text='创建节日', command=self._create).pack(side='top') self._msg_label = MessageLabel(frame, text='') - self._msg_label.grid(row=msg_row, column=0, columnspan=10) - - for er in empty_rows: - ttk.Label(frame).grid(row=er, column=0, columnspan=7) + self._msg_label.pack(side='top') - # init form values self._vm.init() self._festival_detail = tk.StringVar() From 3ea8715c1fbbb9021ada20743af6fc6504222b7f Mon Sep 17 00:00:00 2001 From: kinegratii Date: Thu, 22 Feb 2024 20:17:24 +0800 Subject: [PATCH 04/10] :pencil: Add Borax.Capp changelog --- docs/capp_changelog.md | 11 +++++++++++ mkdocs.yaml | 1 + 2 files changed, 12 insertions(+) create mode 100644 docs/capp_changelog.md diff --git a/docs/capp_changelog.md b/docs/capp_changelog.md new file mode 100644 index 0000000..63d1a5f --- /dev/null +++ b/docs/capp_changelog.md @@ -0,0 +1,11 @@ +# Borax.Capp 变更日志 + +Borax.Capp 是一个自带基于 tkinter 开发的日历应用程序。其版本号同 Borax 库。 + +## v4.1.1 + +- 优化节日创建界面布局 + +## v4.1.0 + +- 发布 Borax.Capp \ No newline at end of file diff --git a/mkdocs.yaml b/mkdocs.yaml index b80922b..31bfc45 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -50,6 +50,7 @@ nav: - 农历开发笔记: posts/lunardate-development.md - 开发&更新日志: - 更新日志: changelog.md + - Capp更新日志: capp_changelog.md - 开发日志: develop_note.md - 发布日志: - v4.1.0: release-note/v410.md From 534d80e87aeb07c377dea7df6902d2bd2e343d4b Mon Sep 17 00:00:00 2001 From: kinegratii Date: Sat, 24 Feb 2024 21:25:10 +0800 Subject: [PATCH 05/10] :sparkles: add validate logic for festival creator --- borax/capp/festival_creator.py | 76 ++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/borax/capp/festival_creator.py b/borax/capp/festival_creator.py index 1e7ebb4..abdce1a 100644 --- a/borax/capp/festival_creator.py +++ b/borax/capp/festival_creator.py @@ -95,11 +95,17 @@ def init(self): def _validate_f0(self): freq, month, reverse, day = self.gets('s_freq', 's_month', 's_reverse', 's_day') + _d = abs(day) + if (month == 0 and _d > 366) or _d > 31: + raise ValidateError('公历 日/天 数值范围不正确。') festival = SolarFestival(day=reverse * day, freq=freq, month=month) return festival def _validate_f1(self): freq, leap, reverse, month, day = self.gets('l_freq', 'l_leap', 'l_reverse', 'l_month', 'l_day') + _d = abs(day) + if (month == 0 and _d > 384) or _d > 30: + raise ValidateError('农历 日/天 数值范围不正确。') festival = LunarFestival(day=reverse * day, freq=freq, leap=leap, month=month) return festival @@ -130,7 +136,7 @@ class FestivalCreatePanel(ttk.Frame): def __init__(self, master=None, **kwargs): super().__init__(master, **kwargs) - lf = ttk.LabelFrame(self, labelanchor='n', text='节日管理工具') + lf = ttk.LabelFrame(self, labelanchor='n', text='节日创建工具') lf.pack(side='top', fill='x', padx=10, pady=10, expand=True) ttk.Label(lf, text=' 本工具支持创建公历型、农历型、星期型、节气型节日,并导出为csv文件。').pack( side='top', padx=5, pady=5, fill='both', expand=True) @@ -138,8 +144,6 @@ def __init__(self, master=None, **kwargs): main_frame.pack(side='top', fill='both') self._vm = VarModel() - - n_row, s_row, l_row, w_row, t_row, btn_row, msg_row = 0, 0, 0, 0, 0, 0, 0 ccb_w = 10 freq_choices = ((FreqConst.YEARLY, '每年'), (FreqConst.MONTHLY, '每月')) @@ -148,7 +152,7 @@ def __init__(self, master=None, **kwargs): day_reverse_choices = [(1, '正向'), (-1, '倒数')] week_choices = list(enumerate('一二三四五六日')) term_choices = list(enumerate(TERMS_CN)) - index_choices = [(i, f'第{i}个') for i in range(1, 10)] + index_choices = [(0, '当日')] + [(i, f'第{i}个') for i in range(1, 10)] index2_choices = [(i, f'第{i}个') for i in range(1, 10)] + [(-i, f'倒数第{i}个') for i in range(1, 10)] delta_choices = [(0, '当日'), (-1, '之前'), (1, '之后')] gz_day_choices = list(TextUtils.BRANCHES + TextUtils.STEMS) @@ -160,76 +164,76 @@ def __init__(self, master=None, **kwargs): name_ui = ttk.Frame(frame) name_ui.pack(side='top', fill='x') - ttk.Label(name_ui, text='名称').grid(row=n_row, column=0) - ttk.Entry(name_ui, textvariable=self._vm.vars['name']).grid(row=n_row, column=1, columnspan=3, sticky='we') - ttk.Label(name_ui, text='分类').grid(row=n_row, column=4) - ttk.Entry(name_ui, textvariable=self._vm.vars['catalog']).grid(row=n_row, column=5, columnspan=3, sticky='we') - # Solar Festival + ttk.Label(name_ui, text='名称').pack(side='left') + ttk.Entry(name_ui, textvariable=self._vm.vars['name']).pack(side='left') + ttk.Label(name_ui, text='分类').pack(side='left') + ttk.Entry(name_ui, textvariable=self._vm.vars['catalog']).pack(side='left') + # Solar Festival s_rb = ttk.Radiobutton(frame, text='公历型', value=FestivalSchema.SOLAR.value, variable=self._vm.vars['schema']) s_ui = ttk.LabelFrame(frame, labelwidget=s_rb, padding=5) s_ui.pack(side='top', fill='x', expand=True, pady=10) - ChoicesCombobox(s_ui, choices=freq_choices, val_variable=self._vm.vars['s_freq'], width=ccb_w).grid(row=s_row, + ChoicesCombobox(s_ui, choices=freq_choices, val_variable=self._vm.vars['s_freq'], width=ccb_w).grid(row=0, column=1) ChoicesCombobox(s_ui, choices=month_choices, val_variable=self._vm.vars['s_month'], width=ccb_w).grid( - row=s_row, column=3) - ttk.Label(s_ui, text='月').grid(row=s_row, column=4) + row=0, column=3) + ttk.Label(s_ui, text='月 ').grid(row=0, column=4) ChoicesCombobox(s_ui, choices=day_reverse_choices, val_variable=self._vm.vars['s_reverse'], width=ccb_w).grid( - row=s_row, column=5) - ttk.Combobox(s_ui, values=list(range(1, 32)), textvariable=self._vm.vars['s_day'], width=ccb_w).grid(row=s_row, + row=0, column=5) + ttk.Combobox(s_ui, values=list(range(1, 32)), textvariable=self._vm.vars['s_day'], width=ccb_w).grid(row=0, column=6) - ttk.Label(s_ui, text='日/天').grid(row=s_row, column=7) + ttk.Label(s_ui, text='日/天').grid(row=0, column=7) # Lunar Festival l_rb = ttk.Radiobutton(frame, text='农历型', value=FestivalSchema.LUNAR.value, variable=self._vm.vars['schema']) l_ui = ttk.LabelFrame(frame, labelwidget=l_rb, padding=5) l_ui.pack(side='top', fill='x', expand=True, pady=10) - ChoicesCombobox(l_ui, choices=freq_choices, val_variable=self._vm.vars['l_freq'], width=ccb_w).grid(row=l_row, + ChoicesCombobox(l_ui, choices=freq_choices, val_variable=self._vm.vars['l_freq'], width=ccb_w).grid(row=0, column=1) - ChoicesCombobox(l_ui, choices=leap_choices, val_variable=self._vm.vars['l_leap'], width=ccb_w).grid(row=l_row, + ChoicesCombobox(l_ui, choices=leap_choices, val_variable=self._vm.vars['l_leap'], width=ccb_w).grid(row=0, column=2) ChoicesCombobox(l_ui, choices=month_choices, val_variable=self._vm.vars['l_month'], width=ccb_w).grid( - row=l_row, column=3) - ttk.Label(l_ui, text='月').grid(row=l_row, column=4) + row=0, column=3) + ttk.Label(l_ui, text='月 ').grid(row=0, column=4) ChoicesCombobox(l_ui, choices=day_reverse_choices, val_variable=self._vm.vars['l_reverse'], width=ccb_w).grid( - row=l_row, column=5) - ttk.Combobox(l_ui, values=list(range(1, 31)), textvariable=self._vm.vars['l_day'], width=ccb_w).grid(row=l_row, + row=0, column=5) + ttk.Combobox(l_ui, values=list(range(1, 31)), textvariable=self._vm.vars['l_day'], width=ccb_w).grid(row=0, column=6) - ttk.Label(l_ui, text='日/天').grid(row=l_row, column=7) + ttk.Label(l_ui, text='日/天').grid(row=0, column=7) # Week Festival w_rb = ttk.Radiobutton(frame, text='星期型', value=FestivalSchema.WEEK.value, variable=self._vm.vars['schema']) w_ui = ttk.LabelFrame(frame, labelwidget=w_rb, padding=5) w_ui.pack(side='top', fill='x', expand=True, pady=10) - ttk.Label(w_ui, text='每年').grid(row=w_row, column=1) + ttk.Label(w_ui, text='每年 ').grid(row=0, column=1) ChoicesCombobox(w_ui, choices=month_choices, val_variable=self._vm.vars['w_month'], width=ccb_w).grid( - row=w_row, column=3) - ttk.Label(w_ui, text='月').grid(row=w_row, column=4) + row=0, column=3) + ttk.Label(w_ui, text='月 ').grid(row=0, column=4) ChoicesCombobox(w_ui, choices=index2_choices, val_variable=self._vm.vars['w_index'], width=ccb_w).grid( - row=w_row, column=5, columnspan=1) - ttk.Label(w_ui, text='星期').grid(row=w_row, column=6, columnspan=1) + row=0, column=5, columnspan=1) + ttk.Label(w_ui, text=' 星期').grid(row=0, column=6, columnspan=1) ChoicesCombobox(w_ui, choices=week_choices, val_variable=self._vm.vars['w_week'], width=ccb_w).grid( - row=w_row, column=7, columnspan=1) + row=0, column=7, columnspan=1) # Term Festival t_rb = ttk.Radiobutton(frame, text='节气型', value=FestivalSchema.TERM.value, variable=self._vm.vars['schema']) t_ui = ttk.LabelFrame(frame, labelwidget=t_rb, padding=5) t_ui.pack(side='top', fill='x', expand=True, pady=10) - ttk.Label(t_ui, text='每年').grid(row=t_row, column=1) + ttk.Label(t_ui, text='每年 ').grid(row=0, column=1) ChoicesCombobox(t_ui, choices=term_choices, val_variable=self._vm.vars['t_term'], width=ccb_w).grid( - row=t_row, column=2, columnspan=1) - ttk.Label(t_ui, text='节气').grid(row=t_row, column=3) + row=0, column=2, columnspan=1) + ttk.Label(t_ui, text='节气 ').grid(row=0, column=3) ChoicesCombobox(t_ui, choices=delta_choices, val_variable=self._vm.vars['t_delta'], width=ccb_w).grid( - row=t_row, column=4, columnspan=1) + row=0, column=4, columnspan=1) ChoicesCombobox(t_ui, choices=index_choices, val_variable=self._vm.vars['t_index'], empty_value=0, - width=ccb_w).grid(row=t_row, column=5, columnspan=1) + width=ccb_w).grid(row=0, column=5, columnspan=1) ttk.Combobox(t_ui, values=gz_day_choices, textvariable=self._vm.vars['t_day_gz'], state='readonly', width=ccb_w).grid( - row=t_row, column=6, columnspan=1) - ttk.Label(t_ui, text='日').grid(row=t_row, column=7) + row=0, column=6, columnspan=1) + ttk.Label(t_ui, text='日').grid(row=0, column=7) - ttk.Button(frame, text='创建节日', command=self._create).pack(side='top') + ttk.Button(frame, text='创建节日', command=self._create).pack(side='top', fill='x') self._msg_label = MessageLabel(frame, text='') self._msg_label.pack(side='top') From 6abc5350c5f9608ffad5c7df442b51b3eaba8101 Mon Sep 17 00:00:00 2001 From: kinegratii Date: Fri, 29 Mar 2024 13:29:53 +0800 Subject: [PATCH 06/10] :sparkles: Refactor with borax.ui.widgets.MessageLabel --- borax/capp/borax_calendar_app.py | 13 +++++++------ borax/capp/festival_creator.py | 23 +++++++---------------- borax/ui/widgets.py | 9 +++++++++ 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/borax/capp/borax_calendar_app.py b/borax/capp/borax_calendar_app.py index bc5e2fb..dc6c165 100644 --- a/borax/capp/borax_calendar_app.py +++ b/borax/capp/borax_calendar_app.py @@ -15,6 +15,7 @@ from borax.calendars.ui import CalendarFrame, FestivalTableFrame from borax.calendars.utils import ThreeNineUtils from borax.capp.festival_creator import FestivalCreatePanel +from borax.ui.widgets import MessageLabel library = FestivalLibrary.load_builtin().sort_by_countdown() @@ -82,7 +83,7 @@ def __init__(self, master=None): ttk.Button(self._tool_form_frame, text='计算', command=self.run_date_delta).grid( row=2, column=0, columnspan=4, pady=8) - self.result1_label = ttk.Label(self._tool_form_frame, text='') + self.result1_label = MessageLabel(self._tool_form_frame, text='') self.result1_label.grid(row=3, column=0, columnspan=4) notebook.add(self._tool_form_frame, text='日期间隔', padding=4) @@ -104,7 +105,7 @@ def __init__(self, master=None): delta_days_com.grid(row=2, column=2, columnspan=2, sticky=tk.E + tk.W + tk.N + tk.S) ttk.Button(deduction_frame, text='计算', command=self.run_date_deduction).grid( row=3, column=0, columnspan=4, pady=8) - self.result2_label = ttk.Label(deduction_frame, text='') + self.result2_label = MessageLabel(deduction_frame, text='') self.result2_label.grid(row=4, column=0, columnspan=4) # init self.day_delta_s.set(1) @@ -131,17 +132,17 @@ def run_date_delta(self): d1, d2 = self._data_stores['d1'].get_date(), self._data_stores['d2'].get_date() if d1 and d2: ndays = (d2.solar - d1.solar).days - self.result1_label.config(text=f'相差 {ndays} 天') + self.result1_label.show_text(text=f'相差 {ndays} 天') else: - self.result1_label.config(text='未选择日期,无法计算') + self.result1_label.show_error_splash(text='未选择日期,无法计算') def run_date_deduction(self): d3 = self._data_stores['d3'].get_date() if d3: result2 = d3 + timedelta(self.day_delta_s.get() * self.delta_days.get()) - self.result2_label.config(text=str(result2)) + self.result2_label.show_text(str(result2)) else: - self.result2_label.config(text='未选择日期,无法计算') + self.result2_label.show_error_splash(text='未选择日期,无法计算') class DateDetailFrame(ttk.LabelFrame): diff --git a/borax/capp/festival_creator.py b/borax/capp/festival_creator.py index abdce1a..b62e73c 100644 --- a/borax/capp/festival_creator.py +++ b/borax/capp/festival_creator.py @@ -8,6 +8,7 @@ ) from borax.calendars.lunardate import TextUtils, TERMS_CN from borax.calendars.ui import FestivalTableFrame +from borax.ui.widgets import MessageLabel __all__ = ['FestivalCreatePanel', 'start_festival_creator'] @@ -120,16 +121,6 @@ def _validate_f4(self): return festival -class MessageLabel(ttk.Label): - def splash(self, text: str, timeout: int = 1000, foreground='black', **kwargs): - self.config({'text': text, 'foreground': foreground, **kwargs}) - if timeout != 0: - self.after(timeout, self._clear) - - def _clear(self): - self.config({'text': '', 'foreground': 'black'}) - - class FestivalCreatePanel(ttk.Frame): """A UI panel with festival-CRUD futures.""" @@ -269,26 +260,26 @@ def _create(self, event=None): try: festival = self._vm.validate() self._festival_table.add_festival(festival) - self._msg_label.splash(f'{festival.description} {festival.encode()}', foreground='green') + self._msg_label.show_success_splash(f'{festival.description} {festival.encode()}') except ValidateError as e: - self._msg_label.splash(str(e), foreground='red') + self._msg_label.show_error_splash(str(e)) def _delete(self, event=None): self._festival_table.delete_selected_festivals() def _clear_data(self, event=None): self._festival_table.clear_data() - self._msg_label.splash('清空成功!', foreground='green') + self._msg_label.show_success_splash('清空成功!') def _export(self, event=None): if len(self._festival_table.tree_view.get_children()) == 0: - self._msg_label.splash('表格无数据!', foreground='red') + self._msg_label.show_warning_splash('表格无数据!') return filename = filedialog.asksaveasfilename(parent=self, title='保存到', defaultextension='.csv', filetypes=(('csv', 'csv'),)) if filename: self._festival_table.festival_library.to_csv(filename) - self._msg_label.splash('导出成功', foreground='green') + self._msg_label.show_success_splash('导出成功') def _on_source_selected(self, val: str): if val == 'custom': @@ -306,7 +297,7 @@ def _open_and_add(self, event=None): def _load_new_festival_library(self, f_library: FestivalLibrary): self._festival_table.add_festivals_from_library(f_library) - self._msg_label.splash(f'加载成功,共{self._festival_table.row_count}条', foreground='green') + self._msg_label.show_success_splash(f'加载成功,共{self._festival_table.row_count}条') def start_festival_creator(): diff --git a/borax/ui/widgets.py b/borax/ui/widgets.py index 7fddc82..d0b8fae 100644 --- a/borax/ui/widgets.py +++ b/borax/ui/widgets.py @@ -66,5 +66,14 @@ def show_text(self, text: str, text_color: str = 'black', splash_ms: int = 0): if splash_ms: self.after(splash_ms, self._clear) + def show_error_splash(self, text: str, splash_ms: int = 1000): + self.show_text(text, text_color='error', splash_ms=splash_ms) + + def show_warning_splash(self, text: str, splash_ms: int = 1000): + self.show_text(text, text_color='warning', splash_ms=splash_ms) + + def show_success_splash(self, text: str, splash_ms: int = 1000): + self.show_text(text, text_color='success', splash_ms=splash_ms) + def _clear(self): self.config({'text': ''}) From 09d6fdb284bbd44153aa385563682e216d83de35 Mon Sep 17 00:00:00 2001 From: kinegratii Date: Fri, 29 Mar 2024 21:38:32 +0800 Subject: [PATCH 07/10] :sparkles: New LunarDate for last day in a year --- borax/calendars/festivals2.py | 10 ++++++---- borax/calendars/lunardate.py | 8 +++++++- docs/changelog.md | 2 ++ tests/test_festival2_list.py | 8 ++++++++ tests/test_lunardate.py | 2 ++ 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/borax/calendars/festivals2.py b/borax/calendars/festivals2.py index 49f0ccd..de999a4 100644 --- a/borax/calendars/festivals2.py +++ b/borax/calendars/festivals2.py @@ -169,8 +169,9 @@ class Period: """A shortcut methods for some specified date Period.""" @staticmethod - def solar_year(year: int) -> Tuple[date, date]: - return date(year, 1, 1), date(year, 12, 31) + def solar_year(year: int, end_year: int = 0) -> Tuple[date, date]: + end_year = end_year or year + return date(year, 1, 1), date(end_year, 12, 31) @staticmethod def solar_month(year: int, month: int) -> Tuple[date, date]: @@ -178,8 +179,9 @@ def solar_month(year: int, month: int) -> Tuple[date, date]: return date(year, month, 1), date(year, month, ndays) @staticmethod - def lunar_year(year: int) -> Tuple[LunarDate, LunarDate]: - return LunarDate(year, 1, 1), LunarDate(year + 1, 1, 1) - timedelta(days=1) + def lunar_year(year: int, end_year: int = 0) -> Tuple[LunarDate, LunarDate]: + end_year = end_year or year + return LunarDate(year, 1, 1), LunarDate.last_day_of_year(end_year) @staticmethod def lunar_month(year: int, month: int, leap: int = _IGNORE_LEAP_MONTH) -> Tuple[LunarDate, LunarDate]: diff --git a/borax/calendars/lunardate.py b/borax/calendars/lunardate.py index c00591d..8250037 100644 --- a/borax/calendars/lunardate.py +++ b/borax/calendars/lunardate.py @@ -658,6 +658,12 @@ def tomorrow(cls) -> 'LunarDate': sd = datetime.date.today() + datetime.timedelta(days=1) return cls.from_solar_date(sd.year, sd.month, sd.day) + @classmethod + def last_day_of_year(cls, year: int) -> 'LunarDate': + """return the last day in a lunar year.""" + month, day, leap = list(LCalendars.iter_year_month(year))[-1] + return cls(year, month, day, leap) + @classmethod def strptime(cls, date_str: str, date_fmt: str) -> 'LunarDate': """Parse a LunarDate object from a whole string. @@ -678,7 +684,7 @@ def __sub__(self, other): :param other: a instance of LunarDate / date / timedelta :return: """ - if hasattr(other, 'solar'): + if hasattr(other, 'solar'): # For WrappedDate in festivals2 module return self.to_solar_date() - other.solar elif isinstance(other, LunarDate): return self.to_solar_date() - other.to_solar_date() diff --git a/docs/changelog.md b/docs/changelog.md index 0de0594..06b76fa 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,9 @@ ## v4.1.1 +- 新增创建农历年最后一天的方法 `LunarDate.last_day_of_year` - `SolarFestival` 和`LunarFestival` 初始化函数 `freq` 参数支持字符串设置( [ #56](https://github.com/kinegratii/borax/issues/56) ) +- `Period.solar_year` 和 `Period.lunar_year` 新增 `end_year` 参数,支持跨年份计算 ## v4.1.0 (20240131) diff --git a/tests/test_festival2_list.py b/tests/test_festival2_list.py index 5d46530..fd2b9a3 100644 --- a/tests/test_festival2_list.py +++ b/tests/test_festival2_list.py @@ -167,6 +167,10 @@ def test_solar_period(self): self.assertEqual(date(2020, 5, 1), sd2) self.assertEqual(date(2020, 5, 31), ed2) + sd3, ed3 = Period.solar_year(2020, 2021) + self.assertEqual(date(2020, 1, 1), sd3) + self.assertEqual(date(2021, 12, 31), ed3) + def test_lunar_period(self): sd1, ed1 = Period.lunar_year(2020) self.assertEqual(LunarDate(2020, 1, 1), sd1) @@ -188,6 +192,10 @@ def test_lunar_period(self): self.assertEqual(LunarDate(2020, 5, 1, 0), sd5) self.assertEqual(LunarDate(2020, 5, 30, 0), ed5) + sd6, ed6 = Period.lunar_year(2020, 2021) + self.assertEqual(LunarDate(2020, 1, 1), sd6) + self.assertEqual(LunarDate(2021, 12, 29), ed6) + class WrappedDateTestCase(unittest.TestCase): diff --git a/tests/test_lunardate.py b/tests/test_lunardate.py index 6bfc51e..1ae8465 100644 --- a/tests/test_lunardate.py +++ b/tests/test_lunardate.py @@ -37,6 +37,8 @@ def test_create_specific_dates(self): self.assertEqual(5, LCalendars.delta(today.after(5), today)) self.assertEqual(-5, LCalendars.delta(today.before(5), today)) + self.assertEqual(LunarDate(2023, 12, 30), LunarDate.last_day_of_year(2023)) + def test_convert_datetime(self): dt = LunarDate(1976, 8, 8, 1).to_solar_date() self.assertEqual(date(1976, 10, 1), dt) From 960ddbf2fca9a247554a2291f0c04787ee7eac8b Mon Sep 17 00:00:00 2001 From: kinegratii Date: Sun, 31 Mar 2024 22:28:58 +0800 Subject: [PATCH 08/10] :pencil: Update docs --- docs/changelog.md | 1 + docs/guides/choices.md | 54 ++++++++++++++++++++++++------------------ mkdocs.yaml | 1 + 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 06b76fa..8da96ea 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -5,6 +5,7 @@ - 新增创建农历年最后一天的方法 `LunarDate.last_day_of_year` - `SolarFestival` 和`LunarFestival` 初始化函数 `freq` 参数支持字符串设置( [ #56](https://github.com/kinegratii/borax/issues/56) ) - `Period.solar_year` 和 `Period.lunar_year` 新增 `end_year` 参数,支持跨年份计算 +- 废弃模块:`borax.choices` ## v4.1.0 (20240131) diff --git a/docs/guides/choices.md b/docs/guides/choices.md index 770c1a6..cf868f6 100644 --- a/docs/guides/choices.md +++ b/docs/guides/choices.md @@ -2,7 +2,11 @@ > 模块: `borax.choices` -> Update in v3.4.0 +> Changed in v3.4.0 :适配 Django choices + +## 重要信息 + +从 Borax 4.1.0 开始,本模块被标记为 废弃 状态,推荐使用标准的 Django Choices 。关于如何迁移参考下列文档。 ## 开发背景 @@ -85,14 +89,14 @@ class YearInSchoolChoices(choices.ConstChoices): 可以直接使用 `YearInShoolChoices.FRESHMAN` 访问该选项具体的值。 - ```bash - >>> YearInShoolChoices.FRESHMAN +```bash +>>> YearInShoolChoices.FRESHMAN 'FR' >>> YearInShoolChoices.is_valid('SR') True >>> YearInShoolChoices.is_valid('Et') False - ``` +``` ## 选项(Item) ### 显式Item定义 @@ -209,7 +213,7 @@ class DirectionChoices(VerticalChoices): -## 关于Django.Choices +## 与Django.Choices ### 概述 @@ -234,12 +238,12 @@ from django.db import models from borax import choices -class MyChoices(models.TextChoices): +class MyChoices(models.TextChoices): # Django choices style GREEN = 'g', 'green' RED = 'r', 'red' YELLOW = 'y', 'yellow' -class MyChoices(choices.Choices): +class MyChoices(choices.Choices): # Borax choices style GREEN = 'g', 'green' RED = 'r', 'red' YELLOW = 'y', 'yellow' @@ -248,19 +252,23 @@ class MyChoices(choices.Choices): 表 Borax.Choices *VS* Django.Choices -| | Borax.Choices | Django.Choices | 备注 | -| --------------------------- | ------------- | ------------------ | ---- | -| MyChoices.choices | `(...)` | `(...)` | | -| MyChoices.GREEN | `'g'` | `` | | -| MyChoices['GREEN'] | - | `` | | -| MyChoices.is_valid('g') | True | - | | -| MyChoices.GREEN.name | - | `'g'` | | -| MyChoices.GREEN.label | - | `'green'` | | -| MyChoices.GREEN.value | - | - | | -| MyChoices.GREEN.display | - | - | | -| MyChoices.get_value_display | `