• 深入理解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设置

    生老病死过于平淡,唯有求知聊以慰藉。
  • 相关阅读:
    Python动态生成Bean类,并且带toString,equals,hashcode方法
    逆向工程核心原理——第十八章
    逆向工程核心原理——第十七章
    逆向工程核心原理——第十六章
    逆向工程核心原理——第十五章
    Creckme_bjanes.1
    CTF_python-trade
    大二下学期阅读笔记(人月神话)
    大二下学期个人作业(三个和尚)
    大二下学期团队项目(收藏转移)
  • 原文地址:https://www.cnblogs.com/wangbs95/p/13858230.html
Copyright © 2020-2023  润新知