Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#next_time breaks on DST end #97

Open
trafium opened this issue Mar 25, 2024 · 4 comments
Open

#next_time breaks on DST end #97

trafium opened this issue Mar 25, 2024 · 4 comments
Assignees

Comments

@trafium
Copy link

trafium commented Mar 25, 2024

Issue description

According to https://www.timeanddate.com/time/change/estonia?year=2012
In Estonia, 2012 DST ended on October 28 after 03:59 (EEST) clock turned backward to 03:00 (EET).
Some evidence confirming this when working with Time class:

irb(main):030> Time.parse("2012-10-28 03:00:00 Europe/Tallinn")
=> 2012-10-28 03:00:00 +0200
irb(main):031> Time.parse("2012-10-28 02:00:00 Europe/Tallinn") + 60.minutes
=> 2012-10-28 03:00:00 +0300
irb(main):032> Time.parse("2012-10-28 02:00:00 Europe/Tallinn")
=> 2012-10-28 02:00:00 +0300
irb(main):033> Time.parse("2012-10-28 02:00:00 Europe/Tallinn") + 60.minutes
=> 2012-10-28 03:00:00 +0300
irb(main):034> Time.parse("2012-10-28 02:00:00 Europe/Tallinn") + 119.minutes
=> 2012-10-28 03:59:00 +0300
irb(main):035> Time.parse("2012-10-28 02:00:00 Europe/Tallinn") + 120.minutes
=> 2012-10-28 03:00:00 +0200
irb(main):036> Time.parse("2012-10-28 02:00:00 Europe/Tallinn").zone
=> "EEST"
irb(main):037> (Time.parse("2012-10-28 02:00:00 Europe/Tallinn") + 120.minutes).zone
=> "EET"

But Fugit::Cron breaks on an attempt to calculate next time from cron */1 * * * * starting from 03:00:00 EEST:

irb(main):044> Fugit::Cron.parse("*/1 * * * *").next_time(Time.parse("2012-10-28 02:59:00 EEST"))
=> #<EtOrbi::EoTime:0x000000010884ac98 @rday=nil, @rweek=nil, @seconds=1351382400.0, @time=nil, @zone=#<TZInfo::DataTimezone: Etc/UTC>>

irb(main):045> Fugit::Cron.parse("*/1 * * * *").next_time(Time.parse("2012-10-28 02:59:00 EEST") + 1.minute)
/Users/traf/.rbenv/versions/3.2.3/lib/ruby/gems/3.2.0/gems/et-orbi-1.2.11/lib/et-orbi/time.rb:82:in `initialize': Cannot determine timezone from "EEST" (ArgumentError)
(secs:1351382400.0,utc~:"2012-10-28 00:00:00.0",ltz~:"UTC")
(etz:nil,tnz:"EET",tziv:"2.0.6",tzidv:"1.2024.1",rv:"3.2.3",rp:"arm64-darwin23",win:false,rorv:"7.1.3.2",astz:[ActiveSupport::TimeZone, "Etc/UTC"],eov:"1.2.11",eotnz:#<TZInfo::DataTimezone: Etc/UTC>,eotnfz:"+0000",eotlzn:"Etc/UTC",eotnfZ:"UTC",
debian:nil,centos:nil,osx:"zoneinfo/Europe/Tallinn")
Try setting `ENV['TZ'] = 'Continent/City'` in your script (see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)

irb(main):046> Fugit::Cron.parse("*/1 * * * *").next_time(Time.parse("2012-10-28 03:00:00 EEST"))
/Users/traf/.rbenv/versions/3.2.3/lib/ruby/gems/3.2.0/gems/et-orbi-1.2.11/lib/et-orbi/time.rb:82:in `initialize': Cannot determine timezone from "EEST" (ArgumentError)
(secs:1351382400.0,utc~:"2012-10-28 00:00:00.0",ltz~:"UTC")
(etz:nil,tnz:"EET",tziv:"2.0.6",tzidv:"1.2024.1",rv:"3.2.3",rp:"arm64-darwin23",win:false,rorv:"7.1.3.2",astz:[ActiveSupport::TimeZone, "Etc/UTC"],eov:"1.2.11",eotnz:#<TZInfo::DataTimezone: Etc/UTC>,eotnfz:"+0000",eotlzn:"Etc/UTC",eotnfZ:"UTC",
debian:nil,centos:nil,osx:"zoneinfo/Europe/Tallinn")
Try setting `ENV['TZ'] = 'Continent/City'` in your script (see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)

