zoukankan      html  css  js  c++  java
  • 深入理解Go时间设计(time.Time)

    前言

    时间包括时间值和时区, 没有包含时区信息的时间是不完整的、有歧义的. 和外界传递或解析时间数据时, 应当像HTTP协议或unix-timestamp那样, 使用没有时区歧义的格式, 如果使用某些没有包含时区的非标准的时间表示格式(如yyyy-mm-dd HH:MM:SS), 是有隐患的, 因为解析时会使用场景的默认设置, 如系统时区, 数据库默认时区可能引发事故. 确保服务器系统、数据库、应用程序使用统一的时区, 如果因为一些历史原因, 应用程序各自保持着不同时区, 那么编程时要小心检查代码, 知道时间数据在使用不同时区的程序之间交换时的行为。

    Time 结构

    go1.9之前

    time.Time的定义为

    type Time struct {
    	// sec gives the number of seconds elapsed since
    	// January 1, year 1 00:00:00 UTC.
    	sec int64
    	// nsec specifies a non-negative nanosecond
    	// offset within the second named by Seconds.
    	// It must be in the range [0, 999999999].
    	nsec int32
    	// loc specifies the Location that should be used to
    	// determine the minute, hour, month, day, and year
    	// that correspond to this Time.
    	// The nil location means UTC.
    	// All UTC times are represented with loc==nil, never loc==&utcLoc.
    	loc *Location
    }
    

    go1.9之后

    time.Time的定义为

    type Time struct {
    	// wall and ext encode the wall time seconds, wall time nanoseconds,
    	// and optional monotonic clock reading in nanoseconds.
    	//
    	// From high to low bit position, wall encodes a 1-bit flag (hasMonotonic),
    	// a 33-bit seconds field, and a 30-bit wall time nanoseconds field.
    	// The nanoseconds field is in the range [0, 999999999].
    	// If the hasMonotonic bit is 0, then the 33-bit field must be zero
    	// and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext.
    	// If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit
    	// unsigned wall seconds since Jan 1 year 1885, and ext holds a
    	// signed 64-bit monotonic clock reading, nanoseconds since process start.
    	wall uint64
    	ext  int64
    
    	// loc specifies the Location that should be used to
    	// determine the minute, hour, month, day, and year
    	// that correspond to this Time.
    	// The nil location means UTC.
    	// All UTC times are represented with loc==nil, never loc==&utcLoc.
    	loc *Location
    }
    

    变量释义

    两者只是命名上区别,Time.sec=Time.wallTime.nesc=Time.ext,下面说明以 go1.9之后变量名为准。

    时间点关系说明

    一个 Time变量表示的是一个标准的 Unix 时间点以及时区信息,Time.wallTime.ext处理没有歧义的时间值, Time.loc处理代表的时区相对于UTC 时间的偏移(其只代表时区,实际偏移量处理由方法计算)。由于 Time.walltype 为 uint64,所以 Time 不能表示 A点以前的时间点(即公元前)。

    以图为例,现有一个表示时间点 D 的 Time 变量,它 的Time.wall表示从公元1年1月1日00:00:00UTC(点 A)点 D的整数秒数, Time.ext表示余下的纳秒数, Time.loc表示时区。

    时间戳

    // /usr/local/go/src/time/time.go:1127
    func (t Time) Unix() int64 {
    	return t.unixSec()
    }
    // /usr/local/go/src/time/time.go:176
    // unixSec returns the time's seconds since Jan 1 1970 (Unix time).
    func (t *Time) unixSec() int64 { return t.sec() + internalToUnix }
    
    
    const (
    	// The unsigned zero year for internal calculations.
    	// Must be 1 mod 400, and times before it will not compute correctly,
    	// but otherwise can be changed at will.
    	absoluteZeroYear = -292277022399
    
    	// The year of the zero Time.
    	// Assumed by the unixToInternal computation below.
    	internalYear = 1
    
    	// Offsets to convert between internal and absolute or Unix times.
    	absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay
    	internalToAbsolute       = -absoluteToInternal
    
    	unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay
      // /usr/local/go/src/time/time.go:418
    	internalToUnix int64 = -unixToInternal
    
    	wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay
    	internalToWall int64 = -wallToInternal
    )
    

    通过Time.Unix()方法可以获取到代表某个时间点的 Time 变量的时间戳(单位:秒),值得注意的是,时间戳是从 1970 年 1 月 1 日(时间点 C) 到 Time 代表时间点的时间差。

    以图为例,D 点时间戳计算过程是

    [D.Unix()=D.wall-unixToInternal ]

    [unixToInternal=C.wall-A.wall ]

    显然点 D 与点 C 的差为正值,而点 B 与点 C 的差是负值。所以此方法返回值 type 是 int64。(1970 年之前的时间戳是负值)

    与时间戳有关的 Time 行为

    func TimeFeature() {
    	zeroTime := time.Time{}
    	fmt.Println("############## zeroTime ################")
    	fmt.Println("是否是零值:", zeroTime.IsZero())
    	fmt.Println("时间戳:", zeroTime.Unix())
    	fmt.Println("格式化输出", zeroTime.String())
    	fmt.Println()
    
    	time1970,_:=time.Parse("2006-01-02","1970-01-01")
    	fmt.Println("############## time1970 ################")
    	fmt.Println("是否是零值:", time1970.IsZero())
    	fmt.Println("时间戳:", time1970.Unix())
    	fmt.Println("格式化输出", time1970.String())
    	fmt.Println()
    
    	timeAfter1970,_:=time.Parse("2006-01-02","2020-10-22")
    	fmt.Println("############## timeAfter1970 ################")
    	fmt.Println("是否是零值:", timeAfter1970.IsZero())
    	fmt.Println("时间戳:", timeAfter1970.Unix())
    	fmt.Println("格式化输出", timeAfter1970.String())
    	fmt.Println()
    
    
    	timeBefore1970,_:=time.Parse("2006-01-02","1930-10-22")
    	fmt.Println("############## timeBefore1970 ################")
    	fmt.Println("是否是零值:", timeBefore1970.IsZero())
    	fmt.Println("时间戳:", timeBefore1970.Unix())
    	fmt.Println("格式化输出", timeBefore1970.String())
    	fmt.Println()
    }
    

    输出:

    === RUN   TestTimeFeature
    ############## zeroTime ################
    是否是零值: true
    时间戳: -62135596800
    格式化输出 0001-01-01 00:00:00 +0000 UTC
    
    ############## time1970 ################
    是否是零值: false
    时间戳: 0
    格式化输出 1970-01-01 00:00:00 +0000 UTC
    
    ############## timeAfter1970 ################
    是否是零值: false
    时间戳: 1603324800
    格式化输出 2020-10-22 00:00:00 +0000 UTC
    
    ############## timeBefore1970 ################
    是否是零值: false
    时间戳: -1236902400
    格式化输出 1930-10-22 00:00:00 +0000 UTC
    
    --- PASS: TestTimeNil (0.00s)
    

    时区

    关于时区概念请阅读百度百科

    关于 Unix 时区设置、查看,请参阅UNIX中的时区TZ设置

    func TimeZoneFeature() {
    	timeAfter1970, _ := time.Parse("2006-01-02", "2020-10-22")
    	fmt.Println("############## UTC ################")
    	fmt.Println("格式化输出", timeAfter1970.String())
    	fmt.Println()
    
    	fmt.Println("############## Local(CST) ################")
    	timeAfter1970Local:=timeAfter1970.Local()
    	fmt.Println("格式化输出", timeAfter1970Local.String())
    	fmt.Println()
    
    	fmt.Println("############## Local(CST)(Now) ################")
    	timeNow:=time.Now()
    	fmt.Println("格式化输出", timeNow.String())
    	fmt.Println()
    }
    

    输出:

    === RUN   TestTimeZoneFeature
    ############## UTC ################
    格式化输出 2020-10-22 00:00:00 +0000 UTC
    
    ############## Local(CST) ################
    格式化输出 2020-10-22 08:00:00 +0800 CST
    
    ############## Local(CST)(Now) ################
    格式化输出 2020-10-22 14:24:14.182418 +0800 CST m=+0.000724561
    
    --- PASS: TestTimeZoneFeature (0.00s)
    PASS
    

    现象:

    • 前两者比较可得,同一个时间点,设置了不同的时区(time.Parse()默认 UTC),格式化输出即存在时间差。
    • time.Now()获取到的时间时区是当前机器的时区。

    相关源码:

    // /usr/local/go/src/time/time.go:1066
    // Now returns the current local time.
    func Now() Time {
    	sec, nsec, mono := now()
    	mono -= startNano
    	sec += unixToInternal - minWall
    	if uint64(sec)>>33 != 0 {
        // Local 为获取到的代码运行机器设置的时区
    		return Time{uint64(nsec), sec + minWall, Local}
    	}
    	return Time{hasMonotonic | uint64(sec)<<nsecShift | uint64(nsec), mono, Local}
    }
    

    时区变量 loc 主要在 Time 与字符串的相互转化中起作用,对应方法有 time.Time.Format()time.Parse()

    参考文章

    深入理解Go时间处理(time.Time)

    百度百科

    UNIX中的时区TZ设置

    生老病死过于平淡,唯有求知聊以慰藉。
  • 相关阅读:
    swift 第十四课 可视化view: @IBDesignable 、@IBInspectable
    swift 第十三课 GCD 的介绍和使用
    swift 第十二课 as 的使用方法
    swift 第十一课 结构体定义model类
    swift 第十课 cocopod 网络请求 Alamofire
    swift 第九课 用tableview 做一个下拉菜单Menu
    swift 第八课 CollectView的 添加 footerView 、headerView
    swift 第七课 xib 约束的优先级
    swift 第六课 scrollview xib 的使用
    swift 第五课 定义model类 和 导航栏隐藏返回标题
  • 原文地址:https://www.cnblogs.com/wangbs95/p/13858230.html
Copyright © 2011-2022 走看看