Ruby Tricks 5 - Adding a Time Zone to a Date Time String

Every now and then, we get data that has a date time string in the local time zone but with no time zone specified. This short note is on how to attach the correct time zone to the data so that it can be used in systems that rely on the time zone.

The problem is common – we might have data that looks like 2020-07-21 16:30 or even like 202007211630 and we want to make it a DateTime object so that it can be used in other places. The normal approach is to use DateTime.parse for this.

1
2
3
4
5
require 'net/http'
require 'Date'
txt = '2020-07-21 16:30' # or txt = '202007211630'
ts = DateTime.parse(txt) 
#=> #<DateTime: 2020-07-21T16:30:00+00:00 ((2459052j,59400s,0n),+0s,2299161j)>

You’ll notice that we get this:

#<DateTime: 2020-07-21T16:30:00+00:00 ((2459052j,59400s,0n),+0s,2299161j)>

It correctly parses the time stamp but the time zone is set to UTC since there was no time zone specified. For me, this is wrong since the string refers to a time stamp in the Singapore zone (UTC+8). However, it is surprisingly difficult to change the time zone of a DateTime object. You can’t just set change the zone or the offset from UTC. Support is better in Rails (of course!). It’s best to use something from Rails since it takes care of problems with time zones such as Daylight Saving Time and so on. Fortunately, Singapore is simple since we are on the equator and don’t have any issue with Daylight Saving Time.

If you don’t want to use help from Rails in your Ruby code, there seem to be two ways to handle this:

  • Once you get the DateTime object, you can subtract the number of hours from it to convert the time into UTC and then the UTC time zone is correct. This is done by hours/24 being subtracted from the timestamp you have. (note: it’s important to have it as a floating point number such as 8.0, 7.5 or such so that integer division does not happen)
    1
    2
    3
    4
    5
    6
    7
    require 'net/http'
    require 'Date'
    txt = '2020-07-21 16:30' # or txt = '202007211630'
    ts = DateTime.parse(txt)
    #=> #<DateTime: 2020-07-21T16:30:00+00:00 ((2459052j,59400s,0n),+0s,2299161j)>
    ts = ts - (8.0/24) 
    #=> #<DateTime: 2020-07-21T08:30:00+00:00 ((2459052j,30600s,0n),+0s,2299161j)>
    

  • The other alternative is to not use Date.parse and add the time zone to the string itself – this is described next

Adding the Time Zone

If you have a string such as 2020-07-21 16:30+08:00 with the timezone embedded in the string, actually DateTime.parse does a great job with it and will correctly set the timezone for the timestamp. So, the first option is to treat your string to be str + '+08:00' and then get DateTime.parse to work with that.

However, if you have it as 202007211630 and add the time zone to it so that you get 202007211630+08:00, the complexity of the input is too much for parse and it ignores the timezone suggestion. You still get the same data and with timezone set to UTC. This is where it helps to go to the not-so-sexy but super-reliable strptime method instead. The DateTime.strptime method allows you to specify the format of the input string while parsing the string (it’s basically like strftime but much less frequently used).

Here’s what we will do:

1
2
3
4
5
6
7
8
require 'net/http'
require 'Date'
time = '202007211630'
dtxt = time + "+08:00" # force add the time zone for SGT

# using strptime to force read the time zone in SGT
ts = DateTime.strptime(dtxt, "%Y%m%d%H%M%z") 
#=> #<DateTime: 2020-07-21T16:30:00+08:00 ((2459052j,30600s,0n),+28800s,2299161j)>

Keep in mind the two disadvantages of doing this:

  • You need to provide the format string which means that this works with a defined format only. In comparison, DateTime.parse is very flexible.
  • Since we are adding a fixed timezone string (‘+08:00’), this will not work for places where you have to deal with Daylight Saving.

If you keep these two restrictions in mind, this might work in your case and is likely better than adding a number of hours to your information, etc.

As always, this is for me to be able to remember how to do it but if it helps someone, that’s great! Also, if you have some comments, please add below so that I can reflect changes here, especially if you know a better way to do this.

comments powered by Disqus