As programmers, we often have to deal with time. In Go, the standard library time provides a corresponding capability.

This article will introduce some important functions and methods in the time library, hoping to help those children who need Baidu when they encounter Go time processing problems.

Coping with time zone issues

In programming, we often encounter the eight- hour time difference . This is caused by time zone differences, and in order to better address them, we need to understand several time definition criteria.

GMT (Greenwich Mean Time), Greenwich Mean Time. GMT, which calculates time based on the Earth's rotation and revolution, specifies that the sun passes the Royal Greenwich Observatory in the suburbs of London, England, at 12 noon each day. GMT is the former Universal Standard Time.

UTC (Coordinated Universal Time), Coordinated Universal Time. UTC is more accurate than GMT, and it calculates time based on atomic clocks. In cases where precision to the second is not required, consider UTC=GMT. UTC is the current Universal Time.

From the prime meridian of Greenwich, it is positive to the east and negative to the west. The world is divided into 24 standard time zones, and the difference between adjacent time zones is one hour.

package main

import (
 "fmt"
 "time"
)

func main() {
 fmt.Println(time.Now())
}

Mainland China uses the standard time of the East Eight time zone, namely Beijing time CST, China Standard Time.

$ go run main.go 
2022-07-17 16:37:31.186043 +0800 CST m=+0.000066647

This is the result in the default time zone time.Now()and will be noted in the print +0800 CST.

Assuming we are in the Los Angeles time zone, what is the result?

$ TZ="America/Los_Angeles" go run main.go
2022-07-17 01:39:12.391505 -0700 PDT m=+0.000069514

It can be seen that the result at this time is the -0700 PDTtime, which is PDT (Pacific Daylight Time) Pacific Daylight Time. Due to the time zone difference, the time results of the two executions differ by 15 hours.

Note that when using Docker containers, the default time zone of the system is UTC time (time zone 0), which is eight hours different from the actual Beijing time we need. This is a classic scenario that causes the eight- hour time difference .

For strategies to deal with time zone issues, you can check the loading logic of the initLocal() function in src/time/zoneinfo_unix.go in detail. For example, it can be solved by specifying the environment variable TZ, modifying the /etc/localtime file, etc.

Because the time zone issue is very important, it is described in the first part of the article. Let's start to introduce the use of the time library.

time instant time.Time

The core object of the time library is the time.Time structure. It is defined as follows to represent the time of a certain instant.

type Time struct {
  // wall and ext encode the wall time seconds, wall time nanoseconds,
 // and optional monotonic clock reading in nanoseconds.
   wall uint64
   ext  int64
   loc *Location
}

In terms of time processing, computers mainly involve two kinds of clocks.

  • Wall time, also known as clock time, is used to indicate a specific date and time.

  • Monotonic clocks, which are always guaranteed to move forward, do not have the problem of dialing back the wall clock, so they are well suited for measuring durations.

The wall and ext fields are used to record wall clocks and monotonic clocks with nanosecond precision. The corresponding digits of the field are associated with the specific year, month, day, hour, minute, second and other information used to determine the time.

The loc field records the time zone location. When loc is nil, the default is UTC time.

Because time.Time is used to represent instants of time with nanosecond precision, programs should generally store and pass it as a value, not a pointer.

That is, in time variables or struct fields, we should use time.Time instead of *time.Time.

get time.Time

We can get the current local time through the Now function

func Now() Time {}

You can also use the Date function to obtain the specified time according to the time and time zone parameters such as year, month, day, etc.

func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time {}
Convert Timestamp

In the computer world, January 1, 1970 UTC 0:0:0:0 is used as Unix time 0. The so-called time instant is converted into a Unix timestamp, that is, the number of seconds, microseconds, etc. elapsed from Unix time 0 to the specified instant is calculated.

