Golang时间处理中容易踩的坑分析解决

目录
  • 简介
  • 类型
  • 时区
  • 小心有坑
  • 时间解析的使用场景
  • 时间操作
    • 获取当前时间
    • 时区设置
    • 时间格式化(时间类型转字符串)
    • 时间类型转时间戳
    • 时间戳转时间类型
    • 时间字符串转时间类型
  • 时间计算
    • 获取时间类型具体内容
    • 时间加减
    • 时间间隔(耗时)
    • 时间取整(向上取整向下取整)
  • 拓展
    • json时间转换

简介

在各个语言之中都有时间类型的处理,因为这个地球是圆的(我仿佛在讲废话),有多个时区,每个时区的时间不一样,在程序中有必要存在一种方式,或者说一种类型存储时间,还可以通过一系列的方法转换成不同国家的时间。

上问提到了时间、时区,还有一个概念为两个时间之间的差值,比如小熊每次可以坚持1个小时(锻炼),1个小时这种时间形容词就是时间间隔。

这就是三种时间处理的类型。

类型

TimeLocationDuration 时间、时区、时间间隔。它们都在time包里面。

Time时间类型

程序中应使用 Time 类型值来保存和传递时间,一个结构体,精确到纳秒。里面的变量都是私有的用不到,先不去管他。

type Time struct {
    sec int64 //秒
    nsec int32 //纳秒
    loc *Location //时区
}
  • 一个Time类型值可以被多个go程同时使用。因为它是 time.Time 类型,而不是 指针*time.Time 类型。
  • 时间需要初始化:IsZero 方法提供了检验时间是否是显式初始化。
  • 时区类型作为Time结构体中的一个字段,标记这个时间当前是哪个时区。

Duration  时间间隔,两个时间之间的差值,以纳秒为单位,最长 290 年,作为常识即可。

type Duration int64

时区

我们在使用time.Time类型一般都是Local时间,也就是本地时间,现在就是中国时间。

// 本地时间(如果是在中国,获取的是东八区时间)
	curLocalTime := time.Now()
	// UTC时间
	curUTCTime := time.Now().UTC()

time 包提供了 Location (也就是时区)的两个实例:LocalUTC

  • Local 代表当前系统本地时区;UTC 代表通用协调时间,也就是零时区。
  • time 包默认(为显示提供时区)使用 Local 时区。
  • 平时使用的都是 Local 时间,数据库存储的时候要注意,一般 orm 框架会自动实现这个。

默认就是Local中国时间!

问题:时区这个怎么设置?传字符串进去吗?

curLocalTime := time.Now() //这是local
curUtcTime := curLocalTime.In(time.UTC) //这是UTC

时区特别容易出错,Time 我们使用都是本地时间,但是!坑来了!

小心有坑

timeStr := "2022-01-13 22:32:17"
    utcTimeObj, err := time.Parse("2006-01-02 15:04:05", timeStr)
    if err == nil {
        fmt.Println(utcTimeObj, utcTimeObj.Unix())
    }

你猜猜会输出什么?返回的竟然是UTC时间2022-01-13 22:32:17 +0000 UTC。这个经常有人出错。解析字符串时,都以协调时UTC时间为准。

还有另一个办法,比较稳。我们应该总是使用 time.ParseInLocation 来解析时间,并给第三个参数传递 time.Local

localTimeObj, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local)
    if err == nil {
        fmt.Println(localTimeObj)
    }

它返回的是time 类型是吗?没错!这两个返回的都是time类型。

问:这个会用在哪个场景?

好问题,问到点子上了!

时间解析的使用场景

前后端传输json数据的时候,或者数据库存储读取的时候。前后端建议使用时间戳传输,不要使用时间字符串可以大大省心。数据库如果使用orm的框架,一般是会自动处理时间存储。

我们约定好用时间戳传递,总是有一些比较轴的同事一定要用字符串传输,你有没有这样的同事?如果非要使用字符串传输,在传递json的时候就需要反复的做解析相当的不友善。

但也不是不能做~~

大家了解过json解析和反解析有哪两个方法吗?有没有人重写过 UnmarshalJSONMarshalJSON。我们来复习一下。

书里面的提到在不同办法的接口,有可能json字段的类型会发生改变,一般做兼容性处理的时候会重写到。

看这个截图,字符串转换成结构体,反过来结构体转换成字符串,就是用MarshalJSON

type Person struct {
    Id       int64  `json:"id"`
    Name     string `json:"name"`
    Birthday Time   `json:"_"`
}

