百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Go语言进阶之路(九):时间处理和格式化输出

bigegpt 2024-10-03 14:50 19 浏览

数据库的日期和时间

日期和时间是任何语言都必须处理好的事情。我们知道,在MySQL中,日期部分有date类型,时间部分有time类型,日期和时间都包含的类型有datetime类型(时间戳有timestamp类型)。比如,“2020-05-18 08:53:34.083”的日期部分就是“2020-05-18”,时间部分就是“08:53:34.083”。

语言对时间的诉求

在任何语言的使用过程中,我们无非是以下几点诉求:

  • 时间能按照我们想要的格式化输出
  • 时间能从字符串被解析成语言的内置时间类型
  • 时间能加减、互相比较
  • 能轻松计算一段代码的运行时间
  • 能与数据库时间轻松地相互转换
  • 支持时区设置,能轻松转换成某一地区的时间

Go语言中的时间能满足这上面的所有需求。

Go语言内置时间库

Go语言中的时间类型在内置的time库中,为Time类型。看一下Time的定义,wall是秒数,ext是纳秒,loc表示时区。这几个属性是私有属性,我们不必太关注。

type Time struct {
    wall uint64
    ext  int64
    loc *Location
}

新建Time实例可以使用time.Parse和time.ParseInLocation方法,或者使用time.Now()获取当前机器的时间。


还有Duration类型,表示两个时间之间的间隔,精确到纳秒,最大能表示大约290年的时间间隔。

type Duration int64

新建Duration实例可以使用time.ParseDuration方法或者直接获取Duration常量比如time.Second或者time.Minute等。


还有Timer类型,表示计时器。Timer中含有通道C,如果计时器不是使用AfterFunc方法创建的,则,当计时器到时间了,当前时间就会被传到通道C中;如果计时器是AfterFunc创建的,则计时器到时间了,会调用AfterFunc传入的f那个函数。

// The Timer type represents a single event.
// When the Timer expires, the current time will be sent on C,
// unless the Timer was created by AfterFunc.
// A Timer must be created with NewTimer or AfterFunc.
type Timer struct {
  C <-chan Time
  r runtimeTimer
}

上面提到语言对时间的诉求,我们来看Go语言如何满足我们的诉求。

第一条:时间能按照我们想要的格式化输出

在Java里,格式化输出都是使用“yyyy-MM-dd HH:mm:ss”,但是得知道这格式化字符串里面什么时候大写什么时候小写。Go语言可以很方便的按照我们的要求格式化输出日期,但是需要传入一个很奇怪的格式化字符串“2006-01-02 15:04:05”。看一下格式化当前时间:

curTime := time.Now()  // 获取当前时间
fmt.Println(curTime)  // 2020-05-19 10:32:07.1855433 +0800 CST m=+0.005985301
fmt.Println(curTime.Format("2006-01-02 15:04:05.000"))  // 2020-05-19 10:32:07.185
fmt.Println(curTime.Unix())  // 获取时间戳,精确到秒

网上有人说这个时间是Go语言诞生时间,其实不是,这只是方便Go语言处理罢了。不过,这很好记忆,“2006-01-02 15:04:05”我们记作“六一二三四五”就好了。time.Unix()方法获取时间戳,精确到秒。

第二条:时间能从字符串被解析成语言的内置时间类型

在Java里,同样以格式化字符串“yyyy-MM-dd HH:mm:ss”可以把字符串表示的时间解析成java.util.Date类型。在Go语言里同理,还是使用“2006-01-02 15:04:05”把时间解析成Go内置的Time类型:

t, _ := time.Parse("2006-01-02 15:04:05.000", "2020-05-19 10:32:07.185")  // 将会使用默认时区+0000进行解析
fmt.Printf("%T", t)  // time.Time

%T表示输出变量的数据类型。可以看到,“2020-05-19 10:32:07.185”成功的被解析成了time.Time类型。

第三条:时间能加减、互相比较

时间互相比较,After和Before可以比较“时间先后”,Equal比较“相等”:

t1, _ := time.ParseInLocation("2006-01-02 15:04:05.000", "2020-05-19 10:32:07.185", time.Local)
t2 := time.Now()
fmt.Printf("t1:%v\n", t1)  // t1:2020-05-19 10:32:07.185 +0800 CST
fmt.Printf("t2:%v\n", t2)  // t2:2020-05-19 12:07:34.9199443 +0800 CST m=+0.027924401
fmt.Println("t1 before t2: ", t1.Before(t2))  // t1 before t2:  true
fmt.Println("t1 after t2: ", t1.After(t2))  // t1 after t2:  false
fmt.Println("t1 equal t2: ", t1.Equal(t2))  // t1 equal t2:  false

时间加减,在时间Time实例上使用Add传入时间间隔Duration,返回新的时间Time实例。从本文最开始处我们知道,Duration是int64类型,因此可以直接在Duration使用负号表示往前的时间间隔:

