最近在解析 Go 的日期数据格式时(mysqldatetime 类型)时遇到个问题,在网上搜了很多方案都试了以后发现不可行,于是自己尝试解决后将解决方案发布出来。

Go 自身的 time.Time 类型默认解析的日期格式是 RFC3339 标准,也就是 2006-01-02T15:04:05Z07:00 的格式。如果我们想要在 GinshouldBindJSON 方法中,传入 YYYY-MM-DD hh:mm:ss 格式的日期格式作为 time.Time 类型的值,就会引发类似于 parsing time xx as xx: cannot parse xx as xx 的报错信息。这是因为 time.Time 类型默认支持的日期格式与我们传入的格式不同,导致解析出错。。

遇到这个问题后,我在网上找了很多方案,发现都失败了。有的可以完成正常解析,但是无法正确写入到数据库。有的可以正常写入和写出,但是会使得 gin 自带的验证规则如 binding:"required" 规则失效,失去校验的功能。

自定义 LocalTime 类型

解决这个问题的关键就是解决 c.ShouldBindJSONgorm.Updates 的问题,我们需要定义一个新的 Time 类型和自定义的日期格式解析(如下),并将我们的 struct 结构体 datetime 字段指定为我们自定义的类型(如下)

  • 自定义 LocalTime 类型
// model.LocalTime
package modelconst TimeFormat = "2006-01-02 15:04:05"type LocalTime time.Time
  • 业务代码结构
// You Application Struct
package ordertype OrderTest struct {OrderId     int              `json:"order_id"`Test        string           `json:"test"`PaymentTime *model.LocalTime `json:"payment_time" binding:"required"`TestTime    *model.LocalTime `json:"test_time"`
}

解析 JSON 格式数据 - UnmarshalJSONMarshalJSON

c.ShouldBindJSON 时,会调用 field.UnmarshalJSON 方法,所以我们需要先设置这个方法(如下):

func (t *LocalTime) UnmarshalJSON(data []byte) (err error) {// 空值不进行解析if len(data) == 2 {*t = LocalTime(time.Time{})return}// 指定解析的格式now, err := time.Parse(`"`+TimeFormat+`"`, string(data))*t = LocalTime(now)return
}

UnmarshalJSON 解析后,shouldBindJSON 就可以正常解析 YYYY-MM-DD hh:mm:ss 格式的日期格式了,这样一来就解决了 parsing time xx as xx: cannot parse xx as xx 的问题。

既然解决了 shouldBindJSON 的问题,我们还需要解决 c.JSON 时解析值的问题(实现如下)

func (t LocalTime) MarshalJSON() ([]byte, error) {b := make([]byte, 0, len(TimeFormat)+2)b = append(b, '"')b = time.Time(t).AppendFormat(b, TimeFormat)b = append(b, '"')return b, nil
}

数据库写入和写出问题 - ValueScan

在实现了 JSON 格式数据的解析取值后,会发现我们的值依然无法通过 gorm 被存储到 mysql 数据库中,通过抓包我们可以看看正常的请求和错误的请求的区别(见下图)

上图 1 (正常情况) 可以看出,payment_time 字段被传递,这样就可以正常存入更新。

上图 2(我们现在的情况) 可以看出,我们的 payment_time 字段根本没有被传递,从而导致更新失败。

所以这个问题属于 gorm 对字段取值的问题,gorm 内部是通过 ValueScan 这两个方法完成值的写入和检出。那么从这个角度出发,我们就需要给我们的类型实现 ValueScan 方法,分别对应写入的时候获取值和检出的时候解析值。(实现如下)

// 写入 mysql 时调用
func (t LocalTime) Value() (driver.Value, error) {// 0001-01-01 00:00:00 属于空值,遇到空值解析成 null 即可if t.String() == "0001-01-01 00:00:00" {return nil, nil}return []byte(time.Time(t).Format(TimeFormat)), nil
}// 检出 mysql 时调用
func (t *LocalTime) Scan(v interface{}) error {// mysql 内部日期的格式可能是 2006-01-02 15:04:05 +0800 CST 格式,所以检出的时候还需要进行一次格式化tTime, _ := time.Parse("2006-01-02 15:04:05 +0800 CST", v.(time.Time).String())*t = LocalTime(tTime)return nil
}// 用于 fmt.Println 和后续验证场景
func (t LocalTime) String() string {return time.Time(t).Format(TimeFormat)
}

如此一来,我们就可以正常解析存取 YYYY-MM-DD hh:mm:ss 格式的时间数据了(见下图)

LocalTime 完整代码如下:

package modelimport ("database/sql/driver""time"
)const TimeFormat = "2006-01-02 15:04:05"type LocalTime time.Timefunc (t *LocalTime) UnmarshalJSON(data []byte) (err error) {if len(data) == 2 {*t = LocalTime(time.Time{})return}now, err := time.Parse(`"`+TimeFormat+`"`, string(data))*t = LocalTime(now)return
}func (t LocalTime) MarshalJSON() ([]byte, error) {b := make([]byte, 0, len(TimeFormat)+2)b = append(b, '"')b = time.Time(t).AppendFormat(b, TimeFormat)b = append(b, '"')return b, nil
}func (t LocalTime) Value() (driver.Value, error) {if t.String() == "0001-01-01 00:00:00" {return nil, nil}return []byte(time.Time(t).Format(TimeFormat)), nil
}func (t *LocalTime) Scan(v interface{}) error {tTime, _ := time.Parse("2006-01-02 15:04:05 +0800 CST", v.(time.Time).String())*t = LocalTime(tTime)return nil
}func (t LocalTime) String() string {return time.Time(t).Format(TimeFormat)
}

解决验证器 binding:"required" 无法正常工作

在完成上述步骤后,你的 go 应用已经可以正常存取自定义的日期格式格式了。但是还有一个问题,那就是 binding:"required" 并不能正常工作了,如果你传入一个空字符串 "" 日期数据,也会通过校验,并在数据库写入 null

这个问题是因为 gin 内置的 validator 对我们的 model.LocalTime 还没有一个完善的空值检测机制,我们只需要加上这个检测机制即可。(实现如下)

package appfunc ValidateJSONDateType(field reflect.Value) interface{} {if field.Type() == reflect.TypeOf(model.LocalTime{}) {timeStr := field.Interface().(model.LocalTime).String()// 0001-01-01 00:00:00 是 go 中 time.Time 类型的空值// 这里返回 Nil 则会被 validator 判定为空值,而无法通过 `binding:"required"` 规则if timeStr == "0001-01-01 00:00:00" {return nil}return timeStr}return nil
}func Run() {router := gin.Default()if v, ok := binding.Validator.Engine().(*validator.Validate); ok {// 注册 model.LocalTime 类型的自定义校验规则v.RegisterCustomTypeFunc(ValidateJSONDateType, model.LocalTime{})}
}

加上这条自定义规则后,我们的校验规则又可以生效了,问题完美解决!(见下图)

这个问题困惑了我好几天,一开始想快点解决,在网上找了很多方案拿过来 copy 后,都没有解决问题。最后决定静下来心来,思考其背后的原理,仔细分析,最终靠自己攻克了这个问题,真是不容易。

这件事也让我明白了一个道理,授人予鱼不如授人予渔,所以我在这里也把解决问题的思路分享出来,希望对大家也能有一点理解上的提升。

最后一件事

如果本文对您有帮助的话,请点个赞和收藏吧!

您的点赞是对作者的最大鼓励,也可以让更多人看到本篇文章!

原文地址

Go 自定义日期时间格式解析解决方案 - 解决 `parsing time xx as xx: cannot parse xx as xx` 错误相关推荐

  1. Go 自定义日期时间格式解析解决方案 - 解决 parsing time xx as xx: cannot parse xx as xx 错误

    最近在解析 Go 的日期数据格式时(mysql 的 datetime 类型)时遇到个问题,在网上搜了很多方案都试了以后发现不可行,于是自己尝试解决后将解决方案发布出来. Go 自身的 time.Tim ...

  2. java 解析日期格式_日期/时间格式/解析,Java 8样式

    java 解析日期格式 自Java 几乎 开始以来,Java开发人员就通过java.util.Date类(自JDK 1.0起)和java.util.Calendar类(自JDK 1.1起 )来处理日期 ...

  3. 日期/时间格式/解析,Java 8样式

    自Java 几乎 开始以来,Java开发人员就通过java.util.Date类(自JDK 1.0起)和java.util.Calendar类(自JDK 1.1起 )来处理日期和时间. 在这段时间内, ...

  4. .Net Core Json序列化和反序列化以及自定义JsonConverterT来转化特殊日期时间格式

    System.Text.Json 命名空间提供用于序列化和反序列化 JavaScript 对象表示法 (JSON) 的功能. System.Text.Json 命名空间包含所有入口点和主要类型. Sy ...

  5. SpringBoot 项目 返回时间 日期、格式不正确 解决办法

    文章目录 SpringBoot 项目返回时间格式不正确 解决办法 1.遇到问题 2.解决方法 (1)问题所在 (2)如何解决 (3)效果 SpringBoot 项目返回时间格式不正确 解决办法   今 ...

  6. WPF-数据绑定:日期时间格式

    WPF-数据绑定:日期时间格式 原文:WPF-数据绑定:日期时间格式 WPF-数据绑定:日期时间格式绑定后自定义格式的例子. 我刚才遇到的问题是绑定完之后,星期始终显示为英文.需要一个属性Conver ...

  7. 日期时间格式之间的相互转换

    import java.time.LocalDate; import java.time.Period; import java.time.format.DateTimeFormatter; impo ...

  8. sqoop导入hive时间格式问题解决方案

    sqoop导入hive时间格式问题解决方案 从mysql导入数据时,发现时间格式有问题,要么是时间后面多一位零,要么要使用时间戳,还能不能好好玩耍了?! 于是,我就逛论坛,找大神,最终无果,也许这个问 ...

  9. python时间格式化代码_Python代码中如何将”日期时间”格式化为自己所需的样式呢?...

    摘要: 下文讲述Python代码中将日期时间格式化为时间的方法分享,如下所示: 实现思路: 使用time.strftime函数将 一个时间元组格式化自定义的模式 strftime函数语法: time. ...

最新文章

  1. MVC架构 在Android中的使用
  2. 线上分享 | 产品架构搭建:从业务到体系
  3. python数据分析实战:数据可视化的一些基本操作
  4. 释放内容化势能 聚划算《划算8点档》给出新思路
  5. 【工程项目经验】之32/64位平台printf uint64的方法
  6. Mvc5 EF6 CodeFirst Mysql (一) 新建一个Mvc项目并使用EF连接到Mysql数据库
  7. 传说中Python最难理解的点|看这完篇就够了
  8. Solr Windows环境安装配置
  9. golang 图片处理,剪切,base64数据转换,文件存储
  10. 2017年下半年网络工程师真题+答案解析
  11. Exception in thread main java.lang.UnsatisfiedLinkError: no awt in java.library.path:
  12. 基于认证服务器的认证协议演化
  13. 什么是裸金属云服务器,适用于哪些场景,又有哪些优势?
  14. 算法笔记(二叉树、红黑树、b+树等)
  15. CSS布局示例 1 - 页面色块布局
  16. dlopen failed: couldn‘t map “/data/xxxx.so“ segment 1: Permission denied
  17. linux某服务启动失败,提示Authorization not available. Check if polkit...问题解决
  18. 保持健康和活力 - 腰间盘突出康复指南
  19. 前尘往事入梦来 - IT十年回首
  20. Java 13---JDBC简介

热门文章

  1. v7000存储硬盘离线如何恢复数据
  2. 8位阿里P8合著“Dubbo微服务进阶笔记”一经面世,Github上标星93K+
  3. SAP 银企直连 电子回单
  4. Win7 wifi热点设置
  5. 还原计算机或重装windows,windows一键还原,教您怎么解决
  6. 深圳实验室设计合理化事项
  7. 金融数据分析导论基于R语言 第二章 金融时间序列的线性模型课后习题答案
  8. jquery 定义php变量,php定义常量_php 定义常量define与普通变量
  9. java 图片压缩不改变分辨率
  10. 星河璀璨 | GBASE南大通用两项成果获评2022大数据“星河”标杆、优秀案例