262 lines
9.4 KiB
Go
262 lines
9.4 KiB
Go
|
|
package main
|
|||
|
|
|
|||
|
|
// holidays.go 给 AI 任务拆解提供「本月 + 未来几个月」节假日公历对照。
|
|||
|
|
// 农历节日每年换算不同,法定放假以国务院已公布年份为准;未公布年份只给节日当天。
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"fmt"
|
|||
|
|
"sort"
|
|||
|
|
"strings"
|
|||
|
|
"time"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
type holidayItem struct {
|
|||
|
|
day time.Time
|
|||
|
|
offStart time.Time // 放假开始;零值表示仅节日当天
|
|||
|
|
offEnd time.Time // 放假结束(含)
|
|||
|
|
work []time.Time
|
|||
|
|
zh, en string
|
|||
|
|
aka string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// upcomingHolidays 从本月 1 日到未来 months 个月月末(默认 6)的节日,按日期排序。
|
|||
|
|
func upcomingHolidays(now time.Time, months int) []holidayItem {
|
|||
|
|
if months <= 0 {
|
|||
|
|
months = 6
|
|||
|
|
}
|
|||
|
|
loc := now.Location()
|
|||
|
|
start := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, loc)
|
|||
|
|
end := start.AddDate(0, months+1, 0) // 不含:覆盖「未来几个月」整月
|
|||
|
|
var out []holidayItem
|
|||
|
|
for y := start.Year(); y <= end.Year(); y++ {
|
|||
|
|
for _, h := range yearHolidays(y, loc) {
|
|||
|
|
if !h.day.Before(start) && h.day.Before(end) {
|
|||
|
|
out = append(out, h)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
sort.Slice(out, func(i, j int) bool { return out[i].day.Before(out[j].day) })
|
|||
|
|
return out
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func formatHolidayBlock(now time.Time, en bool) string {
|
|||
|
|
list := upcomingHolidays(now, 6)
|
|||
|
|
if len(list) == 0 {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
var b strings.Builder
|
|||
|
|
if en {
|
|||
|
|
b.WriteString("Holidays this month and the next 6 months (use these exact dates; do not guess):\n")
|
|||
|
|
} else {
|
|||
|
|
b.WriteString("本月及未来 6 个月节假日对照(必须用表中公历,禁止自己换算农历):\n")
|
|||
|
|
}
|
|||
|
|
for _, h := range list {
|
|||
|
|
name := h.zh
|
|||
|
|
if en {
|
|||
|
|
name = h.en
|
|||
|
|
if h.aka != "" {
|
|||
|
|
name += " / " + h.aka
|
|||
|
|
} else if h.zh != "" {
|
|||
|
|
name += " / " + h.zh
|
|||
|
|
}
|
|||
|
|
} else if h.aka != "" {
|
|||
|
|
name += " / " + h.aka
|
|||
|
|
}
|
|||
|
|
line := fmt.Sprintf("- %s:%s", name, h.day.Format("2006-01-02"))
|
|||
|
|
if !h.offStart.IsZero() && !h.offEnd.IsZero() {
|
|||
|
|
line += fmt.Sprintf("(放假 %s~%s", h.offStart.Format("2006-01-02"), h.offEnd.Format("2006-01-02"))
|
|||
|
|
if len(h.work) > 0 {
|
|||
|
|
ws := make([]string, len(h.work))
|
|||
|
|
for i, w := range h.work {
|
|||
|
|
ws[i] = w.Format("2006-01-02")
|
|||
|
|
}
|
|||
|
|
line += ";调休上班 " + strings.Join(ws, "、")
|
|||
|
|
}
|
|||
|
|
line += ")"
|
|||
|
|
}
|
|||
|
|
b.WriteString(line + "\n")
|
|||
|
|
}
|
|||
|
|
if en {
|
|||
|
|
b.WriteString("Phrases: “before X” → day before the festival; “during X” → official holiday span if listed, else the festival day; “after X” → day after the holiday ends.\n")
|
|||
|
|
} else {
|
|||
|
|
b.WriteString("说法对照:「X前 / X之前」= 节日前一天;「X期间 / X放假」= 表中放假区间(没有区间就用当天);「X后 / X之后」= 假期结束后一天。国庆可说十一,春节可说过年,元旦可说新年(公历)。\n")
|
|||
|
|
}
|
|||
|
|
return b.String()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func yearHolidays(year int, loc *time.Location) []holidayItem {
|
|||
|
|
d := func(m, day int) time.Time {
|
|||
|
|
return time.Date(year, time.Month(m), day, 0, 0, 0, 0, loc)
|
|||
|
|
}
|
|||
|
|
items := []holidayItem{
|
|||
|
|
{day: d(1, 1), zh: "元旦", en: "New Year's Day", aka: "新年"},
|
|||
|
|
{day: d(2, 14), zh: "情人节", en: "Valentine's Day"},
|
|||
|
|
{day: d(3, 8), zh: "妇女节", en: "Women's Day"},
|
|||
|
|
{day: d(3, 12), zh: "植树节", en: "Arbor Day (CN)"},
|
|||
|
|
{day: d(4, 1), zh: "愚人节", en: "April Fools' Day"},
|
|||
|
|
{day: d(5, 1), zh: "劳动节", en: "Labor Day", aka: "五一"},
|
|||
|
|
{day: d(5, 4), zh: "青年节", en: "Youth Day"},
|
|||
|
|
{day: nthWeekday(year, time.May, time.Sunday, 2, loc), zh: "母亲节", en: "Mother's Day"},
|
|||
|
|
{day: d(6, 1), zh: "儿童节", en: "Children's Day"},
|
|||
|
|
{day: nthWeekday(year, time.June, time.Sunday, 3, loc), zh: "父亲节", en: "Father's Day"},
|
|||
|
|
{day: d(7, 1), zh: "建党节", en: "CPC Founding Day"},
|
|||
|
|
{day: d(8, 1), zh: "建军节", en: "PLA Day"},
|
|||
|
|
{day: d(9, 10), zh: "教师节", en: "Teachers' Day"},
|
|||
|
|
{day: d(10, 1), zh: "国庆节", en: "National Day", aka: "十一、国庆黄金周"},
|
|||
|
|
{day: d(10, 24), zh: "程序员节", en: "Programmer's Day"},
|
|||
|
|
{day: d(10, 31), zh: "万圣节", en: "Halloween"},
|
|||
|
|
{day: nthWeekday(year, time.November, time.Thursday, 4, loc), zh: "感恩节", en: "Thanksgiving"},
|
|||
|
|
{day: d(11, 11), zh: "双十一", en: "Singles' Day", aka: "光棍节"},
|
|||
|
|
{day: d(12, 12), zh: "双十二", en: "Double 12"},
|
|||
|
|
{day: d(12, 24), zh: "平安夜", en: "Christmas Eve"},
|
|||
|
|
{day: d(12, 25), zh: "圣诞节", en: "Christmas"},
|
|||
|
|
}
|
|||
|
|
// 黑色星期五 = 感恩节次日
|
|||
|
|
for i := range items {
|
|||
|
|
if items[i].zh == "感恩节" {
|
|||
|
|
bf := items[i].day.AddDate(0, 0, 1)
|
|||
|
|
items = append(items, holidayItem{day: bf, zh: "黑色星期五", en: "Black Friday"})
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
items = append(items, lunarHolidays(year, loc)...)
|
|||
|
|
items = append(items, solarTermHolidays(year, loc)...)
|
|||
|
|
applyOfficialOff(year, loc, items)
|
|||
|
|
return items
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func nthWeekday(year int, month time.Month, wd time.Weekday, n int, loc *time.Location) time.Time {
|
|||
|
|
d := time.Date(year, month, 1, 0, 0, 0, 0, loc)
|
|||
|
|
delta := int(wd - d.Weekday())
|
|||
|
|
if delta < 0 {
|
|||
|
|
delta += 7
|
|||
|
|
}
|
|||
|
|
return d.AddDate(0, 0, delta+7*(n-1))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// lunarHolidays 农历节日的公历对照(2025–2029)。春节 day=正月初一。
|
|||
|
|
func lunarHolidays(year int, loc *time.Location) []holidayItem {
|
|||
|
|
type row struct{ m, d int }
|
|||
|
|
// key → 公历月日
|
|||
|
|
table := map[int]map[string]row{
|
|||
|
|
2025: {
|
|||
|
|
"除夕": {1, 28}, "春节": {1, 29}, "元宵": {2, 12}, "端午": {5, 31},
|
|||
|
|
"七夕": {8, 29}, "中元": {9, 6}, "中秋": {10, 6}, "重阳": {10, 29},
|
|||
|
|
},
|
|||
|
|
2026: {
|
|||
|
|
"除夕": {2, 16}, "春节": {2, 17}, "元宵": {3, 3}, "端午": {6, 19},
|
|||
|
|
"七夕": {8, 19}, "中元": {8, 27}, "中秋": {9, 25}, "重阳": {10, 18},
|
|||
|
|
},
|
|||
|
|
2027: {
|
|||
|
|
"除夕": {2, 5}, "春节": {2, 6}, "元宵": {2, 20}, "端午": {6, 9},
|
|||
|
|
"七夕": {8, 8}, "中元": {8, 16}, "中秋": {9, 15}, "重阳": {10, 8},
|
|||
|
|
},
|
|||
|
|
2028: {
|
|||
|
|
"除夕": {1, 25}, "春节": {1, 26}, "元宵": {2, 9}, "端午": {5, 28},
|
|||
|
|
"七夕": {8, 26}, "中元": {9, 3}, "中秋": {10, 3}, "重阳": {10, 26},
|
|||
|
|
},
|
|||
|
|
2029: {
|
|||
|
|
"除夕": {2, 12}, "春节": {2, 13}, "元宵": {2, 27}, "端午": {6, 16},
|
|||
|
|
"七夕": {8, 16}, "中元": {8, 24}, "中秋": {9, 22}, "重阳": {10, 16},
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
names := map[string][2]string{
|
|||
|
|
"除夕": {"除夕", "Lunar New Year's Eve"},
|
|||
|
|
"春节": {"春节", "Spring Festival"},
|
|||
|
|
"元宵": {"元宵节", "Lantern Festival"},
|
|||
|
|
"端午": {"端午节", "Dragon Boat Festival"},
|
|||
|
|
"七夕": {"七夕", "Qixi Festival"},
|
|||
|
|
"中元": {"中元节", "Ghost Festival"},
|
|||
|
|
"中秋": {"中秋节", "Mid-Autumn Festival"},
|
|||
|
|
"重阳": {"重阳节", "Double Ninth Festival"},
|
|||
|
|
}
|
|||
|
|
aka := map[string]string{
|
|||
|
|
"春节": "过年、农历新年、正月初一",
|
|||
|
|
"端午": "端阳",
|
|||
|
|
"中秋": "八月十五",
|
|||
|
|
"除夕": "年三十",
|
|||
|
|
}
|
|||
|
|
m, ok := table[year]
|
|||
|
|
if !ok {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
var out []holidayItem
|
|||
|
|
for k, r := range m {
|
|||
|
|
n := names[k]
|
|||
|
|
out = append(out, holidayItem{
|
|||
|
|
day: time.Date(year, time.Month(r.m), r.d, 0, 0, 0, 0, loc),
|
|||
|
|
zh: n[0], en: n[1], aka: aka[k],
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
return out
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// solarTermHolidays 常用节气(用户会当日期说)。
|
|||
|
|
func solarTermHolidays(year int, loc *time.Location) []holidayItem {
|
|||
|
|
type row struct {
|
|||
|
|
m, d int
|
|||
|
|
zh, en string
|
|||
|
|
}
|
|||
|
|
table := map[int][]row{
|
|||
|
|
2025: {{2, 3, "立春", "Start of Spring"}, {4, 4, "清明", "Qingming"}, {6, 21, "夏至", "Summer Solstice"}, {12, 21, "冬至", "Winter Solstice"}},
|
|||
|
|
2026: {{2, 4, "立春", "Start of Spring"}, {4, 5, "清明", "Qingming"}, {6, 21, "夏至", "Summer Solstice"}, {12, 22, "冬至", "Winter Solstice"}},
|
|||
|
|
2027: {{2, 4, "立春", "Start of Spring"}, {4, 5, "清明", "Qingming"}, {6, 21, "夏至", "Summer Solstice"}, {12, 22, "冬至", "Winter Solstice"}},
|
|||
|
|
2028: {{2, 4, "立春", "Start of Spring"}, {4, 4, "清明", "Qingming"}, {6, 21, "夏至", "Summer Solstice"}, {12, 21, "冬至", "Winter Solstice"}},
|
|||
|
|
2029: {{2, 3, "立春", "Start of Spring"}, {4, 4, "清明", "Qingming"}, {6, 21, "夏至", "Summer Solstice"}, {12, 22, "冬至", "Winter Solstice"}},
|
|||
|
|
}
|
|||
|
|
var out []holidayItem
|
|||
|
|
for _, r := range table[year] {
|
|||
|
|
aka := ""
|
|||
|
|
if r.zh == "清明" {
|
|||
|
|
aka = "清明节"
|
|||
|
|
}
|
|||
|
|
out = append(out, holidayItem{
|
|||
|
|
day: time.Date(year, time.Month(r.m), r.d, 0, 0, 0, 0, loc),
|
|||
|
|
zh: r.zh, en: r.en, aka: aka,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
return out
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type offSpan struct {
|
|||
|
|
offStart [2]int // month, day(可能早于节日当天,如春节)
|
|||
|
|
offEnd [2]int
|
|||
|
|
work [][2]int
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// applyOfficialOff 套上已公布的国务院放假调休(2026)。
|
|||
|
|
func applyOfficialOff(year int, loc *time.Location, items []holidayItem) {
|
|||
|
|
spans, ok := officialOff[year]
|
|||
|
|
if !ok {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
md := func(p [2]int) time.Time {
|
|||
|
|
return time.Date(year, time.Month(p[0]), p[1], 0, 0, 0, 0, loc)
|
|||
|
|
}
|
|||
|
|
for i := range items {
|
|||
|
|
sp, hit := spans[items[i].zh]
|
|||
|
|
if !hit {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
items[i].offStart = md(sp.offStart)
|
|||
|
|
items[i].offEnd = md(sp.offEnd)
|
|||
|
|
for _, w := range sp.work {
|
|||
|
|
items[i].work = append(items[i].work, md(w))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// officialOff 国务院已公布年份。2026:国办发〔2025〕通知。
|
|||
|
|
var officialOff = map[int]map[string]offSpan{
|
|||
|
|
2026: {
|
|||
|
|
"元旦": {offStart: [2]int{1, 1}, offEnd: [2]int{1, 3}, work: [][2]int{{1, 4}}},
|
|||
|
|
"春节": {offStart: [2]int{2, 15}, offEnd: [2]int{2, 23}, work: [][2]int{{2, 14}, {2, 28}}},
|
|||
|
|
"清明": {offStart: [2]int{4, 4}, offEnd: [2]int{4, 6}},
|
|||
|
|
"清明节": {offStart: [2]int{4, 4}, offEnd: [2]int{4, 6}},
|
|||
|
|
"劳动节": {offStart: [2]int{5, 1}, offEnd: [2]int{5, 5}, work: [][2]int{{5, 9}}},
|
|||
|
|
"端午节": {offStart: [2]int{6, 19}, offEnd: [2]int{6, 21}},
|
|||
|
|
"中秋节": {offStart: [2]int{9, 25}, offEnd: [2]int{9, 27}},
|
|||
|
|
"国庆节": {offStart: [2]int{10, 1}, offEnd: [2]int{10, 7}, work: [][2]int{{9, 20}, {10, 10}}},
|
|||
|
|
},
|
|||
|
|
}
|