t1, _ := time.ParseInLocation("2006-01-02 15:04:05.000", "2020-05-19 10:32:07.185", time.Local)
fmt.Println(t1)  // 2020-05-19 10:32:07.185 +0800 CST

oneDay, _ := time.ParseDuration("24h")
t3 := t1.Add(oneDay)  // 往后24小时
t4 := t1.AddDate(0, 0, 1)  // 往后1天
fmt.Println(t3)  // 2020-05-20 10:32:07.185 +0800 CST
fmt.Println(t4)  // 2020-05-20 10:32:07.185 +0800 CST

t5 := t1.Add(-oneDay)  // 往前1天
fmt.Println(t5)  // 2020-05-18 10:32:07.185 +0800 CST
d := t1.Sub(t5)
fmt.Println(d)  // 24h0m0s

如上面这个例子,还可以使用AddDate更方便的加减年月日的时间。另外,两个Time实例使用Sub相减能得到代表着这两个时间差的Duration实例,见上面Sub的使用案例。

第四条:能轻松计算一段代码的运行时间

在Java里,我们要计算代码运行时间,一般会在开始处使用System.currentTimeMillis()获取当前时间,在结尾处再次获取,两个时间相减得到中间这段代码的运行时间。在Go语言中有更方便的处理方式:

startTime := time.Now()
for i:=0;i<200000;i++ {
  fmt.Println(i)
}
duration := time.Since(startTime)
fmt.Println(duration)  // 输出:554.5179ms

time.Since传入一个起始时间作为参数,则会返回当前时间和这个起始时间的间隔Duration实例。上面这个例子中循环运行了554.5179毫秒。

第五条:能与数据库时间轻松地相互转换

Go语言连接MySQL有比较著名的开源库go-sql-driver,支持MySQL, MariaDB, Google CloudSQL等等数据库,Github就可以搜到。在Go语言中,MySQL中date和datetime类型的默认输出值是[]byte,这是为了方便自己的代码将时间值转换为字节切片、字符串或者sql.RawBytes类型进行处理。要想让date和datetime类型直接转换为Go语言的time.Time类型,只需要在连接字符串上加上parseTime=true即可。

db, err := sql.Open("mysql", "user:password@/dbname?charset=utf8mb4&parseTime=true")

我们还可以在连接字符串上指定loc或者time_zone来指定时区。

第六条:支持时区设置,能轻松转换成某一地区的时间

上一条提到了,可以在连接字符串上指定时区,来决定时间值存入数据库的时区。在Go语言中,Time类型自带时区属性,可以很方便的切换时区输出。Go语言默认时区是UTC时区,time.Parse方法解析不带时区的时间会自动使用UTC时区。北京时间是东八区时间,在时间字符串可以使用CST或者+0800来表示东八区时间。看下面的例子:

timeUTC, _ := time.Parse("2006-01-02 15:04:05.000", "2020-05-19 10:32:07.185")
fmt.Println(timeUTC)  // 2020-05-19 10:32:07.185 +0000 UTC

timeCST1, _ := time.Parse("2006-01-02 15:04:05.000 MST", "2020-05-19 10:32:07.185 CST")
fmt.Println(timeCST1)  // 2020-05-19 10:32:07.185 +0800 CST

timeCST2, _ := time.Parse("2006-01-02 15:04:05.000 -0700", "2020-05-19 10:32:07.185 +0800")
fmt.Println(timeCST2)  // 2020-05-19 10:32:07.185 +0800 CST
fmt.Println(timeCST1 == timeCST2)  // true

Go语言切换时区可以使用time.In方法,首先获取一个Location实例,然后转换即可:

timeCST, _ := time.Parse("2006-01-02 15:04:05.000 MST", "2020-05-19 10:32:07.185 CST")
fmt.Println(timeCST)  // 2020-05-19 10:32:07.185 +0800 CST

location, _ := time.LoadLocation("America/New_York")
newYorkTime1 := timeCST.In(location)
fmt.Println(newYorkTime1)  // 2020-05-18 22:32:07.185 -0400 EDT

location2 := time.FixedZone("EDT", -4*3600)
newYorkTime2 := timeCST.In(location2)
fmt.Println(newYorkTime2)  // 2020-05-18 22:32:07.185 -0400 EDT

我们可以使用time.LoadLocation和time.FixedZone来获取Location实例。LoadLocation是通过IANA标准的时区字符串来获取时区。FixedZone是通过相对UTC时间的偏移量来获取时间的,第一个参数是时区字符串,第二个参数是相对UTC偏移的秒数。

其他黑魔法:超时控制

Go语言time提供了两个很重要的方法来实现超时控制:time.After和time.AfterFunc。

time.After传入一个Duration实例参数,返回一个单向的时间通道,该方法不会阻塞。在过了Duration时间后,那个时刻的时间将会传入通道中返回(通道是无缓冲通道)。这样我们可以很方便的控制我们自己的代码是否需要阻塞等待或者在Duration时间内做其他的事情。

import (
  "time"
)

func main () {
  ch := time.After(time.Second*10)
  <-ch // 此处阻塞10秒,10秒后程序结束
}

