refactor(calendar): 移除月历干支显示与开关

This commit is contained in:
ysicing authored and 缘生 committed 2026-09-30 23:50:16 +08:00
1 parent 222b2baa89
commit 3aae19a4c6
14 files changed
+24 -34

No files matched your search

+1 -1
View File
@@ -55,7 +55,7 @@ GitHub Actions 只在分支 push 且 `server/**` 发生变化时构建并发布
有 Developer ID Application 证书时,Taskfile 会自动使用钥匙串中第一个证书的唯一指纹;否则由构建脚本逐层补齐 ad-hoc 签名与 entitlement。临时签名没有团队身份,不能加载启用团队验证的 Sparkle 动态库,因此仅此本地/CI 路径关闭 Hardened Runtime;正式 Developer ID 构建仍强制启用,且临时签名构建不能在线安装或操作特权辅助工具。
桌面小组件与主应用通过 `group.work.12306.xstats` App Group 共享只读展示摘要。
菜单栏日历在所有应用语言下均提供“日期与星期”“日期与农历”“农历”“自定义”显示方式;日期与农历按应用语言呈现,切换语言保留用户选择。自定义可自由组合 y/M/d 字段与分隔符,年份可省略,输入时显示预览并提示无效格式,独立于月历的星期开关;支持农历字号与对比度、可选悬停展开,按分钟刷新,休眠或关闭功能后停止。日程和到期提醒默认关闭,通过明确的授权按钮请求 EventKit 权限,只在日历面板可见时查询当前 42 格日期范围;只读展示,不修改日程,也不向 Widget、设置备份或服务端传递内容。列表 ID 仅保存在本机,显示偏好参与设置备份。节假日覆盖年份来自内置 Tyme 数据并随应用更新;假期倒计时与最多 3 天的请假建议只基于已公布的中国调休与双休安排,跨到未知年份时停止推算。
日历首次初始化按 macOS 地区设置决定默认值,不按设备销售地或应用语言判断:中国大陆(CN)默认启用农历、星期、大陆节假日与调休、传统与公历节日、节气、黄历及假期概览;其他地区(含台湾、香港、澳门)默认只启用星期。干支、三伏/梅雨/数九、藏历与回历各地区均默认关闭。用户可自行启用或关闭,首次默认值保存后不随语言、地区变化重置;已有本机偏好及旧备份保持原选择。每周起始日首次采用 macOS 系统日历设置并保存,支持星期日到星期六,手动选择及备份恢复优先;后续系统设置变化不覆盖已保存的值,主应用月历与小组件使用同一周起始日。大陆节假日数据不能用于推断其他地区的法定安排。单日及整月小组件遵循日历选项,未启用大陆节假日时“明天上班吗”提示“仅支持中国大陆节假日”;黄历关闭后不再计算宜忌摘要。
日历首次初始化按 macOS 地区设置决定默认值,不按设备销售地或应用语言判断:中国大陆(CN)默认启用农历、星期、大陆节假日与调休、传统与公历节日、节气、黄历及假期概览;其他地区(含台湾、香港、澳门)默认只启用星期。三伏/梅雨/数九、藏历与回历各地区均默认关闭。月历不再显示干支,也不提供对应开关;年月日干支只在启用黄历后的日期详情中显示,旧偏好与备份中的干支选项被忽略,其他选项保留。用户可自行启用或关闭,首次默认值保存后不随语言、地区变化重置;已有本机偏好及旧备份保持原选择。每周起始日首次采用 macOS 系统日历设置并保存,支持星期日到星期六,手动选择及备份恢复优先;后续系统设置变化不覆盖已保存的值,主应用月历与小组件使用同一周起始日。大陆节假日数据不能用于推断其他地区的法定安排。单日及整月小组件遵循日历选项,未启用大陆节假日时“明天上班吗”提示“仅支持中国大陆节假日”;黄历关闭后不再计算宜忌摘要。
月历使用固定六行布局,日期 ID 在生成日期时计算一次。面板打开时测量初始高度,后续翻月、详情与日程内容变化在内部滚动,不从内容几何回调反向调整窗口,避免窗口高度与懒布局估算形成反馈循环。
月历计算仅缓存最近一个月份(包含周起始日和时区),供实际面板、高度测量和重复打开共用;翻月替换缓存,避免浏览历史导致内存累积。日程日期标记在工作 actor 内随查询结果生成,界面不再逐格扫描日程列表;连续 EventKit 通知按 300 毫秒窗口合并,关闭面板时取消待执行刷新。
用于正式签名的 Apple Developer 团队须为主应用和 Widget 扩展启用同一个 App Group;
@@ -18,7 +18,6 @@ extension Translations {
中国大陆节假日与调休 Mainland China holidays and makeup workdays
传统与公历节日 Festivals
二十四节气 Solar terms
干支 Sexagenary cycle
三伏天 Dog days
梅雨天 Plum-rain days
三伏·梅雨·数九 Fu · Plum rain · Nines
@@ -108,7 +107,6 @@ extension Translations {
小寒 Minor Cold
大寒 Major Cold
返回月历 Back to calendar
控制月历格中的日干支;黄历详情始终显示年月日干支 Show the day cycle in month cells; almanac details always include year, month and day cycles
宜 Favorable
忌 Avoid
纳音 Na Yin
@@ -1317,7 +1317,6 @@ Apple 智能 Apple Intelligence
中国大陆节假日与调休 العطلات وأيام العمل التعويضية في البر الرئيسي للصين
传统与公历节日 المناسبات التقليدية والميلادية
二十四节气 الفصول الشمسية الأربعة والعشرون
干支 الدورة الستينية
三伏天 أيام القيظ
梅雨天 أيام أمطار البرقوق التقليدية
三伏·梅雨·数九 فو · ميو · التسعات
@@ -1393,7 +1392,6 @@ Apple 智能 Apple Intelligence
小寒 البرد الصغير
大寒 البرد الكبير
返回月历 العودة إلى التقويم
控制月历格中的日干支;黄历详情始终显示年月日干支 عرض دورة اليوم في خلايا الشهر؛ تتضمن تفاصيل التقويم التقليدي دائمًا دورات السنة والشهر واليوم
宜 مستحب
忌 يُتجنّب
纳音 نا يين
1 # Copyright (C) 2026 ysicing
1317 中国大陆节假日与调休
1318 传统与公历节日
1319 二十四节气
干支
1320 三伏天
1321 梅雨天
1322 三伏·梅雨·数九
1392 小寒
1393 大寒
1394 返回月历
控制月历格中的日干支;黄历详情始终显示年月日干支
1395 宜
1396 忌
1397 纳音
@@ -1317,7 +1317,6 @@ Apple 智能 Apple Intelligence
中国大陆节假日与调休 Feiertage und Ausgleichsarbeitstage in Festlandchina
传统与公历节日 Traditionelle und gregorianische Feste
二十四节气 24 Sonnenperioden
干支 Sechzigjahreszyklus
三伏天 Hundstage
梅雨天 Traditionelle Pflaumenregenzeit
三伏·梅雨·数九 Fu · Meiyu · Neuner
@@ -1393,7 +1392,6 @@ Apple 智能 Apple Intelligence
小寒 Kleine Kälte
大寒 Große Kälte
返回月历 Zurück zum Kalender
控制月历格中的日干支;黄历详情始终显示年月日干支 Tageszyklus in Monatszellen anzeigen; Almanachdetails enthalten immer Jahres-, Monats- und Tageszyklen
宜 Günstig
忌 Meiden
纳音 Na Yin
1 # Copyright (C) 2026 ysicing
1317 中国大陆节假日与调休
1318 传统与公历节日
1319 二十四节气
干支
1320 三伏天
1321 梅雨天
1322 三伏·梅雨·数九
1392 小寒
1393 大寒
1394 返回月历
控制月历格中的日干支;黄历详情始终显示年月日干支
1395 宜
1396 忌
1397 纳音
@@ -1317,7 +1317,6 @@ Apple 智能 Apple Intelligence
中国大陆节假日与调休 Festivos y días laborales compensatorios de China continental
传统与公历节日 Fiestas tradicionales y gregorianas
二十四节气 24 términos solares
干支 Ciclo sexagenario
三伏天 Días caniculares
梅雨天 Lluvias de ciruela tradicionales
三伏·梅雨·数九 Fu · Meiyu · Nueves
@@ -1393,7 +1392,6 @@ Apple 智能 Apple Intelligence
小寒 Frío menor
大寒 Frío mayor
返回月历 Volver al calendario
控制月历格中的日干支;黄历详情始终显示年月日干支 Muestra el ciclo diario en las celdas; el almanaque siempre incluye los ciclos del año, mes y día
宜 Favorable
忌 Evitar
纳音 Na Yin
1 # Copyright (C) 2026 ysicing
1317 中国大陆节假日与调休
1318 传统与公历节日
1319 二十四节气
干支
1320 三伏天
1321 梅雨天
1322 三伏·梅雨·数九
1392 小寒
1393 大寒
1394 返回月历
控制月历格中的日干支;黄历详情始终显示年月日干支
1395 宜
1396 忌
1397 纳音
@@ -1317,7 +1317,6 @@ Apple 智能 Apple Intelligence
中国大陆节假日与调休 Jours fériés et jours de travail compensatoires en Chine continentale
传统与公历节日 Fêtes traditionnelles et grégoriennes
二十四节气 24 périodes solaires
干支 Cycle sexagésimal
三伏天 Jours caniculaires
梅雨天 Pluies des prunes traditionnelles
三伏·梅雨·数九 Fu · Meiyu · Neuvaines
@@ -1393,7 +1392,6 @@ Apple 智能 Apple Intelligence
小寒 Petit froid
大寒 Grand froid
返回月历 Retour au calendrier
控制月历格中的日干支;黄历详情始终显示年月日干支 Affiche le cycle du jour dans les cases ; l’almanach inclut toujours les cycles de l’année, du mois et du jour
宜 Favorable
忌 À éviter
纳音 Na Yin
1 # Copyright (C) 2026 ysicing
1317 中国大陆节假日与调休
1318 传统与公历节日
1319 二十四节气
干支
1320 三伏天
1321 梅雨天
1322 三伏·梅雨·数九
1392 小寒
1393 大寒
1394 返回月历
控制月历格中的日干支;黄历详情始终显示年月日干支
1395 宜
1396 忌
1397 纳音
@@ -1317,7 +1317,6 @@ Apple 智能 Apple Intelligence
中国大陆节假日与调休 中国本土の祝日・振替出勤日
传统与公历节日 伝統と西暦の祝祭日
二十四节气 二十四節気
干支 干支
三伏天 三伏
梅雨天 入梅・出梅
三伏·梅雨·数九 三伏・梅雨・数九
@@ -1393,7 +1392,6 @@ Apple 智能 Apple Intelligence
小寒 小寒
大寒 大寒
返回月历 月表示に戻る
控制月历格中的日干支;黄历详情始终显示年月日干支 月表示の日干支を設定します。暦の詳細には年・月・日の干支を常に表示します
宜 宜
忌 忌
纳音 納音
1 # Copyright (C) 2026 ysicing
1317 中国大陆节假日与调休
1318 传统与公历节日
1319 二十四节气
干支
1320 三伏天
1321 梅雨天
1322 三伏·梅雨·数九
1392 小寒
1393 大寒
1394 返回月历
控制月历格中的日干支;黄历详情始终显示年月日干支
1395 宜
1396 忌
1397 纳音
@@ -1317,7 +1317,6 @@ Apple 智能 Apple Intelligence
中国大陆节假日与调休 중국 본토 공휴일 및 대체 근무일
传统与公历节日 전통 및 양력 기념일
二十四节气 24절기
干支 간지
三伏天 삼복
梅雨天 입매·출매
三伏·梅雨·数九 삼복·매우·구구
@@ -1393,7 +1392,6 @@ Apple 智能 Apple Intelligence
小寒 소한
大寒 대한
返回月历 월 달력으로 돌아가기
控制月历格中的日干支;黄历详情始终显示年月日干支 월 달력 칸의 일진 표시를 설정합니다. 황력 상세에는 연·월·일 간지가 항상 표시됩니다
宜 길
忌 기
纳音 납음
1 # Copyright (C) 2026 ysicing
1317 中国大陆节假日与调休
1318 传统与公历节日
1319 二十四节气
干支
1320 三伏天
1321 梅雨天
1322 三伏·梅雨·数九
1392 小寒
1393 大寒
1394 返回月历
控制月历格中的日干支;黄历详情始终显示年月日干支
1395 宜
1396 忌
1397 纳音
@@ -1318,7 +1318,6 @@ Apple 智能 Apple Intelligence
中国大陆节假日与调休 中國大陸節假日與調休
传统与公历节日 傳統與公曆節日
二十四节气 二十四節氣
干支 干支
三伏天 三伏天
梅雨天 梅雨天
三伏·梅雨·数九 三伏·梅雨·數九
@@ -1394,7 +1393,6 @@ Apple 智能 Apple Intelligence
小寒 小寒
大寒 大寒
返回月历 返回月曆
控制月历格中的日干支;黄历详情始终显示年月日干支 控制月曆格中的日干支;黃曆詳情始終顯示年月日干支
宜 宜
忌 忌
纳音 納音
1 # Copyright (c) 2026 GiantAccel, LLC
1318 中国大陆节假日与调休
1319 传统与公历节日
1320 二十四节气
干支
1321 三伏天
1322 梅雨天
1323 三伏·梅雨·数九
1393 小寒
1394 大寒
1395 返回月历
控制月历格中的日干支;黄历详情始终显示年月日干支
1396 宜
1397 忌
1398 纳音
@@ -22,8 +22,6 @@ struct CalendarAlmanacView: View {
}
if let almanac {
Text(almanac.ganzhiSummary).font(.system(size: 13)).foregroundStyle(.secondary)
} else if features.contains(.ganzhi) {
Text(day.ganzhiSummary).font(.system(size: 13)).foregroundStyle(.secondary)
}
}
if let almanac {
@@ -9,7 +9,7 @@ import WidgetData
/// 公历是月历的坐标;其余信息可独立隐藏,设置以稳定 rawValue 保存和同步。
public enum CalendarFeature: String, CaseIterable, Identifiable, Sendable {
case lunar, weekdays, holidays, festivals, solarTerms, ganzhi, seasonal, tibetan, hijri
case lunar, weekdays, holidays, festivals, solarTerms, seasonal, tibetan, hijri
public var id: String { rawValue }
public static var defaults: Set<Self> { defaults(for: .current) }
@@ -25,7 +25,6 @@ public enum CalendarFeature: String, CaseIterable, Identifiable, Sendable {
case .holidays: tr("中国大陆节假日与调休")
case .festivals: tr("传统与公历节日")
case .solarTerms: tr("二十四节气")
case .ganzhi: tr("干支")
case .seasonal: tr("三伏·梅雨·数九")
case .tibetan: tr("藏历")
case .hijri: tr("回历")
@@ -34,6 +33,7 @@ public enum CalendarFeature: String, CaseIterable, Identifiable, Sendable {
/// 旧偏好和备份里的两个独立开关合并为一个;任一项曾开启就保留用户的显示意愿。
static func restored(from rawValues: [String]) -> Set<Self> {
// 已移除的月历干支(ganzhi)与未知选项一起忽略,不影响其他已保存的选择。
var features = Set(rawValues.compactMap(Self.init(rawValue:)))
if rawValues.contains("dogDays") || rawValues.contains("plumRain") { features.insert(.seasonal) }
return features
@@ -65,8 +65,6 @@ struct CalendarDay: Identifiable, Equatable {
let weekday: Int
let lunarLabel: String
let lunarSummary: String
let ganzhiDay: String
let ganzhiSummary: String
let festivals: [String]
let solarTerm: String?
let holiday: Holiday?
@@ -173,7 +171,6 @@ enum CalendarEngine {
let calendar = gregorian(timeZone: timeZone)
guard let date = calendar.date(from: DateComponents(year: year, month: month, day: day, hour: 12)) else { return nil }
let lunar = solar.getLunarDay()
let cycle = solar.getSixtyCycleDay()
let term = solar.termDay
let festivals = [lunar.festival?.getName(), solar.festival?.getName()].compactMap { $0 }
let dog = solar.dogDay
@@ -187,8 +184,7 @@ enum CalendarEngine {
return CalendarDay(date: date, year: year, month: month, day: day,
weekday: calendar.component(.weekday, from: date),
lunarLabel: lunar.day == 1 ? lunar.lunarMonth.getName() : lunar.getName(),
lunarSummary: lunar.description, ganzhiDay: cycle.sixtyCycle.getName(),
ganzhiSummary: "\(cycle.year.getName())年 \(cycle.month.getName())月 \(cycle.sixtyCycle.getName())日",
lunarSummary: lunar.description,
festivals: festivals, solarTerm: term.dayIndex == 0 ? term.solarTerm.getName() : nil,
holiday: solar.legalHoliday.map { .init(name: $0.name, isWork: $0.isWork) },
dogDays: dog?.description, plumRain: plumRain?.description,
@@ -349,9 +349,6 @@ private struct CalendarDayCell: View {
weight: preferences.strongerLunarText ? .semibold : .regular))
.lineLimit(1)
.minimumScaleFactor(0.75)
if features.contains(.ganzhi) {
Text(day.ganzhiDay).font(.system(size: sizing.cellHeight < 50 ? 8 : 9)).opacity(0.75)
}
}
.foregroundStyle(foreground)
.frame(maxWidth: .infinity)
@@ -56,7 +56,7 @@ struct CalendarOptionsPopover: View {
}
ForEach(CalendarFeature.allCases) { feature in
GroupRow {
SettingRow(title: feature.title, subtitle: feature == .ganzhi ? tr("控制月历格中的日干支;黄历详情始终显示年月日干支") : nil) {
SettingRow(title: feature.title) {
DSToggle(isOn: Binding(get: { settings.calendarFeatures.contains(feature) }, set: { enabled in
if enabled { settings.calendarFeatures.insert(feature) }
else { settings.calendarFeatures.remove(feature) }
@@ -183,6 +183,21 @@ struct CalendarTests {
#expect(CalendarEngine.selection(old, in: CalendarMonth(year: 2024, month: 4), timeZone: newZone)?.id == "2024-04-01")
}
@Test func retiredGanzhiSettingPreservesOtherCalendarChoices() throws {
let name = "CalendarRetiredGanzhiTests.\(UUID())"
let defaults = try #require(UserDefaults(suiteName: name))
defer { defaults.removePersistentDomain(forName: name) }
defaults.set(["ganzhi", "lunar", "holidays"], forKey: "calendarFeatures")
let settings = AppSettings(defaults: defaults)
#expect(!CalendarFeature.allCases.map(\.rawValue).contains("ganzhi"))
#expect(settings.calendarFeatures == [.lunar, .holidays])
var backup = SettingsDocument()
backup.calendarFeatures = ["ganzhi", "hijri"]
settings.apply(backup)
#expect(settings.calendarFeatures == [.hijri])
#expect(settings.exportDocument().calendarFeatures == ["hijri"])
}
@Test func preferencesPersistAndRoundTripWhileOldBackupsPreserveThem() throws {
let name = "CalendarTests.\(UUID().uuidString)"
let defaults = try #require(UserDefaults(suiteName: name))
@@ -191,19 +206,19 @@ struct CalendarTests {
#expect(settings.calendarEnabled == false)
#expect(settings.calendarFeatures == CalendarFeature.defaults)
settings.calendarEnabled = true
settings.calendarFeatures = [.hijri, .ganzhi]
settings.calendarFeatures = [.hijri, .lunar]
settings.calendarFirstWeekday = 1
let restored = AppSettings(defaults: defaults)
#expect(restored.calendarEnabled)
#expect(restored.calendarFeatures == [.hijri, .ganzhi])
#expect(restored.calendarFeatures == [.hijri, .lunar])
#expect(restored.calendarFirstWeekday == 1)
let doc = try JSONDecoder().decode(SettingsDocument.self, from: JSONEncoder().encode(settings.exportDocument()))
restored.calendarFeatures = []
restored.apply(doc)
#expect(restored.calendarFeatures == [.hijri, .ganzhi])
#expect(restored.calendarFeatures == [.hijri, .lunar])
restored.apply(SettingsDocument())
#expect(restored.calendarEnabled)
#expect(restored.calendarFeatures == [.hijri, .ganzhi])
#expect(restored.calendarFeatures == [.hijri, .lunar])
var invalid = SettingsDocument()
invalid.calendarFeatures = ["hijri", "future-feature"]
invalid.calendarFirstWeekday = 99