My first ever real job was to develop code which handled dates and times. today, more than 25 years later -- and that scares even me - I've been back to handling dates and times again.
My experience is that dates and times are disproportionately risky for code. I have found more bugs in date handling code, even my own, than I care to admit. A colleague found a classic example of code that worked on Wednesdays (See: http://web.media.mit.edu/~lieber/Lieberary/Softviz/CACM-Debugging/Hairie...) and when we worked on Meet-O-Matic, we found at one stage that our code worked all months of the year except January. Obviously good testing is essential -- the point of these cases is that good testing can be very hard, as this code tends to be sensitive to environmental factors. For example, if you write code on a Wednesday, and it works on a Wednesday, you won't notice it's broken until it suddenly fails to behave on Thursday or Friday. Since no code has changed, this can be somewhat inexplicable.
Today I found another example. Some code that had worked for years suddenly started to hang. It was written by a former employee, probably about five years ago, and landed on my inbox. I had upgraded a whole bunch of CPAN modules, so it did seem likely that one of them was to blame, but there was nothing obvious.
Fortunately, it took very little time to find the culprit - some code iterated through days, using Date::Manip to add a day, until it matched the last day of a range. This code now started hanging on a day, specifically the 4th April. Yes, you may have guessed, it was the daylight savings transition. Adding a day at the DST transition only adds 23 hours, so you end up on the same day. If you then remove the time (which the code did) you end up on the same day, and there's the loop condition.
I don't really like Date::Manip. I use it for parsing crontabs, but for most purposes I use DateTime, which is slightly clearer on the object-orientedness. DateTime handles durations differently. A day is a day -- and does not need to be 24 hours long. If you add a day to midnight on the 4th April 2010, you get midnight on the 5th April 2010. This is different from Date::Manip, which gives you 11pm on the 4th April. (Date::Manip has "business day" logic which is similar, but also skips weekends, and we didn't want that.)
I like the intuitiveness of DateTime's handling of durations with subtlety. Adding a day (and adding a month, or a year) do not correspond to fixed multiples, but they are (usually) intuitive. What DateTime has done is bundle up all that highly complex intuitive logic, with all the nasty complexity of timezones and transitions, into a clear module which works well.
We still need to port the rest of our module to use DateTime, but using it for the straight math of adding a day broke out of the loop just fine. However, I'm feeling a Test::DateTime module would be helpful. One which would hack into the time handling in a way which would allow testing with different time environments, including a range of timezones, a range of years, and all months of the year/days of the week, and the usual start-and-ends-of-months. This would have caught this bug much sooner, as one of the reasons we'd missed it was that in testing the DST had corresponded with a weekend, and we were skipping weekends internally. Just trying a few different years we'd have caught the problem sooner.
And for the record, I'm not the only one that noticed this. It's covered at Date::Manip::Problems. I stand by the view that adding a day is not always the same as 24 hours. Personally, I'd make "+1d" and "+24h" behave differently, as conceptually, they are different.