func (t Time) Unix() int64 {}      // 从 Unix 时间 0 经过的秒数
func (t Time) UnixMicro() int64 {} // 从 Unix 时间 0 经过的微秒数
func (t Time) UnixMilli() int64 {} // 从 Unix 时间 0 经过的毫秒数
func (t Time) UnixNano() int64 {}  // 从 Unix 时间 0 经过的纳秒数
Get basic fields
 t := time.Now()
 fmt.Println(t.Date())      // 2022 July 17
 fmt.Println(t.Year())      // 2022
 fmt.Println(t.Month())     // July
 fmt.Println(t.ISOWeek())   // 2022 28
 fmt.Println(t.Clock())     // 22 21 56
 fmt.Println(t.Day())       // 17
 fmt.Println(t.Weekday())   // Sunday
 fmt.Println(t.Hour())      // 22
 fmt.Println(t.Minute())    // 21
 fmt.Println(t.Second())    // 56
 fmt.Println(t.Nanosecond())// 494313000
 fmt.Println(t.YearDay())   // 198

duration time.Duration

The duration time.Duration is used to represent the elapsed time between two time instants time.Time. It represents nanosecond counts by int64, and the limit that can be represented is about 290 years.

// A Duration represents the elapsed time between two instants
// as an int64 nanosecond count. The representation limits the
// largest representable duration to approximately 290 years.
type Duration int64

In Go, duration is just a number in nanoseconds. If the duration is equal to 1000000000, it means 1 second or 1000 milliseconds or 1000000 microseconds or 1000000000 nanoseconds.

For example, two time instants time.Time values ​​1 hour apart, the duration time.Duration value between them is

1*60*60*1000*1000*1000

These duration constant values ​​are defined in Go's time package

const (
 Nanosecond  Duration = 1
 Microsecond          = 1000 * Nanosecond
 Millisecond          = 1000 * Microsecond
 Second               = 1000 * Millisecond
 Minute               = 60 * Second
 Hour                 = 60 * Minute
)

At the same time, time.Duration provides a method to obtain the value of each time granularity

func (d Duration) Nanoseconds() int64 {}   // 纳秒
func (d Duration) Microseconds() int64 {}  // 微秒
func (d Duration) Milliseconds() int64 {}  // 毫秒
func (d Duration) Seconds() float64 {}     // 秒
func (d Duration) Minutes() float64 {}     // 分钟
func (d Duration) Hours() float64 {}       // 小时

time calculation

After learning about time instants and durations, let's see how to do time calculations.

func (t Time) Add(d Duration) Time {}
  • The Add function is used to increase/decrease (a positive value of d means increase, a negative value means decrease) the duration of time.Time. We can increase or decrease a specified time in nanoseconds or more for an instantaneous time.
func (t Time) Sub(u Time) Duration {}
  • The Sub function can derive the duration between two instants of time.
func (t Time) AddDate(years int, months int, days int) Time {}
  • The AddDate function increments/decrements the value of time.Time based on the dimensions of year, month and day.

Of course, calculations based on the current time instant time.Now() are the most common needs. Therefore, the time package also provides the following convenient time calculation functions.

func Since(t Time) Duration {}

The Since function is a shortcut for time.Now().Sub(t).

func Until(t Time) Duration {}

The Until function is a shortcut for t.Sub(time.Now()).

Example of use
 t := time.Now()
 fmt.Println(t)                      // 2022-07-17 22:41:06.001567 +0800 CST m=+0.000057466

 //时间增加 1小时
 fmt.Println(t.Add(time.Hour * 1))   // 2022-07-17 23:41:06.001567 +0800 CST m=+3600.000057466
 //时间增加 15 分钟
 fmt.Println(t.Add(time.Minute * 15))// 2022-07-17 22:56:06.001567 +0800 CST m=+900.000057466
 //时间增加 10 秒钟
 fmt.Println(t.Add(time.Second * 10))// 2022-07-17 22:41:16.001567 +0800 CST m=+10.000057466

 //时间减少 1 小时
 fmt.Println(t.Add(-time.Hour * 1))  // 2022-07-17 21:41:06.001567 +0800 CST m=-3599.999942534
 //时间减少 15 分钟
 fmt.Println(t.Add(-time.Minute * 15))// 2022-07-17 22:26:06.001567 +0800 CST m=-899.999942534
 //时间减少 10 秒钟
 fmt.Println(t.Add(-time.Second * 10))// 2022-07-17 22:40:56.001567 +0800 CST m=-9.999942534

 time.Sleep(time.Second * 5)
 t2 := time.Now()
 // 计算 t 到 t2 的持续时间
 fmt.Println(t2.Sub(t))              // 5.004318874s
 // 1 年之后的时间
 t3 := t2.AddDate(100)
 // 计算从 t 到当前的持续时间
 fmt.Println(time.Since(t))          // 5.004442316s
 // 计算现在到明年的持续时间
 fmt.Println(time.Until(t3))         // 8759h59m59.999864s

