I’ve written a library called
intime, which provides exhaustive integration
between the classes in the
library and some common Scala libraries. The most interesting problem I encountered was defining an
Order for Cats) for the
class. Because months and years cannot simply be expressed as a number of days, This post will discuss those issues.
is one of my favourite APIs to work with. Using these classes when they were released along with Java 8 taught me most
of what I know about working with dates and times. The first time I tried to convert the truly awful
was a formative programming experience. Maybe I’ll write more about the lessons I learned from that experience in
Among the key improvements that came with the
java.time classes was the ability to represent “amounts” of time in a
semantically clear way. This came through two classes:
Duration, which is basically a wrapper around an amount of elapsed nanoseconds, and
Period, which represents a number of elapsed days, months and years.
Duration is convenient, but fundamentally is a pretty simple class. Two given points on the “time line” will always be
separated by a number of seconds/nanoseconds. When it came time for me to define integrations for
Duration for my
intime library, they were pretty simple.
Period is an entirely different beast. Whereas the idea of a “day” is pretty simple in this context, the fact that it
is composed of “months” and “years” introduces lots of complexity. Exactly how long a month or a year is, depends on
what your calendar rules are, and where you sit in history.
In the end, the rules for combining two
Period instances (ie, defining a
were pretty simple. The additive operation is provided by
Period forms an abelian or commutative group (Cats
with this operation.
Period values, though, was an interesting problem.
Intuitively, we know that a
Period has an ordering. 1 day is clearly shorter than 2 days. 14 days is obviously shorter
than a month. One year is definitely longer than 150 days.
But there are some comparisons that don’t work intuitively. Is 1 month shorter or longer than 30 days? It’s shorter if if it’s February (28 or 29 days), but it’s longer if it’s March (31 days). A 5 year period might have two leap years (1827 days), or none (1825 days).
We should consider periods to be a partially ordered set.
That is, not all pairs of
Period values can be compared (eg 29 days vs 1 month), but most pairs can be compared. In
Scala we can represent this relationship with the
trait in the standard library, and
PartialOrder in Cats.
In this way, we can construct a simple formulation of how this partial ordering should work:
Adding a period
pto a base date
d' = d + p. Here,
d'is the date after
phas elapsed from the base date
Then, for any two periods
p₁ = p₂if
d + p₁ = d + p₂for all dates
p₁ > p₂if
d + p₁ > d + p₂for all dates
p₁ < p₂if
d + p₁ < d + p₂for all dates
p₂are incomparable otherwise
- 1 month
>27 days because no matter what date you pick, a date 1 month later will always be after a date 27 days later.
- 1 month is incomparable with 30 days because 1 month can be longer than, the same as, or shorter than 30 days depending on the date.
So how long is a month?
To efficiently compare two periods, we need to determine their minimum and maximum lengths. For periods under 1 year, variation comes from the fact that months vary in length. We can see this behaviour in the following table:
|Num months||Min length||Max length||Variation|
At first glance, you might imagine that because the shortest 1-month period is 28 days, you can just double this number to get the shortest two month period (56 days). In fact, the shortest 2-month period is 59 days. This is because you never have two back-to-back 28-day months. February is always preceded by January and followed by March, both of which have 31 days.
If we plot the “variation” in periods of n months, we can also see that something weird happens at multiples of 12 months:
We can see here that every 12 month period has either 365 or 366 days. This variation is obviously down to leap years, but it is interesting that when you pick a multiple of 12 months, the variation due to month length disappears.
How long is a year?
Periods over a year vary in length due to leap years. The algorithm for leap years introduces a bit more variation in period length than you might expect, since you “skip” a leap year in 3 of every 4 centuries.
The effect of this is that over a 400 year period, the variation in period length due to the number of leap years varies from 0 days to 3 days.
Interestingly, no matter what the start date, every period that’s a multiple of 400 years has exactly 146097 days.
Pulling it all together
Period then, can vary in length by up to 3 days due to the number of months, and by another 3 days depending on the
number of years. This means that for any given
Period, there are up to 6 other
Period values to which it cannot be