

Ruby 1.9 doesn't implement ISO 8601 properly - gourlaysama
http://tommorris.org/posts/8114

======
haberman
The article is making two somewhat unrelated complaints:

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#...](http://ruby-
doc.org/stdlib-1.9.3/libdoc/date/rdoc/Date.html#method-c-iso8601)) 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.

~~~
jeffdavis
If you support only a subset of the standard, or if you extend the standard,
that may still be OK (not without problems, but still OK in some situations).

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.

~~~
badgar
So it's not even a subset of ISO 8601, it's just the umpteenth ad-hoc date
parsing method with semantics defined as "portion of a 3k line C file with no
comments":
[https://github.com/ruby/ruby/blob/7ea675732ac1dac72f07756498...](https://github.com/ruby/ruby/blob/7ea675732ac1dac72f07756498706678d8725719/ext/date/date_parse.c#L2600)

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.

------
viraptor
Actually the article seems to be a bit confused:

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

~~~
tommorris
Yeah, I wrote it in a bit of a hurry on the train. Will fix.

------
zdw
Maybe he should subclass it and make it strict, renaming it the RFC3339
method:

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

------
secure
I think it would make the point much clearer if the author of that article
would actually explain a use case for this (apparently incorrectly handled)
syntax which I have never ever come in contact with :).

~~~
bvdbijl
Well the point is that they shouldn't call the method iso8601 if it doesn't
implement the whole iso8601 standard

~~~
viraptor
That's a bold statement. I don't think I've ever seen a program / library
which implements any standard completely and without issues. It's not uncommon
to see a list of things that are not done, incomplete, or just called out as
wrong and rejected from the implementation.

It's not perfect, but no implementation will ever be imho.

~~~
harshreality
Not implementing part of a standard is different from implementing it
differently than the standard specifies.

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.

------
wyuenho
So do tons of other libraries and PL standard libraries. jodatime in Java,
moment.js, JS' Date object, python's datetime stdlib module, python-dateutil
etc. ISO-8601 is actually quite a difficult standard to program for. 9 out of
10 times, these libraries either don't support just year, year-month, or both.
9 out of 10 times, when a datetime object is formatted to a string, you see
inconsistencies about that dangling Z marker at the end.

Tom Morris is right, they all suck when it comes to ISO-8601 support.

~~~
eCa
I wonder how Perl's DateTime::Format::ISO8601 [1] holds up...

[1] <https://metacpan.org/module/DateTime::Format::ISO8601>

~~~
draegtun
Seems to be fine:

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

2 - <https://metacpan.org/author/ZEFRAM>

3 - <https://metacpan.org/module/Date::Darian::Mars>

------
petercooper
Not only that, but ISO 8601 also allows you to just specify the year, but
_Date.iso8601("2012")_ raises an ArgumentError "invalid date". Nor does it
support "week dates", _Date.iso8601("2012-W01")_ raises an ArgumentError too.

I don't know the standard, but either Wikipedia is wrong or the Date
implementation is sorely lacking.

~~~
eli
It may well be lacking in completeness, but I bet it covers over 99% of actual
real-world ISO8601 dates. When was the last time you ran across a "week date"?

~~~
lobster_johnson
Very common in Europe, where businesses often operate relative to weeks; we
frequently use week numbers for that reason (an abomination in my opinion).
Ruby has decent but not great support for ISO week numbers.

~~~
tommorris
Yep, an example: I edit OpenStreetMap. The opening_hours property allows one
to represent a wide range of opening hours.
<https://wiki.openstreetmap.org/wiki/Key:opening_hours>

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"

------
jeffdavis
Was it just me or was the wording in the article slightly confusing? The
second paragraph changes from talking about the standard to talking about the
ruby implementation with out saying so in either case and no transition.

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.

------
madisp
Maybe it's rfc3339 and not iso8601? e.g. the method has been named wrong?

~~~
ellie42
It is. Luckily enough that was fixed in rails recently:
[https://github.com/rails/rails/commit/31f807c7aaaf12c16ea157...](https://github.com/rails/rails/commit/31f807c7aaaf12c16ea1572559c00f49d54d6f43)

------
gcv
While we're on this subject, does anyone know of a decent C or C++ date-time
library? Something like Joda Time. I know Boost has a couple of options but
they look a bit less capable.

~~~
gcv
Replying to my own post, in case someone stumbles on this while searching:
ICU. It's rather baroque, but seems to do the right thing. I recommend writing
a C++11 wrapper for it (I might actually release this at some point in the
future).

------
vorg
> Ruby 1.9 doesn't [do such and such] properly

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!

------
zimbatm
Well done you found an issue. The next step is to report it and if possible
submit a patch: <http://bugs.ruby-lang.org/projects/ruby/wiki/HowtoReport>

~~~
pjscott
This isn't exactly a bug; it's an API-design disagreement.

~~~
zimbatm
In any case if he wants to champion the change he will get more success
discussing it over there. It's not that hard but yeah it's easier to moan over
a blog post than actually fixing the issue.

~~~
tommorris
I'm actually working with other data-folk trying to document ISO 8601
compliance across a wide range of date-time libraries and document it
publicly, probably on the microformats.org wiki or W3C wiki.

------
piglop
Why not just fill a bug report?

~~~
Skoofoo
That would make _too much sense_! Whining about it as though the world owes
you something is the way to go.

~~~
tommorris
I'm very sorry that I posted an entry on my personal blog.

