diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 88d60d4..cd82671 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -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; diff --git a/Packages/XStatsKit/Sources/Localization/CalendarTranslations.swift b/Packages/XStatsKit/Sources/Localization/CalendarTranslations.swift index 6618231..27f1ac7 100644 --- a/Packages/XStatsKit/Sources/Localization/CalendarTranslations.swift +++ b/Packages/XStatsKit/Sources/Localization/CalendarTranslations.swift @@ -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 diff --git a/Packages/XStatsKit/Sources/Localization/Resources/ar.tsv b/Packages/XStatsKit/Sources/Localization/Resources/ar.tsv index 4b9a878..e5903b0 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/ar.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/ar.tsv @@ -1317,7 +1317,6 @@ Apple 智能 Apple Intelligence 中国大陆节假日与调休 العطلات وأيام العمل التعويضية في البر الرئيسي للصين 传统与公历节日 المناسبات التقليدية والميلادية 二十四节气 الفصول الشمسية الأربعة والعشرون -干支 الدورة الستينية 三伏天 أيام القيظ 梅雨天 أيام أمطار البرقوق التقليدية 三伏·梅雨·数九 فو · ميو · التسعات @@ -1393,7 +1392,6 @@ Apple 智能 Apple Intelligence 小寒 البرد الصغير 大寒 البرد الكبير 返回月历 العودة إلى التقويم -控制月历格中的日干支;黄历详情始终显示年月日干支 عرض دورة اليوم في خلايا الشهر؛ تتضمن تفاصيل التقويم التقليدي دائمًا دورات السنة والشهر واليوم 宜 مستحب 忌 يُتجنّب 纳音 نا يين diff --git a/Packages/XStatsKit/Sources/Localization/Resources/de.tsv b/Packages/XStatsKit/Sources/Localization/Resources/de.tsv index d9730f6..d2451d6 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/de.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/de.tsv @@ -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 diff --git a/Packages/XStatsKit/Sources/Localization/Resources/es.tsv b/Packages/XStatsKit/Sources/Localization/Resources/es.tsv index 1091495..694181c 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/es.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/es.tsv @@ -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 diff --git a/Packages/XStatsKit/Sources/Localization/Resources/fr.tsv b/Packages/XStatsKit/Sources/Localization/Resources/fr.tsv index efcf840..d4555aa 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/fr.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/fr.tsv @@ -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 diff --git a/Packages/XStatsKit/Sources/Localization/Resources/ja.tsv b/Packages/XStatsKit/Sources/Localization/Resources/ja.tsv index b78f718..d76fac8 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/ja.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/ja.tsv @@ -1317,7 +1317,6 @@ Apple 智能 Apple Intelligence 中国大陆节假日与调休 中国本土の祝日・振替出勤日 传统与公历节日 伝統と西暦の祝祭日 二十四节气 二十四節気 -干支 干支 三伏天 三伏 梅雨天 入梅・出梅 三伏·梅雨·数九 三伏・梅雨・数九 @@ -1393,7 +1392,6 @@ Apple 智能 Apple Intelligence 小寒 小寒 大寒 大寒 返回月历 月表示に戻る -控制月历格中的日干支;黄历详情始终显示年月日干支 月表示の日干支を設定します。暦の詳細には年・月・日の干支を常に表示します 宜 宜 忌 忌 纳音 納音 diff --git a/Packages/XStatsKit/Sources/Localization/Resources/ko.tsv b/Packages/XStatsKit/Sources/Localization/Resources/ko.tsv index 8848b4a..5e11881 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/ko.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/ko.tsv @@ -1317,7 +1317,6 @@ Apple 智能 Apple Intelligence 中国大陆节假日与调休 중국 본토 공휴일 및 대체 근무일 传统与公历节日 전통 및 양력 기념일 二十四节气 24절기 -干支 간지 三伏天 삼복 梅雨天 입매·출매 三伏·梅雨·数九 삼복·매우·구구 @@ -1393,7 +1392,6 @@ Apple 智能 Apple Intelligence 小寒 소한 大寒 대한 返回月历 월 달력으로 돌아가기 -控制月历格中的日干支;黄历详情始终显示年月日干支 월 달력 칸의 일진 표시를 설정합니다. 황력 상세에는 연·월·일 간지가 항상 표시됩니다 宜 길 忌 기 纳音 납음 diff --git a/Packages/XStatsKit/Sources/Localization/Resources/zh-Hant.tsv b/Packages/XStatsKit/Sources/Localization/Resources/zh-Hant.tsv index cc8fd37..90d3974 100644 --- a/Packages/XStatsKit/Sources/Localization/Resources/zh-Hant.tsv +++ b/Packages/XStatsKit/Sources/Localization/Resources/zh-Hant.tsv @@ -1318,7 +1318,6 @@ Apple 智能 Apple Intelligence 中国大陆节假日与调休 中國大陸節假日與調休 传统与公历节日 傳統與公曆節日 二十四节气 二十四節氣 -干支 干支 三伏天 三伏天 梅雨天 梅雨天 三伏·梅雨·数九 三伏·梅雨·數九 @@ -1394,7 +1393,6 @@ Apple 智能 Apple Intelligence 小寒 小寒 大寒 大寒 返回月历 返回月曆 -控制月历格中的日干支;黄历详情始终显示年月日干支 控制月曆格中的日干支;黃曆詳情始終顯示年月日干支 宜 宜 忌 忌 纳音 納音 diff --git a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarAlmanacView.swift b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarAlmanacView.swift index ed9296a..4e3bf9a 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarAlmanacView.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarAlmanacView.swift @@ -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 { diff --git a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarEngine.swift b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarEngine.swift index ad61105..e0ea7e5 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarEngine.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarEngine.swift @@ -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 { 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 { + // 已移除的月历干支(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, diff --git a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarPopover.swift b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarPopover.swift index cc39b37..4ffc193 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarPopover.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarPopover.swift @@ -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) diff --git a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarSettings.swift b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarSettings.swift index 6128942..1243e86 100644 --- a/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarSettings.swift +++ b/Packages/XStatsKit/Sources/XStatsUI/Calendar/CalendarSettings.swift @@ -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) } diff --git a/Packages/XStatsKit/Tests/XStatsUITests/CalendarTests.swift b/Packages/XStatsKit/Tests/XStatsUITests/CalendarTests.swift index 8671706..0ef8828 100644 --- a/Packages/XStatsKit/Tests/XStatsUITests/CalendarTests.swift +++ b/Packages/XStatsKit/Tests/XStatsUITests/CalendarTests.swift @@ -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