https://bugs.ruby-lang.org/https://bugs.ruby-lang.org/favicon.ico?17113305112018-06-29T04:47:39ZRuby Issue Tracking SystemRuby master - Bug #14879: Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is createdhttps://bugs.ruby-lang.org/issues/14879?journal_id=726992018-06-29T04:47:39Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><p>I have added some specs for this behaviour here: <a href="https://github.com/ioquatix/time-zone/blob/cdde65cd8f29d6d9fc645e2182093a7146048b44/spec/time/zone_spec.rb#L91-L99" class="external">https://github.com/ioquatix/time-zone/blob/cdde65cd8f29d6d9fc645e2182093a7146048b44/spec/time/zone_spec.rb#L91-L99</a></p>
<p>You can see test results here: <a href="https://travis-ci.org/ioquatix/time-zone" class="external">https://travis-ci.org/ioquatix/time-zone</a></p>
<p>The work around for this bug was to convert whatever is the resulting time back into the correct timezone: <a href="https://github.com/ioquatix/time-zone/blob/cdde65cd8f29d6d9fc645e2182093a7146048b44/lib/time/zone/timestamp.rb#L79-L82" class="external">https://github.com/ioquatix/time-zone/blob/cdde65cd8f29d6d9fc645e2182093a7146048b44/lib/time/zone/timestamp.rb#L79-L82</a></p>
<p>I'm open to suggestions for ways to improve this.</p> Ruby master - Bug #14879: Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is createdhttps://bugs.ruby-lang.org/issues/14879?journal_id=727012018-06-29T05:30:32Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><p>I checked the specs and JRuby doesn't have these issues, just FYI.</p> Ruby master - Bug #14879: Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is createdhttps://bugs.ruby-lang.org/issues/14879?journal_id=727042018-06-29T08:19:11ZHanmac (Hans Mackowiak)hanmac@gmx.de
<ul></ul><p>the problem might not be the add or substract</p>
<p>what does this show for you?<br>
<code>TZ=UTC ruby -e 'require "time"; puts Time.parse("5pm NZT")'</code></p>
<p>because on my system it shows +0000 too</p> Ruby master - Bug #14879: Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is createdhttps://bugs.ruby-lang.org/issues/14879?journal_id=727052018-06-29T08:51:05Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><p>I see, you may well be right! I will check on my end because this was supposed to be a simplified version of the behaviour I was seeing.</p> Ruby master - Bug #14879: Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is createdhttps://bugs.ruby-lang.org/issues/14879?journal_id=727072018-06-29T10:13:12Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><p>Okay, I tried more complex example which was my original repro.</p>
<pre><code>git clone https://github.com/ioquatix/time-zone
cd time-zone
^_^ > rake console
[1] pry(main)> time, zone = Time::Zone.parse("5pm", "Pacific/Auckland")
=> [2018-06-29 17:00:00 +1200, "Pacific/Auckland"]
[2] pry(main)> time
=> 2018-06-29 17:00:00 +1200
[3] pry(main)> time + 1
=> 2018-06-29 17:00:01 +1200
[4] pry(main)> time - 1
=> 2018-06-29 16:59:59 +1200
[5] pry(main)>
^_^ > TZ=UTC rake console
[1] pry(main)> time, zone = Time::Zone.parse("5pm", "Pacific/Auckland")
=> [2018-06-29 17:00:00 +1200, "Pacific/Auckland"]
[2] pry(main)> time + 1
=> 2018-06-29 05:00:01 +0000
[3] pry(main)> time - 1
=> 2018-06-29 04:59:59 +0000
[4] pry(main)>
</code></pre>
<p>It does seem like something odd is going on here. My apologies if I've overlooked something. Thanks for your input so far.</p> Ruby master - Bug #14879: Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is createdhttps://bugs.ruby-lang.org/issues/14879?journal_id=727152018-06-29T14:24:18Zjeremyevans0 (Jeremy Evans)merch-redmine@jeremyevans.net
<ul></ul><p>As Hans correctly showed, addition/subtraction does not change the utc_offset, the utc_offset is already set to 0 before the addition/subtraction. That is because <code>TZ=UTC</code> and the fact that NZT is not a recognized time zone by the time library (the time library only supports a few time zones specified by ISO 8601 and RFC 2822). You can add time zones if you want to support them:</p>
<pre><code>TZ=UTC ruby -e 'require "time"; Time.singleton_class::ZoneOffset["NZT"] = 12; puts Time.parse("5pm NZT")
2018-06-30 17:00:00 +1200
</code></pre>
<p>I think this should be closed.</p> Ruby master - Bug #14879: Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is createdhttps://bugs.ruby-lang.org/issues/14879?journal_id=727212018-06-29T21:45:43Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><p>Jeremy, thanks for your interest.</p>
<p>Here is a minimal repro:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1">#!/usr/bin/env ruby</span>
<span class="c1"># https://bugs.ruby-lang.org/issues/14879</span>
<span class="nb">require</span> <span class="s1">'time'</span>
<span class="no">ENV</span><span class="p">[</span><span class="s1">'TZ'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"Pacific/Auckland"</span>
<span class="n">time</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"5pm"</span><span class="p">)</span>
<span class="no">ENV</span><span class="p">[</span><span class="s1">'TZ'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"UTC"</span>
<span class="nb">puts</span> <span class="n">time</span>
<span class="nb">puts</span> <span class="n">time</span><span class="p">.</span><span class="nf">utc_offset</span>
<span class="nb">puts</span> <span class="p">(</span><span class="n">time</span> <span class="o">+</span> <span class="mi">1</span><span class="p">).</span><span class="nf">utc_offset</span>
<span class="nb">puts</span> <span class="n">time</span> <span class="o">+</span> <span class="mi">1</span>
</code></pre>
<p>Thanks for the suggestion regarding <code>ZoneOffset</code>. However, that does not seem sufficient for capturing DST rules?</p> Ruby master - Bug #14879: Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is createdhttps://bugs.ruby-lang.org/issues/14879?journal_id=727222018-06-29T21:46:05Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><p>Here is the output of running the minimal repro:</p>
<pre><code>2018-06-30 17:00:00 +1200
43200
0
2018-06-30 05:00:01 +0000
</code></pre> Ruby master - Bug #14879: Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is createdhttps://bugs.ruby-lang.org/issues/14879?journal_id=727232018-06-29T22:17:22Zjeremyevans0 (Jeremy Evans)merch-redmine@jeremyevans.net
<ul><li><strong>Subject</strong> changed from <i>Adding/subtracting from time can change utc_offset unexpectedly.</i> to <i>Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is created</i></li></ul><p>ioquatix (Samuel Williams) wrote:</p>
<blockquote>
<p>Jeremy, thanks for your interest.</p>
<p>Here is a minimal repro:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1">#!/usr/bin/env ruby</span>
<span class="c1"># https://bugs.ruby-lang.org/issues/14879</span>
<span class="nb">require</span> <span class="s1">'time'</span>
<span class="no">ENV</span><span class="p">[</span><span class="s1">'TZ'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"Pacific/Auckland"</span>
<span class="n">time</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"5pm"</span><span class="p">)</span>
<span class="no">ENV</span><span class="p">[</span><span class="s1">'TZ'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"UTC"</span>
<span class="nb">puts</span> <span class="n">time</span>
<span class="nb">puts</span> <span class="n">time</span><span class="p">.</span><span class="nf">utc_offset</span>
<span class="nb">puts</span> <span class="p">(</span><span class="n">time</span> <span class="o">+</span> <span class="mi">1</span><span class="p">).</span><span class="nf">utc_offset</span>
<span class="nb">puts</span> <span class="n">time</span> <span class="o">+</span> <span class="mi">1</span>
</code></pre>
<p>Thanks for the suggestion regarding <code>ZoneOffset</code>. However, that does not seem sufficient for capturing DST rules?</p>
</blockquote>
<p>Correct, <code>Time.parse</code> doesn't support that. It uses different timezones for DST rules (e.g. PST and PDT). In your case you may want NZST and NZDT.</p>
<p>Your example results are due to the fact that you are changing <code>ENV['TZ']</code> after creating the <code>Time</code> instance, which affects new time instances, and <code>(time + 1)</code> creates a new <code>Time</code> instance.</p>
<p>Here's a modified example:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="nb">require</span> <span class="s1">'time'</span>
<span class="no">ENV</span><span class="p">[</span><span class="s1">'TZ'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"Pacific/Auckland"</span>
<span class="n">time</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"5pm"</span><span class="p">)</span>
<span class="nb">puts</span> <span class="n">time</span>
<span class="c1"># 2018-06-30 17:00:00 +1200</span>
<span class="nb">puts</span> <span class="n">time</span><span class="p">.</span><span class="nf">utc_offset</span>
<span class="c1"># 43200</span>
<span class="nb">puts</span> <span class="n">time</span> <span class="o">+</span> <span class="mi">1</span>
<span class="c1"># 2018-06-30 17:00:01 +1200</span>
<span class="nb">puts</span> <span class="p">(</span><span class="n">time</span> <span class="o">+</span> <span class="mi">1</span><span class="p">).</span><span class="nf">utc_offset</span>
<span class="c1"># 43200</span>
<span class="no">ENV</span><span class="p">[</span><span class="s1">'TZ'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"UTC"</span>
<span class="n">time</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"5pm"</span><span class="p">)</span>
<span class="nb">puts</span> <span class="n">time</span>
<span class="c1"># 2018-06-29 17:00:00 +0000</span>
<span class="nb">puts</span> <span class="n">time</span><span class="p">.</span><span class="nf">utc_offset</span>
<span class="c1"># 0</span>
<span class="nb">puts</span> <span class="n">time</span> <span class="o">+</span> <span class="mi">1</span>
<span class="c1"># 2018-06-29 17:00:01 +0000</span>
<span class="nb">puts</span> <span class="p">(</span><span class="n">time</span> <span class="o">+</span> <span class="mi">1</span><span class="p">).</span><span class="nf">utc_offset</span>
<span class="c1"># 0</span>
</code></pre>
<p>I think it is unreasonable to expect <code>Time#+</code> and <code>Time#-</code> to have special handling for the case where <code>ENV['TZ']</code> is modified after the <code>Time</code> instance is created, but I'll admit that it is subjective whether the methods should preserve the receiver's <code>utc_offset</code> in such cases. I would say it shouldn't be expected, because <code>Time#+</code> and <code>Time#-</code> modifies <code>utc_offset</code> when crossing DST boundaries, and keeping the same <code>utc_offset</code> would lead to undesired behavior:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Time</span><span class="p">.</span><span class="nf">now</span>
<span class="c1"># => 2018-06-29 15:13:22 -0700</span>
<span class="no">Time</span><span class="p">.</span><span class="nf">now</span> <span class="o">-</span> <span class="mi">86400</span><span class="o">*</span><span class="mi">200</span>
<span class="c1"># => 2017-12-11 14:13:30 -0800</span>
</code></pre>
<p>I'm updating the subject to more accurately reflect the issue.</p> Ruby master - Bug #14879: Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is createdhttps://bugs.ruby-lang.org/issues/14879?journal_id=727262018-06-29T22:58:52Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><blockquote>
<p>In your case you may want NZST and NZDT.</p>
</blockquote>
<p>Expecting users to know the time zone in advance is not feasible. User should be able to say "This date, time at this location" and it computes the correct offset. Anyway, it's a separate issue.</p>
<blockquote>
<p>Your example results are due to the fact that you are changing ENV['TZ'] after creating the Time instance, which affects new time instances, and (time + 1) creates a new Time instance.</p>
</blockquote>
<p>Yes that is what I suspected.</p>
<blockquote>
<p>I think it is unreasonable to expect Time#+ and Time#- to have special handling for the case where ENV['TZ'] is modified after the Time instance is created, but I'll admit that it is subjective whether the methods should preserve the receiver's utc_offset in such cases. I would say it shouldn't be expected, because Time#+ and Time#- modifies utc_offset when crossing DST boundaries, and keeping the same utc_offset would lead to undesired behavior.</p>
</blockquote>
<p>I think there are two separate cases you discuss. The first one being whether <code>Time#+/-</code> should retain the <code>utc_offset</code>, and whether <code>Time#+/-</code> should be able to change <code>utc_offset</code> when going cross DST changes. You've used the second case to argue against the first.</p>
<p>I think the 1st case is a bug.</p>
<p>I think the 2nd case is almost a bug but in theory is reasonable behaviour.</p>
<p>The problem is, <code>-0700</code> is a offset from UTC, and doesn't encode enough information to relate to DST rules AFAIK. Let's use Canada as an example. Some areas are -7 hours all year around, other areas use -6 hours for DST. So, how do you know enough information what case it is, considering you only have <code>-0700</code>? (<a href="https://en.wikipedia.org/wiki/Time_in_Canada" class="external">https://en.wikipedia.org/wiki/Time_in_Canada</a>)</p>
<p>So, I'd be interested to know where those DST rules are coming from, because that stuff is hard to encode, subject to the whims of governments, and changes at the drop of a vote.</p>
<p>Coming back to the bug report, I don't think adding/subtracting seconds should change the <code>utc_offset</code>, unless the user has actually specified a physical time zone on which DST rules are known (e.g. "Pacific/Auckland") and the new time crosses a DST boundary which affects the <code>utc_offset</code>. Every other case where the UTC offset changes, I'd assert, is a bug.</p> Ruby master - Bug #14879: Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is createdhttps://bugs.ruby-lang.org/issues/14879?journal_id=727272018-06-29T23:07:08Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><p>By the way, perhaps it's not clear, but <code>utc_offset</code> is not a time zone, nor is <code>Time.zone</code>, because just stating <code>MST</code> is not enough to disambiguate. You need to specify <code>Canada/Saskatchewan</code> (MST only) or <code>Canada/Mountain</code> (MST/MDT). Practically speaking, the best computation we have for <code>utc_offset</code> is the time zone database (i.e. government rules), a specific date+time in UTC, and a specific zone name from said database. That can then give us the <code>utc_offset</code>. Since several time zones might map to the same UTC offset, it's not easy to go in reverse without additional help.</p> Ruby master - Bug #14879: Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is createdhttps://bugs.ruby-lang.org/issues/14879?journal_id=787212019-06-19T19:55:37Zjeremyevans0 (Jeremy Evans)merch-redmine@jeremyevans.net
<ul></ul><p>Using Ruby 2.6's new timezone support for Time, I think you can get the behavior you want, so that time calculations do not change based on <code>TZ</code>.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">ENV</span><span class="p">[</span><span class="s1">'TZ'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"Pacific/Auckland"</span>
<span class="n">time</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">at</span><span class="p">(</span><span class="no">Time</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="s2">"5pm"</span><span class="p">),</span> <span class="ss">in: </span><span class="no">TZInfo</span><span class="o">::</span><span class="no">Timezone</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="s1">'Pacific/Auckland'</span><span class="p">))</span>
<span class="no">ENV</span><span class="p">[</span><span class="s1">'TZ'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"UTC"</span>
<span class="n">time</span>
<span class="c1"># => 2019-06-20 17:00:00 +1200</span>
<span class="n">time</span> <span class="o">+</span> <span class="mi">1</span>
<span class="c1"># => 2019-06-20 17:00:01 +1200</span>
<span class="n">time</span><span class="p">.</span><span class="nf">utc_offset</span>
<span class="c1"># => 43200</span>
<span class="p">(</span><span class="n">time</span> <span class="o">+</span> <span class="mi">1</span><span class="p">).</span><span class="nf">utc_offset</span>
<span class="c1"># => 43200</span>
</code></pre> Ruby master - Bug #14879: Time#+ and Time#- do not preserve receiver's utc_offset if ENV['TZ'] is modified after receiver is createdhttps://bugs.ruby-lang.org/issues/14879?journal_id=807002019-08-13T05:27:49Zjeremyevans0 (Jeremy Evans)merch-redmine@jeremyevans.net
<ul><li><strong>Status</strong> changed from <i>Open</i> to <i>Closed</i></li></ul>