Revel 框架中自定义模板时间函数必须在 app/init.go 的 init() 函数中注册到 revel.TemplateFuncs,否则模板调用会报 undefined function;需重启服务生效,函数名首字母大写且推荐接收 interface{} 类型。

Revel 框架里不能在模板中直接调用 time.Since() 或写 .CreatedAt.Sub(time.Now()),会报错 can't evaluate field Since in type time.Time。必须提前算好、或注册函数——但 Revel 的模板函数注册有特殊时机要求,容易漏掉重启或注册位置错误。
Revel 中注册自定义时间函数必须在 app/init.go 的 init() 里
Revel 的模板函数注册不是“随用随加”,而是在应用启动初期一次性注入全局 revel.TemplateFuncs。如果写在 controller 里、或写在 main() 之后、或漏掉 revel.TemplateFuncs 赋值,模板里调用 {{.CreatedAt | relativeTime}} 就会提示 undefined function "relativeTime"。
- 必须在
app/init.go文件的func init() { ... }块内注册 - 注册后需重启服务(Revel 不热重载模板函数)
- 函数签名要导出(首字母大写),且推荐接收
interface{}避免 panic - 示例注册代码:
func init() {
revel.TemplateFuncs["relativeTime"] = func(v interface{}) string {
t, ok := v.(time.Time)
if !ok {
if ts, ok := v.(int64); ok {
t = time.Unix(ts, 0)
} else {
return ""
}
}
d := time.Since(t)
switch {
case d.Minutes() < 1:
return "刚刚"
case d.Hours() < 1:
return fmt.Sprintf("%.0f分钟前", d.Minutes())
case d.Hours() < 24:
return fmt.Sprintf("%.0f小时前", d.Hours())
case d.Hours() < 24*30:
return fmt.Sprintf("%d天前", int(d.Hours()/24))
default:
return t.Format("2006-01-02")
}
}
}
模板里调用 relativeTime 前要确保传入的是有效时间值
Revel 模板不校验字段类型,如果结构体字段是空值、nil 指针、或传了字符串没做转换,relativeTime 函数内部类型判断失败就会返回空字符串,页面上就“消失”了,且无任何错误提示。
- 后端传参时尽量用
time.Time字段,避免传string或int64后再靠函数硬转 - 若必须传时间戳,在 handler 里先转成
time.Time再塞进 view data,更可控 - 模板调用写法:{{
.Item.CreatedAt | relativeTime}},不是 {{relativeTime .Item.CreatedAt}} - 注意:Revel 的
field函数不支持嵌套管道,所以{{field "item.CreatedAt" . | relativeTime}}会失败,得先提取再处理
相对时间函数里时区处理要显式调用 In()
Go 的 time.Since() 返回的是本地时区差值,但 time.Now() 默认是机器本地时区(可能和用户期望的时区不一致)。如果数据存的是 UTC 时间,而你希望按「北京时间」计算“几小时前”,就得统一时区。
- 别依赖系统时区,显式加载:
shanghai, _ := time.LoadLocation("Asia/Shanghai") - 所有参与比较的时间都应转到同一时区:
t.In(shanghai)和time.Now().In(shanghai) - 否则可能出现“刚创建的记录显示为 8 小时后”这类反直觉结果(UTC 存储 + 本地时钟未对齐)
真正麻烦的不是写函数,而是函数注册时机、参数类型容错、时区对齐这三点——任一遗漏都会让相对时间在页面上静默失效,而不是报错提醒。


















