Bryan

Bryan

twitter
medium

Talk about time zones.

Usually, when localizing, there is often a problem of time zone conversion. Usually, before we really pay attention to the time zone, the time zone we "default" to use is UTC or "local".

This article takes Go as an example to analyze the use of time zones in Go.

Reading Time Zone#

In Go, the LoadLocation function is used to read the time zone.

// LoadLocation returns the Location with the given name.
//
// If the name is "" or "UTC", LoadLocation returns UTC.
// If the name is "Local", LoadLocation returns Local.
//
// Otherwise, the name is taken to be a location name corresponding to a file
// in the IANA Time Zone database, such as "America/New_York".
//
// LoadLocation looks for the IANA Time Zone database in the following
// locations in order:
//
// - the directory or uncompressed zip file named by the ZONEINFO environment variable
// - on a Unix system, the system standard installation location
// - $GOROOT/lib/time/zoneinfo.zip
// - the time/tzdata package, if it was imported
func LoadLocation(name string) (*Location, error)

Reading the comments, if the name is empty or "UTC", UTC is used. If the name is "Local", the local time zone is used (explained later). Otherwise, it is read from a specific location.

The so-called reading refers to reading the tzfile time zone file, and you can read more information in the tzfile document. In simple terms, the time zone file is a binary file starting with TZif, which contains information such as the time zone offset, leap seconds, and daylight saving time. Go can read and parse the relevant files.

  1. If the ZONEINFO environment variable exists, it is used to read the directory/compressed file it points to.
  2. On a Unix system, the system's standard installation location is used.
  3. (Mainly used when compiling Go) Read from $GOROOT/lib/time/zoneinfo.zip.
  4. (If time/tzdata is imported) Read from the embedded data in the program.

We are particularly concerned about 2, which is the storage location of the standard time zone file on Unix systems. In Unix-like systems, the time zone file is usually stored in the /usr/share/zoneinfo/ directory (depending on the system, it may also be /usr/share/lib/zoneinfo/ or /usr/lib/locale/TZ/). For example, the time zone definition file for China (Asia/Shanghai) is /usr/share/zoneinfo/Asia/Shanghai. Therefore, the program can usually directly obtain the time zone information from the system.

Note that there are no time zone definition files in the Alpine environment, so we need to pay special attention to the processing.

  1. You can use import _ "time/tzdata" in the program to embed the time zone file into the program during compilation, so that you can find the standard IANA time zone definition even if you cannot find the time zone definition in the system.
  2. If we don't need a particularly dynamic time zone, we can avoid using LoadLocation and use FixedZone to provide our own time zone name and offset. For example, for China (UTC+8), you can use time.FixedZone("Asia/Shanghai", 8*60*60).

Local Time Zone#

Usually, before we really consider the time zone issue, the time zone we "default" to use is the so-called "local time zone".

Take time.Now as an example,

type Time struct {
	wall uint64
	ext  int64

	loc *Location
}

// Now returns the current local time.
func Now() Time {
	sec, nsec, mono := now()
	mono -= startNano
	sec += unixToInternal - minWall
	if uint64(sec)>>33 != 0 {
		// Seconds field overflowed the 33 bits available when
		// storing a monotonic time. This will be true after
		// March 16, 2157.
		return Time{uint64(nsec), sec + minWall, Local}
	}
	return Time{hasMonotonic | uint64(sec)<<nsecShift | uint64(nsec), mono, Local}
}

It can be seen that the last field loc *Location of the Time structure is the time zone, and the time zone used in time.Now is Local.

We mainly focus on the time zone in this article. If you are interested in other factors in this code, please read Do You Really Understand time.Now() in Go?.

The Local here is the local time zone, which is the time zone of the machine where the program is running.

// Local represents the system's local time zone.
// On Unix systems, Local consults the TZ environment
// variable to find the time zone to use. No TZ means
// use the system default /etc/localtime.
// TZ="" means use UTC.
// TZ="foo" means use file foo in the system timezone directory.
var Local *Location = &localLoc

Reading the explanation of Local in Go, it can be understood that Go will prioritize the time zone specified by the TZ environment variable. If no special specification is made, it will read the current time zone from the /etc/localtime file.

So, how is Local initialized?

// localLoc is separate so that initLocal can initialize
// it even if a client has changed Local.
var localLoc Location
var localOnce sync.Once

func (l *Location) get() *Location {
	if l == nil {
		return &utcLoc
	}
	if l == &localLoc {
		localOnce.Do(initLocal)
	}
	return l
}

From the logic of this code, it is not difficult to guess that Local is not really read the above information when the program starts, but is actually initialized by executing the initLocal function when it is first used. At the same time, this code implicitly makes a requirement for using Location: you must call the get method to get the "real Location".

The initLocal function is defined in zoneinfo_*.go and has different implementations on different machines, but essentially

If the content of TZ starts with :, the colon will be ignored.

  1. If the TZ environment variable is not specified, read /etc/localtime (usually a symbolic link pointing to the real time zone file).
  2. If the specified TZ environment variable is an absolute path, read that file.
  3. Otherwise, follow the LoadLocation process analyzed above to read the time zone file.

In addition, if the above 3 steps fail, it will fallback to using UTC time.

Extra: tzdata#

tzdata defines the change history of historical time zones, including daylight saving time, leap seconds, etc., so Asia/Shanghai is more versatile and can handle historical data correctly compared to a simple GMT+8.

If you are interested, you can use zdump Asia/Shanghai -i to view the time zone changes in Shanghai and compare them with the time using daylight saving time zdump America/Chicago -i.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.