lib/et-orbi.rb in et-orbi-1.0.4 vs lib/et-orbi.rb in et-orbi-1.0.5

- old
+ new

@@ -5,142 +5,183 @@ require 'tzinfo' module EtOrbi - VERSION = '1.0.4' + VERSION = '1.0.5' # # module methods - def self.now(zone=nil) + class << self - EoTime.new(Time.now.to_f, zone) - end + def now(zone=nil) - def self.parse(str, opts={}) - - if defined?(::Chronic) && t = ::Chronic.parse(str, opts) - return EoTime.new(t, nil) + EoTime.new(Time.now.to_f, zone) end - #rold = RUBY_VERSION < '1.9.0' - #rold = RUBY_VERSION < '2.0.0' + def parse(str, opts={}) -#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` + if defined?(::Chronic) && t = ::Chronic.parse(str, opts) + return EoTime.new(t, nil) + end - zone = izone = get_tzone(list_iso8601_zones(str).last) + #rold = RUBY_VERSION < '1.9.0' + #rold = RUBY_VERSION < '2.0.0' + 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` - list_olson_zones(str).each { |s| break if zone; zone = get_tzone(s) } + str_zone = get_tzone(list_iso8601_zones(str).last) - zone ||= local_tzone + zone = + opts[:zone] || + str_zone || + find_olson_zone(str) || + 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 str_zone + 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.make_time(o) + def make_time(*a) - ot = +#p a + zone = a.length > 1 ? get_tzone(a.last) : nil + a.pop if zone +#p [ :mt, zone ] + + o = a.length > 1 ? a : a.first +#p o + 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 + when Time then make_from_time(o, zone) + when Date then make_from_date(o, zone) + when Array then make_from_array(o, zone) + when String then make_from_string(o, zone) + when Numeric then make_from_numeric(o, zone) + when ::EtOrbi::EoTime then make_from_eotime(o, zone) + else fail ArgumentError.new( + "cannot turn #{o.inspect} to a ::EtOrbi::EoTime instance") end + end - ot = EoTime.new(Time.now.to_f + ot, nil) if ot.is_a?(Numeric) + def make_from_time(t, zone) - fail ArgumentError.new( - "cannot turn #{o.inspect} to a EoTime instance" - ) unless ot.is_a?(EoTime) + z = + zone || + get_tzone(t.zone) || + ( + local_tzone.period_for_local(t).abbreviation.to_s == t.zone && + local_tzone + ) || + t.zone - ot - end + EoTime.new(t.to_f, z) + end - def self.get_tzone(o) + def make_from_date(d, zone) - return o if o.is_a?(::TZInfo::Timezone) - return nil if o == nil - return local_tzone if o == :local - return ::TZInfo::Timezone.get('Zulu') if o == 'Z' + make_from_time( + d.respond_to?(:to_time) ? + d.to_time : + Time.parse(d.strftime('%Y-%m-%d %H:%M:%S')), + zone) + end - o = to_offset(o) if o.is_a?(Numeric) + def make_from_array(a, zone) - return nil unless o.is_a?(String) + t = Time.utc(*a) + s = t.strftime("%Y-%m-%d %H:%M:%S.#{'%06d' % t.usec}") - (@custom_tz_cache ||= {})[o] || - get_offset_tzone(o) || - (::TZInfo::Timezone.get(o) rescue nil) - end + make_from_string(s, zone) + end - def self.local_tzone + def make_from_string(s, zone) - @local_tzone = nil \ - if @local_tzone_loaded_at && (Time.now > @local_tzone_loaded_at + 1800) - @local_tzone = nil \ - if @local_tzone_tz != ENV['TZ'] + parse(s, zone: zone) + end - @local_tzone ||= - begin - @local_tzone_tz = ENV['TZ'] - @local_tzone_loaded_at = Time.now - determine_local_tzone - end - end + def make_from_numeric(f, zone) - def self.platform_info + EoTime.new(Time.now.to_f + f, zone) + end - etos = Proc.new { |k, v| "#{k}:#{v.inspect}" } + def make_from_eotime(eot, zone) - '(' + - { - '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 + return eot if zone == nil || zone == eot.zone + EoTime.new(eot.to_f, zone) + end - class << self + def get_tzone(o) + return o if o.is_a?(::TZInfo::Timezone) + return nil if o == nil + return local_tzone if o == :local + return ::TZInfo::Timezone.get('Zulu') if o == 'Z' + + o = to_offset(o) if o.is_a?(Numeric) + + return nil unless o.is_a?(String) + + (@custom_tz_cache ||= {})[o] || + get_offset_tzone(o) || + (::TZInfo::Timezone.get(o) rescue nil) + end + + def local_tzone + + @local_tzone = nil \ + if @local_tzone_loaded_at && (Time.now > @local_tzone_loaded_at + 1800) + @local_tzone = nil \ + if @local_tzone_tz != ENV['TZ'] + + @local_tzone ||= + begin + @local_tzone_tz = ENV['TZ'] + @local_tzone_loaded_at = Time.now + determine_local_tzone + end + end + + def platform_info + + etos = Proc.new { |k, v| "#{k}:#{v.inspect}" } + + '(' + + { + '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 + alias make make_time end # # our EoTime class (which quacks like a ::Time) @@ -148,38 +189,41 @@ class EoTime # # class methods - def self.now(zone=nil) + class << self - EtOrbi.now(zone) - end + def now(zone=nil) - def self.parse(str, opts={}) + EtOrbi.now(zone) + end - EtOrbi.parse(str, opts) - end + def parse(str, opts={}) - def self.get_tzone(o) + EtOrbi.parse(str, opts) + end - EtOrbi.get_tzone(o) - end + def get_tzone(o) - def self.local_tzone + EtOrbi.get_tzone(o) + end - EtOrbi.local_tzone - end + def local_tzone - def self.platform_info + EtOrbi.local_tzone + end - EtOrbi.platform_info - end + def platform_info - def self.make(o) + EtOrbi.platform_info + end - EtOrbi.make_time(o) + def make(o) + + EtOrbi.make_time(o) + end end # # instance methods @@ -218,10 +262,19 @@ def utc Time.utc(1970, 1, 1) + @seconds end + def utc? + + %w[ zulu utc gmt ].include?(@zone.canonical_identifier.downcase) + + #t = Time.now + #@zone.period_for_local(t).utc_offset == 0 && + #@zone.period_for_local(t + 183 * 24 * 3600).utc_offset == 0 + end + alias getutc utc alias getgm utc def to_f @@ -295,12 +348,12 @@ def >=(o); @seconds >= _to_f(o); end def <(o); @seconds < _to_f(o); end def <=(o); @seconds <= _to_f(o); end def <=>(o); @seconds <=> _to_f(o); end - def add(t); @time = nil; @seconds += t.to_f; end - def subtract(t); @time = nil; @seconds -= t.to_f; end + def add(t); @time = nil; @seconds += t.to_f; self; end + def subtract(t); @time = nil; @seconds -= t.to_f; self; end def +(t); inc(t, 1); end def -(t); inc(t, -1); end WEEK_S = 7 * 24 * 3600 @@ -352,12 +405,51 @@ def to_time_s strftime("%H:%M:%S.#{'%06d' % usec}") end + def inc(t, dir=1) + + case t + when Numeric + nt = self.dup + nt.seconds += dir * t.to_f + nt + when ::Time, ::EtOrbi::EoTime + fail ArgumentError.new( + "cannot add #{t.class} to EoTime") if dir > 0 + @seconds + dir * t.to_f + else + fail ArgumentError.new( + "cannot call add or subtract #{t.class} to EoTime instance") + end + end + + def localtime(zone=nil) + + EoTime.new(self.to_f, zone) + end + + def wday_in_month + + [ count_weeks(-1), - count_weeks(1) ] + end + protected + def count_weeks(dir) + + c = 0 + t = self + until t.month != self.month + c += 1 + t += dir * (7 * 24 * 3600) + end + + c + end + def render_nozone_time(seconds) t = Time.utc(0) + seconds ts = @@ -396,208 +488,195 @@ end fmt % [ sn, hr, mn, sc ] end - def inc(t, dir) - - if t.is_a?(Numeric) - nt = self.dup - nt.seconds += dir * t.to_f - nt - elsif t.respond_to?(:to_f) - @seconds + dir * t.to_f - else - fail ArgumentError.new( - "cannot call EoTime #- or #+ with arg of class #{t.class}") - end - end - def _to_f(o) fail ArgumentError( "comparison of EoTime with #{o.inspect} failed" ) unless o.is_a?(EoTime) || o.is_a?(Time) o.to_f end end - # - # not so public module methods + class << self - def self.time_to_eo_time(t) + # + # extra public methods - z = - get_tzone(t.zone) || - ( - local_tzone.period_for_local(t).abbreviation.to_s == t.zone && - local_tzone - ) || - t.zone + # https://en.wikipedia.org/wiki/ISO_8601 + # Postel's law applies + # + def list_iso8601_zones(s) - EoTime.new(t.to_f, z) - end + 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.to_offset(n) + def list_olson_zones(s) - 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 + s.scan( + %r{ + (?<=\s|\A) + (?:[A-Za-z][A-Za-z0-9+_-]+) + (?:\/(?:[A-Za-z][A-Za-z0-9+_-]+)){0,2} + }x) + end - def self.get_offset_tzone(str) + def find_olson_zone(str) - # custom timezones, no DST, just an offset, like "+08:00" or "-01:30" + list_olson_zones(str).each { |s| z = get_tzone(s); return z if z } + nil + end - m = str.match(/\A([+-][0-1][0-9]):?([0-5][0-9])?\z/) - return nil unless m + def determine_local_tzone - hr = m[1].to_i - mn = m[2].to_i + etz = ENV['TZ'] - hr = nil if hr.abs > 11 - hr = nil if mn > 59 - mn = -mn if hr && hr < 0 + tz = ::TZInfo::Timezone.get(etz) rescue nil + return tz if tz - 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 + tz = Time.zone.tzinfo \ + if Time.respond_to?(:zone) && Time.zone.respond_to?(:tzinfo) + return tz if tz - nil - end + tzs = determine_local_tzones - def self.determine_local_tzone + (etz && tzs.find { |z| z.name == etz }) || tzs.first + end - etz = ENV['TZ'] + # + # protected module methods - tz = ::TZInfo::Timezone.get(etz) rescue nil - return tz if tz + protected - tz = Time.zone.tzinfo \ - if Time.respond_to?(:zone) && Time.zone.respond_to?(:tzinfo) - return tz if tz + def to_offset(n) - tzs = determine_local_tzones + 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 - (etz && tzs.find { |z| z.name == etz }) || tzs.first - end + def get_offset_tzone(str) - def self.determine_local_tzones + # custom timezones, no DST, just an offset, like "+08:00" or "-01:30" - tabbs = (-6..5) - .collect { |i| (Time.now + i * 30 * 24 * 3600).zone } - .uniq - .sort + m = str.match(/\A([+-][0-1][0-9]):?([0-5][0-9])?\z/) + return nil unless m - t = Time.now - tu = t.dup.utc # /!\ dup is necessary, #utc modifies its target + hr = m[1].to_i + mn = m[2].to_i - twin = Time.utc(t.year, 1, 1) # winter - tsum = Time.utc(t.year, 7, 1) # summer + hr = nil if hr.abs > 11 + hr = nil if mn > 59 + mn = -mn if hr && hr < 0 - ::TZInfo::Timezone.all.select do |tz| + 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 - pabbs = - [ - tz.period_for_utc(twin).abbreviation.to_s, - tz.period_for_utc(tsum).abbreviation.to_s - ].uniq.sort - - pabbs == tabbs + nil end - end - # https://en.wikipedia.org/wiki/ISO_8601 - # Postel's law applies - # - def self.list_iso8601_zones(s) + def determine_local_tzones - s.scan( - %r{ - (?<=:\d\d) - \s* - (?: - [-+] - (?:[0-1][0-9]|2[0-4]) - (?:(?::)?(?:[0-5][0-9]|60))? - (?![-+]) - | - Z - ) - }x - ).collect(&:strip) - end + tabbs = (-6..5) + .collect { |i| (Time.now + i * 30 * 24 * 3600).zone } + .uniq + .sort - def self.list_olson_zones(s) + t = Time.now + tu = t.dup.utc # /!\ dup is necessary, #utc modifies its target - s.scan( - %r{ - (?<=\s|\A) - (?:[A-Za-z][A-Za-z0-9+_-]+) - (?:\/(?:[A-Za-z][A-Za-z0-9+_-]+)){0,2} - }x) - end + twin = Time.utc(t.year, 1, 1) # winter + tsum = Time.utc(t.year, 7, 1) # summer - #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 + ::TZInfo::Timezone.all.select do |tz| - # - # system tz determination + pabbs = + [ + tz.period_for_utc(twin).abbreviation.to_s, + tz.period_for_utc(tsum).abbreviation.to_s + ].uniq.sort - def self.debian_tz + pabbs == tabbs + end + end - path = '/etc/timezone' + # + # system tz determination - File.exist?(path) ? File.read(path).strip : nil - rescue; nil; end + def debian_tz - def self.centos_tz + path = '/etc/timezone' - path = '/etc/sysconfig/clock' + File.exist?(path) ? File.read(path).strip : nil + rescue; nil; end - 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) + def centos_tz - nil - rescue; nil; end + path = '/etc/sysconfig/clock' - def self.osx_tz + 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) - path = '/etc/localtime' - - File.symlink?(path) ? - File.readlink(path).split('/')[4..-1].join('/') : nil - rescue; nil; end + rescue; nil; end -# def self.find_tz + def osx_tz + + path = '/etc/localtime' + + File.symlink?(path) ? + File.readlink(path).split('/')[4..-1].join('/') : + nil + rescue; nil; end + +# def find_tz # -# debian_tz || centos_tz || osx_tz -# end +# debian_tz || centos_tz || osx_tz +# end - def self.gather_tzs + def gather_tzs - { :debian => debian_tz, :centos => centos_tz, :osx => osx_tz } + { :debian => debian_tz, :centos => centos_tz, :osx => osx_tz } + end 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 end