Hacker News new | past | comments | ask | show | jobs | submit login
Time in Go (ocks.org)
128 points by laex on Sept 10, 2015 | hide | past | favorite | 75 comments



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.


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.


Erlang is an example of a language correctly handling these aspects, regarding time correction, drift, and so on: http://learnyousomeerlang.com/time


Agree with everything you said, but to add more lets talk about Timezones too! For example, I'm continually annoyed at the pervasive usage of US/* Timezones by engineers. I routinely have to bring up Arizona, but then I start to blow their mind when I get to Indiana. Usually by the time I bring up India and Nepal I've completely lost them and I don't even get a chance to bring up countries changing Timezones (or even crazier, changing whole days by "moving" themselves to the other side of the international date line)


I'd actually prefer to keep timezones out of the time library itself, and have them only part of string <=> time conversion. Basically move all TZ handling to the parse/format phase of input & output, with the understanding that times in the time lib are all UTC, all the time.

... because I've had to write TZ-handling code way too many times, on top of systems with poor support. Think "implement timezones in T-SQL" and you'll shudder along with me.


> Go's "time" library is deceptively simple.

Has any language initially shipped with a non-broken date / time library? (See: SQL, java, python,...)

Clearly a very hard thing to get right, but post-JodaTime there a fewer excuses for APIs like:

> AddDate(y, m, d) to add a given number of years, months, days:

which pretty much defines 'deceptively simple'.


With Rust, we pulled time out of the standard library before 1.0, specifically because we knew it wasn't up to the task. So if "no library" counts as a "non-broken" library...

We still don't have great libraries yet, but they're coming, slowly. Chrono is pretty good.


  > Chrono is pretty good.
For purposes of comparison to other libraries, here's a link to Chrono's documentation: https://lifthrasiir.github.io/rust-chrono/chrono/


> With Rust, we pulled time out of the standard library before 1.0, specifically because we knew it wasn't up to the task.

This honestly strikes me a very good response. Saddling a still-growing community with sub-par core datatypes is worse than their absence.

> So if "no library" counts as a "non-broken" library...

Of course if I had phrased my question in terms of the 'first shipped date / time library' then Rust still has a chance to follow this illustrious trajectory for languages (i.e. really mess up). :-)


.Net's is pretty good.

    var date = DateTime.UtcNow.AddDays(24).AddYears(-10);
In use:

   var billFor = 12; // months

   var firstDayOfMonth = DateTime.Today
       .AddDays(1 - DateTime.Today.Day);
    
   var billDates = Enumerable.Range(billFor)
       .Select(n => firstDayOfMonth.AddMonths(n));


I'm going to have to disagree. DateTime is pretty limited, especially when it comes to timezones. I highly recommend NodaTime instead.


Apple's date/time support (part of the Foundation framework in both iOS and Mac OS X) is fantastic. Better than anything else I've ever seen.


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...


> 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.


And I would also add Perl's DateTime library to this list - https://metacpan.org/pod/DateTime


The power grid people do that, but after the leap second. It takes about four hours, as every 1800 RPM synchronous motor and generator on the grid makes 30 extra turns.


Haskell's `time` (and its faster, lensier, API-compatible copy, `thyme`) handle these issues with aplomb and have been around a while.

You'll also want to include timezone-series and timezone-olson for accurate TZ handling.

    https://hackage.haskell.org/package/time
    https://hackage.haskell.org/package/thyme
    https://hackage.haskell.org/package/timezone-series
    https://hackage.haskell.org/package/timezone-olson


I was going to mention the same thing wrt leap seconds: if a company at Google's scale could apply a "hacky" solution to this, then you don't really need a more "scalable" solution. The fact that it simplifies things massively for the developers can be a turn off for some people, though.


You've got it backwards: a company of Google's scale can do this because they're already providing and administering their own NTP servers. For a company of a smaller scale, that's an unnecessary cost.

That said, I'm intrigued at the idea of a public NTP server that does Google-style leap-second smearing. Does anyone know if one exists?


All the Network Time Protocol servers that I know of handle leap-seconds in one way or another. Some do leap-second smearing and I believe that newer versions of NTPD and OpenNTPD both support smearing. However, it appears (with only a cursory examination) that the Google time servers start smearing before the actual leap second is inserted and I've never seen that done anywhere else (or I missed it by reading too quickly).

See https://news.ycombinator.com/item?id=10199686

Also note that big changes in the way time is handled (leap seconds, etc.) in the near future because of the upcoming meeting of the International Telecommunication Union (ITU) at the World Radiocommunication Conference in November 2015.


The ITU has 4 methods for dealing with leap seconds, and method D is to change nothing. See the lack of consensus for any change in last week's presentations at http://www.itu.int/en/ITU-R/conferences/wrc/2015/irwsp/2015/... especially the "Input Document WRC-15-IRWSP-15/8" presentation by Zuzek.


I think most language standard libraries just crib heavily off of the C interface, to their detriment.

Using a preset date (the 2006-01-02) instead of format strings was really inspired; it took me about 10 minutes of staring at the documentation to understand how it worked, but it's a lot more readable once you get it.


The only problem is the numbering scheme is brain dead because its based off a specific layout which isn't very sensible to start with.

They could've easily gone with something following significance and it would be much more memorable:

2006-05-04 03:02:01

But instead in gotime you write it with month and day out of order because they referenced this completely nonsensical format instead: Mon Jan 2 15:04:05 MST 2006

