Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

time: set specific rule for Date on daylight savings transitions #24551

Open
jleben opened this Issue Mar 27, 2018 · 31 comments

Comments

Projects
None yet
6 participants
@jleben
Copy link

jleben commented Mar 27, 2018

What version of Go are you using (go version)?

go1.7.4 linux/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH=""
GORACE=""
GOROOT="/usr/lib/go-1.7"
GOTOOLDIR="/usr/lib/go-1.7/pkg/tool/linux_amd64"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build628236315=/tmp/go-build -gno-record-gcc-switches"
CXX="g++"
CGO_ENABLED="1

What did you do?

I called time.Date for March 11, 2018, 2:00 AM, and printed the resulting time.
https://play.golang.org/p/N-XUwlGxpaF

What did you expect to see?

March 11, 2018, 2:00 AM is the moment when the clock in Vancouver changes to 3:00 AM due to switching from PST to PDT. So I would expect one of the following:

  • Either 2:00 is interpreted in PST, and the time printed is 2:00 PST
  • Or 2:00 is interpreted in PDT, and the time printed is 2:00 PDT

What did you see instead?

Instead, time.Date apparently interprets 2:00 in PDT, but prints 1:00 PST.

@ALTree ALTree changed the title Unexpected time printed when daylight savings time begins time: unexpected time printed when daylight savings time begins Mar 27, 2018

@odeke-em

This comment has been minimized.

Copy link
Member

odeke-em commented Mar 28, 2018

@crvv

This comment has been minimized.

Copy link
Contributor

crvv commented Mar 28, 2018

2018-03-11 02:00:00 America/Vancouver doesn't exist.
Neither 2018-03-11 02:00:00 PST nor 2018-03-11 02:00:00 PDT can be observed in time zone America/Vancouver.

Go chooses PDT to parse time, which is not wrong.
2018-03-11 02:00:00 PDT is 2018-03-11 01:00:00 America/Vancouver, which is 2018-03-11 01:00:00 PST.

I don't think this is a bug.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

ianlancetaylor commented Mar 28, 2018

The specific time does not exist. The documentation for time.Date says "In such cases, the choice of time zone, and therefore the time, is not well-defined. Date returns a time that is correct in one of the two zones involved in the transition, but it does not guarantee which." So this is working as documented.

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 28, 2018

I would like this issue to be reopened, as I have reasons other than those addressed in the comments above.

Specifically, I am not saying that the behavior does not conform to the documentation and is therefore a bug. I am saying that I'd like to see a stronger guarantee from the library, beyond what is currently documented.

More specifically, my issue is with the library interpreting the input in one time zone, but printing the time in another time zone. I would prefer if it printed the same time zone as the one in which it interprets the input.

To reply to some of the comments above: 1:00 PST does not "exist" in precisely the same way as 2:00 PDT does not exist. The library therefore has no more reason to print the former and not the latter from that perspective. However, there is another reason to prefer printing 2:00 PDT - the fact that that's how it interpreted the input in the first place.

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 28, 2018

You can think of this as a feature request rather than a bug report if you wish. So, rather than being convinced that this is not a bug, I need to be convinced that this is not a useful feature.

To support my claim that this is a useful feature, think of the following analogy. Imagine a program that can answer any question asked in any natural language. Suppose you ask that program in English: "Is the Earth round?" How happy would you be with the program providing the answer: "Oui"?

By analogy, I would like the library to print the resulting time in the timezone in which it intepreted the input, to avoid surprises.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

ianlancetaylor commented Mar 28, 2018

Neither 2:00 PDT nor 2:00 PST exist. The time goes from 1:59:59 to 3:00:00.

The time package is using the same location to look up the time and to return the time: that location is the one you loaded, America/Vancouver.

You are asking the time package to represent a time that simply doesn't exist. Probably the best response would be to return an error--that is what the Unix date command does--but unfortunately the time.Date API does not provide for an error return. I believe that the function is doing the best it can, and I believe it is behaving as documented.

I think you may be operating under a misapprehension that the time package is first picking PDT or PST and then looking up the time in that zone. That's not what it does. It actually first calculates the time without regard to timezone, then looks up the timezone offset for that time in the selected location, and then adjusts the time by that offset. Since the time 2:00 does not exist on that day on that location, the result is unpredictable, just as the documentation says.

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 28, 2018

I do not agree.

The documentation says:

Date returns a time that is correct in one of the two zones involved in the transition, but it does not guarantee which.

