Almost every single programmer has to deal with DateTime nuances in their app. Here at Bevel, we’re building a subscription based service, and I’ve banged my head against the wall many times. A wise person once said to learn from your mistakes. An even wiser person once said, learn from other people’s mistakes too.
Mistake #3: DateTime Comparisons or Thinking Today is Less than Yesterday
Say you’re trying to query all orders made before today, but should
created_at be greater than or less than today?
DateTime is stored as a super long integer in the database, a representation of seconds that has passed since 1970-01-01 00:00:00 UTC. So for example, Jan 1, 2014 happened 1391126400 seconds after midnight on Jan 1, 1970. As we move forward in time, the current time is greater than any time in the past. Therefore, the following is true:
Mistake #2: Time Zones or Thinking The World Resolves Around You
Ok, what the heck is UTC? And who decided what is Central time? A SF’er recently lamented the fact that every time s/he sets up a conference call, people assume the time zone is NYC. So s/he will ding interviewees for not doing the research of where we’re located. Instead of asking why UTC, just know it is the canonical time zone that you will be dealing with. In Rails, the default setting is to save your DateTime records in UTC. Don’t try to change this. This is universally accepted, and it’ll be easier to calculate timezone differences off of UTC than from your specific time zone. At 4pm out in California, all my scheduling related specs would fail on Semaphoreapp, but they would pass locally. Turns out that it’s actually midnight in UTC, and therefore the next day. One easy way to avoid this is to use
Date.current, instead of
Date.today. They will always return the current time/date in UTC.
Tricks and Helpful Tools
Rails comes with ActiveSupport::TimeZone, a nice library of timezone helpers and worth checking out.
When you write your tests, don’t get cute with created_at. Just use timecop. With this gem, you can artificially travel through time, to get a sense how an object’s behavior will change.
Most of our customers reside in the U.S., but on the east coast. Displaying dates that are inherently UTC end up confusing everyone. One easy way to handle this is to specify the timezone that the date is being displayed in. Another is to use jQuery-timeago. This jQuery library determines the timezone on the client side, and displays the date and/or time accordingly. This means you can cache the page, and the time will automatically update. You get readable things like “1 minute ago,” instead of ugly timestamps
Mistake #1: Overlooking Nuances of the Gregorian Calendar
Don’t fall into the assumption that a month is 30 days, especially if you’re in the monthly billing business like us. 4 months out of the 12 have 30 days, 7 have 31 and 1 has 28, well, most of the time. If you assume that all months have 30 days, you will fall into this trap.
1 2 3 4 5 6 7 8 9
In the first example, 30 days skips over February. In the second, 30 days isn’t enough to move you to the next.
Because of the inconsistencies in the calendar, there isn’t an elegant method that you can write to handle all situations. ActiveSupport does make a valiant effort to help, BUT beware, write tests for each situation: leap years and the different types of months. DO NOT rely on built in ActiveSupport (do I sound jaded yet?)!
That being said, here is how Rails can help you. Rails is smart enough to know if you want to increment a date by 1 month, what that means. It will always go to the same day of that month. If that day doesn’t exist, like Feb 30th, it defaults to the last day of the month.
1 2 3 4
There are some limitations, though, mainly arithmetic and subtraction of time does not always get what you’d expect.
1 2 3 4
Write tests. Bang Head. Try Again.