比如一个结构体,里面有一个时间类型,你的前端同事又不传时间戳,你就得手动转换成时间类型,或者时间戳,这个你自己决定。这里是 Birthday 举例,我的注解里面用的json是一个下划线,在解析的时候就不会写入。

问:这个不写入, 是 json库实现的,还是自己实现的?

json库。json库读取注解,匹配json中的字段名称,写入到结构体中。我的注解里写成了下划线,这只是一个占位符,习惯上这么写。你也可以写成-中杠线。

我先写了一个People的反解析函数,json.UnmarshalJSON会尝试调用。看截图

  • 先解析到匿名结构体变量中,birthday字段赋值给了s.Brithday,其他字段给了s.tmp
  • s.Birthday是一个字符串类型,再把这个类型转换成时间类型。
  • localtime 放到 tmp 里面,tmp 就是之前的 people

所以返回的就是tmp, 才是我们要的。

*p = People(s.tmp)

最后再创建一个People,把tmp传递过去。

【思考题】为什么这里还要创建一个,直接赋值s.tmp*p可以不?(这里我给你们挖了一个坑)。

我定义的是新类型,并不是创建,实际上是一个强制类型转换。哈哈哈,我就是蔫坏。

关于时间处理的各种函数我也列在下面了,大家收藏看就行了。还是刚刚提到的各种完整代码。喜欢这篇文章的话点个在看,么么哒。

时间操作

获取当前时间

import time
func getCurTime() {
	// 本地时间(如果是在中国,获取的是东八区时间)
	curLocalTime := time.Now()
	// UTC时间
	curUTCTime := time.Now().UTC()
	fmt.Println(curLocalTime, curUTCTime)
}

时区设置

不同国家(有时甚至是同一个国家内的不同地区)使用不同的时区。对于要输入和输出时间的程序来说,必须对系统所处的时区加以考虑。Go 语言使用 Location 来表示地区相关的时区,一个 Location 可能表示多个时区。展开讲解 time 包提供了 Location 的两个实例:LocalUTC

  • Local 代表当前系统本地时区;UTC 代表通用协调时间,也就是零时区。
  • time 包默认(为显示提供时区)使用 Local 时区。
  • 平时使用的都是Local 时间,数据库存储的时候要注意,一般orm 框架会自动实现这个。
func setTimeZone() {
	curLocalTime := time.Now()
	curUtcTime := curLocalTime.In(time.UTC)
	fmt.Println(curUtcTime)
}

通常,我们使用 time.Local 即可,偶尔可能会需要使用 UTC。在解析时间时,心中一定记得有时区这么回事。当你发现时间出现莫名的情况时,很可能是因为时区的问题,特别是当时间相差 8 小时时。

时间格式化(时间类型转字符串)

func time2TimeStr() {
	localTimeStr := time.Now().Format("2006-01-02 15:04:05")
	// UTC时间
	utcTimeStr := time.Now().UTC().Format("2006-01-02 15:04:05")
	fmt.Println(localTimeStr, utcTimeStr)
}

时间类型转时间戳

func getCurTimeStamp() {
	// 时间戳,精确到秒
	timestamp := time.Now().Unix()
	// 时间戳,精确到纳秒
	timestampNano := time.Now().UnixNano()
	fmt.Println(timestamp, timestampNano)
}

相关函数或方法:

  • time.Unix(sec, nsec int64) 通过 Unix 时间戳生成 time.Time 实例;
  • time.Time.Unix() 得到 Unix 时间戳;
  • time.Time.UnixNano() 得到 Unix 时间戳的纳秒表示;

时间戳转时间类型

func timestamp2Time() {
	timestamp := time.Now().Unix()
	localTimeObj := time.Unix(timestamp, 0)
	fmt.Println(localTimeObj)
}

时间字符串转时间类型

func timeStr2Time() {
	timeStr := "2020-01-13 22:32:17"
	// 返回的是UTC时间 2020-01-13 22:32:17 +0000 UTC
	utcTimeObj, err := time.Parse("2006-01-02 15:04:05", timeStr)
	if err == nil {
		fmt.Println(utcTimeObj, utcTimeObj.Unix())
	}
	// 返回的是当地时间 2020-01-13 22:32:17 +0800 CST
	localTimeObj, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local)
	if err == nil {
		fmt.Println(localTimeObj)
	}
}

time.Parse 解析出来的时区却是 time.UTC(可以通过 Time.Location() 函数知道是哪个时区)。在中国,它们相差 8 小时。 所以,一般的,我们应该总是使用 time.ParseInLocation 来解析时间,并给第三个参数传递 time.Local。

