Time in Go
bl.ocks.org
bl.ocks.org
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.
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.
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.
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.html2 - 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...
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.
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...
JSR 310/Project Kenai/Joda Next is often held up as the gold standard of complete, correct datetime handling.
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?
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.
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... 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.
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'.
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));We still don't have great libraries yet, but they're coming, slowly. Chrono is pretty good.
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). :-)
> 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/"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.
Because it seems that golang does not recognize the abbreviations at all.
I can only assume that the time zone name alone is insufficient to resolve ambiguous times.
Unless there is a way to get "America/Los_Angeles" from "PST"
On the other hand, -0700 isn't much longer than PST and parses directly.
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.
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.
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.
> 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 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...
I'm new to the language but I'm really happy with the simplicity and the design decisions made by the core team.
and it did NOT work as expected: http://play.golang.org/p/Xd9oEeSffd
(timezone offset is zero)
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.
It seems counter-intuitive to me that the behavior of time.Parse would depend on a "system location", but apparently it's by design.
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.
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?If you format it in different timezones (PDT and PST) then yeah, the formatted times will be at different hours of the day.
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 ?
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.
Also, the minimum/maximum timestamp and overflow behavior seem to be poorly-defined.
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.
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.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-tutorialAnother 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.
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?
That's why you usually write it like this:
time.Now().Add(10 * time.Minute)