hara.time - time as a clojure map
by Chris Zheng,
cronj was the first real world project I ever wrote with clojure and my first library that was properly concurrent. I remember sitting there in my living room about 4 years watching Rich Hickey's thinking to myself: "this guy's got 100 threads running simultaneously and I can't even get 2 threads to coordinate properly". As much as I despaired back then, it provided the impetus to start learning about concurrency. I set my sights high. What better way to do it than to build a task scheduler?
Although the code for cronj has evolved, the architecture has not. Each part of the system has been kept very clean and work together like lego blocks. The system consists of:
- a clock,
- a mutable array of task handlers
- a ticker that coordinates between the two.
It actually very simple but it took a while to get there. As time went by, I saw reuse in the parts of the system so I then started stripping bits and pieces out of the cronj codebase, generalising the concept putting it into a generic utility library called hara. Eventually, almost every single bit of cronj became absorbed, broken down and reconstitued into hara - all except the time library.... until now.
The Gold Standard
clj-time is the gold standard for time in the clojure word. It was there 4 years ago and it's still very much a part of any self respecting clojure code base that deals with time (that statement pretty much covers any project with real world application). So needless to say, it's very popular.
At the time (4 years ago), Java did not have a very good library for time and the defacto standard was the joda-time library. clj-time wrapped joda-time and became the goto package for dealing with time. Like all the other projects, cronj also used clj-time with great result.
However, even then, I noticed particular quirks about the library that I did not like.
Partial Time
One thing that aggrevates me is the fact that I have to learn the intricate relationships between these particular joda-time datastructures:
- DateMidnight
- DateTime
- MutableDateTime
- LocalDateTime
- LocalDate
- LocalTime
I'm fine with DateTime
and MutableDateTime
because it's cool if we wish to optimise a little bit. But DateMidnight
is really stretching it. Then we have all the Local*
types. After a bit of head scratching Local*
types are just the standard DateTime
type that's missing data. So LocalDateTime
is missing a timezone, LocalTime
is missing both the timezone and the date and LocalDate
is missing both the timezone and the time of the day.
Having a seperate type for something that is missing data is silly in my opinion. It's a little bit like labelling a Policeman
an UncappedPoliceman
if he (and for record, I could have used she if the gender of the officer is female - but I'm assuming that the current police officer is male because technically, I would be labelling a female police officer a Policewoman
) is missing his hat, an UnarmedPoliceman
if he is missing his gun and baton, an UnarmedPolicemanWithGun
if he has a gun and... you get the point here.
I'm not saying that this library is badly designed. It more or less reflects how time has been butchered in our current society. Personally, it's confusing. And to make things even more interesting, we define concepts like Periods (which retain daylight saving) and Intervals (with retain strict time). The icing on the cake is when Locales and Timezones are thrown in and then, the mother of all confusions - Daylight Savings is then sprinkled like fairy dust on top of everything to make the world go round.
No wonder joda-time has lasted this long.
Replacing clj-time
4 years later, with the new java.time package, we suddenly have too much choice for time libraries. I had already implemented hara.io.scheduler
to work with java.util.Date
because I've always had a soft spot for java.util.Date
. However, cronj has been a popular library, and telling people to rewrite their date processing functions wasn't an option. Not to mention that there was a joda-time successor with the native java.time implementation and even the joda-time dudes are telling people to migrate to java.time on their website.
I decided that I should also write a generic, extensible wrapper for time and that hara.time was gunna support them all.
hara.time
hara.time is based around the simple observation that time really has two parts:
- the absolute value (a long)
- the representation (a long plus a timezone)
Once an absolute value and a timezone is known, then the representation is also known. So there is a very simple two way transformation that one can make to get this, and most date representations should have a way to support this:
long + timezone <=> representation
First and foremost, I decided that what I needed was a map. Calling (time/now)
should return a clojure map (the representation), and then this can be converted back and forth to different date datastructures that the framework
supports:
- java.lang.Long
- java.util.Date
- java.util.Calendar
- java.sql.Timestamp
- java.time.Instant
- java.time.Clock
- java.time.ZonedDateTime
- clojure.lang.IPersistantMap
As well as
- org.joda.time.DateTime (if the
hara.time.joda
package has been included)
There's alot more to tell but it's better if I leave it to the reader to explore the docs:
- hara.time - documentation page
- hara.time.joda - joda-time extensions
- hara.io.scheduler - how to upgrade cronj in your code
cronj
I'm pretty excited about this because I've tagged cronj as deprecated for a while but there was no other option for users. Now with the latest hara - 2.2.17
as well as the hara.time.joda extentions, hara.io.scheduler is now at feature parity with cronj (actually, it's much much much more advanced as of right now). My advice to all is to switch! Now! Please!