时间计算

获取时间类型具体内容

t := time.Now()
fmt.Println("time.Now():", t) // 2020-10-24 22:10:53.328973 +0800 CST m=+0.006015101
year, month, day := t.Date()
fmt.Println("日期:", year, month, day) // 2020 October 24
fmt.Println("一年中的第几天:", t.YearDay()) // 298
fmt.Println("星期几:", t.Weekday()) // Saturday
fmt.Println("年:", t.Year()) // 2020
fmt.Println("月:", t.Month()) // October
fmt.Println("日:", t.Day()) // 24
fmt.Println("时:", t.Hour()) // 22
fmt.Println("分:", t.Minute()) // 10
fmt.Println("秒:", t.Second()) // 53
fmt.Println("纳秒:", t.Nanosecond()) // 328973000
fmt.Println("秒时间戳:", t.Unix()) // 1603548653
fmt.Println("纳秒时间戳:", t.UnixNano()) // 1603548653328973000
fmt.Println("毫秒时间戳:", t.UnixNano() / 1e6) // 1603548653328

时间加减

转换为Time类型比较容易做加减。

  • 时间点可以使用 Before、After 和 Equal 方法进行比较。
  • Sub 方法让两个时间点相减,生成一个 Duration 类型值(代表时间段)。
  • Add 方法给一个时间点加上一个时间段,生成一个新的 Time 类型时间点。
func addTime() {
	curTime := time.Now()
	// 加1秒
	addSecondTime := curTime.Add(time.Second * 1)
	// 加1分钟
	addMinuteTime := curTime.Add(time.Minute * 1)
	addMinuteTime2 := curTime.Add(time.Second * time.Duration(60*1))
	fmt.Println(addSecondTime, addMinuteTime, addMinuteTime2)
}

时间类型中有提前定义固定的时间长度常量,比如一小时的长度就是time.Hour

t := time.Now()
addOneHour := t.Add(time.Hour)
addTwoHour := t.Add(2 * time.Hour)
fmt.Println("增加1小时:", addOneHour)
fmt.Println("增加2小时:", addTwoHour)
subTwoHour := t.Add(-2 * time.Hour)
fmt.Println("减去2小时:", subTwoHour)
addDate := t.AddDate(1, 0, 0)
fmt.Println("增加1年:", addDate) // 2021-10-24 22:10:53.328973 +0800 CST
subDate := t.AddDate(-1, 0, 0)
fmt.Println("减去1年:", subDate) // 2019-10-24 22:10:53.328973 +0800 CST
before := t.Before(t.Add(time.Hour))
fmt.Println("before:", before)
after := t.After(t.Add(time.Hour))
fmt.Println("after:", after)

时间间隔(耗时)

t := time.Now()
time.Sleep(2e9) // 休眠2秒
delta := time.Now().Sub(t)
fmt.Println("时间差:", delta) // 2.0534341s

时间取整(向上取整向下取整)

t, _ := time.ParseInLocation("2006-01-02 15:04:05", "2016-06-13 15:34:39", time.Local)
// 整点(向下取整)
fmt.Println(t.Truncate(1 * time.Hour))
// 整点(最接近)
fmt.Println(t.Round(1 * time.Hour))
// 整分(向下取整)
fmt.Println(t.Truncate(1 * time.Minute))
// 整分(最接近)
fmt.Println(t.Round(1 * time.Minute))
t2, _ := time.ParseInLocation("2006-01-02 15:04:05", t.Format("2006-01-02 15:00:00"), time.Local)
fmt.Println(t2)

拓展

json时间转换

前后端建议使用时间戳传输,不要使用时间字符串可以大大省心,如果非要使用字符串传输,在传递json的时候就需要反复的做解析相当的不友善,但也不是不能做。 方式一、省心方式,重定义时间类型

type Time time.Time
const (
    timeFormart = "2006-01-02 15:04:05"
)
func (t *Time) UnmarshalJSON(data []byte) (err error) {
    now, err := time.ParseInLocation(`"`+timeFormart+`"`, string(data), time.Local)
    *t = Time(now)
    return
}
func (t Time) MarshalJSON() ([]byte, error) {
    b := make([]byte, 0, len(timeFormart)+2)
    b = append(b, '"')
    b = time.Time(t).AppendFormat(b, timeFormart)
    b = append(b, '"')
    return b, nil
}
func (t Time) String() string {
    return time.Time(t).Format(timeFormart)
}
type Person struct {
    Id       int64  `json:"id"`
    Name     string `json:"name"`
    Birthday Time   `json:"birthday"`
}
  • 这种时间重定义了时间类型time.TimeTime类型,所以在结构体使用的时候要注意不要用错,结构体直接调用json的解析反解析方法就可以,传入字符串类型,解析为时间类型。

