et-orbi-1.2.2/0000755000004100000410000000000013531124120013075 5ustar www-datawww-dataet-orbi-1.2.2/Makefile0000644000004100000410000000211313531124120014532 0ustar www-datawww-data ## 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 != '' }" find spec -name "*_spec.rb" | xargs cat | ruby -e "p STDIN.readlines.count { |l| l = l.strip; l[0, 1] != '#' && l != '' }" cl: count_lines scan: scan lib/**/*.rb gemspec_validate: @echo "---" ruby -e "s = eval(File.read(Dir['*.gemspec'].first)); p s.validate" @echo "---" name: gemspec_validate @echo "$(NAME) $(VERSION)" cw: find lib -name "*.rb" -exec ruby -cw {} \; build: gemspec_validate gem build $(NAME).gemspec mkdir -p pkg mv $(NAME)-$(VERSION).gem pkg/ push: build gem push pkg/$(NAME)-$(VERSION).gem spec: bundle exec rspec test: spec ## specific to project ## info: uname -a bundle exec ruby -v bundle exec ruby -Ilib -r et-orbi -e "EtOrbi._make_info" ## done ## .PHONY: count_lines scan gemspec_validate name cw build push spec info et-orbi-1.2.2/README.md0000644000004100000410000000626113531124120014361 0ustar www-datawww-data # et-orbi [![Build Status](https://secure.travis-ci.org/floraison/et-orbi.svg)](http://travis-ci.org/floraison/et-orbi) [![Build status](https://ci.appveyor.com/api/projects/status/6tbo9lk9qdor8ipl?svg=true)](https://ci.appveyor.com/project/jmettraux/et-orbi) [![Gem Version](https://badge.fury.io/rb/et-orbi.svg)](http://badge.fury.io/rb/et-orbi) [![Join the chat at https://gitter.im/floraison/fugit](https://badges.gitter.im/floraison/fugit.svg)](https://gitter.im/floraison/fugit?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 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" EtOrbi::EoTime.new(0, 'Europe/Moscow').to_zs # => "1970-01-01 03:00:00 Europe/Moscow" # "be precise in your speech" EtOrbi.parse('1970-01-01 03:00:00 Europe/Moscow') # => #, @time=nil> ``` More about `EtOrbi::EoTime` instances: ``` eot = EtOrbi::EoTime.new(0, 'Europe/Moscow') eot.to_local_time.class # => Time eot.to_local_time.to_s # => "1970-01-01 09:00:00 +0900" (at least on my system) # For the rest, EtOrbi::EoTime mimicks ::Time ``` 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, unless `ENV['TZ']` is set to a valid timezone name. Setting `ENV['TZ']` to nil can give back precedence to `Time.zone`. 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 occurrence computing. Timestamps and more. ## LICENSE MIT, see [LICENSE.txt](LICENSE.txt) et-orbi-1.2.2/CHANGELOG.md0000644000004100000410000000666313531124120014721 0ustar www-datawww-data # CHANGELOG.md ## et-orbi 1.2.2 released 2019-08-19 - Let EoTime#== accept EoTime or ::Time instances, gh-20, gh-7 ## et-orbi 1.2.1 released 2019-05-01 - More US time zone corrections, Vais Salikhov, gh-19 ## et-orbi 1.2.0 released 2019-04-25 - Add missing US time zone aliases, Vais Salikhov, gh-18 - Stop fooling around and stick to https://semver.org, start with 1.2.0 ## et-orbi 1.1.8 released 2019-04-11 - Work hard to make it work on Windows - Implement EoTime#rweek and #rday (reference week, reference day) - Alias EoTime#in_time_zone(zone) to #localtime(zone) - Stop fooling around and stick to https://semver.org ## et-orbi 1.1.7 released 2019-01-14 - Rework Chronic integration, prevent conflict with ActiveSupport Time.zone - Implement EtOrbi.extract_zone(s) (returns s1 and zone name) - Adapt specs and EoTime#to_debug_s to Windows on Appveyor ## et-orbi 1.1.6 released 2018-09-05 - Ensure Olson timezone name regex covers all timezone names https://github.com/floraison/fugit/issues/9 ## et-orbi 1.1.5 released 2018-08-25 - Prevent encoding issue on Windows with "Mitteleuropaeische Sommerzeit", gh-15 ## et-orbi 1.1.4 released 2018-07-25 - Silence 3 Ruby warnings (thanks Jamie Stackhouse, gh-13) - Introduce EtOrbi::Eotime.reach(points) ## et-orbi 1.1.3 released 2018-07-14 - Introduce EtOrbi::EoTime#ambiguous? - Introduce EtOrbi::EoTime#to_z for precise timezones (not offsets) ## et-orbi 1.1.2 released 2018-05-24 - Let EtOrbi.get_tzone understand "CST+0800" - Introduce EtOrbi.to_windows_tz (Asia/Kolkata to IST-5:30) ## et-orbi 1.1.1 released 2018-05-04 - Stop caching the local tzone, cache the tools used for determining it ## et-orbi 1.1.0 released 2018-03-25 - Implement EoTime .utc and .local (based on Time .utc and .local) - Add EoTime#translate(target_zone) as #localtime(target_zone) alias - Correct EoTime#iso8601 (was always returning zulu iso8601 string) ## et-orbi 1.0.9 released 2018-01-19 - Silence EoTime#strfz warning - Silence warnings reported by @mdave16, gh-10 - @philr added support for upcoming tzinfo 2.x, gh-9 ## et-orbi 1.0.8 released 2017-10-24 - Ensure ::EoTime.new accepts ActiveSupport::TimeZone, closes gh-8 ## et-orbi 1.0.7 released 2017-10-07 - Leverage ActiveSupport::TimeWithZone when present, gh-6 - Start error messages with a capital ## et-orbi 1.0.6 released 2017-10-05 - Introduce `make info` - Alias EoTime#to_utc_time to #utc - Alias EoTime#to_t to #to_local_time - Implement EoTime#to_local_time (since #to_time returns a UTC Time instance) ## 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.2.2/lib/0000755000004100000410000000000013531124120013643 5ustar www-datawww-dataet-orbi-1.2.2/lib/et-orbi/0000755000004100000410000000000013531124120015204 5ustar www-datawww-dataet-orbi-1.2.2/lib/et-orbi/make.rb0000644000004100000410000000446213531124120016454 0ustar www-datawww-data module EtOrbi class << self def now(zone=nil) EoTime.new(Time.now.to_f, zone) end def parse(str, opts={}) str, str_zone = extract_zone(str) if defined?(::Chronic) && t = ::Chronic.parse(str, opts) str = [ t.strftime('%F %T'), str_zone ].compact.join(' ') end begin DateTime.parse(str) rescue fail ArgumentError, "No time information in #{str.inspect}" end #end if RUBY_VERSION < '1.9.0' #end if RUBY_VERSION < '2.0.0' # # is necessary since Time.parse('xxx') in Ruby < 1.9 yields `now` zone = opts[:zone] || get_tzone(str_zone) || determine_local_tzone local = Time.parse(str) secs = zone.local_to_utc(local).to_f EoTime.new(secs, zone) end def make_time(*a) zone = a.length > 1 ? get_tzone(a.last) : nil a.pop if zone o = a.length > 1 ? a : a.first 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 alias make make_time protected def make_from_time(t, zone) z = zone || get_as_tzone(t) || get_tzone(t.zone) || get_local_tzone(t) z ||= t.zone # pass the abbreviation anyway, # it will be used in the resulting error message EoTime.new(t, 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 end end et-orbi-1.2.2/lib/et-orbi/zone.rb0000644000004100000410000000600313531124120016503 0ustar www-datawww-data module EtOrbi class << self def get_tzone(o) return o if o.is_a?(::TZInfo::Timezone) return nil if o == nil return determine_local_tzone if o == :local return ::TZInfo::Timezone.get('Zulu') if o == 'Z' return o.tzinfo if o.respond_to?(:tzinfo) o = to_offset(o) if o.is_a?(Numeric) return nil unless o.is_a?(String) s = tweak_zone_name(o) get_offset_tzone(s) || get_x_offset_tzone(s) || get_tzinfo_tzone(s) end protected # custom timezones, no DST, just an offset, like "+08:00" or "-01:30" # def get_offset_tzone(str) m = str.match(/\A([+-][0-1]?[0-9]):?([0-5][0-9])?\z/) rescue nil # # On Windows, the real encoding could be something other than UTF-8, # and make the match fail # return nil unless m tz = custom_tzs[str] return tz if tz 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 hr ? custom_tzs[str] = create_offset_tzone(hr * 3600 + mn * 60, str) : nil end if defined?(TZInfo::DataSources::ConstantOffsetDataTimezoneInfo) # TZInfo >= 2.0.0 def create_offset_tzone(utc_off, id) off = TZInfo::TimezoneOffset.new(utc_off, 0, id) tzi = TZInfo::DataSources::ConstantOffsetDataTimezoneInfo.new(id, off) tzi.create_timezone end else # TZInfo < 2.0.0 def create_offset_tzone(utc_off, id) tzi = TZInfo::TransitionDataTimezoneInfo.new(id) tzi.offset(id, utc_off, 0, id) tzi.create_timezone end end def get_x_offset_tzone(str) m = str.match(/\A_..-?[0-1]?\d:?(?:[0-5]\d)?(.+)\z/) rescue nil # # On Windows, the real encoding could be something other than UTF-8, # and make the match fail (as in .get_offset_tzone above) m ? ::TZInfo::Timezone.get(m[1]) : nil end 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' % [ sn, hr, mn, sc ] : '%s%02d:%02d' % [ sn, hr, mn ] end def get_tzinfo_tzone(name) #return ::TZInfo::Timezone.get(name) rescue nil loop do return ::TZInfo::Timezone.get(name) if ZONES_OLSON.include?(name) name = name[0..-2] return nil if name.empty? end end def windows_zone_code_x(zone_name) a = [ '_' ] a.concat(zone_name.split('/')[0, 2].collect { |s| s[0, 1].upcase }) a << '_' if a.size < 3 a.join end def get_local_tzone(t) l = Time.local(t.year, t.month, t.day, t.hour, t.min, t.sec, t.usec) (t.zone == l.zone) ? determine_local_tzone : nil end # https://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html # # If it responds to #time_zone, then return that time zone. # def get_as_tzone(t) t.respond_to?(:time_zone) ? t.time_zone : nil end end end et-orbi-1.2.2/lib/et-orbi/info.rb0000644000004100000410000000327213531124120016470 0ustar www-datawww-data module EtOrbi class << self def platform_info etos = Proc.new { |k, v| "#{k}:#{v.inspect}" } h = { 'etz' => ENV['TZ'], 'tnz' => Time.now.zone, 'tziv' => tzinfo_version, 'tzidv' => tzinfo_data_version, 'rv' => RUBY_VERSION, 'rp' => RUBY_PLATFORM, 'win' => Gem.win_platform?, 'rorv' => (Rails::VERSION::STRING rescue nil), 'astz' => ([ Time.zone.class, Time.zone.tzinfo.name ] rescue nil), 'eov' => EtOrbi::VERSION, 'eotnz' => '???', 'eotnfz' => '???', 'eotlzn' => '???' } if ltz = EtOrbi::EoTime.local_tzone h['eotnz'] = EtOrbi::EoTime.now.zone h['eotnfz'] = EtOrbi::EoTime.now.strftime('%z') h['eotnfZ'] = EtOrbi::EoTime.now.strftime('%Z') h['eotlzn'] = ltz.name end "(#{h.map(&etos).join(',')},#{gather_tzs.map(&etos).join(',')})" end # For `make info` # def _make_info puts render_nozone_time(Time.now.to_f) puts platform_info end def render_nozone_time(seconds) t = Time.utc(1970) + seconds ts = t.strftime('%Y-%m-%d %H:%M:%S') + ".#{(seconds % 1).to_s.split('.').last}" tz = EtOrbi.determine_local_tzone z = tz ? tz.period_for_local(t).abbreviation.to_s : nil "(secs:#{seconds},utc~:#{ts.inspect},ltz~:#{z.inspect})" end protected def tzinfo_version #TZInfo::VERSION Gem.loaded_specs['tzinfo'].version.to_s rescue => err err.inspect end def tzinfo_data_version #TZInfo::Data::VERSION rescue nil Gem.loaded_specs['tzinfo-data'].version.to_s rescue nil end end end et-orbi-1.2.2/lib/et-orbi/time.rb0000644000004100000410000002141113531124120016466 0ustar www-datawww-data module EtOrbi # Our EoTime class (which quacks like a ::Time). # # An EoTime instance should respond to most of the methods ::Time instances # respond to. If a method is missing, feel free to open an issue to # ask (politely) for it. If it makes sense, it'll get added, else # a workaround will get suggested. # The immediate workaround is to call #to_t on the EoTime instance to get # equivalent ::Time instance in the local, current, timezone. # 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.determine_local_tzone end def platform_info EtOrbi.platform_info end def make(o) EtOrbi.make_time(o) end def utc(*a) EtOrbi.send(:make_from_array, a, EtOrbi.get_tzone('UTC')) end def local(*a) EtOrbi.send(:make_from_array, a, local_tzone) end end # # instance methods attr_reader :seconds attr_reader :zone def initialize(s, zone) z = zone z = nil if zone.is_a?(String) && zone.strip == '' # # happens with JRuby (and offset tzones like +04:00) # # $ jruby -r time -e "p Time.parse('2012-1-1 12:00 +04:00').zone" # # => "" # ruby -r time -e "p Time.parse('2012-1-1 12:00 +04:00').zone" # # => nil @seconds = s.to_f @zone = self.class.get_tzone(z || :local) fail ArgumentError.new( "Cannot determine timezone from #{zone.inspect}" + "\n#{EtOrbi.render_nozone_time(@seconds)}" + "\n#{EtOrbi.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 # Returns true if this EoTime instance corresponds to 2 different UTC # times. # It happens when transitioning from DST to winter time. # # https://www.timeanddate.com/time/change/usa/new-york?year=2018 # def ambiguous? @zone.local_to_utc(@zone.utc_to_local(utc)) false rescue TZInfo::AmbiguousTime true end # Returns this ::EtOrbi::EoTime as a ::Time instance # in the current UTC timezone. # def utc Time.utc(1970) + @seconds end # Returns true if this ::EtOrbi::EoTime instance timezone is UTC. # Returns false else. # def utc? %w[ gmt utc zulu etc/gmt etc/utc ].include?( @zone.canonical_identifier.downcase) end alias getutc utc alias getgm utc alias to_utc_time 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 this ::EtOrbi::EoTime as a ::Time instance # in the current timezone. # # Has a #to_t alias. # def to_local_time Time.at(@seconds) end alias to_t to_local_time 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_total_offset end %w[ year month day wday yday hour min sec usec asctime ].each do |m| define_method(m) { to_time.send(m) } end def ==(o) if o.is_a?(EoTime) o.seconds == @seconds && (o.zone == @zone || o.zone.current_period == @zone.current_period) elsif o.is_a?(::Time) (to_f * 1000).to_i == (o.to_f * 1000).to_i else false end end # Nota Bene: # # Unlike ==, the equal? method should never be overridden by subclasses # as it is used to determine object identity (that is, a.equal?(b) if and # only if a is the same object as b) # # The eql? method returns true if obj and other refer to the same hash key. # This is used by Hash to test members for equality. 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 DAY_S = 24 * 3600 WEEK_S = 7 * DAY_S 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 def to_zs strftime('%Y-%m-%d %H:%M:%S %/Z') end def iso8601(fraction_digits=0) s = (fraction_digits || 0) > 0 ? ".%#{fraction_digits}N" : '' strftime("%Y-%m-%dT%H:%M:%S#{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 alias translate localtime alias in_time_zone localtime def wday_in_month [ count_weeks(-1), - count_weeks(1) ] end def rweek ((self - EtOrbi.make_time('2019-01-01 00:00:00', @zone)) / WEEK_S) .floor + 1 end def rday ((self - EtOrbi.make_time('2019-01-01 00:00:00', @zone)) / DAY_S) .floor + 1 end def reach(points) t = EoTime.new(self.to_f, @zone) step = 1 s = points[:second] || points[:sec] || points[:s] m = points[:minute] || points[:min] || points[:m] h = points[:hour] || points[:hou] || points[:h] fail ArgumentError.new("missing :second, :minute, and :hour") \ unless s || m || h if !s && !m step = 60 * 60 t -= t.sec t -= t.min * 60 elsif !s step = 60 t -= t.sec end loop do t += step next if s && t.sec != s next if m && t.min != m next if h && t.hour != h break end t end protected # Returns a Ruby Time instance. # # Warning: the timezone of that Time instance will be UTC when used with # TZInfo < 2.0.0. # def to_time @time ||= @zone.utc_to_local(utc) end def count_weeks(dir) c = 0 t = self until t.month != self.month c += 1 t += dir * (7 * 24 * 3600) end c 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 if @zone.name == 'UTC' 'Z' # align on Ruby ::Time#iso8601 elsif code == '%z' '%s%02d%02d' % [ sn, hr, mn ] elsif code == '%:z' '%s%02d:%02d' % [ sn, hr, mn ] else '%s%02d:%02d:%02d' % [ sn, hr, mn, sc ] end end def _to_f(o) fail ArgumentError( "Comparison of EoTime with #{o.inspect} failed" ) unless o.respond_to?(:to_f) o.to_f end end end et-orbi-1.2.2/lib/et-orbi/zones.rb0000644000004100000410000002304513531124120016673 0ustar www-datawww-data module EtOrbi class << self ZONES_ISO8601_REX = %r{ (?<=:\d\d)\s* (?: [-+] (?:[0-1][0-9]|2[0-4]) (?:(?::)?(?:[0-5][0-9]|60))? (?![-+]) |Z ) }x # https://en.wikipedia.org/wiki/ISO_8601 # Postel's law applies # def list_iso8601_zones(s) s.scan(ZONES_ISO8601_REX).collect(&:strip) end ZONES_OLSON = ( ::TZInfo::Timezone.all .collect { |z| z.name }.sort + (0..12) .collect { |i| [ "UTC-#{i}", "UTC+#{i}" ] }) .flatten .sort_by(&:size) .reverse def extract_zone(str) s = str.dup zs = ZONES_OLSON .inject([]) { |a, z| i = s.index(z); next a unless i a << z s[i, z.length] = '' a } s.gsub!(ZONES_ISO8601_REX) { |m| zs << m.strip; '' } #if zs.empty? zs = zs.sort_by { |z| str.index(z) } [ s.strip, zs.last ] end def determine_local_tzone # ENV has the priority etz = ENV['TZ'] tz = etz && get_tzone(etz) return tz if tz # then Rails/ActiveSupport has the priority if Time.respond_to?(:zone) && Time.zone.respond_to?(:tzinfo) tz = Time.zone.tzinfo return tz if tz end # then the operating system is queried tz = ::TZInfo::Timezone.get(os_tz) rescue nil return tz if tz # then Ruby's time zone abbs are looked at CST, JST, CEST, ... :-( tzs = determine_local_tzones tz = (etz && tzs.find { |z| z.name == etz }) || tzs.first return tz if tz # then, fall back to GMT offest :-( n = Time.now get_tzone(n.zone) || get_tzone(n.strftime('%Z%z')) end alias zone determine_local_tzone attr_accessor :_os_zone # test tool def os_tz return (@_os_zone == '' ? nil : @_os_zone) \ if defined?(@_os_zone) && @_os_zone @os_tz ||= (debian_tz || centos_tz || osx_tz) 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 gather_tzs { :debian => debian_tz, :centos => centos_tz, :osx => osx_tz } end # Semi-helpful, since it requires the current time # def windows_zone_name(zone_name, time) twin = Time.utc(time.year, 1, 1) # winter tsum = Time.utc(time.year, 7, 1) # summer tz = ::TZInfo::Timezone.get(zone_name) tzo = tz.period_for_local(time).utc_total_offset tzop = tzo < 0 ? nil : '-'; tzo = tzo.abs tzoh = tzo / 3600 tzos = tzo % 3600 tzos = tzos == 0 ? nil : ':%02d' % (tzos / 60) abbs = [ tz.period_for_utc(twin).abbreviation.to_s, tz.period_for_utc(tsum).abbreviation.to_s ] .uniq if abbs[0].match(/\A[A-Z]/) [ abbs[0], tzop, tzoh, tzos, abbs[1] ] .compact.join else [ windows_zone_code_x(zone_name), tzop, tzoh, tzos || ':00', zone_name ] .collect(&:to_s).join end end def tweak_zone_name(name) return name unless (name.match(/./) rescue nil) # to prevent invalid byte sequence in UTF-8..., gh-15 normalize(name) || unzz(name) || name end protected def normalize(name) ZONE_ALIASES[name.sub(/ Daylight /, ' Standard ')] end def unzz(name) m = name.match(/\A([A-Z]{3,4})([+-])(\d{1,2}):?(\d{2})?\z/) return nil unless m abbs = [ m[1] ]; a = m[1] abbs << "#{a}T" if a.size < 4 off = (m[2] == '+' ? 1 : -1) * (m[3].to_i * 3600 + (m[4] || '0').to_i * 60) t = Time.now twin = Time.utc(t.year, 1, 1) # winter tsum = Time.utc(t.year, 7, 1) # summer tz_all .each { |tz| abbs.each { |abb| per = tz.period_for_utc(twin) return tz.name \ if per.abbreviation.to_s == abb && per.utc_total_offset == off per = tz.period_for_utc(tsum) return tz.name \ if per.abbreviation.to_s == abb && per.utc_total_offset == off } } nil end def determine_local_tzones tabbs = (-6..5) .collect { |i| t = Time.now + i * 30 * 24 * 3600 "#{t.zone}_#{t.utc_offset}" } .uniq .sort .join('|') t = Time.now #tu = t.dup.utc # /!\ dup is necessary, #utc modifies its target twin = Time.local(t.year, 1, 1) # winter tsum = Time.local(t.year, 7, 1) # summer @tz_winter_summer ||= {} @tz_winter_summer[tabbs] ||= tz_all .select { |tz| pw = tz.period_for_local(twin) ps = tz.period_for_local(tsum) tabbs == [ "#{pw.abbreviation}_#{pw.utc_total_offset}", "#{ps.abbreviation}_#{ps.utc_total_offset}" ] .uniq.sort.join('|') } @tz_winter_summer[tabbs] end def custom_tzs; @custom_tzs ||= {}; end def tz_all; @tz_all ||= ::TZInfo::Timezone.all; end end # https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones # https://support.microsoft.com/en-ca/help/973627/microsoft-time-zone-index-values # https://ss64.com/nt/timezones.html ZONE_ALIASES = { 'Coordinated Universal Time' => 'UTC', 'Afghanistan Standard Time' => 'Asia/Kabul', 'FLE Standard Time' => 'Europe/Helsinki', 'Central Europe Standard Time' => 'Europe/Prague', 'UTC-11' => 'Etc/GMT+11', 'W. Europe Standard Time' => 'Europe/Rome', 'W. Central Africa Standard Time' => 'Africa/Lagos', 'SA Western Standard Time' => 'America/La_Paz', 'Pacific SA Standard Time' => 'America/Santiago', 'Argentina Standard Time' => 'America/Argentina/Buenos_Aires', 'Caucasus Standard Time' => 'Asia/Yerevan', 'AUS Eastern Standard Time' => 'Australia/Sydney', 'Azerbaijan Standard Time' => 'Asia/Baku', 'Eastern Standard Time' => 'America/New_York', 'Arab Standard Time' => 'Asia/Riyadh', 'Bangladesh Standard Time' => 'Asia/Dhaka', 'Belarus Standard Time' => 'Europe/Minsk', 'Romance Standard Time' => 'Europe/Paris', 'Central America Standard Time' => 'America/Belize', 'Atlantic Standard Time' => 'Atlantic/Bermuda', 'Venezuela Standard Time' => 'America/Caracas', 'Central European Standard Time' => 'Europe/Warsaw', 'South Africa Standard Time' => 'Africa/Johannesburg', #'UTC' => 'Etc/UTC', # 'UTC' is good as is 'E. South America Standard Time' => 'America/Sao_Paulo', 'Central Asia Standard Time' => 'Asia/Almaty', 'Singapore Standard Time' => 'Asia/Singapore', 'Greenwich Standard Time' => 'Africa/Monrovia', 'Cape Verde Standard Time' => 'Atlantic/Cape_Verde', 'SE Asia Standard Time' => 'Asia/Bangkok', 'SA Pacific Standard Time' => 'America/Bogota', 'China Standard Time' => 'Asia/Shanghai', 'Myanmar Standard Time' => 'Asia/Yangon', 'E. Africa Standard Time' => 'Africa/Nairobi', 'Hawaiian Standard Time' => 'Pacific/Honolulu', 'E. Europe Standard Time' => 'Europe/Nicosia', 'Tokyo Standard Time' => 'Asia/Tokyo', 'Egypt Standard Time' => 'Africa/Cairo', 'SA Eastern Standard Time' => 'America/Cayenne', 'GMT Standard Time' => 'Europe/London', 'Fiji Standard Time' => 'Pacific/Fiji', 'West Asia Standard Time' => 'Asia/Tashkent', 'Georgian Standard Time' => 'Asia/Tbilisi', 'GTB Standard Time' => 'Europe/Athens', 'Greenland Standard Time' => 'America/Godthab', 'West Pacific Standard Time' => 'Pacific/Guam', 'Mauritius Standard Time' => 'Indian/Mauritius', 'India Standard Time' => 'Asia/Kolkata', 'Iran Standard Time' => 'Asia/Tehran', 'Arabic Standard Time' => 'Asia/Baghdad', 'Israel Standard Time' => 'Asia/Jerusalem', 'Jordan Standard Time' => 'Asia/Amman', 'UTC+12' => 'Etc/GMT-12', 'Korea Standard Time' => 'Asia/Seoul', 'Middle East Standard Time' => 'Asia/Beirut', 'Central Standard Time (Mexico)' => 'America/Mexico_City', 'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar', 'Morocco Standard Time' => 'Africa/Casablanca', 'Namibia Standard Time' => 'Africa/Windhoek', 'Nepal Standard Time' => 'Asia/Kathmandu', 'Central Pacific Standard Time' => 'Etc/GMT-11', 'New Zealand Standard Time' => 'Pacific/Auckland', 'Arabian Standard Time' => 'Asia/Dubai', 'Pakistan Standard Time' => 'Asia/Karachi', 'Paraguay Standard Time' => 'America/Asuncion', 'Pacific Standard Time' => 'America/Los_Angeles', 'Russian Standard Time' => 'Europe/Moscow', 'Samoa Standard Time' => 'Pacific/Pago_Pago', 'UTC-02' => 'Etc/GMT+2', 'Sri Lanka Standard Time' => 'Asia/Kolkata', 'Syria Standard Time' => 'Asia/Damascus', 'Taipei Standard Time' => 'Asia/Taipei', 'Tonga Standard Time' => 'Pacific/Tongatapu', 'Turkey Standard Time' => 'Asia/Istanbul', 'Montevideo Standard Time' => 'America/Montevideo', 'CST5CDT' => 'CST6CDT', 'Alaskan Standard Time' => 'America/Anchorage', 'Central Standard Time' => 'America/Chicago', 'Mountain Standard Time' => 'America/Denver', 'US Eastern Standard Time' => 'America/Indiana/Indianapolis', 'US Mountain Standard Time' => 'America/Phoenix' } end et-orbi-1.2.2/lib/et-orbi.rb0000644000004100000410000000035213531124120015531 0ustar www-datawww-data require 'date' if RUBY_VERSION < '1.9.0' require 'time' require 'tzinfo' require 'et-orbi/info' require 'et-orbi/make' require 'et-orbi/time' require 'et-orbi/zones' require 'et-orbi/zone' module EtOrbi VERSION = '1.2.2' end et-orbi-1.2.2/lib/etorbi.rb0000644000004100000410000000002413531124120015450 0ustar www-datawww-data require 'et-orbi' et-orbi-1.2.2/lib/et_orbi.rb0000644000004100000410000000002413531124120015607 0ustar www-datawww-data require 'et-orbi' et-orbi-1.2.2/CREDITS.md0000644000004100000410000000232413531124120014515 0ustar www-datawww-data # et-orbi credits * d-m-u (https://github.com/d-m-u) EoTime#==(Time), gh-20, gh-7 * Vais Salikhov (https://github.com/vais) missing US timezone aliases, gh-18 gh-19 * Wenhui Wang https://github.com/w11th .parse vs Chronic+ActiveSupport, fugit 11 * Marcel https://github.com/MTRNord "Mitteleuropaeische Sommerzeit", gh-15 * Stanisław Pitucha https://github.com/viraptor rubygems link to changelog, gh-14 * Jamie Stackhouse https://github.com/itsjamie reported warnings, gh-13 * mlotfi2005 https://github.com/mlotfi2005 reported infinite loop, gh-12 * Mayur Dave https://github.com/mdave16 reported trailing warning, gh-10 * Phil Ross https://github.com/philr added support for upcoming TZInfo 2.x, gh-9 * Miles Lane https://github.com/mileslane issue with ActiveSupport::TimeZone, gh-8 * Chris Arcand https://github.com/chrisarcand shew various issues around America/Chicago on OSX, gh-4 * Jeremy Strouse https://github.com/jstrouse helped alleviate issue with older versions of TZInfo See also https://github.com/floraison/et-orbi/graphs/contributors ## 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.2.2/et-orbi.gemspec0000644000004100000410000000305713531124120016010 0ustar www-datawww-data 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.metadata = { 'changelog_uri' => s.homepage + '/blob/master/CHANGELOG.md', 'documentation_uri' => s.homepage, 'bug_tracker_uri' => s.homepage + '/issues', #'mailing_list_uri' => 'https://groups.google.com/forum/#!forum/floraison', 'homepage_uri' => s.homepage, 'source_code_uri' => s.homepage, #'wiki_uri' => s.homepage + '/wiki', } #s.files = `git ls-files`.split("\n") s.files = Dir[ 'README.{md,txt}', 'CHANGELOG.{md,txt}', 'CREDITS.{md,txt}', 'LICENSE.{md,txt}', 'Makefile', 'lib/**/*.rb', #'spec/**/*.rb', 'test/**/*.rb', "#{s.name}.gemspec", ] s.add_runtime_dependency 'tzinfo' # # YES, open dependency, fill an issue at # https://github.com/floraison/et-orbi/issues # if you experience a conflict between et-orbi and tzinfo. # # DO NOT raise an issue at tzinfo # this open dependency is my (@jmettraux) responsibility. #s.add_runtime_dependency 'raabro', '>= 1.1.3' s.add_development_dependency 'rspec', '~> 3.8' s.add_development_dependency 'chronic', '~> 0.10' s.require_path = 'lib' end et-orbi-1.2.2/LICENSE.txt0000644000004100000410000000212313531124120014716 0ustar www-datawww-data Copyright (c) 2017-2019, 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. Made in Japan