Why I Rewrote Chronic
It seems like a pretty epic yak shave. If you want to parse natural language dates in ruby, you use Chronic. That’s just how it is. (There’s also Tickle for recurring dates, which is similar, but based on Chronic anyways.) It’s the standard, everyone uses it, so why oh why did I write my own version from scratch?
Three reasons I can see.
Chronic is unmaintained. Check the network graph for Chronic. A more avid historian could turn this into an epic teledrama, but for now here’s the summary: The main repository hasn’t had a commit since late 2008. Evaryont made a valiant attempt to take the reins, but his stamina only lasted an extra year to August 2009. Since then numerous people have forked his efforts, mostly to add 1.9 support. These efforts are fragmented though. The inertia of such a large project with no clear leadership sees every man running for himself.
Further, the new maintainers aren’t providing a rock solid base. From Evaryont’s README:
I decided on my own volition that the 40-some (as reported by Github) network should be merged together. I got it to run, but quite haphazardly. There are a lot of new features (mostly undocumented except the git logs) so be a little flexible in your language passed to Chronic. [emphasis mine]
This does not fill me with confidence.
Chronic has a large barrier to entry. Natural date parsing is a big challenge. In the original README, there are ~50 examples of formats it supports, and that is excluding all of the features added in forks in the last two years. The result is a large code base which is intimidating for a new comer, especially with no high level guidance as to how everything fits together. On a project of this size, “the documentation is in the specs” is insufficient. I know what it does, I need to know how it does it.
Chronic solves the wrong problem. I want an alternative to date pickers. As such, I don’t need time support, and I only need very simple day parsing. Chronic seems geared towards a calendar type application (“tomorrow at 6:45pm”), but also parses many expressions which simply are not useful in a real application either because they are obtuse - “7 hours before tomorrow at noon” - or just not how users think about dates - “3 months ago saturday at 5:00 pm”. (Note the last assertion is a totally unsubstantiated claim with no user research to support it.)
Further, it is not hard to find simple examples that Chronic doesn’t support. Omitting a year is an easy one: 14 Sep, April 9.
So what to do?
Chronic needs a leader. Chronic neads a hero. One man to reunite the forks, document the code, and deliver it to the promised land.
I am not that man.
I sketched out the formats I actually needed to support for my application, looked at it and thought “really it can’t be that hard”. Natural date parsing is hard; parsing only the dates your application requires is easy. One hour later I had a gem that not only had 100% support for all of the Chronic features I had been using, but also covered some extra formats I wanted (“14 Sep”), and could also convert a date back into a human readable description. That’s less time than I had already sunk into trying to get Chronic working.
Less than 100 lines of code, totally specced, totally solved my problem. Ultimately, I don’t want to deal with this problem, so I wanted the easiest solution. While patching Chronic would intuitively appear to be pragmatic, a quick spike in the other direction turned out to be worthwhile. Sometimes 80% just isn’t that hard.
September 23, 2010 at 7:11 PM
Great work Xavier.
I think that on the ruby community, we are "trained" to always try to find something that is already done for our projects, and quite frankly, this work very well the majority of time, as the ruby community is awesome on opensourcing lots of useful stuff, but of course, there are the exceptions, when projects run out of maintainers, like Chronic did.
Anyway, I am trying to think of a good way to add translations to Kronic (I am brazilian). Of course I could just fork it and add it to the two parsing methods (which I did), but I will try to think on a more flexible and unobtrusive way to accomplish that, without increasing complexity too much. Try to to that on the weekend.
Oh, and thanks man!
September 24, 2010 at 2:11 PM
I often see people desperately trying to use an inappropriate gem (or more often jQuery utility). You're right, sometimes it's better to just use the Pareto 80% and get enough to do what you need, particularly as you can do BDD along the way and trust what you've got.
September 24, 2010 at 2:27 PM
Reu, I couldn't see your fork? I had a go at i18n, no idea if it's any good since I don't use it: http://github.com/xaviershay/kronic/tree/i18n
September 24, 2010 at 2:33 PM
I wonder what was the reasoning behind:
Any dates without a year are assumed to be in the past.
That seems counter-intuitive to me. If I'm talking about October 4th, I'm talking about this year (2010). Likewise, talking about "August 1st" means I'm talking about the date in 2010.
In short, surely omitting the year in a date (whether it is in the past or future) should default to the current year not the previous year?
September 24, 2010 at 3:14 PM
Jamie,
In December, if I say 'January', I mean the future one, not the past one. Where do you draw the line?
September 24, 2010 at 4:40 PM
Hi! Thanks for working on Kronic - you're right, I tried, and I didn't do a good job.
At the time I tried merging all the random forks, I was (still? Perhaps...) a very green Rubyist, and even worse git user. There were many issues, and a lot of 'wet' code (as opposed to DRY code). I'm sure there are still more.
I added that warning, mostly expecting that the merged code would end up in the main repository. Of course, not seeing it actually happen hasn't helped, either.
Chronic is great, I just wish it was better. Hopefully, by keeping it slim, Kronic can at least fill some gaps.
PS: Oh yeah: My first actual work on Chronic was to add some stripping code. Can you (properly) add this feature? For example, in a calendaring application, given the string "going to the movies at 5 pm", Kronic.parse could return an array ["going to the movies", <Time 5pm>] as that would just be awesome.
Once again, thanks for trying, and for helping out. I'm sure many people appreciate it.
September 24, 2010 at 5:52 PM
Discovering forks that have eclipsed original projects is a common enough problem that something ought to be done about it. Perhaps GitHub could show a notice on repos that have been inactive for over a month, that have forks that have been active more recently? The notice could show forks sorted by a combination of the # of watchers and how recent the last commit was.
Just a thought...
Also, Kronic looks cool. I need something like it for a project in my queue. I've been thinking that an auto-complete for recent dates would be nice. For example, if I typed in 1, it would suggest "1 minute ago", "10 minutes ago", "1 hour ago", etc. I didn't really think about only showing recent dates until I saw your project on GitHub.
September 25, 2010 at 5:51 PM
Great that your rewrote the abandoned gem. We need clean rewrites. I love your saying you brought it down to 100 lines. Neat code, i checked it out :D
We need minimal stuff that does only its job, not huge "monolithic" messes that are hard to maintain. I am trying to clean up some monsters I made, too !
I was using Chronic with Highline, will move over to Kronic soon.
September 30, 2010 at 6:23 AM
Jamie, my app is a diary, so you're always inputting things that have already happened.
Agree probably not the most intuitive choice for a general gem.
February 11, 2011 at 5:25 PM
I know approximately nothing about ruby, but am interested in substituting kronic for chronic in the timetrap gem, which has trouble on my system due to ruby 1.9.2. How might one do this? I downloaded the source (https://github.com/samg/timetrap) and grepped through every file and only find these instances of "[C/c]hronic":
in cli.rb:
in timer.rb
def process_time(time) case time when Time time when String chronic = begin Chronic.parse(time) rescue => e warn "#{e.class} in Chronic gem parsing time. Falling back to Time.parse" end if parsed = chronic parsed elsif safe_for_time_parse?(time) and parsed = Time.parse(time) parsed else raise ArgumentError, "Could not parse #{time.inspect}, entry not updated" end end endIs this as simple as just replacing "Chronic" with "Kronic"? There isn't a whole lot involving it.
There's a bunch in timetrap_spec.rb as well, but that's a bit long to post. Just interested to hear what your general recommendation would be and if the code above looks like kronic would be a suitable replacement.
February 15, 2011 at 9:49 AM
Answered via email - short answer is no, since Kronic only deals with dates, not times.