如何正确解析时区代码

huangapple go评论83阅读模式
英文:

How to properly parse timezone codes

问题

在下面的示例中,无论您选择哪个时区作为parseAndPrint函数的参数,结果始终为"[date] 05:00:00 +0000 UTC"。这段代码有什么问题?时间应该根据您选择的时区而变化。(Go Playground服务器显然配置为UTC时区)。

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	parseAndPrint(now, "BRT")
	parseAndPrint(now, "EDT")
	parseAndPrint(now, "UTC")
}

func parseAndPrint(now time.Time, timezone string) {
	test, err := time.Parse("15:04:05 MST", fmt.Sprintf("05:00:00 %s", timezone))
	if err != nil {
		fmt.Println(err)
		return
	}

	test = time.Date(
		now.Year(),
		now.Month(),
		now.Day(),
		test.Hour(),
		test.Minute(),
		test.Second(),
		test.Nanosecond(),
		test.Location(),
	)

	fmt.Println(test.UTC())
}

这段代码的问题在于,它使用了time.Parse函数来解析时间字符串,但是在格式字符串中使用了MST作为时区的占位符。MST表示Mountain Standard Time,而不是具体的时区名称。因此,无论您选择哪个时区作为参数,它都会被解析为Mountain Standard Time。要解决这个问题,您可以使用time.LoadLocation函数来加载指定的时区,然后将其传递给time.ParseInLocation函数来解析时间字符串。以下是修改后的代码:

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	parseAndPrint(now, "BRT")
	parseAndPrint(now, "EDT")
	parseAndPrint(now, "UTC")
}

func parseAndPrint(now time.Time, timezone string) {
	loc, err := time.LoadLocation(timezone)
	if err != nil {
		fmt.Println(err)
		return
	}

	test, err := time.ParseInLocation("15:04:05", "05:00:00", loc)
	if err != nil {
		fmt.Println(err)
		return
	}

	test = time.Date(
		now.Year(),
		now.Month(),
		now.Day(),
		test.Hour(),
		test.Minute(),
		test.Second(),
		test.Nanosecond(),
		test.Location(),
	)

	fmt.Println(test.UTC())
}

这样修改后,代码将根据您选择的时区正确地解析和打印时间。

英文:

In the example bellow the result is always "[date] 05:00:00 +0000 UTC" regardless the timezone you choose for the parseAndPrint function. What is wrong with this code? The time should change depending on the timezone you choose. (Go Playground servers are apparently configured in UTC timezone).

http://play.golang.org/p/wP207BWYEd

package main

import (
    "fmt"
	"time"
)

func main() {
	now := time.Now()
	parseAndPrint(now, "BRT")
	parseAndPrint(now, "EDT")
	parseAndPrint(now, "UTC")
}

func parseAndPrint(now time.Time, timezone string) {
	test, err := time.Parse("15:04:05 MST", fmt.Sprintf("05:00:00 %s", timezone))
	if err != nil {
		fmt.Println(err)
		return
	}

	test = time.Date(
		now.Year(),
    	now.Month(),
		now.Day(),
		test.Hour(),
		test.Minute(),
		test.Second(),
		test.Nanosecond(),
		test.Location(),
	)

	fmt.Println(test.UTC())
}

答案1

得分: 22

当你解析时间时,你是在当前位置解析的,只要你预期的是这样,并且时区缩写在你的位置内是已知的,那就没问题。

如果你可以不考虑时区,将你处理的所有时间转换为协调世界时(UTC)会更容易。

下一个最简单的方法是使用显式偏移量,比如“-05:00”。

如果你想处理来自其他时区的时间,你需要使用time.Location。你可以使用time.LoadLocation从本地时区数据库加载位置,并使用time.ParseInLocation在那里解析时间。

英文:

When you Parse a time, you are parsing it in your current location, which is OK as long as that's what you're expecting, and the timezone abbreviation is known from within your location.

If you can forgo timezones, it's far easier to normalize all the times you're dealing with into UTC.

The next easiest is handling everything with explicit offsets, like -05:00.

If you want to deal with times originating in other timezones, you need to use time.Location. You can load Locations from the local timezone db with time.LoadLocation, and parse times there with time.ParseInLocation.

答案2

得分: 3

问题:如何正确解析带有缩写时区名称(如UTCCETBRT等)的时间?

答案:最好不要这样做。正如JimB和其他人在这个问题https://stackoverflow.com/questions/49084316/why-doesnt-gos-time-parse-parse-the-timezone-identifier中仔细建议的那样,你可以期望Go正确解析两个时区:UTC和本地时区。
他们没有明确说明的是,你不能期望Go正确解析任何其他时区的时间。至少在我的个人经验中是这样的(go1.16.1,Ubuntu 20.04)。

此外,缩写的时区是有歧义的。IST可能表示印度标准时间、爱尔兰标准时间或以色列标准时间。除非你知道时区的位置,否则无法消除歧义,如果你知道位置,你应该使用time.ParseInLocation

