
Time in Go - laex
http://bl.ocks.org/joyrexus/a56717634a672dcdfd48
======
mitchellh
More languages should copy Go's time library.

As someone who has written a lot of Go for years, I am always very quick to
say that "time" is my favorite Go standard library. I love it.

The "time" library is to Go what the "requests" [non-standard] library is to
Python, in terms of a great API and simplicity. For some reason in every other
language I've used (important, I don't know every time library! :P) the time
library has always been adequate, but not much else.

I think Python's "datetime" is a great example of a not great time library. At
one point I was writing Python every day for a couple years. During that time,
I could never ever remember the API for datetime, despite it being able to do
what I needed to do well enough. I always had to open the docs. I think this
is an example of an adequate library that just doesn't have a great API.

Or, take the more generic problem of: I have a time, I have a duration, how do
I tell if that duration has passed? Most developers I know (including myself)
stumble on this for some period of time (see what I did there?), remembering
what do I add to what and is it a greater than or a less than or do I
subtract, etc. It is an easy problem, but always seems to require a little bit
of thinking.

Go's "time" library is nothing like this.

Go's "time" library you will remember the API, it is intuitive. You can even
guess it after writing Go for some period of time. It is that easy (ignoring
the constants and non-standard printf-like function in it).

Go's "time" comparison/arithmetic is incredible. Comparing times? Determining
expiration? You'll almost never get this wrong the first time. (But write
tests anyways)

What I'm trying to say is: even if you don't like Go, take a look at how Go's
"time" library works. I think it would be valuable for other languages. Other
languages can probably even do it better (imagining better type systems around
units), but I've never seen an API that feels as right for manipulating time
as Go's time stdlib.

~~~
jcranmer
Go's "time" library is deceptively simple. Time, in real life, is quite
incredibly complex, and hiding that complexity from developers (as Go's
library does) does them a great disservice.

To start with, there are actually two distinct notions of time: monotonic and
wall-clock. In monotonic time, you want to measure time intervals with high
accuracy: a second of monotonic time should correspond as much as possible to
the physical span of a second (in the inertial frame of reference of the
computer in question, because screw relativity). More importantly, when it's
not possible to correspond that time, you at least want guarantees like "time
never ticks backwards." In contrast, wall-clock time is about matching a
(there's more than one, and the definitions can and do change unpredictably,
possibly even retroactively) civil reference clock as accurately as possible.

If you mix those two notions of time, you can evidence problematic results--
changing the system clock five months back, for example, caused my music
player to suddenly stop playing music (most likely because a callback timer
was scheduled on wall-clock time and therefore would be waiting a few hundred
days to start playing again).

Which brings into the next obvious problem: leap seconds. Any span of time
greater than 1 second is actually an ill-defined span of time in that it can
have multiple values. Once every few years, a minute has 61 seconds
(theoretically, it can also have 59, but that hasn't happened yet in
practice). So if you're requesting to add a minute, do you really mean "add 60
seconds" or do you mean "add 1 to the minutes field"? POSIX (and anyone else
who counts time as seconds since an epoch) opted to handle leap seconds by
pretending they don't exist, which causes leap seconds to be so dangerous
that, for example, the NYSE stopped trading for about an hour around the last
leap second to avoid problems. I can personally attest to seeing date/time
parsers choking on leap seconds. And it's not entirely clear if leap seconds
actually exist in civil time in some jurisdictions, because there is a small
amount of vagueness in the laws.

While on the topic of the differences between jurisdictions, do also note
that, for historical dates, you really need to think about the difference
between Julian and Gregorian calendars, as well as the fact that the date of
adoption of the Gregorian calendar varies from country to country (thanks
Protestant Reformation!). Oh, and the year didn't necessarily start on January
1, either, and the change from March 1 to January 1 happened differently for
different countries and differently from the Julian->Gregorian adoptions.

Time is hideously complicated.

~~~
enneff
So what's a better time library? Or, rather, how could Go's time library
better accommodate these things? It's also not clear that a standard library
time package should be the be-all, end-all time management solution. I don't
see any reason for a standard time package to handle historical dates, for
example.

Google found a nice solution to the leap second issue: we "smear" the second
over the day before it occurs by making tiny changes to the time over the day.
This means user applications don't need to be aware of it:
[http://googleblog.blogspot.com.au/2011/09/time-technology-
an...](http://googleblog.blogspot.com.au/2011/09/time-technology-and-leaping-
seconds.html)

~~~
masklinn
> So what's a better time library?

JSR 310/Project Kenai/Joda Next is often held up as the gold standard of
complete, correct datetime handling.

~~~
draegtun
And I would also add Perl's DateTime library to this list -
[https://metacpan.org/pod/DateTime](https://metacpan.org/pod/DateTime)

------
neoyagami
Golang to me has the best time manipulation I ever seen, wannabe change the
timezone, easy as eat pie, wanna add seconds? No prob, wanna compare dates? No
biggie

~~~
dvirsky
True, and for most cases the parsing is great too, but once you get off the
beaten path of standard time formats, parsing times can be annoying.

~~~
nulltype
Do you have any examples of annoying to parse time formats?

~~~
paradite
[http://play.golang.org/p/hCoZ3tdeM_](http://play.golang.org/p/hCoZ3tdeM_) am
I doing this correctly?

Because it seems that golang does not recognize the abbreviations at all.

~~~
nulltype
If you use time zones without offsets (just the name like PST instead of
-0700) then you have to parse the time in a specific location
[http://play.golang.org/p/JzKAq09NtE](http://play.golang.org/p/JzKAq09NtE)

I can only assume that the time zone name alone is insufficient to resolve
ambiguous times.

~~~
paradite
Then I think it is not very convenient to type out strings like
"America/Los_Angeles" when you are dealing with timezones that come with
abbreviations only. The whole point of using abbreviations is to avoid typing
out the full name.

Unless there is a way to get "America/Los_Angeles" from "PST"

~~~
nulltype
Yeah, I don't get why they require a location.

On the other hand, -0700 isn't much longer than PST and parses directly.

~~~
bitwalker
Abbreviations are reused in different locales. PST in one locale can have
different rules than PST in another locale. The reason the fully qualified
name is needed is because it uniquely identifies a locale for which timezone
rules can be followed.

Offsets aren't always the right thing to use either. If you are in
America/Chicago and use -0600 for your timezone, that's only accurate during
standard time, and is off by an hour for daylight savings time. Knowing
how/when to shift offsets is part of the timezone rules associated with a
locale, because they are not constant historically.

~~~
nulltype
Ah thanks, that confirms my theory of why the location is required to parse
time zone names like PST.

Offsets are probably going to be fine for recorded timestamps, if all you need
is the absolute time that the timestamp represents. But yeah, timezones make
things so complicated that there is no one true solution I guess.

------
paradite
So I copied the example from
[http://golang.org/pkg/time/#Parse](http://golang.org/pkg/time/#Parse)

and it did NOT work as expected:
[http://play.golang.org/p/Xd9oEeSffd](http://play.golang.org/p/Xd9oEeSffd)

(timezone offset is zero)

~~~
teraflop
That seems to be an issue with the playground:
[https://github.com/golang/go/issues/12388](https://github.com/golang/go/issues/12388)

It seems counter-intuitive to me that the behavior of time.Parse would depend
on a "system location", but apparently it's by design.

~~~
mratzloff
Time is location-dependant (time zones).

~~~
paradite
You are missing the point. My point is that that particular datetime is parsed
as

2013-02-03 19:54:00 +0000 PST

on some machines, and

2013-02-03 19:54:00 -0800 PST

on others. This is means that the timezone offset IS different and the
absolute time IS different. One is 8 hours ahead of another even though the
original string is the same.

------
polskibus
Slightly offtopic but I always thought that Mike Bostock's bl.ocks.org was
created to support D3 visualization sharing. This is the first time I see it
used for another language, somewhat contrary to it's designed use.

------
panic

        fmt.Printf("\n... and %v days after that ...\n", days)
        t2 := t1.Add(time.Duration(days) * time.Hour * 24)
        printTime(t2)
    

How is that supposed to work with daylight saving time? Won't _t2_ end up one
hour off from _t1_ if there is a daylight saving shift?

~~~
nulltype
I find it easier to think of times in UTC rather than local timezones. In UTC
(and in reality), those times will always be the same number of hours distance
from each other.

If you format it in different timezones (PDT and PST) then yeah, the formatted
times will be at different hours of the day.

~~~
heinrich5991
That's not true for UTC, you'd need TAI for that. UTC includes leap seconds.

------
riobard
Has anybody ever wondered how Go's date formatting would work if the language
is not English?

------
chmike
I'm surprised about the method to define a time format by example. There could
be an ambiguity between month and day if the example is badly chosen.

I prefer the C way with the struct and format.

How do I get the time offset to UTC time at a given date and location ?

~~~
tumdum_
There is no ambiguity - you are to provide predefined date in your layout.
That predefined date (Mon Jan 2 15:04:05 -0700 MST 2006) is chosen in a way so
that there is no ambiguity.

~~~
chmike
Thanks for the clarification. That info was missing in the date API
presentation. Indeed, there is no ambiguity.

Just two comments: 1\. The date is not easy to remember 2\. There is still an
ambiguity with hours representation. What if I want to represent hours without
a leading 0 for values smaller than 10 ? I guess I should than use my own
formatting instead of predefined formating.

~~~
wallyhs
You can remember it as 1 2 3 4 5 6 7: First month, second day, 3pm and four
minutes and five seconds in the year '06, offset -7.

------
JonnieCache
My favorite is time.Ticker: It returns a channel of Time objects, dispensed at
intervals of your choice. It even auto adjusts the interval based on how long
your receiver takes to run.

------
p1mrx
It's unfortunate that Go's time library cannot represent an infinite duration,
or timestamps in the infinite past or infinite future. This makes it difficult
to represent things like "this cache entry never expires", without relying on
auxiliary data.

Also, the minimum/maximum timestamp and overflow behavior seem to be poorly-
defined.

~~~
nulltype
You could use the zero value (t.IsZero()) for that if you wanted to. That
might be more confusing than a boolean flag though.

~~~
p1mrx
IsZero works okay as an "infinite past" value, such as marking that a cache
entry has already expired, but it's cumbersome to use as an "infinite future"
because zero is naturally _less_ than all the timestamps you're likely to
encounter.

It's also not a great idea to manually define the largest possible timestamp
as a sentinel, because Add(Duration) doesn't check for overflow.

------
NKCSS
Looking at the docs it looks fine; only thing I can remark is that it's too
bad that all sorts of constants are in the same namespace; I'd prefer to have
all the formatting formats in a separate namespace for intance to make it more
clear.

~~~
jerf
The problem with that is that Go has no support for a package that imports
something from one package and re-exports it, so if you put things in two
packages, you are in the general case requiring users to import two packages
now.

Another one of those things that makes sense to me at scale, as I've been
bitten before in the dynamic languages by excessively complicated re-exporting
systems, but locally is sometimes annoying.

------
alblue
Java is an excellent example of how not to do date/time APIs (pre Java 8
anyway). My answer (from years ago):

[http://stackoverflow.com/a/1969651](http://stackoverflow.com/a/1969651)

------
tel
Time libraries like this are a basket of inscrutable bugs waiting to happen. A
simply API deceiving its users into thinking they're dealing with a simple
subject. Time is anything but.

There are a few notions of time, each wildly different from the other, which
we as humans transparently conflate but which will throw computers into wild
undecipherable loops:

* Dates, specifically delimited by days, which are logical items of a calendar, not absolute points in time. They're never even _points_ , for that matter, but explicit intervals. * Which calendar? In modern, western times you're okay with just one, but historical, international, and especially historical international dates you will fail.

* Wall times or local times, e.g. what a human being might think the local time is. This is what we tend to mean when something ought to happen "at 3pm" but you should realize that there's no way to treat this uniformly: it holds _on a particular day_ , _in a particular location_. Even simple ideas like "3pm will occur once a day" may not genuinely be true.

* Universal times, of which there are a few variations each dealing with leap seconds differently, UTC, UT0/1/1R/2, TAI ([https://en.wikipedia.org/wiki/Universal_Time](https://en.wikipedia.org/wiki/Universal_Time))

* Intervals of universal time, like "10 seconds" which are the only things which behave sanely from a physical point of view. This is why CPU time or Epoch time is nice. Adding two universal time intervals produces an interval twice as long. Adding a universal time interval to a universal time point produces a new universal time point which, subject to leap seconds, may or may not appear to be the proper number of seconds away. Adding a universal time interval to anything else is nonsense.

* Intervals of wall-times like "half a day" which can be added meaningfully to combinations of wall-time and dates in a given calendar, useful for setting up human-interpretable re-occurrences.

* Intervals of dates which can be added to dates within a calendar

Other caveats apply, mostly having to do with the need to have an accurate
geopolitical rundown of time disputes (the "Olson" database is probably
sufficient) and a reasonably exact notion of where someone is in space that
they are interpreting times (go look up Indiana's time zone and then throw
away your standard US 4-tz notion).

Generally, the idea is that when dealing with humans you want to think of time
as being arranged into approximately 24-hour chunks (possibly longer or
shorter and then overlapping or with weird gaps which should be smoothed out)
assigned to each "day" of some assumed calendar. Then you can, given that
person's exact point in space, convert points in this notion of time into a
universal one using the Olson database. Converting intervals is harder and
must be done by converting both ends and then subtracting in universal time,
handling leap seconds if you care.

The only time library I've ever used in anger which handles all of this is
Haskell's `time` library.

    
    
        https://hackage.haskell.org/package/time
        http://two-wrongs.com/haskell-time-library-tutorial

------
MichaelGG
> time.Duration(mins) * time.Minute

Seems a bit clunky. Why not time.FromMinutes(mins) or something to that
effect? time.Duration is just "casting" an int64 to a Duration type?

~~~
chrisbroadfoot
Yes.

That's why you usually write it like this:

    
    
        time.Now().Add(10 * time.Minute)

