The question Alexey Vassiliev (who reported the bug) asked me is “how many days are there between March 1, 2012 and March 31, 2012?” Assuming Midnight for both dates the common sense answer is, of course, 30 days. My component however was returning 29 days… why?
At the root of the problem is question of precision. My component treated “days” as 24 hour periods… period. So, in this (somewhat fringe but common enough) case the two dates being passed happened to fall within two different two different timezone offsets due to the damned, damned devil that is Daylight Savings Time. So, because of DST this calculation returned 29 days and 23 hours, not 30 days. I “fixed” this to match the common sense expectation by automatically normalizing DST (a behavior that can be disabled) for difference calculations.
Subtraction is Simple?
However this revealed a rabbit hole that has consumed a week of my brain. When you say “How many days (or years, weeks, months, hours, minutes, seconds, etc) difference is there between two dates?” what do you really mean? After giving this question way too much thought I realized that there are at least three distinct ways to consider it:
1) The “Actual” difference deals with full periods – 24 hour days, 60 minute hours, etc. In this sense 12pm on a Tuesday is zero days difference from 10am on a Wednesday. There’s only 22 hours difference – not a full day. Feb. 15th to Mar. 15th and Sep. 15th to Nov. 15th are both “one month apart” even though they’re different absolute lengths of time.
2) The “Logical” difference is concerned with ordinal calendar or clock values. Logically 2pm is different from 3pm and Mar. 4th is different from Mar. 5th. Logically (and yes, “logical” as the label for this is debatable) 11pm on a Tuesday is “one day” away from 2am on a Wednesday – Tuesday and Wednesday are two different days – there must be a difference between them (even though the actual time difference is only two hours)!
3) “Complete” or “Whole” periods are another way to think of things that basically combine the two concepts: only full, complete instances of defined ordinal values are considered. So here 12pm on a Tuesday is NOT one day difference from 12pm on a Wednesday. There may be 24 hours between them but they’re split between two partial days. However 12pm on a Tuesday and 12pm on a Thursday (48 hours difference) have a “one day” difference between – the complete day Wednesday being that one day.
Each of these concepts has their place and after considering it so deeply for so long it’s become clear to me that we interchange them often and seamlessly throughout our days. All of this led me to completely rewrite the diff() method of my date component to allow the user to specify which of the concepts to apply to the calculation.
In addition I’ve also added (and standardized to all other appropriate methods) several common sense, non-standard date parts like “quarter-hour”, “half hour” and “business day” (this last being one of the biggest pains in the ass I’ve encountered for a long time).
To support the changes to the diff() and add() functions I created several date-versions of common math methods. The new methods, round(), floor() and ceil() allow you to easily round-to-closest, round down or round-up any date to a specific precision. It honestly surprised me, once I thought of it, that this wasn’t more common.
I’ve also added some convienence methods for dealing more intelligently with Daylight Savings Time, but I’ll leave a discussion that for another time.