In order to explain conformance of the behavior to the documentation, you have to explain what "correct" in the quote above means.

The way I understand the documentation is that time.Date creates a time that conforms to the arguments according to one of the two possible interpretations of the arguments. Specifically, given the time and the location in the arguments, it is possible to interpret the arguments in the PST time zone or the PDT time zone.

Please note again what I already stated above: the time printed by the library is 1:00 PST, which does not exist any more thant 2:00 PDT exists.

Also, my apprehension about how the time package is implemented, and how it is actually implemented, has nothing to do with this argument: I am discussing the interface of the time package, not the implementation.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

ianlancetaylor commented Mar 28, 2018

Perhaps I'm completely confused, but why do you say that 1:00 PST does not exist? As far as I can tell, it does. The times before 2:00 are PST. The times 3:00 and after are PDT.

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 28, 2018

I agree with the following though: returning an error would make sense, and would be less surprising than the current behavior.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

ianlancetaylor commented Mar 28, 2018

Unfortunately we can't change the API, due to https://golang.org/doc/go1compat.

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 28, 2018

Oh, you're right, the time 1:00 PST does indeed exist. Sorry.

Okay, I see how the documentation should be interpreted now: by "correct in one of the time zones" it probably means "exists in one of the time zones."

Still, I think the behavior is rather surprising, and the documentation doesn't help much. If an error can not be returned, then I would at least like a stronger guarantee from the documentation as to how the input to Date is interpreted.

I am concerned, because the Date function may be used in contexts where calling it for a time that "does not exist" is not necessarily an end-user error. I have opened this issue because of the surprising behavior of the Concourse time resource. There, the user requests a task to be executed, for example, daily at 2:00 AM in Vancouver. When this time is passed on to time.Date, the result will be predictable on all days except on the day when the daylight savings time begins or ends. This could be improved if the behavior of time.Date had a stronger guarantee about how such time is handled.

@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

ianlancetaylor commented Mar 28, 2018

Any thoughts on what that guarantee could look like?

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 28, 2018

Also, for the record, I would like to add to the discussion about the "existence" of times.

I do not actually agree with a statement that the time "2018, March 11, 2:00 PST" does not exist. I regard PST, PDT, UTC and similar entities as bijective functions from the set of all "real" time moments to the set of notations representing those time moments. In this sense 2:00 PST or even 2:30 PST and 2:30 PDT do exist: they are notations unambiguously assigned to some moments in real time by the PST or PDT function.

So from this perspective, I don't think it's absolutely wrong to ever print "2018, March 11, 2:00 PST". It's simply a fact that at the time denoted by this notation, the clocks in Vancouver do not actually show "2:00".

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 28, 2018

Here's an idea for a stronger guarantee for time.Date:

  1. When the requested time is not observed at the given location, the function returns instead the latest observed time before the requested time.
  2. When the requested time is observed multiple times at the given location, the function returns the earliest of the observed times.

Note: In the above rules "observed" means the time clocks are set to according to the laws.

Rule 1. would conform to the current behavior when 2:00 is requested on the day when PST switches to PDT and 2:00 is never observed.

Rule 2. would result in returning 1:00 PDT on the day when PDT switches to PST and 1:00 is observed twice.

@ianlancetaylor ianlancetaylor changed the title time: unexpected time printed when daylight savings time begins time: set specific rule for Date on daylight savings transitions Mar 28, 2018

@ianlancetaylor ianlancetaylor added this to the Unplanned milestone Mar 28, 2018

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 28, 2018

Hmm, the example of the effect of rule 1 should be corrected: the latest observed time before 2:00 would actually be 1:59:59 PST. Actually, it's not even well defined because time can be subdivided infinitely.

So Rule 1 should be adjusted...

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 28, 2018

Here is a better idea:

First, let me define a couple terms.

Let a "time zone" be a particular offset of a clock from UTC. For example, the PDT in 2018 is a rule that the clock should be offset by -7 hours from UTC.

Let an "effective time zone period" be a contiguous period when a particular time zone is in effect at a particular location. For example, an effective time zone period is the time between the moment PDT starts to be observed and the moment it stops to be observed in the year 2018 in Vancouver.

