lib/et-orbi.rb in et-orbi-1.0.1 vs lib/et-orbi.rb in et-orbi-1.0.2

- old
+ new

@@ -5,274 +5,182 @@ require 'tzinfo' module EtOrbi - VERSION = '1.0.1' + VERSION = '1.0.2' - class EoTime + # + # module methods - # - # class methods + def self.now(zone=nil) - def self.now(zone=nil) + EoTime.new(Time.now.to_f, zone) + end - EoTime.new(Time.now.to_f, zone) + def self.parse(str, opts={}) + + if defined?(::Chronic) && t = ::Chronic.parse(str, opts) + return EoTime.new(t, nil) end - def self.parse(str, opts={}) + #rold = RUBY_VERSION < '1.9.0' + #rold = RUBY_VERSION < '2.0.0' - if defined?(::Chronic) && t = ::Chronic.parse(str, opts) - return EoTime.new(t, nil) - end - - #rold = RUBY_VERSION < '1.9.0' - #rold = RUBY_VERSION < '2.0.0' - #p [ '---', str ] - begin - DateTime.parse(str) - rescue - fail ArgumentError, "no time information in #{str.inspect}" - end #if rold - # - # is necessary since Time.parse('xxx') in Ruby < 1.9 yields `now` + begin + DateTime.parse(str) + rescue + fail ArgumentError, "no time information in #{str.inspect}" + end #if rold + # + # is necessary since Time.parse('xxx') in Ruby < 1.9 yields `now` - zone = izone = get_tzone(list_iso8601_zones(str).last) + zone = izone = get_tzone(list_iso8601_zones(str).last) - list_olson_zones(str).each { |s| break if zone; zone = get_tzone(s) } + list_olson_zones(str).each { |s| break if zone; zone = get_tzone(s) } - zone ||= local_tzone + zone ||= local_tzone - str = str.sub(zone.name, '') unless zone.name.match(/\A[-+]/) - # - # for 'Sun Nov 18 16:01:00 Asia/Singapore 2012', - # although where does rufus-scheduler have it from? + str = str.sub(zone.name, '') unless zone.name.match(/\A[-+]/) + # + # for 'Sun Nov 18 16:01:00 Asia/Singapore 2012', + # although where does rufus-scheduler have it from? - local = Time.parse(str) + local = Time.parse(str) - secs = - if izone - local.to_f - else - zone.period_for_local(local).to_utc(local).to_f - end + secs = + if izone + local.to_f + else + zone.period_for_local(local).to_utc(local).to_f + end - EoTime.new(secs, zone) - end + EoTime.new(secs, zone) + end - def self.time_to_eo_time(t) + def self.make_time(o) - z = - get_tzone(t.zone) || - ( - local_tzone.period_for_local(t).abbreviation.to_s == t.zone && - local_tzone - ) || - t.zone + ot = + case o + when Time + time_to_eo_time( + o) + when Date + time_to_eo_time( + o.respond_to?(:to_time) ? + o.to_time : + Time.parse(o.strftime('%Y-%m-%d %H:%M:%S'))) + when String + #Rufus::Scheduler.parse_in(o, :no_error => true) || self.parse(o) + parse(o) + else + o + end - EoTime.new(t.to_f, z) - end + ot = EoTime.new(Time.now.to_f + ot, nil) if ot.is_a?(Numeric) - def self.make(o) + fail ArgumentError.new( + "cannot turn #{o.inspect} to a EoTime instance" + ) unless ot.is_a?(EoTime) - ot = - case o - when Time - time_to_eo_time( - o) - when Date - time_to_eo_time( - o.respond_to?(:to_time) ? - o.to_time : - Time.parse(o.strftime('%Y-%m-%d %H:%M:%S'))) - when String - #Rufus::Scheduler.parse_in(o, :no_error => true) || self.parse(o) - parse(o) - else - o - end + ot + end - ot = EoTime.new(Time.now.to_f + ot, nil) if ot.is_a?(Numeric) + def self.get_tzone(o) - fail ArgumentError.new( - "cannot turn #{o.inspect} to a EoTime instance" - ) unless ot.is_a?(EoTime) - - ot - end - - def self.to_offset(n) - - i = n.to_i - sn = i < 0 ? '-' : '+'; i = i.abs - hr = i / 3600; mn = i % 3600; sc = i % 60 - (sc > 0 ? "%s%02d:%02d:%02d" : "%s%02d:%02d") % [ sn, hr, mn, sc ] - end - - def self.get_tzone(o) - #p [ :gtz, o ] - return nil if o == nil - return local_tzone if o == :local - return o if o.is_a?(::TZInfo::Timezone) - return ::TZInfo::Timezone.get('Zulu') if o == 'Z' + return nil if o == nil + return local_tzone if o == :local + return o if o.is_a?(::TZInfo::Timezone) + return ::TZInfo::Timezone.get('Zulu') if o == 'Z' - o = to_offset(o) if o.is_a?(Numeric) + o = to_offset(o) if o.is_a?(Numeric) - return nil unless o.is_a?(String) + return nil unless o.is_a?(String) - (@custom_tz_cache ||= {})[o] || - get_offset_tzone(o) || - (::TZInfo::Timezone.get(o) rescue nil) - end + (@custom_tz_cache ||= {})[o] || + get_offset_tzone(o) || + (::TZInfo::Timezone.get(o) rescue nil) + end - def self.get_offset_tzone(str) + def self.local_tzone - # custom timezones, no DST, just an offset, like "+08:00" or "-01:30" + @local_tzone = nil \ + if @local_tzone_loaded_at && (Time.now > @local_tzone_loaded_at + 1800) + @local_tzone = nil \ + if @local_tzone_tz != ENV['TZ'] - m = str.match(/\A([+-][0-1][0-9]):?([0-5][0-9])?\z/) - return nil unless m + @local_tzone ||= + begin + @local_tzone_tz = ENV['TZ'] + @local_tzone_loaded_at = Time.now + determine_local_tzone + end + end - hr = m[1].to_i - mn = m[2].to_i + def self.platform_info - hr = nil if hr.abs > 11 - hr = nil if mn > 59 - mn = -mn if hr && hr < 0 + etos = Proc.new { |k, v| "#{k}:#{v.inspect}" } - return ( - @custom_tz_cache[str] = - begin - tzi = TZInfo::TransitionDataTimezoneInfo.new(str) - tzi.offset(str, hr * 3600 + mn * 60, 0, str) - tzi.create_timezone - end - ) if hr + '(' + + { + 'etz' => ENV['TZ'], + 'tnz' => Time.now.zone, + 'tzid' => defined?(TZInfo::Data), + 'rv' => RUBY_VERSION, + 'rp' => RUBY_PLATFORM, + 'eov' => EtOrbi::VERSION, + 'rorv' => (Rails::VERSION::STRING rescue nil), + 'astz' => Time.respond_to?(:zone) ? Time.zone.name : nil, + # Active Support Time.zone + }.collect(&etos).join(',') + ',' + + gather_tzs.collect(&etos).join(',') + + ')' + end - nil - end + class << self - def self.local_tzone + alias make make_time + end - @local_tzone = nil \ - if @local_tzone_loaded_at && (Time.now > @local_tzone_loaded_at + 1800) - @local_tzone = nil \ - if @local_tzone_tz != ENV['TZ'] + # + # our EoTime class (which quacks like a ::Time) - @local_tzone ||= - begin - @local_tzone_tz = ENV['TZ'] - @local_tzone_loaded_at = Time.now - determine_local_tzone - end - end + class EoTime - def self.determine_local_tzone + # + # class methods - etz = ENV['TZ'] + def self.now(zone=nil) - tz = ::TZInfo::Timezone.get(etz) rescue nil - return tz if tz - - tz = Time.zone.tzinfo \ - if Time.respond_to?(:zone) && Time.zone.respond_to?(:tzinfo) - return tz if tz - - tzs = determine_local_tzones - - (etz && tzs.find { |z| z.name == etz }) || tzs.first + EtOrbi.now(zone) end - def self.determine_local_tzones + def self.parse(str, opts={}) - tabbs = (-6..5) - .collect { |i| (Time.now + i * 30 * 24 * 3600).zone } - .uniq - .sort - - t = Time.now - tu = t.dup.utc # /!\ dup is necessary, #utc modifies its target - - twin = Time.utc(t.year, 1, 1) # winter - tsum = Time.utc(t.year, 7, 1) # summer - - ::TZInfo::Timezone.all.select do |tz| - - pabbs = - [ - tz.period_for_utc(twin).abbreviation.to_s, - tz.period_for_utc(tsum).abbreviation.to_s - ].uniq.sort - - pabbs == tabbs - end + EtOrbi.parse(str, opts) end - # https://en.wikipedia.org/wiki/ISO_8601 - # Postel's law applies - # - def self.list_iso8601_zones(s) + def self.get_tzone(o) - s.scan( - %r{ - (?<=:\d\d) - \s* - (?: - [-+] - (?:[0-1][0-9]|2[0-4]) - (?:(?::)?(?:[0-5][0-9]|60))? - (?![-+]) - | - Z - ) - }x - ).collect(&:strip) + EtOrbi.get_tzone(o) end - def self.list_olson_zones(s) + def self.local_tzone - s.scan( - %r{ - (?<=\s|\A) - (?:[A-Za-z][A-Za-z0-9+_-]+) - (?:\/(?:[A-Za-z][A-Za-z0-9+_-]+)){0,2} - }x) + EtOrbi.local_tzone end - #def in_zone(&block) - # - # current_timezone = ENV['TZ'] - # ENV['TZ'] = @zone - # - # block.call - # - #ensure - # - # ENV['TZ'] = current_timezone - #end - # - # kept around as a (thread-unsafe) relic - def self.platform_info - etos = Proc.new { |k, v| "#{k}:#{v.inspect}" } + EtOrbi.platform_info + end - '(' + - { - 'etz' => ENV['TZ'], - 'tnz' => Time.now.zone, - 'tzid' => defined?(TZInfo::Data), - 'rv' => RUBY_VERSION, - 'rp' => RUBY_PLATFORM, - 'eov' => EtOrbi::VERSION, - 'rorv' => (Rails::VERSION::STRING rescue nil), - 'astz' => Time.respond_to?(:zone) ? Time.zone.name : nil, - # Active Support Time.zone - }.collect(&etos).join(',') + ',' + - gather_tzs.collect(&etos).join(',') + - ')' + def self.make(o) + + EtOrbi.make_time(o) end # # instance methods @@ -284,11 +192,12 @@ @seconds = s.to_f @zone = self.class.get_tzone(zone || :local) fail ArgumentError.new( "cannot determine timezone from #{zone.inspect}" + - "\n#{self.class.platform_info}" + + "\n#{render_nozone_time(s)}" + + "\n#{self.class.platform_info.sub(',debian:', ",\ndebian:")}" + "\nTry setting `ENV['TZ'] = 'Continent/City'` in your script " + "(see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)" + (defined?(TZInfo::Data) ? '' : "\nand adding gem 'tzinfo-data'") ) unless @zone @@ -444,13 +353,25 @@ def to_time_s strftime("%H:%M:%S.#{'%06d' % usec}") end - # - # protected + protected + def render_nozone_time(seconds) + + t = + Time.utc(0) + seconds + ts = + t.strftime('%Y-%m-%d %H:%M:%S') + + ".#{(seconds % 1).to_s.split('.').last}" + z = + EtOrbi.local_tzone.period_for_local(t).abbreviation.to_s + + "(secs:#{seconds},utc~:#{ts.inspect},zo~:#{z.inspect})" + end + def strfz(code) return @zone.name if code == '%/Z' per = @zone.period_for_utc(utc) @@ -496,50 +417,186 @@ "comparison of EoTime with #{o.inspect} failed" ) unless o.is_a?(EoTime) || o.is_a?(Time) o.to_f end + end - # - # system tz determination + # + # not so public module methods - def self.debian_tz + def self.time_to_eo_time(t) - path = '/etc/timezone' + z = + get_tzone(t.zone) || + ( + local_tzone.period_for_local(t).abbreviation.to_s == t.zone && + local_tzone + ) || + t.zone - File.exist?(path) ? File.read(path).strip : nil - rescue; nil; end + EoTime.new(t.to_f, z) + end - def self.centos_tz + def self.to_offset(n) - path = '/etc/sysconfig/clock' + i = n.to_i + sn = i < 0 ? '-' : '+'; i = i.abs + hr = i / 3600; mn = i % 3600; sc = i % 60 + (sc > 0 ? "%s%02d:%02d:%02d" : "%s%02d:%02d") % [ sn, hr, mn, sc ] + end - File.open(path, 'rb') do |f| - until f.eof? - if m = f.readline.match(/ZONE="([^"]+)"/); return m[1]; end + def self.get_offset_tzone(str) + + # custom timezones, no DST, just an offset, like "+08:00" or "-01:30" + + m = str.match(/\A([+-][0-1][0-9]):?([0-5][0-9])?\z/) + return nil unless m + + hr = m[1].to_i + mn = m[2].to_i + + hr = nil if hr.abs > 11 + hr = nil if mn > 59 + mn = -mn if hr && hr < 0 + + return ( + @custom_tz_cache[str] = + begin + tzi = TZInfo::TransitionDataTimezoneInfo.new(str) + tzi.offset(str, hr * 3600 + mn * 60, 0, str) + tzi.create_timezone end - end if File.exist?(path) + ) if hr - nil - rescue; nil; end + nil + end - def self.osx_tz + def self.determine_local_tzone - path = '/etc/localtime' + etz = ENV['TZ'] - File.symlink?(path) ? - File.readlink(path).split('/')[4..-1].join('/') : - nil - rescue; nil; end + tz = ::TZInfo::Timezone.get(etz) rescue nil + return tz if tz -# def self.find_tz -# -# debian_tz || centos_tz || osx_tz -# end + tz = Time.zone.tzinfo \ + if Time.respond_to?(:zone) && Time.zone.respond_to?(:tzinfo) + return tz if tz - def self.gather_tzs + tzs = determine_local_tzones - { :debian => debian_tz, :centos => centos_tz, :osx => osx_tz } + (etz && tzs.find { |z| z.name == etz }) || tzs.first + end + + def self.determine_local_tzones + + tabbs = (-6..5) + .collect { |i| (Time.now + i * 30 * 24 * 3600).zone } + .uniq + .sort + + t = Time.now + tu = t.dup.utc # /!\ dup is necessary, #utc modifies its target + + twin = Time.utc(t.year, 1, 1) # winter + tsum = Time.utc(t.year, 7, 1) # summer + + ::TZInfo::Timezone.all.select do |tz| + + pabbs = + [ + tz.period_for_utc(twin).abbreviation.to_s, + tz.period_for_utc(tsum).abbreviation.to_s + ].uniq.sort + + pabbs == tabbs end + end + + # https://en.wikipedia.org/wiki/ISO_8601 + # Postel's law applies + # + def self.list_iso8601_zones(s) + + s.scan( + %r{ + (?<=:\d\d) + \s* + (?: + [-+] + (?:[0-1][0-9]|2[0-4]) + (?:(?::)?(?:[0-5][0-9]|60))? + (?![-+]) + | + Z + ) + }x + ).collect(&:strip) + end + + def self.list_olson_zones(s) + + s.scan( + %r{ + (?<=\s|\A) + (?:[A-Za-z][A-Za-z0-9+_-]+) + (?:\/(?:[A-Za-z][A-Za-z0-9+_-]+)){0,2} + }x) + end + + #def in_zone(&block) + # + # current_timezone = ENV['TZ'] + # ENV['TZ'] = @zone + # + # block.call + # + #ensure + # + # ENV['TZ'] = current_timezone + #end + # + # kept around as a (thread-unsafe) relic + + # + # system tz determination + + def self.debian_tz + + path = '/etc/timezone' + + File.exist?(path) ? File.read(path).strip : nil + rescue; nil; end + + def self.centos_tz + + path = '/etc/sysconfig/clock' + + File.open(path, 'rb') do |f| + until f.eof? + if m = f.readline.match(/ZONE="([^"]+)"/); return m[1]; end + end + end if File.exist?(path) + + nil + rescue; nil; end + + def self.osx_tz + + path = '/etc/localtime' + + File.symlink?(path) ? + File.readlink(path).split('/')[4..-1].join('/') : + nil + rescue; nil; end + +# def self.find_tz +# +# debian_tz || centos_tz || osx_tz +# end + + def self.gather_tzs + + { :debian => debian_tz, :centos => centos_tz, :osx => osx_tz } end end