I can't remember this one for the life of me.


> 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.

Then you might be interested in Joda-Time[1] and Boost.Date_time[2]. I'm not saying they are better or worse than Go's time library. They are quite powerful in their own right however.

Taking the example you presented:

  take the more generic problem of: I have
  a time, I have a duration, how do I tell
  if that duration has passed?
In Joda-Time, it could look like (all types are in org.joda.time):

  new Interval (yourStartDate, theDuration).contains (
    candidate
    );
And in Boost.Date_Time (loosely based on this example[3]):

  date d = day_clock::local_day ();
  time_period allDay (ptime (d), ptime (d, hours (24)));

  if (allDay.contains (someTimeInstance)) { ... }
1 - http://joda-time.sourceforge.net/userguide.html

2 - http://www.boost.org/doc/libs/1_59_0/doc/html/date_time.html

3 - http://www.boost.org/doc/libs/1_59_0/doc/html/date_time/exam...


"I think Python's "datetime" is a great example of a not great time library."

"Datetime" is a bit strange at first, but not that bad. The parsing side is weak; I've been trying to get a parser for RFC3339 / ISO8601 timestamps into that library since 2012.[1] Sure, there are libraries for that in PyPi. There were four of them in 2012, all too broken to parse mail and RSS feed timestamps reliably. Now, there are six.

[1] https://bugs.python.org/issue15873


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


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.


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


I can find it later, but I just had this issue this week with timestamps sent from a browser, supposedly in RFC3339, but Go's RFC3339 format was not compliant with it. So naturally I made my own format template. Alas, the milliseconds part had arbitrary precision, which Go's parser doesn't like. So I had to revert to trying the timestamp with one, two ,three trailing digits, etc.

Still, this is the only issue I have with the time library, and even that has only bothered me a couple of times in 3 years of work, so no biggie. Other than that it's indeed the best time library I've ever worked with.


I wasn't able to reproduce this issue, time.Parse(time.RFC3339Nano, "2015-09-10T06:31:39.442Z") seems to work correctly as long as the number of digits between "." and "Z" is 0-9


The problem is that the browser sends it in a different format (it's the HTML5 date-time input), 3399 simplified or something like that. So I ended up doing something like:

> formats := []string{ string(time.RFC3339), "2006-01-02T15:04:05.0", "2006-01-02T15:04:05.00", "2006-01-02T15:04:05", "2006-01-02T15:04",}

To make sure I have all cases covered


I remember we had the same problem parsing time formats. Can't remember any specifics though (might have been timezone offsets), but I think parsing would be less error prone when there wouldn't be that magic date time formatting by example style used.

I can totally understand that it is often easier to use, but compatibility with strftime is good for the experienced developer. There is a reason, why sites like [1] and several 3rd party libs [2] exist.

[1]: http://fuckinggodateformat.com/

[2]: https://github.com/search?q=language%3Ago+strftime&ref=searc...


http://play.golang.org/p/hCoZ3tdeM_ am I doing this correctly?

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


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

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


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"


I mentioned this elsewhere in this thread, but abbreviations are not unique across timezones. The rules for timezones change based on locale, and if you have multiple zones with PST as an abbreviation, which one do you use? It has to be an explicit choice or it's almost certainly going to be wrong. There is no way to get America/Los_Angeles from PST without knowing the locale that the date and time is from.


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

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


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.


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.


Agreed. Coming from JavaScript, I'm really happy with how intuitive it is to handle date and time with Go.

I'm new to the language but I'm really happy with the simplicity and the design decisions made by the core team.


So I copied the example from http://golang.org/pkg/time/#Parse

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

(timezone offset is zero)


That seems to be an issue with the playground: 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.


Time is location-dependant (time zones).


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.


This works correctly if your locale includes the PST timezone (works fine on my computer). They should probably have a better example though.


By "works fine" you mean in your local environment or the go playground?

Because it could potentially cause problems if the execution result varies from geographical locations and user's environment. I for one have no idea if my computer has a locale that includes PST timezone.


Sorry I meant in my local environment.

It's clear that the execution varies, as Parse(), unlike ParseInLocation() depends on the local environment, and play.golang.org probably uses some UTC locale.


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.


    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?


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.


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


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


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 ?


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.


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.


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.


1. http://strftime.org/ is not easy to remember

2. You could format it with a placeholder for the hours, then replace the placeholder with the hours you want. Oddly this is only a problem for 24 hour time, 12 hour time lets you drop the leading zero.


Looks like you could do this: http://play.golang.org/p/Lq3xv6f-PI


Is there a "method" to get day light saving as well ? The possibility to get a loc object is great and missing in the C API. I guess/hope it encapsulate all the info found in the tz database.


You might just want to use the tz info database directly.


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.


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.


I believe the idiomatic way would be to have 2 "Add" functions:

  func (c *Cache) AddExpiring(k Key, e Entry, d time.Duration) {}
  func (c *Cache) Add(k Key, e Entry) {}
  func (c *Cache) GetWithRemaining(k Key) (e Entry, left time.Duration) {}
Not the prettiest, but it does the job. That sentence could be Go's motto.


If only Go had better support for algebraic data structures..


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


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.


You can use nil to represent "no expiration."


Time is a struct, so nil is not a legal value. You would have to store a pointer (var t *time.Time) instead.


Yes, I was suggesting storing a pointer to the entire structure.


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)

* 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


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.


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.


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


> 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?


Yes.

That's why you usually write it like this:

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




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: