How Rails Can Reduce Your Monthly Headaches
Duration’s helping hand when months have varying lengths
https://www.flickr.com/photos/dafnecholet/5374200948
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
ActiveSupport::Duration.new(self * 30.days, [[:months, self]])
end
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
.
> 1.month.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
Comments