irb(main):047> Fugit::Cron.parse("*/1 * * * *").next_time(Time.parse("2012-10-28 02:59:00 Europe/Tallinn") + 1.minute)
/Users/traf/.rbenv/versions/3.2.3/lib/ruby/gems/3.2.0/gems/et-orbi-1.2.11/lib/et-orbi/time.rb:82:in `initialize': Cannot determine timezone from "EEST" (ArgumentError)
(secs:1351382400.0,utc~:"2012-10-28 00:00:00.0",ltz~:"UTC")
(etz:nil,tnz:"EET",tziv:"2.0.6",tzidv:"1.2024.1",rv:"3.2.3",rp:"arm64-darwin23",win:false,rorv:"7.1.3.2",astz:[ActiveSupport::TimeZone, "Etc/UTC"],eov:"1.2.11",eotnz:#<TZInfo::DataTimezone: Etc/UTC>,eotnfz:"+0000",eotlzn:"Etc/UTC",eotnfZ:"UTC",
debian:nil,centos:nil,osx:"zoneinfo/Europe/Tallinn")
Try setting `ENV['TZ'] = 'Continent/City'` in your script (see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)

How to reproduce

The simplest piece of code that reproduces the issue:

require 'fugit'
Fugit::Cron.parse("*/1 * * * *").next_time(Time.parse("2012-10-28 03:00:00 EEST"))

Error and error backtrace (if any)

/Users/traf/.rbenv/versions/3.2.3/lib/ruby/gems/3.2.0/gems/et-orbi-1.2.11/lib/et-orbi/time.rb:82:in `initialize': Cannot determine timezone from "EEST" (ArgumentError)
(secs:1351382400.0,utc~:"2012-10-28 00:00:00.0",ltz~:"UTC")
(etz:nil,tnz:"EET",tziv:"2.0.6",tzidv:"1.2024.1",rv:"3.2.3",rp:"arm64-darwin23",win:false,rorv:"7.1.3.2",astz:[ActiveSupport::TimeZone, "Etc/UTC"],eov:"1.2.11",eotnz:#<TZInfo::DataTimezone: Etc/UTC>,eotnfz:"+0000",eotlzn:"Etc/UTC",eotnfZ:"UTC",
debian:nil,centos:nil,osx:"zoneinfo/Europe/Tallinn")
Try setting `ENV['TZ'] = 'Continent/City'` in your script (see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)

Expected behaviour

Not get error thrown

Given:

Fugit::Cron.parse("*/1 * * * *").next_time(Time.parse("2012-10-28 03:00:00 EEST")).to_t

Output: 2012-10-28 03:01:00 +0300 (+0300 signifies EEST)

Given:

Fugit::Cron.parse("*/1 * * * *").next_time(Time.parse("2012-10-28 03:59:00 EEST")).to_t

Output: 2012-10-28 03:00:00 +0200 (+0200 signifies EET)

Context

