Search code examples
rubytimetimezone

How to alter the timezone of a Time in Ruby?


Suppose I have a Time object with a timezone (say, UTC). I want to reset the timezone to an arbitrary one (aka, not your local timezone) without altering any other parameters of the Time like "hour". Ruby as of Version 3.3 doesn't seem to provide any single method to do it (cf., Official doc of Time and Stackoverflow answers). How can I achieve this, then?

I note that for the DateTime class, the change method (seemingly) works as in this answer [Edit: it is a Rails method]. However,

DateTime class is considered deprecated. Use Time class.

as in Ruby 3 and so I want to know how to do it with Time class.

I also note that Rails implements some convenient methods as mentioned in these answers; but they are not available in pure Ruby (and in fact, in_time_zone of Rails changes another time parameter, hour).

How can I do it in pure Ruby?


Solution

  • The Time instance is immutable, meaning it is impossible to alter an existing Time object. Alternativly, it is possible, though slightly awkward to do in pure Ruby (cf., Official doc), to create a new Time instance based on an existing one, in which you set a new arbitrary "timezone […] without altering any other parameters of the Time", namely copying them all from the existing Time to the new one.

    I here list a few ways (Note: no singleton methods or instance variables are inherited):

    require "time"
    t = Time.now(in: "+00:00")
      # => <2024-02-24 01:23:45 +0000>
    
    t1= Time.new(*(t.to_a.reverse[4..-1]), in: "+09:00")
      # => <2024-02-24 01:23:45 +0900>
    
    t2= Time.new(t.year, t.month, t.day, t.hour, t.min, t.sec+t.subsec, in: "+09:00")
      # => <2024-02-24 01:23:45 ... +0900>
    
    ar = t.strftime("%Y %m %d %H %M %S %N").split.map(&:to_i)
    t3= Time.new(*(ar[0..4]+[ar[5]+ar[6].quo(1_000_000_000), "+09:00"]))
      # => <2024-02-24 01:23:45 ... +0900>
    
    t4= Time.at(t, in: "+09:00") - 9*3600
      # => <2024-02-24 01:23:45.... +0900>
    

    Here, t1 only considers down to a second, whereas the others consider down to a nanosecond.

    t2 is the most intuitive, but it is the most cumbersome. Here,Time#subsec returns a Rational and hence the formula involves no floating-point rounding error.

    t3 may have no benefit over t2. It involves no floating-point rounding error as in t2.

    Although t4 is less lengthy, the facts that it is against the DRY principle and that you must accurately specify the parity (plus or minus?) are significant downsides.