From: "nobu (Nobuyoshi Nakada) via ruby-core" Date: 2024-12-15T08:02:49+00:00 Subject: [ruby-core:120247] [Ruby master Bug#20951] Confusing handling of timezone object's `#utc_to_local` results Issue #20951 has been updated by nobu (Nobuyoshi Nakada). Tracker changed from Misc to Bug Backport set to 3.1: REQUIRED, 3.2: REQUIRED, 3.3: REQUIRED Moved to Bug to back port the documentation update. ---------------------------------------- Bug #20951: Confusing handling of timezone object's `#utc_to_local` results https://bugs.ruby-lang.org/issues/20951#change-111016 * Author: andrykonchin (Andrew Konchin) * Status: Feedback * Backport: 3.1: REQUIRED, 3.2: REQUIRED, 3.3: REQUIRED ---------------------------------------- I am looking into the timezone object feature (that is supported by various Time class methods) now and I am confused by the current implementation. Specifically, how a time-like object **that is not inherited from the Time class** is handled. A time-like object is returned for instance from the timezone object's `#utc_to_local` method. The documentation states that: > A Time-like object is a container object capable of interfacing with timezone libraries for timezone conversion. Also > The zone value may be an object responding to certain timezone methods, an instance of Timezone and TZInfo for example. And indeed the `TZInfo::Timezone` class works as expected. But when I try to use for time-like objects a brand new class not inherited from Time - it works incorrectly. Let's consider an example with `TZInfo::Timezone`: ```ruby require 'tzinfo' zone = TZInfo::Timezone.get("Europe/Kiev") # UTC+2 time = Time.now.utc puts time.to_i # 1734107333 puts Time.now(in: zone) # 2024-12-13 18:28:53 +0200 puts zone.utc_to_local(time) # 2024-12-13 18:28:53 +0200 puts zone.utc_to_local(time).to_i # 1734107333 ``` And now an example with a brand new class. I make an assumption, that as far as `zone.utc_to_local(time).to_i` doesn't change Unix timestamp (it equals `time.to_i`, that's 1734107333), so in a new class also `#utc_to_local` should return not modified value too. ```ruby TimeObj = Struct.new(:year, :mon, :mday, :hour, :min, :sec, :isdst, :to_i) zone_obj = Object.new def zone_obj.utc_to_local(t) TimeObj.new(t.year, t.mon, t.mday, t.hour + 2, t.min, t.sec, t.isdst, t.to_i) # <=== adjust hours (`hours + 2`) to match "Europe/Kiev" timezone (that's UTC+2) end ``` Unfortunately it produces incorrect result: ```ruby puts Time.now(in: zone_obj) # 2024-12-13 18:28:53 +0000 <====== wrong UTC offset puts zone_obj.utc_to_local(time) # # puts zone_obj.utc_to_local(time).to_i # 1734107333 <===== the same Unix timestamp ``` So now result time object has wrong utc offset - `+0000` instead of `+0200`. Okey, so probably Unix timestamp should be adjusted as well. Let's check: ```ruby def zone_obj.utc_to_local(t) TimeObj.new(t.year, t.mon, t.mday, t.hour + 2, t.min, t.sec, t.isdst, t.to_i + 2 * 60 * 60) # <===== adjust #to_i as well so it returns timestamp + 2 hours end puts Time.now(in: zone_obj) # 2024-12-13 18:28:53 +0200 <======= correct UTC offset puts zone_obj.utc_to_local(time) # # puts zone_obj.utc_to_local(time).to_i # 1734114533 <====== different Unix timestamp ``` Now we have correct UTC offset `+0200` despite `zone_obj.utc_to_local(time).to_i` returns not original offset but an adjusted one. I assume the difference is caused by a special treatment of time-like object inherited from the Time class. So its `utc_offset` property is used only. But for all the other classes the `#to_i` is used instead. ```ruby zone.utc_to_local(time).class.ancestors # => [TZInfo::TimeWithOffset, TZInfo::WithOffset, Time, Comparable, Object, PP::ObjectMixin, Kernel, BasicObject] ``` This difference is confusing so I think it makes sense either to document it (I mean to document that `#to_i` should return adjusted value for non-related to Time classes) in case it's intentional or to change behaviour for non-related to Time classes and rely not on `#to_i` to calculate UTC offset but on difference in `sec`/`min`/`hours` values otherwise. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/