Darwin Dmitris-MacBook-Air.local 23.3.0 Darwin Kernel Version 23.3.0: Wed Dec 20 21:30:27 PST 2023; root:xnu-10002.81.5~7/RELEASE_ARM64_T8103 arm64
ruby 3.2.3 (2024-01-18 revision 52bb2ac0a6) [arm64-darwin23]
[:env_tz, nil]
(secs:1711380674.477833,utc~:"2024-03-25 15:31:14.4778330326080322",ltz~:"EET")
(etz:nil,tnz:"EET",tziv:"2.0.6",tzidv:"1.2024.1",rv:"3.2.3",rp:"arm64-darwin23",win:false,rorv:nil,astz:nil,eov:"1.2.11",eotnz:#<TZInfo::TimezoneProxy: Africa/Cairo>,eotnfz:"+0200",eotlzn:"Africa/Cairo",eotnfZ:"EET",debian:nil,centos:nil,osx:"zoneinfo/Europe/Tallinn")
[:fugit, "1.10.1"]
[:now, 2024-03-25 17:31:15.191495 +0200, :zone, "EET"]
@jmettraux jmettraux self-assigned this Mar 25, 2024
@jmettraux
Copy link
Member

Hello,

as a preliminary remark, a reminder that Time.parse(str) does not care about time zones:

# ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-openbsd7.4]

require 'time'


ENV['TZ'] = 'Europe/Tallinn'

p Time.now
  # 2024-03-26 01:19:30.188064864 +0200

p Time.parse("2012-10-28 02:59:00 EEST")
p Time.parse("2012-10-28 02:59:00 Europe/Tallinn")
  # 2012-10-28 02:59:00 +0300
  # 2012-10-28 02:59:00 +0300

p Time.parse("2012-10-28 02:59:00 Some/Where")
p Time.parse("2012-10-28 02:59:00 XXX")
  # 2012-10-28 02:59:00 +0300
  # 2012-10-28 02:59:00 +0300



ENV['TZ'] = 'Asia/Tokyo'

p Time.now
  # 2024-03-26 08:19:30.18919636 +0900

p Time.parse("2012-10-28 02:59:00 EEST")
p Time.parse("2012-10-28 02:59:00 Europe/Tallinn")
  # 2012-10-28 02:59:00 +0900
  # 2012-10-28 02:59:00 +0900

p Time.parse("2012-10-28 02:59:00 Some/Where")
p Time.parse("2012-10-28 02:59:00 XXX")
  # 2012-10-28 02:59:00 +0900
  # 2012-10-28 02:59:00 +0900

jmettraux added a commit to floraison/et-orbi that referenced this issue Mar 26, 2024
@jmettraux
Copy link
Member

Tinkering:

# ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-openbsd7.4]

require 'fugit'

ENV['TZ'] = 'Europe/Tallinn'
  # approximating OP's situation...


p Fugit::Cron.parse("*/1 * * * *")
  .next_time(Time.parse("2012-10-28 02:59:00 EEST")).to_t
    # => 2012-10-28 03:00:00 +0300  ## because ENV TZ is Europe/Tallinn
p Fugit::Cron.parse("*/1 * * * *")
  .next_time(Fugit.parse("2012-10-28 02:59:00 EEST")).to_t
    # => 2012-10-28 10:00:00 +0200  ## off
p Fugit::Cron.parse("*/1 * * * *")
  .next_time(Fugit.parse("2012-10-28 02:59:00 Europe/Tallin")).to_t
    # => 2012-10-28 03:00:00 +0300 ## OK

##

#p Fugit::Cron.parse("*/1 * * * *")
#  .next_time(Time.parse("2012-10-28 02:59:00 EEST") + 1.minute)
#p Fugit::Cron.parse("*/1 * * * *")
#  .next_time(Time.parse("2012-10-28 02:59:00 EEST") + 60)
#p Fugit::Cron.parse("*/1 * * * *")
#  .next_time(Fugit.parse("2012-10-28 02:59:00 EEST") + 60).to_t
p Fugit::Cron.parse("*/1 * * * *")
  .next_time(Fugit.parse("2012-10-28 02:59:00 Europe/Tallin") + 60).to_t
    # => 2012-10-28 03:01:00 +0300
#/Users/traf/.rbenv/versions/3.2.3/lib/ruby/gems/3.2.0/gems/et-orbi-1.2.11/lib/et-orbi/time.rb:82
#   :in `initialize': Cannot determine timezone from "EEST" (ArgumentError)

##

#p Fugit::Cron.parse("*/1 * * * *")
#  .next_time(Time.parse("2012-10-28 03:00:00 EEST")).to_t
p Fugit::Cron.parse("*/1 * * * *")
  .next_time(Fugit.parse("2012-10-28 03:00:00 EEST")).to_t
    # => 2012-10-28 10:01:00 +0200
p Fugit::Cron.parse("*/1 * * * *")
  .next_time(Fugit.parse("2012-10-28 03:00:00 Europe/Tallinn")).to_t
    # => ~/.gem/ruby/3.2.2/gems/et-orbi-1.2.11/lib/et-orbi/make.rb:65
    #      :in `make_time': Cannot turn nil to a ::EtOrbi::EoTime instance
    #      (ArgumentError)
	#      from ~/w/fugit/lib/fugit/cron.rb:247:in `next_time'
p Fugit.parse("2012-10-28 03:00:00 Europe/Tallinn")
  # => nil
p EtOrbi.parse("2012-10-28 03:00:00 Europe/Tallinn")
  # => ~/.gem/ruby/3.2.2/gems/tzinfo-2.0.6/lib/tzinfo/timezone.rb:525:in `period_for_local':
  #         2012-10-28 03:00:00 is an ambiguous local time. (TZInfo::AmbiguousTime)
  # from ~/.gem/ruby/3.2.2/gems/tzinfo-2.0.6/lib/tzinfo/timezone.rb:652:in `block in local_to_utc'
  # from ~/.gem/ruby/3.2.2/gems/tzinfo-2.0.6/lib/tzinfo/timestamp.rb:144:in `for'
  # from ~/.gem/ruby/3.2.2/gems/tzinfo-2.0.6/lib/tzinfo/timezone.rb:648:in `local_to_utc'
  # from ~/.gem/ruby/3.2.2/gems/et-orbi-1.2.11/lib/et-orbi/make.rb:46:in `parse'
p (Fugit.parse("2012-10-28 02:59:00 Europe/Tallinn") + 60).to_t
  # => 2012-10-28 03:00:00 +0300

##

#p Fugit::Cron.parse("*/1 * * * *")
#  .next_time(Time.parse("2012-10-28 02:59:00 Europe/Tallinn") + 1.minute)
#p Fugit::Cron.parse("*/1 * * * *")
#  .next_time(Time.parse("2012-10-28 02:59:00 Europe/Tallinn") + 60)
p Fugit::Cron.parse("*/1 * * * *")
  .next_time(Fugit.parse("2012-10-28 02:59:00 Europe/Tallinn") + 60).to_t
    # => 2012-10-28 03:01:00 +0300

@jmettraux
Copy link
Member

Further tinkering:

This works:

require 'fugit'

ENV['TZ'] = 'Europe/Tallinn'

#c = Fugit.parse_cron('*/1 * * * *') # equivalent to
c = Fugit.parse_cron('* * * * *')

t = Fugit.parse('2012-10-28 02:59:00 Europe/Tallinn')

70.times do |i|
  puts "%2d - %s" % [ i, t ? t.to_t.inspect : 'nil' ]
  t = c.next_time(t)
end

It transitions smoothly:

...
58 - 2012-10-28 03:57:00 +0300
59 - 2012-10-28 03:58:00 +0300
60 - 2012-10-28 03:59:00 +0300
61 - 2012-10-28 03:00:00 +0200
62 - 2012-10-28 03:01:00 +0200
63 - 2012-10-28 03:02:00 +0200
...

But making it start at '2012-10-28 03:00:00 Europe/Tallinn' instead of '2012-10-28 02:59:00 Europe/Tallinn' has nil for t and it fails immediately. Fugit.parse('2023-10-28 03:00:00') (in Europe/Tallinn) also returns nil.

I need to find a way in fugit or et-orbi to return the right time when parsing an ambiguous timezone.

I will go on investigating tonight after work. Thanks for reporting that.

@trafium
Copy link
Author

trafium commented Mar 26, 2024

Thanks @jmettraux, I was unaware of that weird behaviour of Time.parse with time zones. Still trying to grasp what is going on exactly though.

But keeping that in mind, I found out that I can use Time.find_zone!("Europe/Tallinn").parse("2012-10-28 02:59:00") which gives me ActiveSupport::TimeWithZone and it does not break in Fugit that way.

Time.find_zone!("Europe/Tallinn").parse("2012-10-28 02:59:00") + 1.minute
#=> Sun, 28 Oct 2012 03:00:00.000000000 EEST +03:00
(Time.find_zone!("Europe/Tallinn").parse("2012-10-28 02:59:00") + 1.minute).zone
#=> "EEST"
(Time.find_zone!("Europe/Tallinn").parse("2012-10-28 02:59:00") + 1.minute).dst?
#=> true
(Time.find_zone!("Europe/Tallinn").parse("2012-10-28 02:59:00") + 1.minute) == (Time.parse("2012-10-28 02:59:00 Europe/Tallinn") + 1.minute)
#=> true
(Time.find_zone!("Europe/Tallinn").parse("2012-10-28 02:59:00") + 1.minute).zone == (Time.parse("2012-10-28 02:59:00 Europe/Tallinn") + 1.minute).zone
#=> true
(Time.find_zone!("Europe/Tallinn").parse("2012-10-28 02:59:00") + 1.minute).dst? == (Time.parse("2012-10-28 02:59:00 Europe/Tallinn") + 1.minute).dst?
#=> true
Fugit::Cron.parse("*/1 * * * *").next_time(Time.find_zone!("Europe/Tallinn").parse("2012-10-28 02:59:00") + 1.minute).to_t
#=> 2012-10-28 03:01:00 +0300
Fugit::Cron.parse("*/1 * * * *").next_time(Time.find_zone!("Europe/Tallinn").parse("2012-10-28 03:59:00")).to_s
#=> "2012-10-28 03:00:00 +0200"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants