Go 自定义日期时间格式解析解决方案 - 解决 `parsing time xx as xx: cannot parse xx as xx` 错误
最近在解析 Go
的日期数据格式时(mysql
的 datetime
类型)时遇到个问题,在网上搜了很多方案都试了以后发现不可行,于是自己尝试解决后将解决方案发布出来。
Go
自身的 time.Time
类型默认解析的日期格式是 RFC3339
标准,也就是 2006-01-02T15:04:05Z07:00
的格式。如果我们想要在 Gin
的 shouldBindJSON
方法中,传入 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.ShouldBindJSON
和 gorm.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 格式数据 - UnmarshalJSON
与 MarshalJSON
在 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
}
数据库写入和写出问题 - Value
与 Scan
在实现了 JSON
格式数据的解析取值后,会发现我们的值依然无法通过 gorm
被存储到 mysql
数据库中,通过抓包我们可以看看正常的请求和错误的请求的区别(见下图)
从 上图 1 (正常情况)
可以看出,payment_time
字段被传递,这样就可以正常存入更新。
从 上图 2(我们现在的情况)
可以看出,我们的 payment_time
字段根本没有被传递,从而导致更新失败。
所以这个问题属于 gorm
对字段取值的问题,gorm
内部是通过 Value
和 Scan
这两个方法完成值的写入和检出。那么从这个角度出发,我们就需要给我们的类型实现 Value
和 Scan
方法,分别对应写入的时候获取值和检出的时候解析值。(实现如下)
// 写入 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` 错误相关推荐
- Go 自定义日期时间格式解析解决方案 - 解决 parsing time xx as xx: cannot parse xx as xx 错误
最近在解析 Go 的日期数据格式时(mysql 的 datetime 类型)时遇到个问题,在网上搜了很多方案都试了以后发现不可行,于是自己尝试解决后将解决方案发布出来. Go 自身的 time.Tim ...
- java 解析日期格式_日期/时间格式/解析,Java 8样式
java 解析日期格式 自Java 几乎 开始以来,Java开发人员就通过java.util.Date类(自JDK 1.0起)和java.util.Calendar类(自JDK 1.1起 )来处理日期 ...
- 日期/时间格式/解析,Java 8样式
自Java 几乎 开始以来,Java开发人员就通过java.util.Date类(自JDK 1.0起)和java.util.Calendar类(自JDK 1.1起 )来处理日期和时间. 在这段时间内, ...
- .Net Core Json序列化和反序列化以及自定义JsonConverterT来转化特殊日期时间格式
System.Text.Json 命名空间提供用于序列化和反序列化 JavaScript 对象表示法 (JSON) 的功能. System.Text.Json 命名空间包含所有入口点和主要类型. Sy ...
- SpringBoot 项目 返回时间 日期、格式不正确 解决办法
文章目录 SpringBoot 项目返回时间格式不正确 解决办法 1.遇到问题 2.解决方法 (1)问题所在 (2)如何解决 (3)效果 SpringBoot 项目返回时间格式不正确 解决办法 今 ...
- WPF-数据绑定:日期时间格式
WPF-数据绑定:日期时间格式 原文:WPF-数据绑定:日期时间格式 WPF-数据绑定:日期时间格式绑定后自定义格式的例子. 我刚才遇到的问题是绑定完之后,星期始终显示为英文.需要一个属性Conver ...
- 日期时间格式之间的相互转换
import java.time.LocalDate; import java.time.Period; import java.time.format.DateTimeFormatter; impo ...
- sqoop导入hive时间格式问题解决方案
sqoop导入hive时间格式问题解决方案 从mysql导入数据时,发现时间格式有问题,要么是时间后面多一位零,要么要使用时间戳,还能不能好好玩耍了?! 于是,我就逛论坛,找大神,最终无果,也许这个问 ...
- python时间格式化代码_Python代码中如何将”日期时间”格式化为自己所需的样式呢?...
摘要: 下文讲述Python代码中将日期时间格式化为时间的方法分享,如下所示: 实现思路: 使用time.strftime函数将 一个时间元组格式化自定义的模式 strftime函数语法: time. ...
最新文章
- MVC架构 在Android中的使用
- 线上分享 | 产品架构搭建:从业务到体系
- python数据分析实战:数据可视化的一些基本操作
- 释放内容化势能 聚划算《划算8点档》给出新思路
- 【工程项目经验】之32/64位平台printf uint64的方法
- Mvc5 EF6 CodeFirst Mysql (一) 新建一个Mvc项目并使用EF连接到Mysql数据库
- 传说中Python最难理解的点|看这完篇就够了
- Solr Windows环境安装配置
- golang 图片处理,剪切,base64数据转换,文件存储
- 2017年下半年网络工程师真题+答案解析
- Exception in thread main java.lang.UnsatisfiedLinkError: no awt in java.library.path:
- 基于认证服务器的认证协议演化
- 什么是裸金属云服务器,适用于哪些场景,又有哪些优势?
- 算法笔记(二叉树、红黑树、b+树等)
- CSS布局示例 1 - 页面色块布局
- dlopen failed: couldn‘t map “/data/xxxx.so“ segment 1: Permission denied
- linux某服务启动失败,提示Authorization not available. Check if polkit...问题解决
- 保持健康和活力 - 腰间盘突出康复指南
- 前尘往事入梦来 - IT十年回首
- Java 13---JDBC简介
热门文章
- v7000存储硬盘离线如何恢复数据
- 8位阿里P8合著“Dubbo微服务进阶笔记”一经面世,Github上标星93K+
- SAP 银企直连 电子回单
- Win7 wifi热点设置
- 还原计算机或重装windows,windows一键还原,教您怎么解决
- 深圳实验室设计合理化事项
- 金融数据分析导论基于R语言 第二章 金融时间序列的线性模型课后习题答案
- jquery 定义php变量,php定义常量_php 定义常量define与普通变量
- java 图片压缩不改变分辨率
- 星河璀璨 | GBASE南大通用两项成果获评2022大数据“星河”标杆、优秀案例