time.AfterFunc传入Duration和一个函数作为参数,同样不会阻塞当前代码。在Duration时间之后,将会执行我们传入的函数。

import (
  "fmt"
  "time"
)

func main() {
  time.AfterFunc(time.Second*10, func() {
    fmt.Println("10 seconds later.")
  })

  time.Sleep(time.Second * 15)
}

程序将在10秒后输出“10 seconds later.”,15秒后程序退出。

time.After和time.AfterFunc其实都是使用Timer来实现的。我们也可以直接使用Timer来实现:

import (
  "fmt"
  "time"
)

func main() {
  timer := time.NewTimer(time.Second * 10)
  <- timer.C
  fmt.Println("10 seconds later.")
}

同样在10秒后输出“10 seconds later.”。

相关推荐

得物可观测平台架构升级:基于GreptimeDB的全新监控体系实践

一、摘要在前端可观测分析场景中,需要实时观测并处理多地、多环境的运行情况,以保障Web应用和移动端的可用性与性能。传统方案往往依赖代理Agent→消息队列→流计算引擎→OLAP存储...

warm-flow新春版:网关直连和流程图重构

本期主要解决了网关直连和流程图重构,可以自此之后可支持各种复杂的网关混合、多网关直连使用。-新增Ruoyi-Vue-Plus优秀开源集成案例更新日志[feat]导入、导出和保存等新增json格式支持...

扣子空间体验报告

在数字化时代,智能工具的应用正不断拓展到我们工作和生活的各个角落。从任务规划到项目执行,再到任务管理,作者深入探讨了这款工具在不同场景下的表现和潜力。通过具体的应用实例,文章展示了扣子空间如何帮助用户...

spider-flow:开源的可视化方式定义爬虫方案

spider-flow简介spider-flow是一个爬虫平台,以可视化推拽方式定义爬取流程,无需代码即可实现一个爬虫服务。spider-flow特性支持css选择器、正则提取支持JSON/XML格式...

solon-flow 你好世界!

solon-flow是一个基础级的流处理引擎(可用于业务规则、决策处理、计算编排、流程审批等......)。提供有“开放式”驱动定制支持,像jdbc有mysql或pgsql等驱动,可...

新一代开源爬虫平台:SpiderFlow

SpiderFlow:新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫。-精选真开源,释放新价值。概览Spider-Flow是一个开源的、面向所有用户的Web端爬虫构建平台,它使用Ja...

通过 SQL 训练机器学习模型的引擎

关注薪资待遇的同学应该知道,机器学习相关的岗位工资普遍偏高啊。同时随着各种通用机器学习框架的出现,机器学习的门槛也在逐渐降低,训练一个简单的机器学习模型变得不那么难。但是不得不承认对于一些数据相关的工...

鼠须管输入法rime for Mac

鼠须管输入法forMac是一款十分新颖的跨平台输入法软件,全名是中州韵输入法引擎,鼠须管输入法mac版不仅仅是一个输入法,而是一个输入法算法框架。Rime的基础架构十分精良,一套算法支持了拼音、...

Go语言 1.20 版本正式发布:新版详细介绍

Go1.20简介最新的Go版本1.20在Go1.19发布六个月后发布。它的大部分更改都在工具链、运行时和库的实现中。一如既往,该版本保持了Go1的兼容性承诺。我们期望几乎所...

iOS 10平台SpriteKit新特性之Tile Maps(上)

简介苹果公司在WWDC2016大会上向人们展示了一大批新的好东西。其中之一就是SpriteKitTileEditor。这款工具易于上手,而且看起来速度特别快。在本教程中,你将了解关于TileE...

程序员简历例句—范例Java、Python、C++模板

个人简介通用简介:有良好的代码风格,通过添加注释提高代码可读性,注重代码质量,研读过XXX,XXX等多个开源项目源码从而学习增强代码的健壮性与扩展性。具备良好的代码编程习惯及文档编写能力,参与多个高...

Telerik UI for iOS Q3 2015正式发布

近日,TelerikUIforiOS正式发布了Q32015。新版本新增对XCode7、Swift2.0和iOS9的支持,同时还新增了对数轴、不连续的日期时间轴等;改进TKDataPoin...

ios使用ijkplayer+nginx进行视频直播

上两节,我们讲到使用nginx和ngixn的rtmp模块搭建直播的服务器,接着我们讲解了在Android使用ijkplayer来作为我们的视频直播播放器,整个过程中,需要注意的就是ijlplayer编...

IOS技术分享|iOS快速生成开发文档(一)

前言对于开发人员而言,文档的作用不言而喻。文档不仅可以提高软件开发效率,还能便于以后的软件开发、使用和维护。本文主要讲述Objective-C快速生成开发文档工具appledoc。简介apple...

macOS下配置VS Code C++开发环境

本文介绍在苹果macOS操作系统下,配置VisualStudioCode的C/C++开发环境的过程,本环境使用Clang/LLVM编译器和调试器。一、前置条件本文默认前置条件是,您的开发设备已...