Now, consider the arguments to time.Date. The given time can be interpreted in a number of time zones in effect at different periods at the given location. Return the interpretation according to the following algorithm:

  • Let P = { p_1, p_2, p_3, ... } be all the time zone periods ever in effect at the given location
  • Let Z = { z_1, z_2, z_3, .... } be the time zones such that z_i is the time zone of the period p_i. Note that some pairs of the elements in this set are identical.
  • Let T = { t_1, t_2, t_3, ... } be the real time moments, such that t_i is the interpretation of the given time according to z_i
  • Let P_valid be all p_i such that the beginning of p_i <= t_i
  • Choose p_c in P_valid with the latest beginning
  • Return t_c

Example 1: Daylight savings time begins

  • Let the input be 2:30, March 11, 2018 in Vancouver.
  • For simplicity (but without affecting the result) assume that P is just { p_1 = "PST period ending on March 11, 2018", p_2 = "PDT period beginning on March 11, 2018" }.
  • Then, Z = { z_1 = PST, z_2 = PDT }
  • Then, T = { t_1 = 2:30 PST, t_2 = 2:30 PDT }
  • Then, P_valid = { p_1 }, since p_2 begins at 3:00 PDT, which is later than the interpretation t_2 = 2:30 PDT.
  • Then, choose p_c = p_1 (the only element in P_valid)
  • Return t_1 = 2:30 PST

Example 2: Daylight savings time ends

  • Let the input be 1:30, November 4, 2018 in Vancouver.
  • For simplicity (but without affecting the result) assume that P is just { p_1 = "PDT period ending on Nov 4, 2018", p_2 = "PST period beginning on Nov 4, 2018" }.
  • Then, Z = { z_1 = PDT, z_2 = PST }
  • Then, T = { t_1 = 1:30 PDT, t_2 = 1:30 PST }
  • P_valid = P, since p_1 begins before t_1 and p_2 also begins before t_2
  • Then, choose p_c = p_2, because p_2 begins after p_1
  • Return t_2 = 1:30 PST

This algorithm also returns the usual result when the input time is observed unambiguously at exactly one moment in history at the given location.

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 28, 2018

The above algorithm chooses the alternative to the current implementation for both examples. The algorithm can be adjusted slightly though, to produce the result of the current implementation:

Algorithm:

  • ...
  • Let P_valid be all p_i such that the ending of p_i >= t_i
  • Choose p_c in P_valid with the earliest beginning

Example 1:

  • ...
  • P_valid = { p_2 }, since p_1 ends at 2:00 PST which is before t_1 = 2:30 PST
  • Choose p_2 (the only one)
  • Return t_2 = 2:30 PDT = 1:30 PST

Example 2:

  • ...
  • P_valid = { p_1, p_2 }, since p_1 ends after 1:30 PDT and p_2 also ends after 1:30 PST
  • Choose p_1, because it begins before p_2
  • Return t_1 = 1:30 PDT
@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 29, 2018

For the purpose of documentation, the proposed algorithm provides the following guarantees, stated in plain English:

Variant 1:

  • When the time is not observed due to a daylight savings transition, the interpretation according to the outgoing time zone is returned.
  • When the time is observed twice due to a daylight savings transition, the later observation is returned.

Variant 2:

  • When the time is not observed due to a daylight savings transition, the interpretation according to the incoming time zone is returned.
  • When the time is observed twice due to a daylight savings transition, the earlier observation is returned.

The documentation could also be clarified by replacing the term "correct" with the term "observed": "Regardless of how the arguments are interpreted, Date always returns a representation that is in fact observed at some point in history."
This is in accordance with the W3C terminology:
https://www.w3.org/TR/timezone/#observedtime

I believe the algorithm could also be tweaked to produce a mix of the effects of variant 1 and variant 2, if desired.

In addition, the algorithm precisely states how more complex transitions are handled, for example:

  • when the clock changes by more than 1 hour
  • when a time is observed more than twice
@crvv

This comment has been minimized.

Copy link
Contributor

crvv commented Mar 29, 2018

What you requested is to make time.Date return 2018-03-11 02:00:00 PST or 2018-03-11 02:00:00 PDT.
The problem is, neither of them can be observed in time zone America/Vancouver.
If Date chooses PST, the result will be 2018-03-11 03:00:00 PDT.

Returning 02:00:00 PST or 02:00:00 PDT is similar to returning 10:00:00 UTC, which I think is a bug.
Because none of the three can be observed in the requested time zone America/Vancouver.

Besides, I don't think a specific rule for DST is a better thing. No matter want time zone you choose, some people will want the other.

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 29, 2018

