Words and Code

One writer’s journey from words to code.

Time: The Black Sheep of the Programming Family

#technicaltuesdays, rails, ruby

No matter where you’re from, what programming language you prefer, or which JavaScript framework you’re committed to, I think you’ll agree with me on one thing: time is a bitch. And no, I don’t mean the wrinkles on your forehead kind of time (although that sucks too); I’m talking about time as a programming concept.

I mean, you create some migrations, build some models, make some API calls, but then time shows up and brings down the party. I happened to learn all about programming’s royal pain in the ass while working with the Instagram API last week. After I finally got my code working, I took a look at my JSON response and saw this strange creature:

1
"created_time": "1423694433"

Yeah, I didn’t know what that was either. So I went down the rabbit hole and learned about all the different things there are to know about dealing with time in your Rails applications. So save yourself some time (HAHA get it, get it?) and get the lowdown below.

In The Beginning, It Was…1970?

Okay, so you know that weird JSON response I got? The one that looked like this crazysauce: "1423694433".

Well, believe it or not, that’s not just a random number – it’s a representation of time. When I first saw this in my API reponse, I knew that this definitely was neither a typical Ruby Time object, nor something I’d ever seen in any of the databases of my Rails applications (which usually uses the datetime format).

So, I asked the interwebz for help. It turns out that this format is something called Unix time (also sometimes referred to as POSIX time or Epoch time). Unix time is short for the “Unix Epoch”, which is what we refer to when we use Unix time formats. Basically what you need to know is this: when the first computer turned on at 00:00:00 Coordinated Universal Time (UTC) on Thursday, January 1st, 1970, time officially began. I mean, time just started. The computer turned on and BOOM! It just started counting.

If you’ve ever played around in irb, you’ve probably used this method to figure out what time it is:

1
2
3
♥ irb
2.1.2 :001 > Time.now
 => 2015-02-16 18:29:23 -0500

But that’s just one format of dealing with time. Want to know what time it is in the Unix Epoch? Simply apply the ever-handy to_i method:

1
2
2.1.2 :002 > Time.now.to_i
 => 1424129416 

And what if you want to convert a Unix timestamp (like the one in my JSON response) back to a Ruby Time object? Just use the Time.at method:

1
2
2.1.2 :003 > Time.at(1423694433)
 => 2015-02-11 17:40:33 -0500 

Okay, okay – but what about all the stuff that happened before 1970? How do we account for that? I’ll tell you how: NEGATIVE. NUMBERS.

1
2
2.1.2 :003 > Time.at('-1423694433'.to_i)
 => 1924-11-19 20:19:27 -0500 

Pretty sweet, right? Don’t worry; I’m about to complicate it a bit further.

Even More Types of Time

Another very common format for handling Time is ISO8601. There have been a lot of arguments in favor of ISO8601, its YYYY-MM-DD formatting being one of the foremost reasons for that debate. The ISO8601 format is also supported by various libraries, and can be sorted easily, which makes it the ideal candidate for your database.

An important thing to remember about using ISO8601 is that it’s part of the Ruby Standard Library, which means you must require it. (If you missed my explanation on the difference between the Ruby Standard Library and the Core Library, head over to last week’s post to catch up.)

But if we play with ISO8601 in irb, you’ll notice something kind of strange:

1
2
3
4
5
6
2.1.2 :004 > require 'time'
 => true 
2.1.2 :005 > Time.now.iso8601
 => "2015-02-16T19:09:38-05:00" 
2.1.2 :006 > Time.now.utc.iso8601
 => "2015-02-17T00:09:34Z" 

See that Z at the end of the string? That’s how the UTC time zone is represented in ISO8601, with the Z short for “Zero” time. It’s also worth reminding ourselves that Unix timestamps are also set in the UTC time zone.

Goddamn Time Zones

Look man, I’m not gonna lie to you: time zones will jack your shit up. Time zone bugs are so common, it’s like they’re just waiting there patiently, hoping that you’ll make a mistake.

But you’re a Rails developer – ain’t no time zone gonna stop you! Instead, you’re going to make your code defensive and subscribe to these best practices:

  1. Set your time zone in the config/application.rb file in your Rails app. Look for config.time_zone, and configure your time zone equal to that value. Rails will refer to that as your base time zone going forward, and ActiveRecord will convert between `UTC and your set time zone whenever you query.

  2. Use the in_time_zone method to convert Time to your system’s current set time zone. For example, Time.now.in_time_zone("EST").iso8601.

  3. Use Time.current whenever you make an ActiveRecord query (ActiveRecord will convert Time.current and convert it to UTC: Comment.where(["comments.published_at > ?", Time.current])

  4. DO NOT use Time.now! This returns your system’s time, and ignores whatever time zone you configured for your Rails app. Instead, use numerical attributes and methods (such as 1.day.from_now or Date.current), which rely on the time zone you configured in your application.rb to determine the return value.

There are a lot of loopholes when it comes to dealing with Time zones. You can read more about them on this super helpful post.

Time: Annoying, But Also…Awesome?

Ok, don’t get me wrong – time makes our lives miserable as developers. But it also presents some pretty epic challenges, which also makes our jobs interesting! (Look, I’m trying to point out the silver lining here.)

For example, leap seconds. Yes, really – it’s a thing. Leap seconds occur 1-2 times a year, on either June 30 and/or December 31st. They’re necessary in order to keep the Earth’s time of day close enough to the Sun’s mean time. But, how do you account for this in programs? Well, Google actually does something pretty cool: instead of adding one second, Google’s servers implement “leap smears”, which extend the seconds surrounding a leap second by just a little bit – enough to account for an entire extra second! Side note: the next leap second is on June 30th of this year! I hope you’re ready.

Another bizarre thing is Y2038. If this sounds reminiscent of Y2K, that’s because it is. Essentially, the 2038 problem boils down to this: On January 19, 2038, Unix timestamps will stop working because of a 32-bit overflow. So, all of the millions of applications that use this time format will have to either reconfigure to 64-bit systems, or choose a new format to store dates and times. There’s supposedly no universal solution to this, but hopefully someone will figure out a fix in the next 22 years.

Hopefully by that point, someone will have figured out how to get rid of time zones completely.

tl;dr?

  • Unix timestamps and ISO8601 are both good formats for storing date and time in your database. Do a little research to decide which of these formats is the best tool for whatever job you’re trying to accomplish.
  • Dealing with time zones is hard, but the Exhaustive Guide to Rails Time Zones will save you hours of banging your head against the wall – well, for time-related things, at least.
  • Want to be a Time object superstar? Read this post, which was featured on HackerNews, and peruse the Ruby documentation to get the skinny on all things time-related.