Files
code-utils/holidays.go
李琦 e71ce451ff 模块介绍 / 今日规划 / 下班日报 / 团队摘要 生成成功后没有上报,也几乎不写「AI」日志。
流结束时的 usage 用了非阻塞发送,通道一满就被丢掉;很多服务商默认还不在流里带 usage。
现在每次真正打到模型的调用都会:

在本地日志(分类 AI,消息 AI 调用完成)写下 scene / prompt / completion / cached
登录后同步上报到后台日汇总
2026-08-17 14:00:47 +08:00

262 lines
9.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 农历节日的公历对照20252029。春节 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}}},
},
}