Module: EtOrbi

Defined in:
lib/et-orbi.rb,
lib/et-orbi/info.rb,
lib/et-orbi/make.rb,
lib/et-orbi/time.rb,
lib/et-orbi/zone.rb,
lib/et-orbi/zones.rb

Defined Under Namespace

Classes: EoTime

Constant Summary collapse

VERSION =
'1.2.11'
ZONES_ISO8601_REX =
%r{
  (?<=:\d\d)\s*
  (?:
    [-+]
    (?:[0-1][0-9]|2[0-4])
    (?:(?::)?(?:[0-5][0-9]|60))?
    (?![-+])
      |Z
  )
}x.freeze
ZONES_OLSON =
(
      ::TZInfo::Timezone.all
        .collect { |z| z.name }.sort +
      (0..12)
        .collect { |i| [ "UTC-#{i}", "UTC+#{i}" ] })
.flatten
.sort_by(&:size)
.reverse
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'
}

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

._os_zoneObject

test tool



89
90
91
# File 'lib/et-orbi/zones.rb', line 89

def _os_zone
  @_os_zone
end

Class Method Details

._make_infoObject

For ‘make info`



36
37
38
39
40
# File 'lib/et-orbi/info.rb', line 36

def _make_info

  puts render_nozone_time(Time.now.to_f)
  puts platform_info
end

.centos_tzObject



109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/et-orbi/zones.rb', line 109

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

.chronic_enabled=(b) ⇒ Object



9
10
11
# File 'lib/et-orbi/make.rb', line 9

def self.chronic_enabled=(b)
  @chronic_enabled = b
end

.chronic_enabled?Boolean

Returns:

  • (Boolean)


6
7
8
# File 'lib/et-orbi/make.rb', line 6

def self.chronic_enabled?
  @chronic_enabled
end

.debian_tzObject

system tz determination



102
103
104
105
106
107
# File 'lib/et-orbi/zones.rb', line 102

def debian_tz

  path = '/etc/timezone'

  File.exist?(path) ? File.read(path).strip : nil
rescue; nil; end

.determine_local_tzoneObject Also known as: zone



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/et-orbi/zones.rb', line 53

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

.extract_zone(str) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/et-orbi/zones.rb', line 35

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

.gather_tzsObject



131
132
133
134
# File 'lib/et-orbi/zones.rb', line 131

def gather_tzs

  { :debian => debian_tz, :centos => centos_tz, :osx => osx_tz }
end

.get_tzone(o) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/et-orbi/zone.rb', line 6

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

.list_iso8601_zones(s) ⇒ Object

en.wikipedia.org/wiki/ISO_8601 Postel’s law applies



21
22
23
24
# File 'lib/et-orbi/zones.rb', line 21

def list_iso8601_zones(s)

  s.scan(ZONES_ISO8601_REX).collect(&:strip)
end

.make_time(*a) ⇒ Object Also known as: make



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/et-orbi/make.rb', line 51

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

.now(zone = nil) ⇒ Object



15
16
17
18
# File 'lib/et-orbi/make.rb', line 15

def now(zone=nil)

  EoTime.new(Time.now.to_f, zone)
end

.os_tzObject



91
92
93
94
95
96
97
# File 'lib/et-orbi/zones.rb', line 91

def os_tz

  return (@_os_zone == '' ? nil : @_os_zone) \
    if defined?(@_os_zone) && @_os_zone

  @os_tz ||= (debian_tz || centos_tz || osx_tz)
end

.osx_tzObject



122
123
124
125
126
127
128
129
# File 'lib/et-orbi/zones.rb', line 122

def osx_tz

  path = '/etc/localtime'

  File.symlink?(path) ?
    File.readlink(path).split('/')[4..-1].join('/') :
    nil
rescue; nil; end

.parse(str, opts = {}) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/et-orbi/make.rb', line 20

def parse(str, opts={})

  str, str_zone = extract_zone(str)

  if t = chronic_parse(str, opts)
    str = [ t.strftime('%F %T'), str_zone ].compact.join(' ')
  end

  dt =
    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
  secs = zone.local_to_utc(dt).to_time.to_f

  EoTime.new(secs, zone)
end

.platform_infoObject



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/et-orbi/info.rb', line 6

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

.render_nozone_time(seconds) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/et-orbi/info.rb', line 42

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

.tweak_zone_name(name) ⇒ Object



164
165
166
167
168
169
170
171
172
# File 'lib/et-orbi/zones.rb', line 164

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

.windows_zone_name(zone_name, time) ⇒ Object

Semi-helpful, since it requires the current time



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/et-orbi/zones.rb', line 138

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