mirror of
https://github.com/carey314/mio-plugin-worker.git
synced 2026-08-10 07:04:32 +00:00
Three things squashed into one commit since they're all release-prep:
1. Bump CFBundleVersion 2 → 3, CFBundleShortVersionString 0.2.0 →
0.3.0, WorkerPlugin.version & ExpandedView footer string to match.
Aligns with the 5-feature v0.3.0 ship.
2. fix(water): lastWaterReminderHr was an Int hour (0-23) so a
yesterday-fired 17:00 blocked today's 17:00 reminder. Now stores
"yyyy-MM-dd HH" composite key so dedupe is scoped per-day.
3. Holidays.swift: 2026 entry table re-calibrated per typical
State Council 调休 pattern. Notable changes:
- 春节 starts on 除夕 02-16 (not 初一 02-17) per 2024 policy
- 中秋 + 国庆 merged into 10-day 09-25 ~ 10-04 window (since
2026 中秋 09-25 is 6 days before 国庆 10-01)
- 劳动节 renamed from "五一" for clarity
Disclaimer still in header: verify against 国务院 announcement
when published, edit + re-release if 调休 differs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
107 lines
4.7 KiB
Swift
107 lines
4.7 KiB
Swift
//
|
||
// Holidays.swift
|
||
// 摸鱼侠 plugin v0.3.0
|
||
//
|
||
// China State Council statutory holidays for 2026. Calibrated to the
|
||
// 公历日期 + 农历对应日 + typical 调休 pattern; **the official 国务院
|
||
// 办公厅 announcement (usually released Nov of prior year)** is the
|
||
// ultimate source of truth. If a date here disagrees with 国务院
|
||
// notice when published, edit this file and re-release.
|
||
//
|
||
// Reference dates used (公历 yyyy-MM-dd → 农历):
|
||
// 2026-02-17 → 农历 正月 初一 (春节)
|
||
// 2026-04-05 → 清明 (公历固定)
|
||
// 2026-06-19 → 农历 五月 初五 (端午)
|
||
// 2026-09-25 → 农历 八月 十五 (中秋)
|
||
//
|
||
// In years where 中秋 falls within 6 days of 国庆, the State Council
|
||
// typically merges them into a single 8-10 day window. 2026 has
|
||
// 中秋 09-25 (Fri) and 国庆 10-01 (Thu) → window 09-25 ~ 10-04 is the
|
||
// most likely pattern (with 10-10 Saturday or earlier 09-20 Sunday
|
||
// for makeup work day).
|
||
//
|
||
// Data structure is a flat list rather than a JSON resource so the
|
||
// plugin bundle stays single-binary and dependency-free.
|
||
//
|
||
|
||
import Foundation
|
||
|
||
struct Holiday {
|
||
/// "元旦" / "春节" / "国庆" / etc.
|
||
let name: String
|
||
/// First day of the holiday window (yyyy-MM-dd, Asia/Shanghai).
|
||
let startDate: String
|
||
/// Total consecutive days off, including the trigger day.
|
||
let days: Int
|
||
}
|
||
|
||
/// Resolved upcoming holiday with the days remaining and start Date.
|
||
struct UpcomingHoliday {
|
||
let name: String
|
||
let startDate: Date
|
||
let days: Int
|
||
/// Days from now to startDate. 0 = today is day 1 of the holiday.
|
||
let daysUntil: Int
|
||
/// True when "now" falls inside [startDate, startDate+days).
|
||
let isOngoing: Bool
|
||
}
|
||
|
||
enum HolidayDatabase {
|
||
/// 2026 法定假期 — best estimate per typical State Council调休 pattern.
|
||
/// VERIFY against the official 国务院办公厅 公告 when published.
|
||
///
|
||
/// 春节 starts on 除夕 (the day before 初一) per 2024 policy update.
|
||
/// 中秋 + 国庆 merged because 2026 中秋 09-25 falls 6 days before
|
||
/// 国庆 10-01 — same pattern as 2017 / 2020 when they were close.
|
||
static let entries2026: [Holiday] = [
|
||
// 元旦 周四 — 单日不调休(2024 政策起元旦只放 1 天)
|
||
Holiday(name: "元旦", startDate: "2026-01-01", days: 1),
|
||
// 春节 02-16 除夕 ~ 02-22 初七,7 天。调休 02-14 周六 + 02-28 周六上班
|
||
Holiday(name: "春节", startDate: "2026-02-16", days: 7),
|
||
// 清明 04-05 周日,调休 04-04 ~ 04-06 共 3 天
|
||
Holiday(name: "清明", startDate: "2026-04-04", days: 3),
|
||
// 五一 05-01 周五 ~ 05-05 周二,5 天。调休 04-26 周日上班
|
||
Holiday(name: "劳动节", startDate: "2026-05-01", days: 5),
|
||
// 端午 06-19 周五 ~ 06-21 周日,3 天(自然连周末,无调休)
|
||
Holiday(name: "端午", startDate: "2026-06-19", days: 3),
|
||
// 中秋 + 国庆 合并 09-25 周五 ~ 10-04 周日,10 天
|
||
// 调休 09-19 / 10-10 周六上班
|
||
Holiday(name: "中秋·国庆", startDate: "2026-09-25", days: 10),
|
||
// 跨年 roll-over — 元旦 2027 估计 01-01 周五 ~ 01-03 周日,3 天
|
||
Holiday(name: "元旦", startDate: "2027-01-01", days: 3),
|
||
]
|
||
|
||
/// Returns the soonest upcoming or currently-ongoing holiday.
|
||
/// `now` is injectable for testability.
|
||
static func upcoming(from now: Date, timeZone: TimeZone) -> UpcomingHoliday? {
|
||
let fmt = DateFormatter()
|
||
fmt.locale = Locale(identifier: "en_US_POSIX")
|
||
fmt.timeZone = timeZone
|
||
fmt.dateFormat = "yyyy-MM-dd"
|
||
var cal = Calendar(identifier: .gregorian)
|
||
cal.timeZone = timeZone
|
||
for h in entries2026 {
|
||
guard let start = fmt.date(from: h.startDate) else { continue }
|
||
guard let end = cal.date(byAdding: .day, value: h.days, to: start) else { continue }
|
||
if now >= start && now < end {
|
||
// In the holiday window already.
|
||
return UpcomingHoliday(
|
||
name: h.name, startDate: start,
|
||
days: h.days, daysUntil: 0, isOngoing: true
|
||
)
|
||
}
|
||
if start > now {
|
||
// Floor-divide of seconds to days for an integer answer
|
||
// independent of locale calendar shenanigans.
|
||
let secs = start.timeIntervalSince(now)
|
||
let daysUntil = Int(ceil(secs / 86400.0))
|
||
return UpcomingHoliday(
|
||
name: h.name, startDate: start,
|
||
days: h.days, daysUntil: daysUntil, isOngoing: false
|
||
)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
}
|