format time

In other languages, a common time template is used to format the time. For example Python, which uses %Y for year, %m for month, %d for day, etc.

However, Go is different, it uses a fixed time (note that it is not possible to use other times) as a layout template, and this fixed time is the birth time of the Go language.

Mon Jan 2 15:04:05 MST 2006

Formatting time involves two conversion functions

func Parse(layout, value string) (Time, error) {}
  • The Parse function is used to convert a time string into a time.Time object according to the layout it can correspond to.
func (t Time) Format(layout string) string {}
  • The Formate function is used to convert a time.Time object to a time string according to the given layout.
Example
const (
   layoutISO = "2006-01-02"
   layoutUS  = "January 2, 2006"
)
date := "2012-08-09"
t, _ := time.Parse(layoutISO, date)
fmt.Println(t)                  // 2012-08-09 00:00:00 +0000 UTC
fmt.Println(t.Format(layoutUS)) // August 9, 2012

In the time library, Go provides some predefined layout template constants, which can be used directly.

const (
 Layout      = "01/02 03:04:05PM '06 -0700" // The reference time, in numerical order.
 ANSIC       = "Mon Jan _2 15:04:05 2006"
 UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
 RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
 RFC822      = "02 Jan 06 15:04 MST"
 RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
 RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
 RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
 RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
 RFC3339     = "2006-01-02T15:04:05Z07:00"
 RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
 Kitchen     = "3:04PM"
 // Handy time stamps.
 Stamp      = "Jan _2 15:04:05"
 StampMilli = "Jan _2 15:04:05.000"
 StampMicro = "Jan _2 15:04:05.000000"
 StampNano  = "Jan _2 15:04:05.000000000"
)

Below is a comparison table of our optional layout parameters

年         06/2006
月         01/1/Jan/January
日         02/2/_2
星期       Mon/Monday
小时       03/3/15
分         04/4
秒         05/5
毫秒       .000/.999
微秒       .000000/.999999
纳秒       .000000000/.999999999
am/pm     PM/pm
时区       MST
时区小时数差-0700/-07/-07:00/Z0700/Z07:00

time zone conversion

At the beginning of the article, we introduced the time zone issue. If in the code, we need to get the results of the same time.Time in different time zones, we can use its In method.

func (t Time) In(loc *Location) Time {}

Its use is very simple, just look at the sample code directly

now := time.Now()
fmt.Println(now)          // 2022-07-18 21:19:59.9636 +0800 CST m=+0.000069242

loc, _ := time.LoadLocation("UTC")
fmt.Println(now.In(loc)) // 2022-07-18 13:19:59.9636 +0000 UTC

loc, _ = time.LoadLocation("Europe/Berlin")
fmt.Println(now.In(loc)) // 2022-07-18 15:19:59.9636 +0200 CEST

loc, _ = time.LoadLocation("America/New_York")
fmt.Println(now.In(loc)) // 2022-07-18 09:19:59.9636 -0400 EDT

loc, _ = time.LoadLocation("Asia/Dubai")
fmt.Println(now.In(loc)) // 2022-07-18 17:19:59.9636 +0400 +04

Summarize

Overall, the time processing functions and methods provided by the time library basically meet our needs.

Interestingly, the Go time format conversion must adopt the birth time of Go, which is indeed narcissistic enough.