如果这是用户输入并且你有控制权,你应该像JimB在他们的答案中建议的那样更改用户输入时间的格式要求。确保不要忘记分钟,即使用-0700-07:00Z0700Z07:00,但不要使用-07Z07。并非所有的偏移都是整小时。例如,印度标准时间是UTC+5:30。

如果你别无选择,被迫解析这样的时间,你可以这样做:

func parseTimeWithTimezone(layout, value string) (time.Time, error) {
	tt, err := time.Parse(layout, value)
	if err != nil {
		return time.Time{}, err
	}
	loc := tt.Location()
	zone, offset := tt.Zone()
	// 如果时区未被识别(或为UTC,但这没关系),偏移将为0。
	// 仔细阅读https://pkg.go.dev/time#Parse
	// 在这种情况下,我们将尝试从区域名称加载位置。
	// 被识别的时区:本地时区、UTC、GMT、GMT-1、GMT-2,...,GMT+1、GMT+2,...
	if offset == 0 {
		// 确保你的系统中有可用的时区数据库,以便time.LoadLocation正常工作。
		// 阅读https://pkg.go.dev/time#LoadLocation,了解Go在哪里查找时区数据库。
		// 也许最简单的解决方案是`import _ "time/tzdata"`,但请注意,这会增加几百千字节的二进制文件大小。
		// 参见https://golang.org/doc/go1.15#time/tzdata
		loc, err = time.LoadLocation(zone)
		if err != nil {
			return time.Time{}, err // 或者如果你更喜欢返回带有命名时区但偏移为零的原始Go语义,则返回`return tt, nil`。
		}
	}
	return time.ParseInLocation(layout, value, loc)
}

请注意,时区名称在时区数据库中不存在的将无法解析。这样的时区有很多。你可以通过检查以下内容来查看存在哪些:

  • 在你的系统上检查/usr/share/zoneinfo/usr/share/lib/zoneinfo的内容,
  • 检查此文件的内容<https://github.com/golang/go/blob/master/lib/time/zoneinfo.zip>。
英文:

Question: How to properly parse time with abbreviated timezone names like UTC, CET, BRT, etc.?

Answer: You better should not. As JimB and others in this question https://stackoverflow.com/questions/49084316/why-doesnt-gos-time-parse-parse-the-timezone-identifier carefully suggest, you can expect that Go correctly parses only two timezones: UTC and the local one.
What they don't make quite explicit is that you can't expect Go to correctly parse time with any other timezone. At least that is so in my personal experience (go1.16.1, Ubuntu 20.04).

Also, abbreviated timezones are ambiguous. IST could mean India Standard Time, Irish Standard Time or Israel Standard Time. There's no way to disambiguate unless you know zone location, and, if you know location, you should use time.ParseInLocation.

If this is user input and you have control, you should change format requirements for users to input time with explicit offsets as JimB is also suggesting in their answer. Make sure you don't forget about minutes, i.e. use -0700, -07:00, Z0700 or Z07:00 but not -07 or Z07 in layout. Not all offsets are whole hours. For instance, Inidia Standard Time is UTC+5:30.

If you have no other choice and forced to parse such times, you can do something like that:

func parseTimeWithTimezone(layout, value string) (time.Time, error) {
	tt, err := time.Parse(layout, value)
	if err != nil {
		return time.Time{}, err
	}
	loc := tt.Location()
	zone, offset := tt.Zone()
	// Offset will be 0 if timezone is not recognized (or UTC, but that&#39;s ok).
	// Read carefully https://pkg.go.dev/time#Parse
	// In this case we&#39;ll try to load location from zone name.
	// Timezones that are recognized: local, UTC, GMT, GMT-1, GMT-2, ..., GMT+1, GMT+2, ...
	if offset == 0 {
		// Make sure you have timezone database available in your system for
		// time.LoadLocation to work. Read https://pkg.go.dev/time#LoadLocation
		// about where Go looks for timezone database.
		// Perhaps the simplest solution is to `import _ &quot;time/tzdata&quot;`, but
		// note that it increases binary size by few hundred kilobytes.
		// See https://golang.org/doc/go1.15#time/tzdata
		loc, err = time.LoadLocation(zone)
		if err != nil {
			return time.Time{}, err // or `return tt, nil` if you more prefer
			// the original Go semantics of returning time with named zone
			// but zero offset when timezone is not recognized.
		}
	}
	return time.ParseInLocation(layout, value, loc)
}

Note that zone names that aren't present as files in timezone database will fail parsing. These are quite many. You can see what is present by checking

  • contents of /usr/share/zoneinfo, /usr/share/lib/zoneinfo on your system,
  • contents of this file <https://github.com/golang/go/blob/master/lib/time/zoneinfo.zip>.

huangapple
  • 本文由 发表于 2014年8月19日 01:02:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/25368415.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定