You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Gin中将API的字符串日期反序列化为time.Time字段?

解决Gin中time.Time字段解析非RFC3339格式时间的问题

Gin的ShouldBind默认依赖Go标准库的encoding/json,而标准库解析time.Time时仅支持RFC3339格式(如2006-01-02T15:04:05Z07:00),对于不带时区的2019-01-01T00:00:00或空格分隔的2022-01-01 20:00:00会解析失败。以下是两种实用解决方案:

方案一:自定义时间类型(推荐)

通过实现json.Unmarshaler接口,让自定义时间类型支持多种格式解析:

package main

import (
    "encoding/json"
    "fmt"
    "time"

    "github.com/gin-gonic/gin"
)

// CustomTime 自定义时间类型,兼容多种格式
type CustomTime time.Time

// 支持解析的时间格式列表
const (
    timeFormatISOWithoutTZ = "2006-01-02T15:04:05"
    timeFormatDateTime     = "2006-01-02 15:04:05"
    timeFormatRFC3339      = time.RFC3339
)

// UnmarshalJSON 实现json.Unmarshaler接口,处理多种时间格式
func (ct *CustomTime) UnmarshalJSON(data []byte) error {
    // 去除JSON字符串的引号
    rawStr := string(data)
    timeStr := rawStr[1 : len(rawStr)-1]

    // 依次尝试解析不同格式
    t, err := time.Parse(timeFormatISOWithoutTZ, timeStr)
    if err == nil {
        *ct = CustomTime(t)
        return nil
    }

    t, err = time.Parse(timeFormatDateTime, timeStr)
    if err == nil {
        *ct = CustomTime(t)
        return nil
    }

    t, err = time.Parse(timeFormatRFC3339, timeStr)
    if err == nil {
        *ct = CustomTime(t)
        return nil
    }

    return fmt.Errorf("无法解析时间格式: %s", timeStr)
}

// MarshalJSON 实现json.Marshaler接口,序列化时统一格式
func (ct CustomTime) MarshalJSON() ([]byte, error) {
    t := time.Time(ct)
    return json.Marshal(t.Format(timeFormatISOWithoutTZ))
}

// Time 转换为标准time.Time类型,方便使用时间方法
func (ct CustomTime) Time() time.Time {
    return time.Time(ct)
}

// 修改User结构体,使用自定义时间类型
type User struct {
    CreatedAt CustomTime `json:"created_at"`
}

func main() {
    r := gin.Default()
    r.POST("/user", func(c *gin.Context) {
        var user User
        if err := c.ShouldBind(&user); err != nil {
            c.JSON(400, gin.H{"error": err.Error()})
            return
        }
        // 转换为标准time.Time使用
        createdAt := user.CreatedAt.Time()
        c.JSON(200, gin.H{"created_at": createdAt.Format(timeFormatDateTime)})
    })
    r.Run(":8080")
}

方案二:使用字符串接收后手动转换

如果不想自定义类型,可以先用string类型接收时间字段,再手动解析为time.Time:

type UserRequest struct {
    CreatedAtStr string `json:"created_at"`
}

// 在绑定后手动解析
var req UserRequest
if err := c.ShouldBind(&req); err != nil {
    c.JSON(400, gin.H{"error": err.Error()})
    return
}

// 尝试多种格式解析
var createdAt time.Time
var err error
createdAt, err = time.Parse(timeFormatISOWithoutTZ, req.CreatedAtStr)
if err != nil {
    createdAt, err = time.Parse(timeFormatDateTime, req.CreatedAtStr)
}
if err != nil {
    createdAt, err = time.Parse(timeFormatRFC3339, req.CreatedAtStr)
}
if err != nil {
    c.JSON(400, gin.H{"error": "时间格式错误"})
    return
}

注意事项

  • 自定义类型方案更优雅,适合重复使用场景;
  • 如果需要处理时区,可以在解析时指定time.LoadLocation(比如time.ParseInLocation),将时间转换为特定时区;
  • 若必须使用标准time.Time,也可以替换Gin默认的JSON解析器为jsoniter,并配置自定义时间格式,但步骤相对复杂。

内容的提问来源于stack exchange,提问作者Alireza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 02:25:40