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}}}, }, }