Golang时间处理容易踩坑,小心损失百万

news2024/10/7 10:26:07
alt

简介

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

上问提到了时间、时区,还有一个概念为两个时间之间的差值,比如小熊每次可以坚持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类型。

alt

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

好问题,问到点子上了!

时间解析的使用场景

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

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

但也不是不能做~~

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

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

alt

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

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

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

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

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

alt

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

  1. 先解析到匿名结构体变量中, birthday字段赋值给了 s.Brithday,其他字段给了 s.tmp
  2. s.Birthday是一个字符串类型,再把这个类型转换成时间类型。
  3. 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(100)
fmt.Println("增加1年:", addDate) // 2021-10-24 22:10:53.328973 +0800 CST

subDate := t.AddDate(-100)
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([]byte0len(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
}

作业

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

喜欢点个赞吧,求求了!

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/154352.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手写RPC框架第8版-通过SpringBoot-Starter接入SpringBoot

源代码地址:https://github.com/lhj502819/IRpc/tree/v9 系列文章: 注册中心模块实现路由模块实现序列化模块实现过滤器模块实现自定义SPI机制增加框架的扩展性的设计与实现基于线程和队列提升框架并发处理能力框架容错性相关设计通过SpringBoot-Starte…

LayoutInflater学习(二)之创建布局View

这篇是在上篇的基础上继续学习LayoutInflater,上篇主要讲了LayoutInflater是怎么解析布局的,但是并没有去仔细地说明LayoutInflater创建View的过程,这篇就补上这部分。 LayoutInflater创建xml布局View是分开创建的: 1. 先创建xml布局最外层的View,也就是布局的根View 2. 递归…

package.json和package-lock.json的区别

前言 今天正在写代码,同学突然问我,package.json和package-lock.json有什么区别,这两个文件有什么用?我愣住了… 模块化开发 经过这么多年的发展,现在的前端开发基本上都是模块化开发了。而node和npm则可以很方便的…

电网调频及一次调频、二次调频

电网调频的基本概念电力系统运行的主要任务之一是对电网频率进行控制—控制电网频率在50Hz附近的一个允许范围内。电网频率偏离额定值50Hz的原因是能源侧(水电、火电、核电……)的供电功率与负荷侧的用电功率之间的平衡被破坏而引起的。负荷的用电功率是…

Sentinel热点参数限流

何为热点? 何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K数据,并对其访问进行限制。比如: 1)商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制&am…

正确的清理内存方式,才能让你的空间更加充裕

我们的手机用了一段时间后,有没有感觉卡变了,运行速度也很慢?估计是手机内存不足造成的,今天就来教教大家如何快速清理空间。方法一:清除微信缓存文件 我们每天都用微信。 其实它最占内存,我们还需要定时打…

原子化带来化学反应,视频号万粉创作者增加3倍,点赞10w+的爆款内容增长186%

1月10日,以“在场”为主题的2023微信公开课PRO正式开讲。本年度微信公开课重点从视频号内容生态发展、微信生态内各类产品助力实体转型以及数字生活服务升级三个方面,展示微信生态各产品的新能力、新计划。在2022年的微信公开课Pro上,“视频号…

广告业务系统 之 数据中转站 —— “日志中心-实时服务监控”

文章目录广告业务系统 之 数据中转站 —— “日志中心-实时服务监控”日志中心实时服务监控 —— 前链路日志分析日志收敛手段 —— “手术开口”基于 metrics 的日志分析 —— Prometheus & Graphite监控服务是怎么监控自身 & 比常规服务更坚强高扩展、高性能的架构设…

[L1 - 5分合集]心理阴影面积

L1-060 心理阴影面积 分数 5 作者 陈越 单位 浙江大学 题目: 这是一幅心理阴影面积图。我们都以为自己可以匀速前进(图中蓝色直线),而拖延症晚期的我们往往执行的是最后时刻的疯狂赶工(图中的红色折线)。由…

外贸邮件营销的优势

邮件营销相对于其他营销方式,历史更悠久。邮件营销具有成本低廉、快速、精准的特点。那么邮件营销有哪些优势,才能获得如此的关注。接下来,米贸搜和大家分享一下邮件营销的优势。1.节约成本的考虑:当前,世界经济复苏乏力&#xff…

Kafka消息队列使用及原理

消息队列作用:异步、削峰、解耦 1、kafka简介 ​ Apache Kafka 是一个分布式的流平台,有三个关键的功能: 能够发布(写入)和订阅(读取)事件流持续可靠的存储事件流在事件发生时回顾性的处理事件…

IB生物笔记:Structure and function of organelles

国际学校生物老师解读IB生物,感兴趣的同学记得收藏哦~IB生物分为SL(standard level)和HL(higher level)SL有6个topic∶细胞生物,分子生物,遗传学,生态学,物种进化以及多样性和人体生理。HL除了上述6个topic外还要加上∶…

C++模板类

目录 前言 类模板 模板类继承 前言 随着c发展,有一部分代码就会出现这样的情况:实现的内容相同,但是参数不同。模板类就是为解决这类情况来的,是一种泛型编码。即与数据类型无关的通用程序设计技术。 模板类本身不占空间&…

C语言模块化

🌞欢迎来到C语言的世界 🌈博客主页:卿云阁 💌欢迎关注🎉点赞👍收藏⭐️留言📝 🌟本文由卿云阁原创! 🙏作者水平很有限,如果发现错误,…

MySQL的行锁总结

文章目录前言一、行锁的介绍二、行锁的使用三、使用行锁所带来的问题四、死锁和死锁检测前言 上篇文章已经学习了MySQL的全局锁和表锁,今天这篇文章我们对行锁进行以下学习 一、行锁的介绍 行锁就是针对数据表中行记录的锁,比如事务A更新了一行&#x…

切面AOP

1.2 AOP体系与概念 简单地去理解,其实AOP要做三类事: 在哪里切入,也就是权限校验等非业务操作在哪些业务代码中执行。 在什么时候切入,是业务代码执行前还是执行后。 切入后做什么事,比如做权限校验、日志记录等。 因…

ES索引切分

提示:对于一些日志类的数据,我们常用到es作为存储,数据量过大时,可能会用到索引切分,这里可以参考 ES索引切分前言方案一:ES索引切分验证(policy策略)配置ilm策略 (max_d…

照片如何修复清晰度?这些修复方法值得你收藏

我们都知道以前的拍照技术落后,拍摄出来的照片像素都比较低,从而导致照片有些模糊不清,再加上我们保存不当,很多旧照片都变得模糊破损,因此很多人为了不让这些旧照片消失,都会选择找人来修复这些旧照片&…

爬虫 大规模数据 采集心得和示例

本篇主要介绍网站数据很是大的采集心得数据库1. 什么样的数据才能称为数据量大:编程我以为这个可能会由于每一个人的理解不太同样,给出的定义 也不相同。我认为定义一个采集网站的数据大小,不单单要看这个网站包括的数据量的大小,…

MySQL高级【锁】

1:锁的概述锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、 RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有 效性是所有数据库必…