et-orbi-1.0.5/0000755000175000017500000000000013154026100012072 5ustar pravipraviet-orbi-1.0.5/CHANGELOG.md0000644000175000017500000000167713154026100013716 0ustar pravipravi # et-orbi CHANGELOG.md ## et-orbi 1.0.5 released 2017-06-23 - Rework EtOrbi.make_time - Let EtOrbi.make_time accept array or array of args - Implement EoTime#localtime(zone=nil) - Move Fugit#wday_in_month into EoTime - Clarify #add, #subtract, #- and #+ contracts - Ensure #add and #subtract return `self` - Make #inc(seconds, direction) public - Implement EoTime#utc? ## et-orbi 1.0.4 released 2017-05-10 - Survive older versions of TZInfo with poor `<=>` impl, gh-1 ## et-orbi 1.0.3 released 2017-04-07 - Let not #render_nozone_time fail when local_tzone is nil ## et-orbi 1.0.2 released 2017-03-24 - Enhance no zone ArgumentError data - Separate module methods from EoTime methods ## et-orbi 1.0.1 released 2017-03-22 - Detail Rails and Active Support info in nozone err ## et-orbi 1.0.0 released 2017-03-22 - First release for rufus-scheduler ## et-orbi 0.9.5 released 2017-03-17 - Empty, initial release, 圓さんの家で et-orbi-1.0.5/README.md0000644000175000017500000000426713154026100013362 0ustar pravipravi # et-orbi [![Build Status](https://secure.travis-ci.org/floraison/et-orbi.svg)](http://travis-ci.org/floraison/et-orbi) [![Gem Version](https://badge.fury.io/rb/et-orbi.svg)](http://badge.fury.io/rb/et-orbi) Time zones for [fugit](https://github.com/floraison/fugit) and for [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler). Urbi et Orbi. `EtOrbi::EoTime` instances quack like Ruby `Time` instances, but their `#zone` returns a `TZInfo::TimeZone` instance. Getting `EoTime` instances: ```ruby require 'et-orbi' EtOrbi.now # => # EtOrbi.parse('2017-12-13 13:00:00 America/Jamaica') # => #...> EtOrbi.make_time(Time.now) # => # EtOrbi.make_time(2017, 1, 31, 12, 'Europe/Moscow').to_debug_s # => 'ot 2017-01-31 12:00:00 +03:00 dst:false' EtOrbi::EoTime.new(0, 'UTC').to_s # => "1970-01-01 00:00:00 +0000" EtOrbi::EoTime.new(0, 'Europe/Moscow').to_s # => "1970-01-01 03:00:00 +0300" ``` Helper methods: ```ruby require 'et-orbi' EtOrbi.get_tzone('Europe/Vilnius') # => # EtOrbi.local_tzone # => # EtOrbi.platform_info # => "(etz:nil,tnz:\"JST\",tzid:nil,rv:\"2.2.6\",rp:\"x86_64-darwin14\",eov:\"1.0.1\", # rorv:nil,astz:nil,debian:nil,centos:nil,osx:\"Asia/Tokyo\")" # # etz: ENV['TZ'] # tnz: Time.now.zone # tzid: defined?(TZInfo::Data) # rv: RUBY_VERSION # rp: RUBY_PLATFORM # eov: EtOrbi::VERSION # rorv: Rails::VERSION::STRING # astz: ActiveSupport provided Time.zone ``` ### Rails? If Rails is present, `Time.zone` is provided and EtOrbi will use it. Rails sets its timezone under `config/application.rb`. ## Related projects ### Sister projects * [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) - a cron/at/in/every/interval in-process scheduler, in fact, it's the father project to this fugit project * [fugit](https://github.com/floraison/fugit) - Time tools for flor and the floraison project. Cron parsing and occurence computing. Timestamps and more. ## LICENSE MIT, see [LICENSE.txt](LICENSE.txt) et-orbi-1.0.5/CREDITS.md0000644000175000017500000000045013154026100013510 0ustar pravipravi # et-orbi credits * Jeremy Strouse https://github.com/jstrouse help alleviate issue with older versions of TZInfo ## since rufus-scheduler Many thanks to all the rufus-scheduler contributors and people who gave feedback. https://github.com/jmettraux/rufus-scheduler/blob/master/CREDITS.txt et-orbi-1.0.5/lib/0000755000175000017500000000000013154026100012640 5ustar pravipraviet-orbi-1.0.5/lib/et-orbi.rb0000644000175000017500000003264513154026100014540 0ustar pravipravi require 'date' if RUBY_VERSION < '1.9.0' require 'time' require 'tzinfo' module EtOrbi VERSION = '1.0.5' # # module methods class << self def now(zone=nil) EoTime.new(Time.now.to_f, zone) end def parse(str, opts={}) 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' 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` str_zone = get_tzone(list_iso8601_zones(str).last) 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? local = Time.parse(str) secs = if str_zone local.to_f else zone.period_for_local(local).to_utc(local).to_f end EoTime.new(secs, zone) end def make_time(*a) #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 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 def make_from_time(t, zone) z = zone || get_tzone(t.zone) || ( local_tzone.period_for_local(t).abbreviation.to_s == t.zone && local_tzone ) || t.zone EoTime.new(t.to_f, z) end def make_from_date(d, zone) make_from_time( d.respond_to?(:to_time) ? d.to_time : Time.parse(d.strftime('%Y-%m-%d %H:%M:%S')), zone) end def make_from_array(a, zone) t = Time.utc(*a) s = t.strftime("%Y-%m-%d %H:%M:%S.#{'%06d' % t.usec}") make_from_string(s, zone) end def make_from_string(s, zone) parse(s, zone: zone) end def make_from_numeric(f, zone) EoTime.new(Time.now.to_f + f, zone) end def make_from_eotime(eot, zone) return eot if zone == nil || zone == eot.zone EoTime.new(eot.to_f, zone) end 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) class EoTime # # class methods class << self def now(zone=nil) EtOrbi.now(zone) end def parse(str, opts={}) EtOrbi.parse(str, opts) end def get_tzone(o) EtOrbi.get_tzone(o) end def local_tzone EtOrbi.local_tzone end def platform_info EtOrbi.platform_info end def make(o) EtOrbi.make_time(o) end end # # instance methods attr_reader :seconds attr_reader :zone def initialize(s, zone) @seconds = s.to_f @zone = self.class.get_tzone(zone || :local) fail ArgumentError.new( "cannot determine timezone from #{zone.inspect}" + "\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 @time = nil # cache for #to_time result end def seconds=(f) @time = nil @seconds = f end def zone=(z) @time = nil @zone = self.class.get_tzone(zone || :current) end 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 @seconds end def to_i @seconds.to_i end def strftime(format) format = format.gsub(/%(\/?Z|:{0,2}z)/) { |f| strfz(f) } to_time.strftime(format) end # Returns a Ruby Time instance. # # Warning: the timezone of that Time instance will be UTC. # def to_time @time ||= begin; u = utc; @zone.period_for_utc(u).to_local(u); end end def is_dst? @zone.period_for_utc(utc).std_offset != 0 end alias isdst is_dst? def to_debug_s uo = self.utc_offset uos = uo < 0 ? '-' : '+' uo = uo.abs uoh, uom = [ uo / 3600, uo % 3600 ] [ 'ot', self.strftime('%Y-%m-%d %H:%M:%S'), "%s%02d:%02d" % [ uos, uoh, uom ], "dst:#{self.isdst}" ].join(' ') end def utc_offset #@zone.period_for_utc(utc).utc_offset #@zone.period_for_utc(utc).utc_total_offset #@zone.period_for_utc(utc).std_offset @zone.period_for_utc(utc).utc_offset end %w[ year month day wday hour min sec usec asctime ].each do |m| define_method(m) { to_time.send(m) } end def iso8601(fraction_digits=0); to_time.iso8601(fraction_digits); end def ==(o) o.is_a?(EoTime) && o.seconds == @seconds && o.zone == @zone end #alias eq? == # FIXME see Object#== (ri) 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 <=>(o); @seconds <=> _to_f(o); 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 def monthdays date = to_time pos = 1 d = self.dup loop do d.add(-WEEK_S) break if d.month != date.month pos = pos + 1 end neg = -1 d = self.dup loop do d.add(WEEK_S) break if d.month != date.month neg = neg - 1 end [ "#{date.wday}##{pos}", "#{date.wday}##{neg}" ] end def to_s strftime('%Y-%m-%d %H:%M:%S %z') end # Debug current time by showing local time / delta / utc time # for example: "0120-7(0820)" # def to_utc_comparison_s per = @zone.period_for_utc(utc) off = per.utc_total_offset off = off / 3600 off = off >= 0 ? "+#{off}" : off.to_s strftime('%H%M') + off + utc.strftime('(%H%M)') end 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 = t.strftime('%Y-%m-%d %H:%M:%S') + ".#{(seconds % 1).to_s.split('.').last}" z = EtOrbi.local_tzone ? EtOrbi.local_tzone.period_for_local(t).abbreviation.to_s : nil "(secs:#{seconds},utc~:#{ts.inspect},ltz~:#{z.inspect})" end def strfz(code) return @zone.name if code == '%/Z' per = @zone.period_for_utc(utc) return per.abbreviation.to_s if code == '%Z' off = per.utc_total_offset # sn = off < 0 ? '-' : '+'; off = off.abs hr = off / 3600 mn = (off % 3600) / 60 sc = 0 fmt = if code == '%z' "%s%02d%02d" elsif code == '%:z' "%s%02d:%02d" else "%s%02d:%02d:%02d" end fmt % [ sn, hr, mn, sc ] 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 class << self # # extra public methods # https://en.wikipedia.org/wiki/ISO_8601 # Postel's law applies # def 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 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 find_olson_zone(str) list_olson_zones(str).each { |s| z = get_tzone(s); return z if z } nil end def determine_local_tzone etz = ENV['TZ'] 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 end # # protected module methods protected def 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 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 ) if hr nil end def 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 # # system tz determination def debian_tz path = '/etc/timezone' File.exist?(path) ? File.read(path).strip : nil rescue; nil; end def 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 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 def gather_tzs { :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 et-orbi-1.0.5/lib/etorbi.rb0000644000175000017500000000002413154026100014445 0ustar pravipravi require 'et-orbi' et-orbi-1.0.5/lib/et_orbi.rb0000644000175000017500000000002413154026100014604 0ustar pravipravi require 'et-orbi' et-orbi-1.0.5/LICENSE.txt0000644000175000017500000000210313154026100013711 0ustar pravipravi Copyright (c) 2017-2017, John Mettraux, jmettraux+flor@gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. et-orbi-1.0.5/Makefile0000644000175000017500000000124213154026100013531 0ustar pravipravi ## gem tasks ## NAME = \ $(shell ruby -e "s = eval(File.read(Dir['*.gemspec'][0])); puts s.name") VERSION = \ $(shell ruby -e "s = eval(File.read(Dir['*.gemspec'][0])); puts s.version") count_lines: find lib -name "*.rb" | xargs cat | ruby -e "p STDIN.readlines.count { |l| l = l.strip; l[0, 1] != '#' && l != '' }" cl: count_lines gemspec_validate: @echo "---" ruby -e "s = eval(File.read(Dir['*.gemspec'].first)); s.validate" @echo "---" name: gemspec_validate @echo "$(NAME) $(VERSION)" build: gemspec_validate gem build $(NAME).gemspec mkdir -p pkg mv $(NAME)-$(VERSION).gem pkg/ push: build gem push pkg/$(NAME)-$(VERSION).gem .PHONY: build push et-orbi-1.0.5/et-orbi.gemspec0000644000175000017500000000145113154026100015001 0ustar pravipravi Gem::Specification.new do |s| s.name = 'et-orbi' s.version = File.read( File.expand_path('../lib/et-orbi.rb', __FILE__) ).match(/ VERSION *= *['"]([^'"]+)/)[1] s.platform = Gem::Platform::RUBY s.authors = [ 'John Mettraux' ] s.email = [ 'jmettraux+flor@gmail.com' ] s.homepage = 'http://github.com/floraison/et-orbi' s.license = 'MIT' s.summary = 'time with zones' s.description = %{ Time zones for fugit and rufus-scheduler. Urbi et Orbi. }.strip #s.files = `git ls-files`.split("\n") s.files = Dir[ 'Makefile', 'lib/**/*.rb', #'spec/**/*.rb', 'test/**/*.rb', '*.gemspec', '*.txt', '*.md' ] s.add_runtime_dependency 'tzinfo' #s.add_runtime_dependency 'raabro', '>= 1.1.3' s.add_development_dependency 'rspec', '~> 3.4' s.require_path = 'lib' end