36

Reading, writing, and serializing dates and times while keeping the time zone constant is becoming annoying. I'm using Ruby (and Rails 3.0) and am trying to alter the time zone of a DateTime. (to UTC) but not the time itself.

I want this:

t = DateTime.now
t.hour
-> 4
t.offset = 0
t.hour
-> 4
t.utc?
-> true

The closest I have come is this, but it's not intuitive.

t = DateTime.now
t.hour
-> 4
t += t.offset
t = t.utc
t.hour
-> 4
t.utc?
-> true

Is there any better way?

Daniel Beardsley
  • 19,907
  • 21
  • 66
  • 79
  • 3
    If you're actually using 'now' as a timestamp, then storing that in the database as a UTC-flagged version of the same time is a lie, correct? I personally experience the least headaches when all portions of the system speak UTC, and only in the views do I potentially adjust to display in the time zone of the server or browser. – Phrogz Dec 17 '10 at 20:43
  • It's difficult because I'm reading in dates from CSV files where the timezone is unknown or irrelevant, so I store them as UTC in the DB. But when queried with dates from the local zone (i.e. greater than `2010/01/01 8:00 pm`), it first converts that time to UTC (the source of my problem) before using it in the query. I'm trying to have the whole application be time-zone agnostic so that a user can load a CSV file with times in the 7:00 - 8:00 range, then query for 7:00 - 8:00 and have it return the same set of records. Instead, Ruby assumes a parsed time is in the local zone. – Daniel Beardsley Dec 17 '10 at 21:17

7 Answers7

48

Using Rails 3, you're looking for DateTime.change()

dt = DateTime.now
=> Mon, 20 Dec 2010 18:59:43 +0100
dt = dt.change(:offset => "+0000")
=> Mon, 20 Dec 2010 18:59:43 +0000
Alex Kremer
  • 1,513
  • 14
  • 10
13

As @Sam pointed out, changing offset is not sufficient and would lead to errors. In order to be resistant to DST clock advancements, the conversion should be done in a following way:

d = datetime_to_alter_time_zone
time_zone = 'Alaska'

DateTime.new
  .in_time_zone(time_zone)
  .change(year: d.year, month: d.month, day: d.day, hour: d.hour, min: d.min, sec: d.sec)
kogitoja
  • 1,050
  • 11
  • 11
  • 1
    This should be the best accepted answer.. You could also do Date.parse("2022-10-10").in_time_zone('Asia/Kolkata') which is much smaller in line length when you don't deal with hours/mins/seconds. – cegprakash Oct 19 '22 at 14:45
11
d = DateTime.now
puts [ d, d.zone ]
#=> 2010-12-17T13:28:29-07:00
#=> -07:00

d2 = d.new_offset(3.0/24)
puts d2, d2.zone
#=> 2010-12-17T23:28:29+03:00
#=> +03:00

Edit: This answer doesn't account for the information provided by comment to another answer that the desire is to have the reported 'hours' be the same after the time zone change.

Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • `new_offset` sounds useful, and docs make it look like it's what I'm after, but it simply coverts from one offset to another. As the question states, I'm looking to change the offset, while leaving the time alone. – Daniel Beardsley Dec 17 '10 at 21:06
  • 1
    For me this `DateTime.now.new_offset(-8.0/24)` was the only thing on this page that worked for my purposes--to place a bank transaction approx. from Pacific time, DST negligible. Probably since my simple script doesn't include frameworks mentioned nor Active anything. – Marcos Jan 27 '12 at 12:26
10

Using a number offset e.g. '+1000' wont work all year round because of daylight savings. Checkout ActiveSupport::TimeZone. More info here

Community
  • 1
  • 1
Sam
  • 6,240
  • 4
  • 42
  • 53
  • 8
    Providing an example of using `ActiveSupport::TimeZone` rather than just linking to the docs is preferred. – Joshua Pinter Aug 13 '17 at 15:08
  • @Sam OP is asking about "converting" to UTC. If we have a valid timestamp and valid offset, would problems could we possibly face? – x-yuri Jun 20 '18 at 14:40
9

I would use a Time object instead. So get the current local time then increment it by the UTC offset and convert to UTC, like so:

t = Time.now # or Time.parse(myDateTime.asctime)
t # => Thu Dec 16 21:07:48 -0800 2010
(t + t.utc_offset).utc # => Thu Dec 16 21:07:48 UTC 2010

Although, per Phrogz comment, if you just want to store timestamps in a location independent way then just use the current UTC time:

Time.now.utc
maerics
  • 151,642
  • 46
  • 269
  • 291
  • That's a big performance hit. This seems slower and more complex than the solution I already have above. – Daniel Beardsley Dec 17 '10 at 20:19
  • If you don't really care about DateTime objects specifically (just the current instant in time) then skip the parsing and use `Time.now`. I've edited my answer slightly. – maerics Dec 17 '10 at 21:06
  • This is exactly the same as the solution I have above, but with an additional string parsing step. – Daniel Beardsley Dec 17 '10 at 21:18
2

If someone(like me) has time zone in string format like "Pacific Time (US & Canada)"

Than this is the best way i found:

datetime.change(ActiveSupport::TimeZone[time_zone_string].formatted_offset(false))
Dorian
  • 7,749
  • 4
  • 38
  • 57
Zia Ul Rehman Mughal
  • 2,119
  • 24
  • 44
-4

You can specify the Time Zone you want to use in your app by adding the following line in the config file (config/application.rb): config.time_zone = 'Mumbai'

You can find the official documentation for the same here: http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html

The other option is that you "Monkey Patch" the 'DateTime' class.

Khoj Badami
  • 151
  • 2
  • 7