I ran into an issue with Go code, related to time zones, that would return either 1900-01-01 01:00:00 +0100 CET or 1900-01-01 00:53:28 +0053 LMT, depending on which machine it is being run:
https://play.golang.org/p/K3ceq1n1KI
I was able to rule out the Go version as source of the difference. Where does Go get its time zone information from?
Actually it depends.
Check time.LoadLocation() source and the comment above it. Particularly, it says this:
// LoadLocation looks in the directory or uncompressed zip file
// named by the ZONEINFO environment variable, if any, then looks in
// known installation locations on Unix systems,
// and finally looks in $GOROOT/lib/time/zoneinfo.zip.
And you should keep in mind that on a Unix/Posix systems (e.g. Linux), proper time zone data files are always available in "known installation locations". But on other systems there no such locations. As a result, on Windows, LoadLocation zone won't find any time zones (unless either ZONEINFO or GOROOT were configured properly) and will just use default instead (which is UTC, if my memory is correct).
However, the LMT issue you described above is a bit different beast. You see, LMT is not exactly a proper time zone. E.g. see here. And I didn't dig out where exactly it comes from, but I suspect it is related to the fact that the time zone was not established yet at the time that you're converting. So, go seems to be calculating LMT of the place instead.
For example, if you just change year from 1900 to 2000 (or even 1905) in your playground example, it will come out with correct time zone (CET).
As to the difference between machines, I'd say it's pretty arguable what is the most natural way of defining time zone rules before they were actually introduced. As a result, I would imagine that in some time zone databases, start time of the first rule in a time zone will be either omitted or adjusted to extend it further to the past. While others will put an LMT offset there instead. (Even though most if not all of them are derived from the same IANA database in one way or another.)
If you want to ensure exact same behavior on different machines, I'd say building your own zoneinfo.zip and setting ZONEINFO variable should help.
Related
Go version: 1.14.2
Operating system: Linux (Ubuntu)
I am working on a dynamic timezone based system in which user can set the timezone. I have a user who set the timezone to US/Pecific-New. For this timezone, the golang time package gives following error:
unknown time zone US/Pacific-New
I have researched on this and found that time package loads the timezone from the local system having file paths:
/usr/share/zoneinfo
/usr/local/go/lib/time
On these paths, on my local system as well as on staging Pacific-New is missing on both locations.
Is there a way to get Pacific-New on these locations ?
US/Pacific-New was never a "real" time zone and should not be selected on any system.
It was created to reflect a potential change to Pacific time that was never enacted into law. The idea at the time was that there would be a time zone ready in advance of the change. However, since the bill wasn't enacted this turned out to be a failed experiment. The TZDB no longer tries to create zones in speculative anticipation of future changes, but only adds or updates them when they are official or otherwise imminent.
Ultimately, US/Pacific-New was only ever a link to US/Pacific and then later updated as a link to the preferred form of America/Los_Angeles The last TZDB release to include such a link for US/Pacific-New was 2020a.
From the 2020b release notes:
The long-obsolete files pacificnew, systemv, and yearistype.sh have been removed from the distribution. (Thanks to Tim Parenti.)
You can read more about the long storied history with this link entry by searching the NEWS file in the TZDB for "Pacific-New" and "pacificnew", and by reading the 2020a version of the pacificnew file from before it was deleted.
As far as practical advice, don't set US/Pacific-New on any system. It is no longer a valid time zone identifier. The whole system will be affected by this. Switch to America/Los_Angeles instead.
Rather than time.LoadLocation() you can use time.LoadLocationFromTZData() which loads a location from the contents of a timezone file, which can contain whatever timezones you like.
You will need a correctly formatted timezone file with the timezones you wish to use, perhaps based on whatever you have in /usr/share/zoneinfo.
This question already has an answer here:
get current location region from local system [closed]
(1 answer)
Closed 2 years ago.
I want to get a system's timezone information in tz database format, e.g. "America/New_York". Also I want it to be platform independent, e.g. code should work on Windows, Linux and MacOS.
Tried two recipes:
viaLocation := time.Now().Location().String() // Gives "Local"
viaZone, _ := time.Now().Zone() // Gives "EST"
"EST" is somewhat better, is there any way to map it into "America/New_York"?
I don't mind migrating to Go 1.15 and import time/tzdata
You can't do this reliably.
On Linux the local time is typically configured via /etc/localtime. The file format doesn't include the IANA name.
But even if it did, that's not the only way to configure the time zone. An obvious alternative is the TZ environment variable. I can set TZ to, say, UTC+4, so my local time zone doesn't have a name at all. This is a trivial example, but the TZ value can be much more complicated too.
The time/tzdata package is only used if the system doesn't provide time zone definitions, so importing that package doesn't help either.
Marc's answer to a similar question shows how you can take a guess on Linux (and possibly MacOS), but it's nothing more than that.
So you see, it can't be done reliably on Linux at least. I assume MacOS works similar. I don't know how local time works on Windows, but I'm sure it's possible to configure a fixed UTC offset too, i.e. a nameless time zone.
I would like to know when a Location's offset from UTC is going to change. I see that this information is known by the time package, obviously or it wouldn't be able to account for daylight savings. That way I could find out that for the location "America/New_York" daylight savings begins on Sunday, March 11 2018 at 07:00 UTC.
Is there a way to do this short of making my own copy of the time package that exports the Location properties or writing my own parser for the time zone files?
As you've seen in the source, no, that information is not exposed. But, as you can also see from the source, the raw TZDB used to generate the timezones is included in the Go distribution:
//go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go
If you take a look at your $GOROOT/lib/time you'll find that file, which contains all the data used to generate the time zone list, and you can look at $GOROOT/src/time/genzabbrs.go to see how it's used.
I'm testing our infrastructure using the powershell command:
[System.TimeZoneInfo]::Local.Id
Which returns a string like
Eastern Standard Time
Our servers are all english right now, but I'm pretty sure this test would fail if I ran it on a non-english windows.
Is there a way to check the timezone without having to check it against an English string?
Rather than using [System.TimeZoneInfo]::Local.Id use [System.Timezoneinfo]::Local.BaseUtcOffset which will give you the result in terms of the number of hours difference between UTC time and the timezone of the server you are working with.
EDIT
#LotPings is correct that the BaseUtcOffset will not take into account DST, which may not matter if you are only concerned with verifying the timezones have not changed from your standard but if it is important you can instead use [System.TimeZoneInfo]::Local.GetUtcOffset($(get-date)) which will get you the current UTC offset.
Perhaps a daft question that demonstrates my lack of understanding of daylight saving fundamentals, but as per the title, how does Time.dst? know whether the time object is indeed true or false?
Presumably this must be discernible by the combination of the Date and the Timezone, but then that doesn't make sense as lower latitudes in timezones don't use daylight savings? Therefore surely it must need location to discern #dst? ?
What am i missing?
To deal with time zones and daylight savings time, Ruby, like just about everything else, is calling the localtime_r C function. This puts the time in a C structure called tm which includes a field called isdst. Ruby is reading that flag.
localtime_r calculates isdst first by getting your time zone from the global tzname variable. tzname is determined by calling tzset. How tzset does its job is system dependent. It can come from the TZ environment variable, reading a file, or querying an OS service.
For example.
# Time zone from the system.
$ ruby -e 'puts Time.now.zone; puts Time.now.dst?'
PDT
true
# Time zone from the TZ environment variable.
$ TZ='Australia/Brisbane' ruby -e 'puts Time.now.zone; puts Time.now.dst?'
AEST
false
Once it has the time zone, localtime_r can convert from GMT to the desired time zone (using the rules which applied on that date) using the "tz database" aka "tzdata" aka "zoneinfo" aka "the Olson database" after its creator Arthur David Olson. Previously a private effort, this is now maintained by IANA. It is a set of files installed on your system, or shipped with Ruby, which contains Far More Than Everything You Ever Want To Know About Time Zones and Daylight Savings.
The tz database treats daylight savings (and other weird things like War and Peace time) as just another time zone. Time zone records are kept going all the way back as far as we've had time zones. Before we had time zones solar noon for that location is used. Because of these historical complications and shifting time zones, the tz database prefers to work with cities (such as "America/New York") and determine the time zone for you.
The time zone data files contain extensive commentary and background, if you're interested in the history of calendaring they're a fascinating read.
The Ruby 2.2.0 Time.dst? method refers to the time_isdst(...) function in time.c:
// ...
return tobj->vtm.isdst ? Qtrue : Qfalse
Which seems correspond to the UNIX struct tm tm_isdt member, after plenty of mangling:
int tm_isdst daylight savings flag
Search for tm_isdst and isdst in time.c to read about how it is computed.