Please disregard my initial request to return 2:00 PST or 2:00 PDT. My interests have shifted, as visible from the discussion above.

I disagree that having a specific rule is not beneficial. Having a specific rule is definitely better than having none. Even if users don't like your choice, at least they can't complain because the choice is clearly stated and documented.

Also, without a specific rule, algorithms can not rely on the behavior of time.Date. One example: As I have stated above, if the library had a specific rule, it would be useful for handling periodic task schedules. Without a specific rule, it is useless for this purpose, as it is not clear when the periodic task would run on the day of the daylight savings change.

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 29, 2018

@cznic Would you please care to comment why you downvoted my proposed algorithm?

@cznic

This comment has been minimized.

Copy link
Contributor

cznic commented Mar 29, 2018

Search the stdlib for functions with such specialized, complex, hard to understand and explain/document behavior as proposed. You should probably/hopefully find none.

Moreover, all of that for handling what basically amounts to an invalid argument.

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 29, 2018

@cznic Can you please explain what the invalid argument is?

Currently, time.Date has undefined behavior. I am just trying to come up with some well-defined behavior.

In the absence of the possibility to return an error when the user requests a time that is not observed at a certain location (the interface of function can not be changed), there must be a well-defined return value.

Yes, the algorithm I proposed above looks complicated, because it is stated precisely and unambiguously. If you look carefully through the discussion above, you will see though that I have also provided a simple, plain English explanation of the effect of the algorithm on handling a daylight savings transition which is usable in documentation.

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 29, 2018

@cznic The algorithm may look complex, because it is generic and handles all kinds of transitions, including when a time is observed more than twice, or the clock jumps by more than an hour, etc..

I strongly believe any documentation for any implementation that will handle such transitions will always be complicated as well.

However, such transitions are very rare, and the behavior of my algorithm in the comparably simple daylight savings transition is easy to explain (as I have proven above).

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 29, 2018

@cznic It seems you are claiming that when the complete behavior of some function is inevitably hard to explain, you would rather leave the behavior undocumented (amounting to undefined behavior) than explain it.

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 29, 2018

@cznic Alternatively, according to your statements, time.Date is simply a horrible function in itself, and should be deprecated and ultimately removed from the Go package.

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 29, 2018

Here is an example from the Python community, proving that I am not the only one fighting for a well-defined behavior:
https://www.python.org/dev/peps/pep-0495/

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 29, 2018

@cznic In fact, the C standard library does have such a function: mktime:
http://en.cppreference.com/w/c/chrono/mktime

If you search the web you'll find that many people question its specification, quarrel about the interpretation of the specification, and some call for disambiguating the standard.

@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 30, 2018

Here is a restatement of the same algorithm I proposed above, in simpler terms. Hopefully, it will prove that the proposed behavior is not all that complicated to explain.

Note: I am borrowing the terms "gap" and "fold" from here:
https://www.python.org/dev/peps/pep-0495/#terminology

  • If the requested time is observed multiple times due to one or more folds, interpret the time according to the time zone in effect just before the earliest (or after the latest if you wish) of the folds.
  • Otherwise, if the requested time is never observed due to one (or more) gaps, interpret the time according to the time zone in effect just before the earliest (or after the latest if you wish) of the gaps.

Mind that if you accept that a given time may only be affected by a single gap or a single fold, the explanation above becomes even simpler.

Again: Please mind that the algorithm only states how the given time is to be interpreted. If one wishes, one can add:

  • The time returned is the interpreted time renormalized to some actually observed time.
@jleben

This comment has been minimized.

Copy link
Author

jleben commented Mar 30, 2018

Another possible solution, which might be more easily digestible:

Always return the latest possible interpretation (measured in UTC)

Where "possible interpretations" are defined as follows:

Possible interpretations include either all observed instances of the given time, or if there is no observed instances, then the interpretations according to all time zones before and after the gaps that skip the given time.

@crvv

This comment has been minimized.

Copy link
Contributor

crvv commented Mar 31, 2018

DST is a very inconvenience in program.
I will use a fixed time zone everywhere.

If time.Date always returns the latest possible interpretation but the user want the other, I don't know how to get the desired value. So I think a specific rule is useless.

If time.Date returns an error on ambiguous time and the error contains the two related zones, the problem may be solved. But I won't vote for this.

I think the requirement is very specific. If I need to do this, I will use some private methods in src/time/zoneinfo.go to write the desired algorithm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.