Go 中实现带序数后缀(如 1st、2nd)的英文日期格式化

go 的 time.format() 不支持自动添加日期序数后缀(如 “1st”“2nd”),需手动拼接后缀逻辑,再结合标准布局字符串完成格式化。

在 Go 中,time.Format() 严格遵循预定义的参考时间(Mon Jan 2 15:04:05 MST 2006)进行布局解析,它不支持动态文本替换(例如 "2nd" 中的 "nd" 并非占位符,而是字面量)。因此,直接使用 "Monday 2nd January" 会导致 2 被固定渲染为字面字符 "2",而后缀 "nd" 始终原样输出,无法随日期变化——这正是示例中 4nd 和 1nd 错误结果的根源。

要实现符合英语习惯的“verbose date”(如 Wednesday 4th March 或 Sunday 1st March),必须将日期序数后缀(st/nd/rd/th)动态计算并注入格式字符串中。以下是一个简洁、健壮的实现:

func formatVerboseDate(t time.Time) string {
    day := t.Day()
    var suffix string
    switch {
    case day%100 >= 11 && day%100 <= 13:
        suffix = "th" // 11th, 12th, 13th are special cases
    case day%10 == 1:
        suffix = "st"
    case day%10 == 2:
        suffix = "nd"
    case day%10 == 3:
        suffix = "rd"
    default:
        suffix = "th"
    }
    // 使用 Format 拼接:先用 "2" 占位日,再替换为带后缀的完整日字符串
    base := t.Format("Monday 2 January 2006")
    return strings.Replace(base, " 2 ", fmt.Sprintf(" %d%s ", day, suffix), 1)
}
✅ 注意:上述实现使用 strings.Replace(..., 1) 精确替换第一个 " 2 "(含空格),避免误替其他数字;同时正确处理了英语中的特殊规则:11–13 日统一用 "th"(即 11th, 12th, 13th),而非 11st。

更推荐的无依赖写法(不引入 strings 包)是直接构造字符串:

func formatVerboseDate(t time.Time) string {
    day := t.Day()
    var suffix string
    switch {
    case day%10

0 >= 11 && day%100 <= 13: suffix = "th" case day%10 == 1: suffix = "st" case day%10 == 2: suffix = "nd" case day%10 == 3: suffix = "rd" default: suffix = "th" } return fmt.Sprintf("%s %d%s %s %d", t.Weekday().String(), day, suffix, t.Month().String(), t.Year(), ) }

该函数输出完全可控,例如:

  • time.Date(2025, 3, 1, 0, 0, 0, 0, time.UTC) → "Sunday 1st March 2025"
  • time.Date(2025, 3, 11, 0, 0, 0, 0, time.UTC) → "Tuesday 11th March 2025"
  • time.Date(2025, 3, 22, 0, 0, 0, 0, time.UTC) → "Friday 22nd March 2025"

? 总结:Go 标准库不提供序数日期格式化能力,但通过组合 t.Day()、条件判断与 fmt.Sprintf(),可轻松、高效、可读地实现符合本地化习惯的 verbose date 输出。关键在于理解 time.Format() 的静态布局本质,并主动承担动态部分的逻辑。