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.
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.
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.
> 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). :-)
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...
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.
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).
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
> 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)) { ... }
"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.
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
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
* 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.
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.
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.