et-orbi 1.1.7 → 1.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/Makefile +4 -1
- data/README.md +1 -0
- data/lib/et-orbi.rb +6 -501
- data/lib/et-orbi/info.rb +74 -0
- data/lib/et-orbi/make.rb +112 -0
- data/lib/et-orbi/{eo_time.rb → time.rb} +18 -4
- data/lib/et-orbi/zone.rb +132 -0
- data/lib/et-orbi/zones.rb +328 -0
- metadata +7 -4
- data/lib/et-orbi/zone_aliases.rb +0 -121
data/lib/et-orbi/info.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
module EtOrbi
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def platform_info
|
7
|
+
|
8
|
+
etos = Proc.new { |k, v| "#{k}:#{v.inspect}" }
|
9
|
+
|
10
|
+
h = {
|
11
|
+
'etz' => ENV['TZ'],
|
12
|
+
'tnz' => Time.now.zone,
|
13
|
+
'tziv' => tzinfo_version,
|
14
|
+
'tzidv' => tzinfo_data_version,
|
15
|
+
'rv' => RUBY_VERSION,
|
16
|
+
'rp' => RUBY_PLATFORM,
|
17
|
+
'win' => Gem.win_platform?,
|
18
|
+
'rorv' => (Rails::VERSION::STRING rescue nil),
|
19
|
+
'astz' => ([ Time.zone.class, Time.zone.tzinfo.name ] rescue nil),
|
20
|
+
'eov' => EtOrbi::VERSION,
|
21
|
+
'eotnz' => '???',
|
22
|
+
'eotnfz' => '???',
|
23
|
+
'eotlzn' => '???' }
|
24
|
+
if ltz = EtOrbi::EoTime.local_tzone
|
25
|
+
h['eotnz'] = EtOrbi::EoTime.now.zone
|
26
|
+
h['eotnfz'] = EtOrbi::EoTime.now.strftime('%z')
|
27
|
+
h['eotnfZ'] = EtOrbi::EoTime.now.strftime('%Z')
|
28
|
+
h['eotlzn'] = ltz.name
|
29
|
+
end
|
30
|
+
|
31
|
+
"(#{h.map(&etos).join(',')},#{gather_tzs.map(&etos).join(',')})"
|
32
|
+
end
|
33
|
+
|
34
|
+
# For `make info`
|
35
|
+
#
|
36
|
+
def _make_info
|
37
|
+
|
38
|
+
puts render_nozone_time(Time.now.to_f)
|
39
|
+
puts platform_info
|
40
|
+
end
|
41
|
+
|
42
|
+
def render_nozone_time(seconds)
|
43
|
+
|
44
|
+
t =
|
45
|
+
Time.utc(1970) + seconds
|
46
|
+
ts =
|
47
|
+
t.strftime('%Y-%m-%d %H:%M:%S') +
|
48
|
+
".#{(seconds % 1).to_s.split('.').last}"
|
49
|
+
tz =
|
50
|
+
EtOrbi.determine_local_tzone
|
51
|
+
z =
|
52
|
+
tz ? tz.period_for_local(t).abbreviation.to_s : nil
|
53
|
+
|
54
|
+
"(secs:#{seconds},utc~:#{ts.inspect},ltz~:#{z.inspect})"
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def tzinfo_version
|
60
|
+
|
61
|
+
#TZInfo::VERSION
|
62
|
+
Gem.loaded_specs['tzinfo'].version.to_s
|
63
|
+
rescue => err
|
64
|
+
err.inspect
|
65
|
+
end
|
66
|
+
|
67
|
+
def tzinfo_data_version
|
68
|
+
|
69
|
+
#TZInfo::Data::VERSION rescue nil
|
70
|
+
Gem.loaded_specs['tzinfo-data'].version.to_s rescue nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
data/lib/et-orbi/make.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
|
2
|
+
module EtOrbi
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def now(zone=nil)
|
7
|
+
|
8
|
+
EoTime.new(Time.now.to_f, zone)
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse(str, opts={})
|
12
|
+
|
13
|
+
str, str_zone = extract_zone(str)
|
14
|
+
|
15
|
+
if defined?(::Chronic) && t = ::Chronic.parse(str, opts)
|
16
|
+
|
17
|
+
str = [ t.strftime('%F %T'), str_zone ].compact.join(' ')
|
18
|
+
end
|
19
|
+
|
20
|
+
begin
|
21
|
+
DateTime.parse(str)
|
22
|
+
rescue
|
23
|
+
fail ArgumentError, "No time information in #{str.inspect}"
|
24
|
+
end
|
25
|
+
#end if RUBY_VERSION < '1.9.0'
|
26
|
+
#end if RUBY_VERSION < '2.0.0'
|
27
|
+
#
|
28
|
+
# is necessary since Time.parse('xxx') in Ruby < 1.9 yields `now`
|
29
|
+
|
30
|
+
zone =
|
31
|
+
opts[:zone] ||
|
32
|
+
get_tzone(str_zone) ||
|
33
|
+
determine_local_tzone
|
34
|
+
|
35
|
+
local = Time.parse(str)
|
36
|
+
secs = zone.local_to_utc(local).to_f
|
37
|
+
|
38
|
+
EoTime.new(secs, zone)
|
39
|
+
end
|
40
|
+
|
41
|
+
def make_time(*a)
|
42
|
+
|
43
|
+
zone = a.length > 1 ? get_tzone(a.last) : nil
|
44
|
+
a.pop if zone
|
45
|
+
|
46
|
+
o = a.length > 1 ? a : a.first
|
47
|
+
|
48
|
+
case o
|
49
|
+
when Time then make_from_time(o, zone)
|
50
|
+
when Date then make_from_date(o, zone)
|
51
|
+
when Array then make_from_array(o, zone)
|
52
|
+
when String then make_from_string(o, zone)
|
53
|
+
when Numeric then make_from_numeric(o, zone)
|
54
|
+
when ::EtOrbi::EoTime then make_from_eotime(o, zone)
|
55
|
+
else fail ArgumentError.new(
|
56
|
+
"Cannot turn #{o.inspect} to a ::EtOrbi::EoTime instance")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
alias make make_time
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def make_from_time(t, zone)
|
64
|
+
|
65
|
+
z =
|
66
|
+
zone ||
|
67
|
+
get_as_tzone(t) ||
|
68
|
+
get_tzone(t.zone) ||
|
69
|
+
get_local_tzone(t)
|
70
|
+
|
71
|
+
z ||= t.zone
|
72
|
+
# pass the abbreviation anyway,
|
73
|
+
# it will be used in the resulting error message
|
74
|
+
|
75
|
+
EoTime.new(t, z)
|
76
|
+
end
|
77
|
+
|
78
|
+
def make_from_date(d, zone)
|
79
|
+
|
80
|
+
make_from_time(
|
81
|
+
d.respond_to?(:to_time) ?
|
82
|
+
d.to_time :
|
83
|
+
Time.parse(d.strftime('%Y-%m-%d %H:%M:%S')),
|
84
|
+
zone)
|
85
|
+
end
|
86
|
+
|
87
|
+
def make_from_array(a, zone)
|
88
|
+
|
89
|
+
t = Time.utc(*a)
|
90
|
+
s = t.strftime("%Y-%m-%d %H:%M:%S.#{'%06d' % t.usec}")
|
91
|
+
|
92
|
+
make_from_string(s, zone)
|
93
|
+
end
|
94
|
+
|
95
|
+
def make_from_string(s, zone)
|
96
|
+
|
97
|
+
parse(s, zone: zone)
|
98
|
+
end
|
99
|
+
|
100
|
+
def make_from_numeric(f, zone)
|
101
|
+
|
102
|
+
EoTime.new(Time.now.to_f + f, zone)
|
103
|
+
end
|
104
|
+
|
105
|
+
def make_from_eotime(eot, zone)
|
106
|
+
|
107
|
+
return eot if zone == nil || zone == eot.zone
|
108
|
+
EoTime.new(eot.to_f, zone)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
@@ -49,12 +49,12 @@ module EtOrbi
|
|
49
49
|
|
50
50
|
def utc(*a)
|
51
51
|
|
52
|
-
EtOrbi.make_from_array
|
52
|
+
EtOrbi.send(:make_from_array, a, EtOrbi.get_tzone('UTC'))
|
53
53
|
end
|
54
54
|
|
55
55
|
def local(*a)
|
56
56
|
|
57
|
-
EtOrbi.make_from_array
|
57
|
+
EtOrbi.send(:make_from_array, a, local_tzone)
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
@@ -198,7 +198,7 @@ module EtOrbi
|
|
198
198
|
end
|
199
199
|
|
200
200
|
%w[
|
201
|
-
year month day wday hour min sec usec asctime
|
201
|
+
year month day wday yday hour min sec usec asctime
|
202
202
|
].each do |m|
|
203
203
|
define_method(m) { to_time.send(m) }
|
204
204
|
end
|
@@ -223,7 +223,8 @@ module EtOrbi
|
|
223
223
|
def +(t); inc(t, 1); end
|
224
224
|
def -(t); inc(t, -1); end
|
225
225
|
|
226
|
-
|
226
|
+
DAY_S = 24 * 3600
|
227
|
+
WEEK_S = 7 * DAY_S
|
227
228
|
|
228
229
|
def monthdays
|
229
230
|
|
@@ -308,12 +309,25 @@ module EtOrbi
|
|
308
309
|
end
|
309
310
|
|
310
311
|
alias translate localtime
|
312
|
+
alias in_time_zone localtime
|
311
313
|
|
312
314
|
def wday_in_month
|
313
315
|
|
314
316
|
[ count_weeks(-1), - count_weeks(1) ]
|
315
317
|
end
|
316
318
|
|
319
|
+
def rweek
|
320
|
+
|
321
|
+
((self - EtOrbi.make_time('2019-01-01 00:00:00', @zone)) / WEEK_S)
|
322
|
+
.floor + 1
|
323
|
+
end
|
324
|
+
|
325
|
+
def rday
|
326
|
+
|
327
|
+
((self - EtOrbi.make_time('2019-01-01 00:00:00', @zone)) / DAY_S)
|
328
|
+
.floor + 1
|
329
|
+
end
|
330
|
+
|
317
331
|
def reach(points)
|
318
332
|
|
319
333
|
t = EoTime.new(self.to_f, @zone)
|
data/lib/et-orbi/zone.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
|
2
|
+
module EtOrbi
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def get_tzone(o)
|
7
|
+
|
8
|
+
return o if o.is_a?(::TZInfo::Timezone)
|
9
|
+
return nil if o == nil
|
10
|
+
return determine_local_tzone if o == :local
|
11
|
+
return ::TZInfo::Timezone.get('Zulu') if o == 'Z'
|
12
|
+
return o.tzinfo if o.respond_to?(:tzinfo)
|
13
|
+
|
14
|
+
o = to_offset(o) if o.is_a?(Numeric)
|
15
|
+
|
16
|
+
return nil unless o.is_a?(String)
|
17
|
+
|
18
|
+
s = tweak_zone_name(o)
|
19
|
+
|
20
|
+
get_offset_tzone(s) ||
|
21
|
+
get_x_offset_tzone(s) ||
|
22
|
+
get_tzinfo_tzone(s)
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
# custom timezones, no DST, just an offset, like "+08:00" or "-01:30"
|
28
|
+
#
|
29
|
+
def get_offset_tzone(str)
|
30
|
+
|
31
|
+
m = str.match(/\A([+-][0-1]?[0-9]):?([0-5][0-9])?\z/) rescue nil
|
32
|
+
#
|
33
|
+
# On Windows, the real encoding could be something other than UTF-8,
|
34
|
+
# and make the match fail
|
35
|
+
#
|
36
|
+
return nil unless m
|
37
|
+
|
38
|
+
tz = custom_tzs[str]
|
39
|
+
return tz if tz
|
40
|
+
|
41
|
+
hr = m[1].to_i
|
42
|
+
mn = m[2].to_i
|
43
|
+
|
44
|
+
hr = nil if hr.abs > 11
|
45
|
+
hr = nil if mn > 59
|
46
|
+
mn = -mn if hr && hr < 0
|
47
|
+
|
48
|
+
hr ?
|
49
|
+
custom_tzs[str] = create_offset_tzone(hr * 3600 + mn * 60, str) :
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
if defined?(TZInfo::DataSources::ConstantOffsetDataTimezoneInfo)
|
54
|
+
# TZInfo >= 2.0.0
|
55
|
+
|
56
|
+
def create_offset_tzone(utc_off, id)
|
57
|
+
|
58
|
+
off = TZInfo::TimezoneOffset.new(utc_off, 0, id)
|
59
|
+
tzi = TZInfo::DataSources::ConstantOffsetDataTimezoneInfo.new(id, off)
|
60
|
+
tzi.create_timezone
|
61
|
+
end
|
62
|
+
|
63
|
+
else
|
64
|
+
# TZInfo < 2.0.0
|
65
|
+
|
66
|
+
def create_offset_tzone(utc_off, id)
|
67
|
+
|
68
|
+
tzi = TZInfo::TransitionDataTimezoneInfo.new(id)
|
69
|
+
tzi.offset(id, utc_off, 0, id)
|
70
|
+
tzi.create_timezone
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_x_offset_tzone(str)
|
75
|
+
|
76
|
+
m = str.match(/\A_..-?[0-1]?\d:?(?:[0-5]\d)?(.+)\z/) rescue nil
|
77
|
+
#
|
78
|
+
# On Windows, the real encoding could be something other than UTF-8,
|
79
|
+
# and make the match fail (as in .get_offset_tzone above)
|
80
|
+
|
81
|
+
m ? ::TZInfo::Timezone.get(m[1]) : nil
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_offset(n)
|
85
|
+
|
86
|
+
i = n.to_i
|
87
|
+
sn = i < 0 ? '-' : '+'; i = i.abs
|
88
|
+
hr = i / 3600; mn = i % 3600; sc = i % 60
|
89
|
+
|
90
|
+
sc > 0 ?
|
91
|
+
'%s%02d:%02d:%02d' % [ sn, hr, mn, sc ] :
|
92
|
+
'%s%02d:%02d' % [ sn, hr, mn ]
|
93
|
+
end
|
94
|
+
|
95
|
+
def get_tzinfo_tzone(name)
|
96
|
+
|
97
|
+
#return ::TZInfo::Timezone.get(name) rescue nil
|
98
|
+
|
99
|
+
loop do
|
100
|
+
return ::TZInfo::Timezone.get(name) if ZONES_OLSON.include?(name)
|
101
|
+
name = name[0..-2]
|
102
|
+
return nil if name.empty?
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def windows_zone_code_x(zone_name)
|
107
|
+
|
108
|
+
a = [ '_' ]
|
109
|
+
a.concat(zone_name.split('/')[0, 2].collect { |s| s[0, 1].upcase })
|
110
|
+
a << '_' if a.size < 3
|
111
|
+
|
112
|
+
a.join
|
113
|
+
end
|
114
|
+
|
115
|
+
def get_local_tzone(t)
|
116
|
+
|
117
|
+
l = Time.local(t.year, t.month, t.day, t.hour, t.min, t.sec, t.usec)
|
118
|
+
|
119
|
+
(t.zone == l.zone) ? determine_local_tzone : nil
|
120
|
+
end
|
121
|
+
|
122
|
+
# https://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html
|
123
|
+
#
|
124
|
+
# If it responds to #time_zone, then return that time zone.
|
125
|
+
#
|
126
|
+
def get_as_tzone(t)
|
127
|
+
|
128
|
+
t.respond_to?(:time_zone) ? t.time_zone : nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
@@ -0,0 +1,328 @@
|
|
1
|
+
|
2
|
+
module EtOrbi
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
ZONES_ISO8601_REX =
|
7
|
+
%r{
|
8
|
+
(?<=:\d\d)\s*
|
9
|
+
(?:
|
10
|
+
[-+]
|
11
|
+
(?:[0-1][0-9]|2[0-4])
|
12
|
+
(?:(?::)?(?:[0-5][0-9]|60))?
|
13
|
+
(?![-+])
|
14
|
+
|Z
|
15
|
+
)
|
16
|
+
}x
|
17
|
+
|
18
|
+
# https://en.wikipedia.org/wiki/ISO_8601
|
19
|
+
# Postel's law applies
|
20
|
+
#
|
21
|
+
def list_iso8601_zones(s)
|
22
|
+
|
23
|
+
s.scan(ZONES_ISO8601_REX).collect(&:strip)
|
24
|
+
end
|
25
|
+
|
26
|
+
ZONES_OLSON = (
|
27
|
+
::TZInfo::Timezone.all
|
28
|
+
.collect { |z| z.name }.sort +
|
29
|
+
(0..12)
|
30
|
+
.collect { |i| [ "UTC-#{i}", "UTC+#{i}" ] })
|
31
|
+
.flatten
|
32
|
+
.sort_by(&:size)
|
33
|
+
.reverse
|
34
|
+
|
35
|
+
def extract_zone(str)
|
36
|
+
|
37
|
+
s = str.dup
|
38
|
+
|
39
|
+
zs = ZONES_OLSON
|
40
|
+
.inject([]) { |a, z|
|
41
|
+
i = s.index(z); next a unless i
|
42
|
+
a << z
|
43
|
+
s[i, z.length] = ''
|
44
|
+
a }
|
45
|
+
|
46
|
+
s.gsub!(ZONES_ISO8601_REX) { |m| zs << m.strip; '' } #if zs.empty?
|
47
|
+
|
48
|
+
zs = zs.sort_by { |z| str.index(z) }
|
49
|
+
|
50
|
+
[ s.strip, zs.last ]
|
51
|
+
end
|
52
|
+
|
53
|
+
def determine_local_tzone
|
54
|
+
|
55
|
+
# ENV has the priority
|
56
|
+
|
57
|
+
etz = ENV['TZ']
|
58
|
+
|
59
|
+
tz = etz && get_tzone(etz)
|
60
|
+
return tz if tz
|
61
|
+
|
62
|
+
# then Rails/ActiveSupport has the priority
|
63
|
+
|
64
|
+
if Time.respond_to?(:zone) && Time.zone.respond_to?(:tzinfo)
|
65
|
+
tz = Time.zone.tzinfo
|
66
|
+
return tz if tz
|
67
|
+
end
|
68
|
+
|
69
|
+
# then the operating system is queried
|
70
|
+
|
71
|
+
tz = ::TZInfo::Timezone.get(os_tz) rescue nil
|
72
|
+
return tz if tz
|
73
|
+
|
74
|
+
# then Ruby's time zone abbs are looked at CST, JST, CEST, ... :-(
|
75
|
+
|
76
|
+
tzs = determine_local_tzones
|
77
|
+
tz = (etz && tzs.find { |z| z.name == etz }) || tzs.first
|
78
|
+
return tz if tz
|
79
|
+
|
80
|
+
# then, fall back to GMT offest :-(
|
81
|
+
|
82
|
+
n = Time.now
|
83
|
+
|
84
|
+
get_tzone(n.zone) ||
|
85
|
+
get_tzone(n.strftime('%Z%z'))
|
86
|
+
end
|
87
|
+
alias zone determine_local_tzone
|
88
|
+
|
89
|
+
attr_accessor :_os_zone # test tool
|
90
|
+
|
91
|
+
def os_tz
|
92
|
+
|
93
|
+
return (@_os_zone == '' ? nil : @_os_zone) \
|
94
|
+
if defined?(@_os_zone) && @_os_zone
|
95
|
+
|
96
|
+
@os_tz ||= (debian_tz || centos_tz || osx_tz)
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# system tz determination
|
101
|
+
|
102
|
+
def debian_tz
|
103
|
+
|
104
|
+
path = '/etc/timezone'
|
105
|
+
|
106
|
+
File.exist?(path) ? File.read(path).strip : nil
|
107
|
+
rescue; nil; end
|
108
|
+
|
109
|
+
def centos_tz
|
110
|
+
|
111
|
+
path = '/etc/sysconfig/clock'
|
112
|
+
|
113
|
+
File.open(path, 'rb') do |f|
|
114
|
+
until f.eof?
|
115
|
+
if m = f.readline.match(/ZONE="([^"]+)"/); return m[1]; end
|
116
|
+
end
|
117
|
+
end if File.exist?(path)
|
118
|
+
|
119
|
+
nil
|
120
|
+
rescue; nil; end
|
121
|
+
|
122
|
+
def osx_tz
|
123
|
+
|
124
|
+
path = '/etc/localtime'
|
125
|
+
|
126
|
+
File.symlink?(path) ?
|
127
|
+
File.readlink(path).split('/')[4..-1].join('/') :
|
128
|
+
nil
|
129
|
+
rescue; nil; end
|
130
|
+
|
131
|
+
def gather_tzs
|
132
|
+
|
133
|
+
{ :debian => debian_tz, :centos => centos_tz, :osx => osx_tz }
|
134
|
+
end
|
135
|
+
|
136
|
+
# Semi-helpful, since it requires the current time
|
137
|
+
#
|
138
|
+
def windows_zone_name(zone_name, time)
|
139
|
+
|
140
|
+
twin = Time.utc(time.year, 1, 1) # winter
|
141
|
+
tsum = Time.utc(time.year, 7, 1) # summer
|
142
|
+
|
143
|
+
tz = ::TZInfo::Timezone.get(zone_name)
|
144
|
+
tzo = tz.period_for_local(time).utc_total_offset
|
145
|
+
tzop = tzo < 0 ? nil : '-'; tzo = tzo.abs
|
146
|
+
tzoh = tzo / 3600
|
147
|
+
tzos = tzo % 3600
|
148
|
+
tzos = tzos == 0 ? nil : ':%02d' % (tzos / 60)
|
149
|
+
|
150
|
+
abbs = [
|
151
|
+
tz.period_for_utc(twin).abbreviation.to_s,
|
152
|
+
tz.period_for_utc(tsum).abbreviation.to_s ]
|
153
|
+
.uniq
|
154
|
+
|
155
|
+
if abbs[0].match(/\A[A-Z]/)
|
156
|
+
[ abbs[0], tzop, tzoh, tzos, abbs[1] ]
|
157
|
+
.compact.join
|
158
|
+
else
|
159
|
+
[ windows_zone_code_x(zone_name), tzop, tzoh, tzos || ':00', zone_name ]
|
160
|
+
.collect(&:to_s).join
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def tweak_zone_name(name)
|
165
|
+
|
166
|
+
return name unless (name.match(/./) rescue nil)
|
167
|
+
# to prevent invalid byte sequence in UTF-8..., gh-15
|
168
|
+
|
169
|
+
normalize(name) ||
|
170
|
+
unzz(name) ||
|
171
|
+
name
|
172
|
+
end
|
173
|
+
|
174
|
+
protected
|
175
|
+
|
176
|
+
def normalize(name)
|
177
|
+
|
178
|
+
ZONE_ALIASES[name.sub(/ Daylight /, ' Standard ')]
|
179
|
+
end
|
180
|
+
|
181
|
+
def unzz(name)
|
182
|
+
|
183
|
+
m = name.match(/\A([A-Z]{3,4})([+-])(\d{1,2}):?(\d{2})?\z/)
|
184
|
+
return nil unless m
|
185
|
+
|
186
|
+
abbs = [ m[1] ]; a = m[1]
|
187
|
+
abbs << "#{a}T" if a.size < 4
|
188
|
+
|
189
|
+
off =
|
190
|
+
(m[2] == '+' ? 1 : -1) *
|
191
|
+
(m[3].to_i * 3600 + (m[4] || '0').to_i * 60)
|
192
|
+
|
193
|
+
t = Time.now
|
194
|
+
twin = Time.utc(t.year, 1, 1) # winter
|
195
|
+
tsum = Time.utc(t.year, 7, 1) # summer
|
196
|
+
|
197
|
+
tz_all
|
198
|
+
.each { |tz|
|
199
|
+
abbs.each { |abb|
|
200
|
+
per = tz.period_for_utc(twin)
|
201
|
+
return tz.name \
|
202
|
+
if per.abbreviation.to_s == abb && per.utc_total_offset == off
|
203
|
+
per = tz.period_for_utc(tsum)
|
204
|
+
return tz.name \
|
205
|
+
if per.abbreviation.to_s == abb && per.utc_total_offset == off } }
|
206
|
+
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
|
210
|
+
def determine_local_tzones
|
211
|
+
|
212
|
+
tabbs = (-6..5)
|
213
|
+
.collect { |i|
|
214
|
+
t = Time.now + i * 30 * 24 * 3600
|
215
|
+
"#{t.zone}_#{t.utc_offset}" }
|
216
|
+
.uniq
|
217
|
+
.sort
|
218
|
+
.join('|')
|
219
|
+
|
220
|
+
t = Time.now
|
221
|
+
#tu = t.dup.utc # /!\ dup is necessary, #utc modifies its target
|
222
|
+
|
223
|
+
twin = Time.local(t.year, 1, 1) # winter
|
224
|
+
tsum = Time.local(t.year, 7, 1) # summer
|
225
|
+
|
226
|
+
@tz_winter_summer ||= {}
|
227
|
+
|
228
|
+
@tz_winter_summer[tabbs] ||= tz_all
|
229
|
+
.select { |tz|
|
230
|
+
pw = tz.period_for_local(twin)
|
231
|
+
ps = tz.period_for_local(tsum)
|
232
|
+
tabbs ==
|
233
|
+
[ "#{pw.abbreviation}_#{pw.utc_total_offset}",
|
234
|
+
"#{ps.abbreviation}_#{ps.utc_total_offset}" ]
|
235
|
+
.uniq.sort.join('|') }
|
236
|
+
|
237
|
+
@tz_winter_summer[tabbs]
|
238
|
+
end
|
239
|
+
|
240
|
+
def custom_tzs; @custom_tzs ||= {}; end
|
241
|
+
def tz_all; @tz_all ||= ::TZInfo::Timezone.all; end
|
242
|
+
end
|
243
|
+
|
244
|
+
# https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones
|
245
|
+
# https://support.microsoft.com/en-ca/help/973627/microsoft-time-zone-index-values
|
246
|
+
# https://ss64.com/nt/timezones.html
|
247
|
+
|
248
|
+
ZONE_ALIASES = {
|
249
|
+
'Coordinated Universal Time' => 'UTC',
|
250
|
+
'Afghanistan Standard Time' => 'Asia/Kabul',
|
251
|
+
'FLE Standard Time' => 'Europe/Helsinki',
|
252
|
+
'Central Europe Standard Time' => 'Europe/Prague',
|
253
|
+
'UTC-11' => 'Etc/GMT+11',
|
254
|
+
'W. Europe Standard Time' => 'Europe/Rome',
|
255
|
+
'W. Central Africa Standard Time' => 'Africa/Lagos',
|
256
|
+
'SA Western Standard Time' => 'America/La_Paz',
|
257
|
+
'Pacific SA Standard Time' => 'America/Santiago',
|
258
|
+
'Argentina Standard Time' => 'America/Argentina/Buenos_Aires',
|
259
|
+
'Caucasus Standard Time' => 'Asia/Yerevan',
|
260
|
+
'AUS Eastern Standard Time' => 'Australia/Sydney',
|
261
|
+
'Azerbaijan Standard Time' => 'Asia/Baku',
|
262
|
+
'Eastern Standard Time' => 'America/New_York',
|
263
|
+
'Arab Standard Time' => 'Asia/Riyadh',
|
264
|
+
'Bangladesh Standard Time' => 'Asia/Dhaka',
|
265
|
+
'Belarus Standard Time' => 'Europe/Minsk',
|
266
|
+
'Romance Standard Time' => 'Europe/Paris',
|
267
|
+
'Central America Standard Time' => 'America/Belize',
|
268
|
+
'Atlantic Standard Time' => 'Atlantic/Bermuda',
|
269
|
+
'Venezuela Standard Time' => 'America/Caracas',
|
270
|
+
'Central European Standard Time' => 'Europe/Warsaw',
|
271
|
+
'South Africa Standard Time' => 'Africa/Johannesburg',
|
272
|
+
#'UTC' => 'Etc/UTC', # 'UTC' is good as is
|
273
|
+
'E. South America Standard Time' => 'America/Sao_Paulo',
|
274
|
+
'Central Asia Standard Time' => 'Asia/Almaty',
|
275
|
+
'Singapore Standard Time' => 'Asia/Singapore',
|
276
|
+
'Greenwich Standard Time' => 'Africa/Monrovia',
|
277
|
+
'Cape Verde Standard Time' => 'Atlantic/Cape_Verde',
|
278
|
+
'SE Asia Standard Time' => 'Asia/Bangkok',
|
279
|
+
'SA Pacific Standard Time' => 'America/Bogota',
|
280
|
+
'China Standard Time' => 'Asia/Shanghai',
|
281
|
+
'Myanmar Standard Time' => 'Asia/Yangon',
|
282
|
+
'E. Africa Standard Time' => 'Africa/Nairobi',
|
283
|
+
'Hawaiian Standard Time' => 'Pacific/Honololu',
|
284
|
+
'E. Europe Standard Time' => 'Europe/Nicosia',
|
285
|
+
'Tokyo Standard Time' => 'Asia/Tokyo',
|
286
|
+
'Egypt Standard Time' => 'Africa/Cairo',
|
287
|
+
'SA Eastern Standard Time' => 'America/Cayenne',
|
288
|
+
'GMT Standard Time' => 'Europe/London',
|
289
|
+
'Fiji Standard Time' => 'Pacific/Fiji',
|
290
|
+
'West Asia Standard Time' => 'Asia/Tashkent',
|
291
|
+
'Georgian Standard Time' => 'Asia/Tbilisi',
|
292
|
+
'GTB Standard Time' => 'Europe/Athens',
|
293
|
+
'Greenland Standard Time' => 'America/Godthab',
|
294
|
+
'West Pacific Standard Time' => 'Pacific/Guam',
|
295
|
+
'Mauritius Standard Time' => 'Indian/Mauritius',
|
296
|
+
'India Standard Time' => 'Asia/Kolkata',
|
297
|
+
'Iran Standard Time' => 'Asia/Tehran',
|
298
|
+
'Arabic Standard Time' => 'Asia/Baghdad',
|
299
|
+
'Israel Standard Time' => 'Asia/Jerusalem',
|
300
|
+
'Jordan Standard Time' => 'Asia/Amman',
|
301
|
+
'UTC+12' => 'Etc/GMT-12',
|
302
|
+
'Korea Standard Time' => 'Asia/Seoul',
|
303
|
+
'Middle East Standard Time' => 'Asia/Beirut',
|
304
|
+
'Central Standard Time (Mexico)' => 'America/Mexico_City',
|
305
|
+
'Ulaanbaatar Standard Time' => 'Asia/Ulaanbaatar',
|
306
|
+
'Morocco Standard Time' => 'Africa/Casablanca',
|
307
|
+
'Namibia Standard Time' => 'Africa/Windhoek',
|
308
|
+
'Nepal Standard Time' => 'Asia/Kathmandu',
|
309
|
+
'Central Pacific Standard Time' => 'Etc/GMT-11',
|
310
|
+
'New Zealand Standard Time' => 'Pacific/Auckland',
|
311
|
+
'Arabian Standard Time' => 'Asia/Dubai',
|
312
|
+
'Pakistan Standard Time' => 'Asia/Karachi',
|
313
|
+
'Paraguay Standard Time' => 'America/Asuncion',
|
314
|
+
'Pacific Standard Time' => 'America/Los_Angeles',
|
315
|
+
'Russian Standard Time' => 'Europe/Moscow',
|
316
|
+
'Samoa Standard Time' => 'Pacific/Pago_Pago',
|
317
|
+
'UTC-02' => 'Etc/GMT+2',
|
318
|
+
'Sri Lanka Standard Time' => 'Asia/Kolkata',
|
319
|
+
'Syria Standard Time' => 'Asia/Damascus',
|
320
|
+
'Taipei Standard Time' => 'Asia/Taipei',
|
321
|
+
'Tonga Standard Time' => 'Pacific/Tongatapu',
|
322
|
+
'Turkey Standard Time' => 'Asia/Istanbul',
|
323
|
+
'Montevideo Standard Time' => 'America/Montevideo',
|
324
|
+
|
325
|
+
'CST5CDT' => 'CST6CDT',
|
326
|
+
}
|
327
|
+
end
|
328
|
+
|