Hugo 时间函数完全指南:AsTime、Format、In 等 6 大函数用法详解与源码解析
【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo
本文围绕 Hugo 模板引擎中的time函数族展开,系统讲解time.AsTime、time.Format、time.In、time.Now、time.Duration、time.ParseDuration六个函数的签名、参数、返回值、时区处理规则与本地化能力,并结合 tpl/time/time.go 等源码揭示其底层实现。读完本文,你将能在 Hugo 模板中熟练完成日期时间的解析、格式化、时区换算、本地化输出与时长计算,并理解timeZone配置与"时区优先级"背后的设计逻辑。
一、概览:Hugo 时间处理的两步走
Hugo 提供了一系列 函数 与 方法 用于格式化、本地化、解析、比较和操作日期/时间值。但在对这些值进行任何操作之前,字符串形式的日期/时间必须先通过time.AsTime函数转换为 Go 的time.Time值。
{{ $t := "2023-10-15T13:18:50-07:00" }} {{ time.AsTime $t }} → 2023-10-15 13:18:50 -0700 PDT (time.Time)一旦得到time.Time值,就可以将其传给time.Format进行格式化与本地化,传给time.In进行时区换算,或直接调用time.Time上的任意 time 方法(如.Year、.Weekday、.Unix)。
二、time.AsTime:把字符串解析为 time.Time
签名与别名
- 签名:
time.AsTime INPUT [TIMEZONE] - 返回类型:
time.Time - 别名:
time(在模板中直接写time "2023-10-15"等价于time.AsTime)
从 tpl/time/init.go 可以看到,time命名空间同时被注册为可调用函数:不传参数时返回命名空间上下文(用于访问其它时间函数),传 1 个或 2 个参数时则分别调用ctx.AsTime(args[0])与ctx.AsTime(args[0], args[1])。
可解析字符串
time.AsTime的第一个参数必须是可解析的日期/时间字符串表示。Hugo 官方文档维护了一张常见可解析格式表(见 docs/content/en/_common/parsable-date-time-strings.md):
| 格式 | 时区 |
|---|---|
2023-10-15T13:18:50-07:00 | America/Los_Angeles |
2023-10-15T13:18:50-0700 | America/Los_Angeles |
2023-10-15T13:18:50Z | Etc/UTC |
2023-10-15T13:18:50 | 默认Etc/UTC |
2023-10-15 | 默认Etc/UTC |
15 Oct 2023 | 默认Etc/UTC |
注意:后三种写法没有携带完整的时区信息,会默认落到Etc/UTC时区。这也是为什么处理不完整日期字符串时必须留意timeZone配置或显式传入时区参数。
覆盖默认时区与优先级
要覆盖默认时区,可以在项目配置中设置timeZone,也可以给time.AsTime传入第二个参数:
{{ time.AsTime "15 Oct 2023" "America/Los_Angeles" }}合法时区列表可能因系统而异,但应至少包含UTC、Local以及 IANA 时区数据库 中的任意地点。time.AsTime确定时区的优先级如下:
- 日期/时间字符串中自带的时区偏移(如
-07:00); - 作为第二个参数传入
time.AsTime的时区; - 项目配置中指定的时区;
Etc/UTC时区。
这一优先级在 tpl/time/time_test.go 的TestTimeLocation用例中得到了直接印证:例如"2020-09-23T20:33:44-0700"即使显式传入America/New_York或Europe/Oslo,结果仍是字符串自带的-0700偏移——字符串内嵌偏移的优先级最高。
源码级实现
在 tpl/time/time.go 中,AsTime的逻辑很直观:默认使用命名空间持有的ns.location(来自语言/站点配置),若提供了第二参数则调用time.LoadLocation加载对应时区,最后交给 common/htime/time.go 中的htime.ToTimeInDefaultLocationE完成解析。该辅助函数还兼容AsTimeProvider接口(由 go-toml 的LocalDate/LocalDateTime实现),解决了 TOML front matter 日期时区名为空的问题(issue #8895)。
三、time.Format:格式化与本地化
签名与别名
- 签名:
time.Format LAYOUT INPUT - 返回类型:
string - 别名:
dateFormat({{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }})
time.Format既可以直接接收time.Time值,也可以接收可解析的字符串:
{{ $t := time.AsTime "2023-10-15T13:18:50-07:00" }} {{ time.Format "2 Jan 2006" $t }} → 15 Oct 2023{{ $t := "15 Oct 2023" }} {{ time.Format "January 2, 2006" $t }} → October 15, 2023与time.AsTime相同,time.Format也遵循时区优先级,但顺序略不同(因为它没有第二参数可用):
- 日期/时间字符串中自带的时区偏移;
- 项目配置中指定的时区;
Etc/UTC时区。
布局字符串(Layout string)
布局字符串基于 Go 的参考时间):
| 描述 | 合法组件 |
|---|---|
| 年 | "2006" "06" |
| 月 | "Jan" "January" "01" "1" |
| 星期 | "Mon" "Monday" |
| 月中的日 | "2" "_2" "02" |
| 年中的日 | "__2" "002" |
| 时 | "15" "3" "03" |
| 分 | "4" "04" |
| 秒 | "5" "05" |
| AM/PM 标记 | "PM" |
| 时区偏移 | "-0700" "-07:00" "-07" "-070000" "-07:00:00" |
把布局中的正负号换成Z,可以在 UTC 时区输出Z而不是偏移量:"Z0700" "Z07:00" "Z07" "Z070000" "Z07:00:00"。
一个完整示例:
{{ $t := "2023-01-27T23:44:58-08:00" }} {{ $t = time.AsTime $t }} {{ $t = $t.Format "Jan 02, 2006 3:04 PM Z07:00" }} {{ $t }} → Jan 27, 2023 11:44 PM -08:00需要特别注意三组容易混淆的概念:
PST、CET这类是时区缩写,不是时区;-07:00、+01:00这类是时区偏移,也不是时区;- 时区是"本地时间相同的地理区域",例如
PST/PDT(视夏令时而定)缩写对应的时区是America/Los_Angeles。
本地化(Localization)
time.Format可以把time.Time值本地化为当前语言与区域对应的展示形式。Hugo 通过bep/golocales配置项确定区域设置,回退到语言键本身(详见 docs/content/en/_common/functions/locales.md)。
既可以使用上面介绍的布局字符串,也可以直接使用下面这些以冒号开头的预置 token。例如:
{{ .Date | time.Format ":date_medium" }} → Jan 27, 2023本地化为 en-US 时的结果:
| Token | 结果 |
|---|---|
:date_full | Friday, January 27, 2023 |
:date_long | January 27, 2023 |
:date_medium | Jan 27, 2023 |
:date_short | 1/27/23 |
:time_full | 11:44:58 pm Pacific Standard Time |
:time_long | 11:44:58 pm PST |
:time_medium | 11:44:58 pm |
:time_short | 11:44 pm |
本地化为 de-DE 时的结果:
| Token | 结果 |
|---|---|
:date_full | Freitag, 27. Januar 2023 |
:date_long | 27. Januar 2023 |
:date_medium | 27.01.2023 |
:date_short | 27.01.23 |
:time_full | 23:44:58 Nordamerikanische Westküsten-Normalzeit |
:time_long | 23:44:58 PST |
:time_medium | 23:44:58 |
:time_short | 23:44 |
源码级实现
Format在 tpl/time/time.go 中先通过htime.ToTimeInDefaultLocationE把输入统一转换为time.Time,再交给ns.timeFormatter.Format。真正的本地化逻辑位于 common/htime/time.go:以:开头的布局走golocales翻译器(FormatDateFull/FormatTimeMedium等)的预置格式分支;普通布局则先按 Go 原生布局格式化,再把布局中出现的January/Jan、Monday/Mon等月份与星期名替换为golocales提供的本地化名称(注意time.Month是 1 起始、月份名切片是 0 起始的细节处理)。
四、time.In:按 IANA 时区换算时间(v0.146.0 新增)
签名
- 签名:
time.In TIMEZONE INPUT - 返回类型:
time.Time
time.In返回给定日期/时间在指定 IANA 时区中的表示:
- 时区为空字符串或
UTC时,返回 UTC 时间; - 时区为
Local时,返回系统本地时区的时间; - 否则,时区必须是合法的 IANA 时区名。
{{ $layout := "2006-01-02T15:04:05-07:00" }} {{ $t := time.AsTime "2025-03-31T14:45:00-00:00" }} {{ $t | time.In "America/Denver" | time.Format $layout }} → 2025-03-31T08:45:00-06:00 {{ $t | time.In "Australia/Adelaide" | time.Format $layout }} → 2025-04-01T01:15:00+10:30 {{ $t | time.In "Europe/Oslo" | time.Format $layout }} → 2025-03-31T16:45:00+02:00注意结果中既有日期跨越(Adelaide 已是 4 月 1 日),也有夏令时导致的偏移差异(Denver 为-06:00)——这正是 IANA 时区数据库能正确处理而单纯加减固定偏移做不到的地方。
源码级实现与性能设计
在 tpl/time/time.go 中,In通过ns.cacheIn这一 dynacache 分区缓存time.LoadLocation的结果(分区键为/tmpl/time/in,权重 30,ClearNever),避免每次调用都重新加载时区数据库。创建该缓存分区的逻辑见 tpl/time/time.go,New中会检查deps.MemCache是否存在。
测试 tpl/time/time_test.go 的TestIn覆盖了America/Denver、Australia/Adelaide、Europe/Oslo、UTC、空字符串以及非法时区名InvalidTimeZoneName(返回错误)等场景;其后的BenchmarkInWithCaching基准测试专门验证了时区加载缓存的性能收益。
五、time.Now:获取当前时间
签名
- 签名:
time.Now - 返回类型:
time.Time - 别名:
now
例如在 2023 年 10 月 15 日于America/Los_Angeles时区构建站点时:
{{ time.Now }}会产生一个time.Time值,其字符串表示类似:
2023-10-15 12:59:28.337140706 -0700 PDT m=+0.041752605要格式化并本地化该值,可以把它传给time.Format:
{{ time.Now | time.Format "Jan 2006" }} → Oct 2023因为time.Now返回的是time.Time值,所以可以直接链式调用任意 time 方法:
{{ time.Now.Year }} → 2023 (int) {{ time.Now.Weekday.String }} → Sunday {{ time.Now.Month.String }} → October {{ time.Now.Unix }} → 1697400955 (int64)源码级实现
Now在 tpl/time/time.go 中直接返回htime.Now()。而 common/htime/time.go 的实现依赖bep/clocks包的Clock变量——它默认使用系统时钟,但 Hugo 支持通过clock标志"伪造"时间,这对测试与可复现构建非常有价值(注释明确写道 "Use this function to fake time inside hugo")。
六、time.Duration 与 time.ParseDuration:时长计算
time.Duration:单位 + 数量
- 签名:
time.Duration TIME_UNIT NUMBER - 返回类型:
time.Duration - 别名:
duration
time.Duration返回一个time.Duration值,可以配合任意Duration方法(如.Seconds、.Hours)使用:
{{ $duration := time.Duration "hour" 24 }} {{ printf "There are %.0f seconds in one day." $duration.Seconds }}渲染结果:
There are 86400 seconds in one day.时间单位必须是下表之一(与 tpl/time/time.go 中的durationUnits映射完全一致):
| 时长 | 合法时间单位 |
|---|---|
| 小时 | hour,h |
| 分钟 | minute,m |
| 秒 | second,s |
| 毫秒 | millisecond,ms |
| 微秒 | microsecond,us,µs |
| 纳秒 | nanosecond,ns |
在 tpl/time/time.go 的实现中,单位字符串在durationUnits中查找,数量通过cast.ToInt64E转为 int64,两者相乘得到time.Duration;单位非法时会返回形如"xxx" is not a valid duration unit的错误。
time.ParseDuration:解析时长字符串
- 签名:
time.ParseDuration DURATION - 返回类型:
time.Duration
时长字符串是一串可能带符号的十进制数字序列,每段可带小数部分和单位后缀,例如300ms、-1.5h或2h45m。合法单位是ns、us(或µs)、ms、s、m、h。
{{ $duration := time.ParseDuration "24h" }} {{ printf "There are %.0f seconds in one day." $duration.Seconds }}渲染结果:
There are 86400 seconds in one day.实现上(tpl/time/time.go)先经cast.ToStringE转为字符串,再直接委托给 Go 标准库的time.ParseDuration,因此语义与 Go 标准库完全一致。
七、timeZone 配置:全局默认时区
time.AsTime与time.Format在输入字符串未携带时区偏移时,都会回落到项目配置中的timeZone设置。该配置项定义于 config/allconfig/allconfig.go(TimeZone string,注释说明它用于解析不含时区信息的 front matter 日期以及 time 函数),其官方文档位于 docs/content/en/configuration/all.md:
timeZone(string):用于解析没有时区偏移的日期(包括 front matter 日期字段以及传给time.AsTime和time.Format的值)的时区。合法值列表可能因系统而异,但应包含UTC、Local及 IANA 时区数据库 中的任意地点,例如America/Los_Angeles和Europe/Oslo。
在 Hugo 中,timeZone属于站点级配置,且在 config/allconfig/allconfig.go 中会随每个语言配置一起传入langs.NewLanguage,因此不同语言可以拥有各自独立的默认时区。当time.AsTime或time.Format的输入字符串既无内嵌偏移、也未显式指定时区时,最终解析结果就由该配置决定,这也是多语言站点保持时间展示一致性的关键设置。
八、实战组合:完整示例
将上述函数串联起来,可以实现"按站点时区解析 front matter 日期 → 换算到读者所在时区 → 本地化输出"的完整链路:
{{/* 1. 解析字符串为 time.Time(默认时区取自 timeZone 配置) */}} {{ $t := time.AsTime .Params.eventDate }} {{/* 2. 换算到目标 IANA 时区 */}} {{ $t = $t | time.In "Europe/Oslo" }} {{/* 3. 本地化格式化输出(跟随站点 locale) */}} {{ $t | time.Format ":date_long" }} → 27. Januar 2023(de-DE 时) {{ $t | time.Format "January 2, 2006 15:04" }} → January 27, 2023 23:44 {{/* 4. 时长计算:距离某个截止时间的剩余秒数 */}} {{ $remaining := time.ParseDuration "48h" | time.Duration "second" | printf "%.0f" }}九、小结
| 函数 | 作用 | 关键点 |
|---|---|---|
time.AsTime | 字符串 →time.Time | 时区优先级:字符串偏移 > 显式参数 >timeZone配置 >Etc/UTC |
time.Format | 格式化/本地化time.Time | 布局字符串基于 Go 参考时间;:前缀 token 走 golocales 本地化 |
time.In | 按 IANA 时区换算 | v0.146.0 新增;时区加载结果被 dynacache 缓存 |
time.Now | 当前时间 | 返回time.Time,可链式调用任意 time 方法;底层支持clock伪造时间 |
time.Duration | 单位 + 数量 → 时长 | 单位映射见 tpl/time/time.go |
time.ParseDuration | 解析时长字符串 | 语义与 Go 标准库time.ParseDuration一致 |
所有函数的实现都集中在 tpl/time/time.go,函数注册、别名(time/duration/dateFormat/now)与示例映射在 tpl/time/init.go,覆盖各种边界场景的测试在 tpl/time/time_test.go。掌握这六个函数,即可在 Hugo 模板中自如应对"解析、格式化、换算、本地化、时长计算"全部时间处理需求。
【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考