TL;DR
将玛雅 13 月亮历的「新年开始日」从硬编码的 07-26(无时间日 07-25)扩展为用户偏好:在 /config 设置页可把任意公历「月-日」(例如用户生日)设为新年开始日,前一天作为无时间日;七年周期锚点改为精确到年。所有消费新年 / 无时间日 / 七年周期的路径统一经由 calendar adapter 获取,避免各处重复实现。
背景
- 现状:
MayanCalendarAdapter.year_start() 硬编码 date(year, 7, 26);模块级 is_mayan_day_out_of_time() 硬编码 month == 7 and day == 25;七年锚点 preferences.calendar_seven_year_anchor_date 为完整日期(YYYY-MM-DD)。
- 需求:新年开始日精确到公历「月-日」(默认 07-26),此日的前一天为无时间日;七年周期起始精确到「年」。
- 现有设计已把历法视为用户偏好(
calendar.system / calendar.first_day_of_week / calendar.seven_year_anchor_date),且已有 MayanCalendarAdapter 作为周期边界唯一实现,本需求是该方向的自然延伸,不引入新的架构模式。
方案评估(对照当前代码最佳实践)
需求合理且仍然有效。实施应以 MayanCalendarAdapter 为唯一事实来源,避免在 stats、task_queries、habit_support、web routers 各处重复推导:
MayanCalendarAdapter 增加新年开始日参数(MM-DD,默认 07-26):year_start()、day_offset()、month_range()/week_range()/year_range()/seven_year_range() 均基于该参数。
- 将 DOOT 判定收敛为 adapter 方法(当前是硬编码的模块级函数,且不带历法参数语义),所有调用点(
timelog_stats._week_month_excluded_local_date、get_timelog_stats_groupby_area_for_range、iter_calendar_periods)改为从偏好取参。
get_calendar_adapter / get_calendar_period_range / iter_calendar_periods 的参数从「完整锚点日期」演进为「新年开始日 + 七年锚点年」,并同步 config.py(新增字段/校验/默认值/序列化)、application/configuration.py(SUPPORTED_CONFIG_KEYS 与 set 分支)、lifeos_web/routers/preferences.py(_CONFIG_KEY_MAP/_META/取值分支)。
- 消费方统一由
get_preferences_settings() 取参:task_queries.py(3 处)、habit_support.py(4 处)、web routers/stats.py、routers/tasks.py。
- 前端 lifeos-web 同步:
MayanCalendarAdapter.ts、createCalendarAdapter.ts、useCalendarAdapter.ts、settingsConfig.tsx、SettingsPage.tsx 与 en/zh 文案(现文案写明「07-26 为新年」需改为可配置描述)。
- CLI/Web 用户可见文案(如
cli_messages.json 中「无时间日(7 月 25 日)」)随行为变化同步更新。
关键设计点与风险
- 闰日与无时间日的交互:现有实现把 2/29 视为不参与月亮/周序号的内插日(
_leap_day 偏移修正)。新年开始日可配置后,2/29 可能落在年度边界附近(如新年 03-01 时,前一天恰为 2/29)。需要显式定义并文档化语义(建议:2/29 仍为内插日不改变月亮/周序号;与「新年前一天」重合时以无时间日为准,或把 02-29 设为非法新年开始日),并补充闰年边界测试。
- 七年锚点兼容:现有
calendar_seven_year_anchor_date(YYYY-MM-DD)已可能被持久化。建议拆分为 calendar_mayan_new_year_start(MM-DD)+ calendar_seven_year_anchor_year(YYYY),并提供一次性迁移(取旧值年份;月-日部分仅作为旧语义留存)。公历七年周期本就只取年份,行为兼容。
- 回归面:默认值必须保持 07-26 新年 / 07-25 无时间日,现有
tests/test_calendar_adapter.py、test_timelog_stats.py、test_web_cli.py、test_domain_services_tasks_and_habits.py 中大量 7/25、7/26 断言需改为参数化默认值用例 + 自定义新年用例。
相关 open 工作(建议协同)
验收标准
实施清单
- 后端:
config.py、application/configuration.py、application/calendar_adapter.py、db/services/timelog_stats.py、db/services/task_queries.py、db/services/habit_support.py、lifeos_web/routers/preferences.py、lifeos_web/routers/stats.py、lifeos_web/routers/tasks.py、CLI help/locales。
- 前端(lifeos-web 独立 PR):
utils/calendar/MayanCalendarAdapter.ts、createCalendarAdapter.ts、hooks/useCalendarAdapter.ts、config/settingsConfig.tsx、pages/SettingsPage.tsx、en/zh locales、相关测试。
Related #192 #324 #329
TL;DR
将玛雅 13 月亮历的「新年开始日」从硬编码的 07-26(无时间日 07-25)扩展为用户偏好:在
/config设置页可把任意公历「月-日」(例如用户生日)设为新年开始日,前一天作为无时间日;七年周期锚点改为精确到年。所有消费新年 / 无时间日 / 七年周期的路径统一经由 calendar adapter 获取,避免各处重复实现。背景
MayanCalendarAdapter.year_start()硬编码date(year, 7, 26);模块级is_mayan_day_out_of_time()硬编码month == 7 and day == 25;七年锚点preferences.calendar_seven_year_anchor_date为完整日期(YYYY-MM-DD)。calendar.system/calendar.first_day_of_week/calendar.seven_year_anchor_date),且已有MayanCalendarAdapter作为周期边界唯一实现,本需求是该方向的自然延伸,不引入新的架构模式。方案评估(对照当前代码最佳实践)
需求合理且仍然有效。实施应以
MayanCalendarAdapter为唯一事实来源,避免在 stats、task_queries、habit_support、web routers 各处重复推导:MayanCalendarAdapter增加新年开始日参数(MM-DD,默认07-26):year_start()、day_offset()、month_range()/week_range()/year_range()/seven_year_range()均基于该参数。timelog_stats._week_month_excluded_local_date、get_timelog_stats_groupby_area_for_range、iter_calendar_periods)改为从偏好取参。get_calendar_adapter/get_calendar_period_range/iter_calendar_periods的参数从「完整锚点日期」演进为「新年开始日 + 七年锚点年」,并同步config.py(新增字段/校验/默认值/序列化)、application/configuration.py(SUPPORTED_CONFIG_KEYS 与 set 分支)、lifeos_web/routers/preferences.py(_CONFIG_KEY_MAP/_META/取值分支)。get_preferences_settings()取参:task_queries.py(3 处)、habit_support.py(4 处)、webrouters/stats.py、routers/tasks.py。MayanCalendarAdapter.ts、createCalendarAdapter.ts、useCalendarAdapter.ts、settingsConfig.tsx、SettingsPage.tsx与 en/zh 文案(现文案写明「07-26 为新年」需改为可配置描述)。cli_messages.json中「无时间日(7 月 25 日)」)随行为变化同步更新。关键设计点与风险
_leap_day偏移修正)。新年开始日可配置后,2/29 可能落在年度边界附近(如新年 03-01 时,前一天恰为 2/29)。需要显式定义并文档化语义(建议:2/29 仍为内插日不改变月亮/周序号;与「新年前一天」重合时以无时间日为准,或把 02-29 设为非法新年开始日),并补充闰年边界测试。calendar_seven_year_anchor_date(YYYY-MM-DD)已可能被持久化。建议拆分为calendar_mayan_new_year_start(MM-DD)+calendar_seven_year_anchor_year(YYYY),并提供一次性迁移(取旧值年份;月-日部分仅作为旧语义留存)。公历七年周期本就只取年份,行为兼容。tests/test_calendar_adapter.py、test_timelog_stats.py、test_web_cli.py、test_domain_services_tasks_and_habits.py中大量 7/25、7/26 断言需改为参数化默认值用例 + 自定义新年用例。相关 open 工作(建议协同)
iter_calendar_periods的 DOOT 单日桶跳过逻辑与本需求直接重叠,且都修改同一批函数。建议先合入 feat(stats): backend owns stats buckets — skip day out of time, return complete timeline #329,再在本分支之上开发(或基于其分支开发,合入后 rebase),避免语义冲突。验收标准
/config可配置新年开始日(月-日)与七年锚点年;web 与 CLI 所有消费路径(周/月/年/七年周期、planning、stats、habits)取同一偏好。bash ./scripts/doctor.sh通过;lifeos-web 侧bash ./scripts/validate.sh通过。实施清单
config.py、application/configuration.py、application/calendar_adapter.py、db/services/timelog_stats.py、db/services/task_queries.py、db/services/habit_support.py、lifeos_web/routers/preferences.py、lifeos_web/routers/stats.py、lifeos_web/routers/tasks.py、CLI help/locales。utils/calendar/MayanCalendarAdapter.ts、createCalendarAdapter.ts、hooks/useCalendarAdapter.ts、config/settingsConfig.tsx、pages/SettingsPage.tsx、en/zh locales、相关测试。Related #192 #324 #329