How Rails Can Reduce Your Monthly Headaches

Duration’s helping hand when months have varying lengths

All months have 30 days. Or 31 days. Oh, and February came to the party late with about 28.

We can live with that in our day-to-day lives by learning tricks and rhymes to remember how many days in a month. If we’re still unsure, we can just look at a calendar to confirm. But what about in code? How can we say “add a month”? Or see if something is one month ago? Do we use 30 days? 31 days?

With ActiveSupport::Duration we can just use #months. 1.month returns a duration with a value of 30.days, but also with an idea of one month.

def months * 30.days, [[:months, self]])

It’s because of this concept of parts ([:months, self]) that we get what we want when we do math with #months. Both Duration and DateTime specify the parts of the time when they can. This allows us to add or subtract a month without worrying about the number of days in that particular month. We can see this when using #parts.

=> [[:months, 1]]
> (2.months + 4.days).parts
=> [[:months, 2], [:days, 4]]

Great! So now we can do whatever we want with months and not have to worry about it. Well… almost. What’s the 30.days that #months uses to create a Duration? This is Duration‍’s Fixnum value it uses in situations where using parts doesn’t make sense, such as when comparing to a number of seconds.

> thirty_days, thirty_one_days = 30.days.to_i, 31.days.to_i
=> [2592000, 2678400]
> 1.month == thirty_days
=> true
> 1.month == thirty_one_days
=> false

This can turn into a problem when looking to see if two times were within a month of each other.

time2 - time1 < 1.month

Here 1.month will always be 30.days, so cases such as July 31 and August 30 will not be within one month of each other. This is an issue.

We can avoid cases such as this if we rewrite it to apply 1.month directly on one of the times:

time1 + 1.month < time2

Moral of the story? Apply the result of #month directly when using mathematical operations whenever you can. If for some reason you can’t, use caution. Is it okay that it is always 30 days? Would it be better to overshoot or undershoot with 31 or 28 days? Could it work to refactor or add an extra check? We’ve dealt with the month of February since King Numa Pompilius, the second king of Rome, introduced it to the calendar in 713 BC. With careful handling, we should be able to account for that tricky month as we keep working with it in computing.

Calendar* by Dafne Cholet is licensed under CC BY 2.0

Photo of Victoria Gonda

Victoria is a software developer working on mobile and full stack web applications. She enjoys exchanging knowledge through conference talks and writing.