Ruby 1.9 doesn't implement ISO 8601 properly
tommorris.org
tommorris.org
1. Date.iso8601 does not correctly parse some ISO 8601 formats like ordinal dates or month-only dates.
2. The Date class does not support year-only or year/month granularity.
In response to the first, the documentation for Date.iso8601 (http://ruby-doc.org/stdlib-1.9.3/libdoc/date/rdoc/Date.html#...) says:
"Creates a new Date object by parsing from a string according to some typical ISO 8601 formats."
Not "all ISO 8601 formats," "some typical ISO 8601 formats." While supporting more formats is obviously desirable, the method never claimed to be a complete ISO 8601 implementation. It would be nicer if it were, but would also take more work and it appears that 100% spec support was neither prioritized nor promised.
I believe the second complaint is less justified. Asking a single class (Date) to support multiple granularities opens up a lot of semantic conundrums that have no obvious resolution. What should this return?
Date.iso8601("2012-01") < Date.iso8601("2012-01-05")
If one is truly month granularity and one is day granularity, then the two aren't directly comparable. It would make more sense to me to have the month granularity representation be a separate class altogether (YearMonth?) with easy and well-defined conversions between the two.Which I'll probably have to write. ;)
I think you’ll find a lot of APIs (or formats) claiming to support an “ISO 8601 date” when in fact they mean a date of the form “YYYY-MM-DD” — it’s a common misunderstanding, but I think better that, than actually allowing the full range of dates allowed by the actual ISO 8601 standard :)
However, I strongly agree that ranges of time are a very useful concept, and I spent a lot of time implementing Range Types[1] in postgresql for that reason. I think Ruby should have both ranges of time and points of time (and ideally work with the SQL counterparts seamlessly).
[1] http://www.postgresql.org/docs/9.2/static/rangetypes.html
I've recently done a lot of work using time ranges (a scheduling app) in C#, using the TimePeriod library available via NuGet. Very helpful library, but it would have been even better if DB2 (which I have to use quite often) supported a range type like you've implemented for PostgreSQL.
I agree, but that gets a little too philosophical. Does an integer exist? What about characters: is my "a" really the same as your "a"?
The way I see it, there are a lot of practical reasons to look at time as a total order for some things, even if ranges are available. Programming languages and databases should model reality, but only in the sense that it's relevant to what you are trying to do. If you are writing a scheduling app, then ranges of time are very important, and should be their own data type. If you just want to tell your customers when they bought some widget, a point in time makes more sense.
If you define the total order of a set of time ranges as an ordering on their beginnings, then you have the ordering you're talking about. If you compare time ranges that have different granularities the results might not make a ton of sense depending on what you're trying to do, but they'll be consistent. Eg: 2012 < 2012-05 < 2012-05-03 < 2012-05-03T15:00:00. Is the year 2012 "less than" May 3, 2012? Interpreted as a point in time, yes, but interpreted as time periods no, not really, because they overlap. But 2012 might be a perfectly valid value for when a customer bought some widget; if you ask them they may not remember the date any more specifically than that, and if you're deciding whether or not to process a warranty claim just knowing the year might be sufficient.
What is the data type of the beginning of a range of time, if not a point in time? I'm having trouble even defining ranges without points.
I'd be interested to hear your suggestions in more detail. There's a temporal data LinkedIn group that you are welcome to join (I haven't been as active lately, but I'll try to get back into it). It would be a better forum to discuss matters like this.
This case seems like an outright violation, however. If the standard says that ####-## is a year-month date with month granularity, and you interpret it as an ordinal date, then that's a clear violation. It means that others communicating with you won't just get an error, they will get the wrong answer, which is much worse.
PS I did my undergrad thesis on Ruby; I love the language. But now that I have a job where people read my code more often than it's written, I understand why things like clear semantics matter.
And why method names should be precise since they lead developers to assume behavior. Didn't your english teacher tell you a poem's title means something? So do method names. "iso8601" implies some meaningful relationship to ISO 8601, but the only relationship here is that the coders of this method were thinking hard about ISO 8601 when they wrote it.
> If you aren’t doing ordinal dates, you aren’t doing ISO 8601.
It seems ruby1.9 does implement ordinal dates just fine:
irb(main):002:0> Date.iso8601("2012-012")
=> #<Date: 2012-01-12 ((2455939j,0s,0n),+0s,2299161j)>
irb(main):005:0> Date.iso8601("2012-366")
=> #<Date: 2012-12-31 ((2456293j,0s,0n),+0s,2299161j)>
What it does not implement is the "YYYY-MM" format: irb(main):010:0> Date.iso8601("2012-12")
=> #<Date: 2012-01-12 ((2455939j,0s,0n),+0s,2299161j)>http://tools.ietf.org/html/rfc3339
Basically, RFC3339 is ISO8601 cut down to the most straightforward implementation (YYYY-MM-DDTHH:MM:SS), without the "Ordinal Date" variant that is at issue here.
It's not perfect, but no implementation will ever be imho.
YYYY-MM is year-month, not year-day365. It's not very reasonable to interpret yyyy-mm as year-day365 in a function called iso8601 when the standard says that's not how to interpret it.
Parsing it incorrectly is bad news.
2) Just because you didn't come in contact with ordinal dates doesn't mean others don't need it.
3) The article has a link to https://en.wikipedia.org/wiki/Ordinal_date. The wiki page doesn't have any specific use cases but just use your imagination.
Tom Morris is right, they all suck when it comes to ISO-8601 support.
$ re.pl
>> use aliased 'DateTime::Format::ISO8601';
>> ISO8601->parse_datetime("2012-012");
2012-01-12T00:00:00
>> ISO8601->parse_datetime("2012-366");
2012-12-31T00:00:00
>> ISO8601->parse_datetime("2012-12");
2012-12-01T00:00:00
>> ISO8601->parse_datetime("2012");
2012-01-01T00:00:00
And if it isn't then there is also Date::ISO8601 [1] which is written by Zefram [2] who is renowned for being a Date/Time nut [3] :)1 - https://metacpan.org/module/Date::ISO8601
I don't know the standard, but either Wikipedia is wrong or the Date implementation is sorely lacking.
I've been reading the source code of one ISO 8601 implementation in JavaScript and writing my own in Java. Looks like I'll have to fix the Ruby one too.
Given that it's used by, oh, HTML and XML, not properly implementing ISO 8601 is really no big deal.</sarcasm>
A lot of public parks in London are open at different times during summer and winter.
Here's an example I added personally, the churchyard of St Anne's Church in Soho. http://www.openstreetmap.org/browse/way/40879988
"week 1-13 Mo-Su 10:00-16:00; week 14-43 10:00-18:00; week 44-52 10:00-16:00"
If I understand correctly, the complaint is that: ISO 8601 defines ####-## to be a year and month only, and it should have the granularity of a month; but Ruby treats it as an ordinal date.
It seems like it could be said much more clearly with an example:
irb(main):011:0> Date.iso8601("2012-12")
=> #<Date: 2012-01-12 ((2455939j,0s,0n),+0s,2299161j)>
It's treating ####-## as an ordinal date, when the standard says that it's a month-granularity date.Expect to see more of these headlines now Ruby 2.0.0 is out, as Rubyists start nudging users over by painting v 1.9 as defective. Good way to avoid the Python 3 and Perl 6 conundrums I suppose!