方式二、重写结构体方法

type Person struct {
    Id       int64  `json:"id"`
    Name     string `json:"name"`
    Birthday Time   `json:"_"`
}
func (p *People) UnmarshalJSON(b []byte) error {
	// 定义临时类型 用来接受非`json:"_"`的字段
	type tmp People
	// 用中间变量接收json串,tmp以外的字段用来接受`json:"_"`属性字段
	var s = &struct {
		tmp
		// string 先接收字符串类型,一会再转换
		Birthday string `json:"birthday"`
	}{}
	// 解析
	err := json.Unmarshal(b, &s)
	if err != nil {
		return err
	}
    localTimeObj, err := time.ParseInLocation("2006-01-02 15:04:05", s.Birthday, time.Local)
	if err == nil {
		return err
	}
	s.tmp.Birthday = localTimeObj
	// tmp类型转换回People,并赋值
	*p = People(s.tmp)
	return nil
}

作业

  • 尝试写出时间戳转字符串的代码
  • 尝试求上个月最后一天

以上就是Golang时间处理中容易踩的坑分析解决的详细内容,更多关于Golang时间处理踩坑解决的资料请关注我们其它相关文章!

(0)

相关推荐

  • golang实现简单的tcp数据传输

    目录 前言 首先介绍什么是Tcp协议 什么是可靠数据传输? TCP的快速重传机制 简单介绍TCP连接的三次握手和四次挥手 三次握手 四次挥手 golang实现简单的tcp连接建立 服务端 完整代码 客户端 完整代码 前言 通过golang实现Tcp的连接与信息传输 本文主要介绍Tcp协议以及如何使用golang来建立一个简单的tcp连接服务,并且实现信息的传输. 首先介绍什么是Tcp协议 Tcp协议是传输层的一个可靠数据传输协议,Tcp协议有以下几个特点: 点对点的发送:一个发送方,一个接收方

  • 详解如何使用Bazel构建Golang程序

    目录 使用Bazel构建Golang程序 从头开始一个 Golang 项目 将现有项目转换为 Bazel 构建 密封测试(Hermetic tests) 使用Bazel构建Golang程序 在这篇简短的文章中,我们将介绍如何将 Golang 与 Bazel 构建系统结合使用. 具体来说,我们将讨论三个场景: 从头开始一个 Golang 项目: 将一个现有的 Golang 项目转换为 Bazel 构建: 以及将一个第三方 Golang 项目引入到您的 Bazel 构建系统. 从头开始一个 Gola

  • 详解Golang如何实现节假日不打扰用户

    目录 1.场景 2.思路 3.接口分析 4.golang实现 4.1.json2go小工具 4.2.代码实现 1.场景 想象下以下场景,嘿嘿...! 一个iphone用户,闹钟是可以按节假日不响的! 每日新闻机器人,节假日是可以不打扰我的! 我的业务,节假日是可以... 2.思路 要实现识别节假日,大概有两种方式: 1.自己收集国家法定节假日数据,离线存储 优势:离线简单 劣势:新一年要去更新,容易忘记,麻烦 2.调用第三方接口数据 优势:不需要我们操心数据本身 劣势:有次数限制 本次介绍调用第

  • Golang Copier入门到入坑探究

    目录 正文 安装 快速入门 入坑 再探坑出坑 再盘一盘坑 结语 正文 github: https://github.com/jinzhu/copier 由于 golang 没有对复杂结构体的 clone 方法,所以,就需要有 copier 这样的工具库. 它看起来很简单,但实际使用中,有些“坑”还是要注意! 本文: 入门为辅,探“坑”为主, 看完再划走,CS我没有. 安装 go get github.com/jinzhu/copier 快速入门 好的,来一段代码快速了解 copier packa

  • Golang时间处理中容易踩的坑分析解决

    目录 简介 类型 时区 小心有坑 时间解析的使用场景 时间操作 获取当前时间 时区设置 时间格式化(时间类型转字符串) 时间类型转时间戳 时间戳转时间类型 时间字符串转时间类型 时间计算 获取时间类型具体内容 时间加减 时间间隔(耗时) 时间取整(向上取整向下取整) 拓展 json时间转换 简介 在各个语言之中都有时间类型的处理,因为这个地球是圆的(我仿佛在讲废话),有多个时区,每个时区的时间不一样,在程序中有必要存在一种方式,或者说一种类型存储时间,还可以通过一系列的方法转换成不同国家的时间.

  • 简单聊聊Go for range中容易踩的坑

    目录 前言 1. for+传值 2. for+传址 3.for+闭包 4. for+goroutine 总结 前言 为了让大家更好的理解本期知识点,先介绍以下几个知识点:线性结构.非线性结构.循环.迭代.遍历.递归. 线性结构:数组.队列 非线性结构:树.图 循环(loop):最基础的概念,所有重复的行为都是循环 递归(recursion):在函数内调用自身,将复杂情况逐步转化成基本情况 (数学)迭代(iterate):在多次循环中逐步接近结果 (编程)迭代(iterate):按顺序访问线性结构

  • 解决golang时间字符串转time.Time的坑

    字符串转时间一定要通过 time.ParseInLocation 不能直接用Parse todayZero, _ := time.ParseInLocation("2006-01-02", "2019-01-01 15:22:22" time.Local) 如果用了Parse,二者就会存在8小时时间差!!!! 补充:golang 常用的日期方法和时区的坑 import( "time" ) 1.获取当前时间 time.Now(),返回类型:time

  • springboot2.x引入feign踩的坑及解决

    目录 springboot2.x引入feign踩的坑 一.需求 二.什么是feign 三.springboot1.x中feign的使用 四.springboot2.x中feign的使用 feign调用方式比较 一.事发原因 二.方式1介绍 三.方式2介绍 四.调用结果测试 五.两种方式对比 六.小结一下 springboot2.x引入feign踩的坑 一.需求 最近公司项目需求,需要调用第三方服务的接口,所以选用了feign来实现(这里只说springboot2.x的pom引用,没有怎么使用,网

  • SpringBoot2.x版本中,使用SpringSession踩的坑及解决

    SpringBoot2.x SpringSession踩坑 Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.session.Ses

  • vue中this.$refs的坑及解决

    目录 vue this.$refs的坑 以下三点小坑坑 vue的坑 this.refs为空 总结来说 vue this.$refs的坑 在开发中,有时需要使用到this.$refs ,用于操作真实的DOM节点. 说一说我在使用的时候,在开发时碰到了一个小需求,需要子组件向父组件传参,而且是不需要通过事件传递的,一开始使用this.$emit()来写的,但是一直没有接受到参数,于是放弃了使用this.$emit()的使用. 于是,使用了在父组件中调用子组件的方法,来获取传递的参数.但是还是没有获取

  • webpack 1.x升级过程中的踩坑总结大全

    前言 大家应该都知道,Webpack 是一个前端资源加载/打包工具.它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源.因为最近在对博客做SSR,无奈vue ssr demo或例子都是基于webpack2的.博主在webpack1.x上折腾了许久,vue-ssr-server-bundle.json文件生成仍然还是遥遥无期.最后还是乖乖地开始了webpack升级之旅. 本文主要记录升级过程中遇到的一些坑和解决办法,可能有些遗漏了,能记多少记多少吧.话不多说了,来一

  • 解决springCache配置中踩的坑

    目录 springCache配置中踩的坑 先附上正确的配置 springCache配置及一些问题的解决 配置 @Cacheable参数 @CacheEvict 参数 @CachePut 参数 springCache配置中踩的坑 项目基于SpringBoot,使用了SpringCache. 早先在网上找了一份SpringCache的配置,后来由于需要使用到自定义序列化方法,注入一个自定义的序列化类.但是在后来发现自定义的序列化类始终没有调用,后来查看源码后终于发现了原因 先附上正确的配置 @Bea

  • 浅谈在django中使用filter()(即对QuerySet操作)时踩的坑

    代码伺候: 先看如下代码: 例1: message = Message.objects.filter(pk=message_id2) message[0].id = message_id2 message[0].content = content2 message[0].message_type = message_type2 print(message[0].id) print(message[0].content) message[0].save() 可正常从QuerySet中读取数据,并打

  • Echarts在Taro微信小程序开发中的踩坑记录

    背景 近期笔者在使用Taro进行微信小程序开发,当引入Echarts图表库时,微信检测单包超限2M的一系列优化措施的踩坑记录,期望能指导读者少走一些弯路. 为什么选择Echarts? 微信小程序目录市面上使用最多的两款图表库,如下: echarts-for-weixin--echarts微信小程序版本 wx-charts--基于微信小程序的图表库 对比两款图表库优缺点刚好相反. echarts-for-weixin:功能强大,但体积非常大 wx-charts:功能相对简单,但体积小 由于笔者对e

随机推荐