How to convert Unix epoch timestamps to Ruby objects

While working on API integrations with third-party services, I get a timestamp of the event in the request payload.

{ 
"timestamp": "1583122008",
...
}

Unix time or Epoch tine

This timestamp is the number of seconds that have elapsed since the Unix epoch, i.e., 00:00:00 UTC on 1 January 1970, minus leap seconds. In Ruby, it is very easy to get Unix time for the current time:

puts Time.now.to_i

But how do you take this epoch time and convert it back to a Ruby Time object?

Time::at to the rescue

Ruby has a handy method, Time::at, that can convert Unix epoch time back to a Time object:

ts = Time.now.to_i
# Time.at function
puts Time.at(ts)

It prints time in the local time zone, which, in my case, is IST. However, Time::at also supports timezone arguments, as shown:

ts = Time.now.to_i
puts Time.at(ts, in: "+01:00")

puts Time.at(ts, in: "-02:00")
=> 2020-03-02 05:13:24 +0100

=> 2020-03-02 02:13:24 -0200

The in keyword argument accepts timezones offset in the form of +HH:MM or -HH:MM.

The in argument was supported from Ruby 2.6 onwards. Before that, Time.at only accepted one argument. Time.at is provided by Ruby, not by Rails or Active Support.

Ruby’s lack of documentation

While looking at the solution to this parsing problem, I checked the documentation of the Time::at method on ruby-doc.org. I came across this:

If an argument is given, the result is in that timezone or UTC offset. If a numeric argument is given, the result is in local time.

This led me down to Ruby’s source code to understand what kind of arguments could be passed into Time::at. After checking the code, I found that the in argument accepts the following:

  • Timezone offset in the form of +HH:MM or -HH:MM:
ts = Time.now.to_i
Time.at(ts, in: "+01:00")

Time.at(ts, in: "-02:00")
=> 2020-03-02 05:13:24 +0100

=> 2020-03-02 02:13:24 -0200
  • UTC string:
ts = Time.now.to_i
Time.at(ts, in: "UTC")
 
=> 2020-03-02 04:13:24 UTC
ts = Time.now.to_i
Time.at(ts, in: "E")

Time.at(ts, in: "Z")
=> 2020-03-02 09:13:24 +0500

=> 2020-03-02 04:13:24 UTC

If we pass anything else, we get an error message:

ts = Time.now.to_i
Time.at(ts, in: "IST")
Traceback (most recent call last):
        2: from (irb):12
        1: from (irb):12:in `at'
ArgumentError ("+HH:MM", "-HH:MM", "UTC" or "A".."I","K"

This error message is from Ruby 2.7. In Ruby 2.6, the error message did not mention military timezones, it only talked about UTC offset.

I opened a pull request to improve the documentation of Time::at, and it got merged quickly. The documentation of Time::at will have all possible combinations for the argument in the next release of Ruby 👏🏻

If you want to perform this same trick in PostgreSQL, read my article on converting Unix epoch time to timestamps in PostgreSQL.

Free Resources

Attributions:
  1. undefined by undefined