et-orbi 1.1.6 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/CREDITS.md +1 -0
- data/LICENSE.txt +1 -1
- data/README.md +1 -1
- data/et-orbi.gemspec +9 -0
- data/lib/et-orbi.rb +144 -512
- data/lib/et-orbi/eo_time.rb +409 -0
- data/lib/et-orbi/zone_aliases.rb +4 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 63be401cf92b7f62cbb8c778d9ec512c89cd7fc1
|
4
|
+
data.tar.gz: 9e9668a7250fe51ead41650067e04e7c4a29d505
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec5bf95f98d1a528e2411b39c7bea0a32154b6c11345cb37b0da89aabdcf2d110dccee14e045433961bf849e78b381b945279cdeb4575bcd4a4da4c3f888e1c0
|
7
|
+
data.tar.gz: 33028c93d34cd0129f09f4dee8213311588f56f87c3b69495c58dae0f4de23803919b406df8538878c9ce0bb4628cf211f1c0cd27bc7c0b4acac6fb33382fcb1
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
# CHANGELOG.md
|
3
3
|
|
4
4
|
|
5
|
+
## et-orbi 1.1.7 released 2019-01-14
|
6
|
+
|
7
|
+
- Rework Chronic integration, prevent conflict with ActiveSupport Time.zone
|
8
|
+
- Implement EtOrbi.extract_zone(s) (returns s1 and zone name)
|
9
|
+
- Adapt specs and EoTime#to_debug_s to Windows on Appveyor
|
10
|
+
|
11
|
+
|
5
12
|
## et-orbi 1.1.6 released 2018-09-05
|
6
13
|
|
7
14
|
- Ensure Olson timezone name regex covers all timezone names
|
data/CREDITS.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
|
2
2
|
# et-orbi credits
|
3
3
|
|
4
|
+
* Wenhui Wang https://github.com/w11th .parse vs Chronic+ActiveSupport, fugit 11
|
4
5
|
* Marcel https://github.com/MTRNord "Mitteleuropaeische Sommerzeit", gh-15
|
5
6
|
* Stanisław Pitucha https://github.com/viraptor rubygems link to changelog, gh-14
|
6
7
|
* Jamie Stackhouse https://github.com/itsjamie reported warnings, gh-13
|
data/LICENSE.txt
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
|
2
|
-
Copyright (c) 2017-
|
2
|
+
Copyright (c) 2017-2019, John Mettraux, jmettraux+flor@gmail.com
|
3
3
|
|
4
4
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -70,7 +70,7 @@ EtOrbi.platform_info
|
|
70
70
|
|
71
71
|
### Rails?
|
72
72
|
|
73
|
-
If Rails is present, `Time.zone` is provided and EtOrbi will use it.
|
73
|
+
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`.
|
74
74
|
|
75
75
|
Rails sets its timezone under `config/application.rb`.
|
76
76
|
|
data/et-orbi.gemspec
CHANGED
@@ -38,9 +38,18 @@ Time zones for fugit and rufus-scheduler. Urbi et Orbi.
|
|
38
38
|
]
|
39
39
|
|
40
40
|
s.add_runtime_dependency 'tzinfo'
|
41
|
+
#
|
42
|
+
# YES, open dependency, fill an issue at
|
43
|
+
# https://github.com/floraison/et-orbi/issues
|
44
|
+
# if you experience a conflict between et-orbi and tzinfo.
|
45
|
+
#
|
46
|
+
# DO NOT raise an issue at tzinfo
|
47
|
+
# this open dependency is my (@jmettraux) responsibility.
|
48
|
+
|
41
49
|
#s.add_runtime_dependency 'raabro', '>= 1.1.3'
|
42
50
|
|
43
51
|
s.add_development_dependency 'rspec', '~> 3.4'
|
52
|
+
s.add_development_dependency 'chronic', '~> 0.10'
|
44
53
|
|
45
54
|
s.require_path = 'lib'
|
46
55
|
end
|
data/lib/et-orbi.rb
CHANGED
@@ -4,12 +4,13 @@ require 'time'
|
|
4
4
|
|
5
5
|
require 'tzinfo'
|
6
6
|
|
7
|
+
require 'et-orbi/eo_time'
|
7
8
|
require 'et-orbi/zone_aliases'
|
8
9
|
|
9
10
|
|
10
11
|
module EtOrbi
|
11
12
|
|
12
|
-
VERSION = '1.1.
|
13
|
+
VERSION = '1.1.7'
|
13
14
|
|
14
15
|
#
|
15
16
|
# module methods
|
@@ -23,63 +24,40 @@ module EtOrbi
|
|
23
24
|
|
24
25
|
def parse(str, opts={})
|
25
26
|
|
27
|
+
str, str_zone = extract_zone(str)
|
28
|
+
|
26
29
|
if defined?(::Chronic) && t = ::Chronic.parse(str, opts)
|
27
|
-
|
30
|
+
|
31
|
+
str = [ t.strftime('%F %T'), str_zone ].compact.join(' ')
|
28
32
|
end
|
29
33
|
|
30
|
-
#rold = RUBY_VERSION < '1.9.0'
|
31
|
-
#rold = RUBY_VERSION < '2.0.0'
|
32
34
|
begin
|
33
35
|
DateTime.parse(str)
|
34
36
|
rescue
|
35
37
|
fail ArgumentError, "No time information in #{str.inspect}"
|
36
|
-
end
|
38
|
+
end
|
39
|
+
#end if RUBY_VERSION < '1.9.0'
|
40
|
+
#end if RUBY_VERSION < '2.0.0'
|
37
41
|
#
|
38
42
|
# is necessary since Time.parse('xxx') in Ruby < 1.9 yields `now`
|
39
43
|
|
40
|
-
str_zone = get_tzone(list_iso8601_zones(str).last)
|
41
|
-
#p [ :parse, str, str_zone ]
|
42
|
-
#p ENV['TZ']
|
43
|
-
|
44
|
-
#p [ :parse, :oz, opts[:zone] ]
|
45
|
-
#p [ :parse, :sz, str_zone ]
|
46
|
-
#p [ :parse, :foz, find_olson_zone(str) ]
|
47
|
-
#p [ :parse, :ltz, local_tzone ]
|
48
44
|
zone =
|
49
45
|
opts[:zone] ||
|
50
|
-
str_zone ||
|
51
|
-
find_olson_zone(str) ||
|
46
|
+
get_tzone(str_zone) ||
|
52
47
|
determine_local_tzone
|
53
|
-
#p [ :parse, :zone, zone ]
|
54
|
-
|
55
|
-
str = str.sub(zone.name, '') unless zone.name.match(/\A[-+]/)
|
56
|
-
#
|
57
|
-
# for 'Sun Nov 18 16:01:00 Asia/Singapore 2012',
|
58
|
-
# although where does rufus-scheduler have it from?
|
59
48
|
|
60
49
|
local = Time.parse(str)
|
61
|
-
|
62
|
-
|
63
|
-
secs =
|
64
|
-
if str_zone
|
65
|
-
local.to_f
|
66
|
-
else
|
67
|
-
zone.local_to_utc(local).to_f
|
68
|
-
end
|
69
|
-
#p [ :parse, :secs, secs ]
|
50
|
+
secs = zone.local_to_utc(local).to_f
|
70
51
|
|
71
52
|
EoTime.new(secs, zone)
|
72
53
|
end
|
73
54
|
|
74
55
|
def make_time(*a)
|
75
56
|
|
76
|
-
#p [ :mt, a ]
|
77
57
|
zone = a.length > 1 ? get_tzone(a.last) : nil
|
78
58
|
a.pop if zone
|
79
|
-
#p [ :mt, zone ]
|
80
59
|
|
81
60
|
o = a.length > 1 ? a : a.first
|
82
|
-
#p [ :mt, :o, o ]
|
83
61
|
|
84
62
|
case o
|
85
63
|
when Time then make_from_time(o, zone)
|
@@ -92,6 +70,7 @@ module EtOrbi
|
|
92
70
|
"Cannot turn #{o.inspect} to a ::EtOrbi::EoTime instance")
|
93
71
|
end
|
94
72
|
end
|
73
|
+
alias make make_time
|
95
74
|
|
96
75
|
def make_from_time(t, zone)
|
97
76
|
|
@@ -156,6 +135,7 @@ module EtOrbi
|
|
156
135
|
s = unalias(o)
|
157
136
|
|
158
137
|
get_offset_tzone(s) ||
|
138
|
+
get_x_offset_tzone(s) ||
|
159
139
|
(::TZInfo::Timezone.get(s) rescue nil)
|
160
140
|
end
|
161
141
|
|
@@ -174,6 +154,20 @@ module EtOrbi
|
|
174
154
|
"(secs:#{seconds},utc~:#{ts.inspect},ltz~:#{z.inspect})"
|
175
155
|
end
|
176
156
|
|
157
|
+
def tzinfo_version
|
158
|
+
|
159
|
+
#TZInfo::VERSION
|
160
|
+
Gem.loaded_specs['tzinfo'].version.to_s
|
161
|
+
rescue => err
|
162
|
+
err.inspect
|
163
|
+
end
|
164
|
+
|
165
|
+
def tzinfo_data_version
|
166
|
+
|
167
|
+
#TZInfo::Data::VERSION rescue nil
|
168
|
+
Gem.loaded_specs['tzinfo-data'].version.to_s rescue nil
|
169
|
+
end
|
170
|
+
|
177
171
|
def platform_info
|
178
172
|
|
179
173
|
etos = Proc.new { |k, v| "#{k}:#{v.inspect}" }
|
@@ -181,7 +175,8 @@ module EtOrbi
|
|
181
175
|
h = {
|
182
176
|
'etz' => ENV['TZ'],
|
183
177
|
'tnz' => Time.now.zone,
|
184
|
-
'
|
178
|
+
'tziv' => tzinfo_version,
|
179
|
+
'tzidv' => tzinfo_data_version,
|
185
180
|
'rv' => RUBY_VERSION,
|
186
181
|
'rp' => RUBY_PLATFORM,
|
187
182
|
'win' => Gem.win_platform?,
|
@@ -194,14 +189,13 @@ module EtOrbi
|
|
194
189
|
if ltz = EtOrbi::EoTime.local_tzone
|
195
190
|
h['eotnz'] = EtOrbi::EoTime.now.zone
|
196
191
|
h['eotnfz'] = EtOrbi::EoTime.now.strftime('%z')
|
192
|
+
h['eotnfZ'] = EtOrbi::EoTime.now.strftime('%Z')
|
197
193
|
h['eotlzn'] = ltz.name
|
198
194
|
end
|
199
195
|
|
200
196
|
"(#{h.map(&etos).join(',')},#{gather_tzs.map(&etos).join(',')})"
|
201
197
|
end
|
202
198
|
|
203
|
-
alias make make_time
|
204
|
-
|
205
199
|
# For `make info`
|
206
200
|
#
|
207
201
|
def _make_info
|
@@ -210,497 +204,104 @@ module EtOrbi
|
|
210
204
|
puts platform_info
|
211
205
|
end
|
212
206
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
# if ab.match(/\A[-+]\d{2}(:?\d{2})?\z/) && lp.utc_offset == t.utc_offset
|
225
|
-
#
|
226
|
-
#nil
|
227
|
-
#
|
228
|
-
# keep that in the fridge for now
|
229
|
-
|
230
|
-
l = Time.local(t.year, t.month, t.day, t.hour, t.min, t.sec, t.usec)
|
231
|
-
|
232
|
-
(t.zone == l.zone) ? determine_local_tzone : nil
|
233
|
-
end
|
234
|
-
|
235
|
-
def get_as_tzone(t)
|
236
|
-
|
237
|
-
t.respond_to?(:time_zone) ? t.time_zone : nil
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
# Our EoTime class (which quacks like a ::Time).
|
242
|
-
#
|
243
|
-
# An EoTime instance should respond to most of the methods ::Time instances
|
244
|
-
# respond to. If a method is missing, feel free to open an issue to
|
245
|
-
# ask (politely) for it. If it makes sense, it'll get added, else
|
246
|
-
# a workaround will get suggested.
|
247
|
-
# The immediate workaround is to call #to_t on the EoTime instance to get
|
248
|
-
# equivalent ::Time instance in the local, current, timezone.
|
249
|
-
#
|
250
|
-
class EoTime
|
251
|
-
|
252
|
-
#
|
253
|
-
# class methods
|
254
|
-
|
255
|
-
class << self
|
256
|
-
|
257
|
-
def now(zone=nil)
|
258
|
-
|
259
|
-
EtOrbi.now(zone)
|
260
|
-
end
|
261
|
-
|
262
|
-
def parse(str, opts={})
|
263
|
-
|
264
|
-
EtOrbi.parse(str, opts)
|
265
|
-
end
|
266
|
-
|
267
|
-
def get_tzone(o)
|
268
|
-
|
269
|
-
EtOrbi.get_tzone(o)
|
270
|
-
end
|
271
|
-
|
272
|
-
def local_tzone
|
273
|
-
|
274
|
-
EtOrbi.determine_local_tzone
|
275
|
-
end
|
276
|
-
|
277
|
-
def platform_info
|
278
|
-
|
279
|
-
EtOrbi.platform_info
|
280
|
-
end
|
281
|
-
|
282
|
-
def make(o)
|
283
|
-
|
284
|
-
EtOrbi.make_time(o)
|
285
|
-
end
|
286
|
-
|
287
|
-
def utc(*a)
|
288
|
-
|
289
|
-
EtOrbi.make_from_array(a, EtOrbi.get_tzone('UTC'))
|
290
|
-
end
|
291
|
-
|
292
|
-
def local(*a)
|
293
|
-
|
294
|
-
EtOrbi.make_from_array(a, local_tzone)
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
#
|
299
|
-
# instance methods
|
300
|
-
|
301
|
-
attr_reader :seconds
|
302
|
-
attr_reader :zone
|
303
|
-
|
304
|
-
def initialize(s, zone)
|
305
|
-
|
306
|
-
@seconds = s.to_f
|
307
|
-
@zone = self.class.get_tzone(zone || :local)
|
308
|
-
|
309
|
-
fail ArgumentError.new(
|
310
|
-
"Cannot determine timezone from #{zone.inspect}" +
|
311
|
-
"\n#{EtOrbi.render_nozone_time(@seconds)}" +
|
312
|
-
"\n#{EtOrbi.platform_info.sub(',debian:', ",\ndebian:")}" +
|
313
|
-
"\nTry setting `ENV['TZ'] = 'Continent/City'` in your script " +
|
314
|
-
"(see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)" +
|
315
|
-
(defined?(TZInfo::Data) ? '' : "\nand adding gem 'tzinfo-data'")
|
316
|
-
) unless @zone
|
317
|
-
|
318
|
-
@time = nil
|
319
|
-
# cache for #to_time result
|
320
|
-
end
|
321
|
-
|
322
|
-
def seconds=(f)
|
323
|
-
|
324
|
-
@time = nil
|
325
|
-
@seconds = f
|
326
|
-
end
|
327
|
-
|
328
|
-
def zone=(z)
|
329
|
-
|
330
|
-
@time = nil
|
331
|
-
@zone = self.class.get_tzone(zone || :current)
|
332
|
-
end
|
333
|
-
|
334
|
-
# Returns true if this EoTime instance corresponds to 2 different UTC
|
335
|
-
# times.
|
336
|
-
# It happens when transitioning from DST to winter time.
|
337
|
-
#
|
338
|
-
# https://www.timeanddate.com/time/change/usa/new-york?year=2018
|
339
|
-
#
|
340
|
-
def ambiguous?
|
341
|
-
|
342
|
-
@zone.local_to_utc(@zone.utc_to_local(utc))
|
343
|
-
|
344
|
-
false
|
345
|
-
|
346
|
-
rescue TZInfo::AmbiguousTime
|
347
|
-
|
348
|
-
true
|
349
|
-
end
|
350
|
-
|
351
|
-
# Returns this ::EtOrbi::EoTime as a ::Time instance
|
352
|
-
# in the current UTC timezone.
|
353
|
-
#
|
354
|
-
def utc
|
355
|
-
|
356
|
-
Time.utc(1970) + @seconds
|
357
|
-
end
|
358
|
-
|
359
|
-
# Returns true if this ::EtOrbi::EoTime instance timezone is UTC.
|
360
|
-
# Returns false else.
|
361
|
-
#
|
362
|
-
def utc?
|
363
|
-
|
364
|
-
%w[ gmt utc zulu etc/gmt etc/utc ].include?(
|
365
|
-
@zone.canonical_identifier.downcase)
|
366
|
-
end
|
367
|
-
|
368
|
-
alias getutc utc
|
369
|
-
alias getgm utc
|
370
|
-
alias to_utc_time utc
|
371
|
-
|
372
|
-
def to_f
|
373
|
-
|
374
|
-
@seconds
|
375
|
-
end
|
376
|
-
|
377
|
-
def to_i
|
378
|
-
|
379
|
-
@seconds.to_i
|
380
|
-
end
|
381
|
-
|
382
|
-
def strftime(format)
|
383
|
-
|
384
|
-
format = format.gsub(/%(\/?Z|:{0,2}z)/) { |f| strfz(f) }
|
385
|
-
|
386
|
-
to_time.strftime(format)
|
387
|
-
end
|
388
|
-
|
389
|
-
# Returns this ::EtOrbi::EoTime as a ::Time instance
|
390
|
-
# in the current timezone.
|
391
|
-
#
|
392
|
-
# Has a #to_t alias.
|
393
|
-
#
|
394
|
-
def to_local_time
|
395
|
-
|
396
|
-
Time.at(@seconds)
|
397
|
-
end
|
398
|
-
|
399
|
-
alias to_t to_local_time
|
400
|
-
|
401
|
-
def is_dst?
|
402
|
-
|
403
|
-
@zone.period_for_utc(utc).std_offset != 0
|
404
|
-
end
|
405
|
-
alias isdst is_dst?
|
406
|
-
|
407
|
-
def to_debug_s
|
408
|
-
|
409
|
-
uo = self.utc_offset
|
410
|
-
uos = uo < 0 ? '-' : '+'
|
411
|
-
uo = uo.abs
|
412
|
-
uoh, uom = [ uo / 3600, uo % 3600 ]
|
413
|
-
|
414
|
-
[
|
415
|
-
'ot',
|
416
|
-
self.strftime('%Y-%m-%d %H:%M:%S'),
|
417
|
-
"%s%02d:%02d" % [ uos, uoh, uom ],
|
418
|
-
"dst:#{self.isdst}"
|
419
|
-
].join(' ')
|
420
|
-
end
|
421
|
-
|
422
|
-
def utc_offset
|
423
|
-
|
424
|
-
@zone.period_for_utc(utc).utc_offset
|
425
|
-
end
|
426
|
-
|
427
|
-
%w[
|
428
|
-
year month day wday hour min sec usec asctime
|
429
|
-
].each do |m|
|
430
|
-
define_method(m) { to_time.send(m) }
|
431
|
-
end
|
432
|
-
|
433
|
-
def ==(o)
|
434
|
-
|
435
|
-
o.is_a?(EoTime) &&
|
436
|
-
o.seconds == @seconds &&
|
437
|
-
(o.zone == @zone || o.zone.current_period == @zone.current_period)
|
438
|
-
end
|
439
|
-
#alias eql? == # FIXME see Object#== (ri)
|
440
|
-
|
441
|
-
def >(o); @seconds > _to_f(o); end
|
442
|
-
def >=(o); @seconds >= _to_f(o); end
|
443
|
-
def <(o); @seconds < _to_f(o); end
|
444
|
-
def <=(o); @seconds <= _to_f(o); end
|
445
|
-
def <=>(o); @seconds <=> _to_f(o); end
|
446
|
-
|
447
|
-
def add(t); @time = nil; @seconds += t.to_f; self; end
|
448
|
-
def subtract(t); @time = nil; @seconds -= t.to_f; self; end
|
449
|
-
|
450
|
-
def +(t); inc(t, 1); end
|
451
|
-
def -(t); inc(t, -1); end
|
452
|
-
|
453
|
-
WEEK_S = 7 * 24 * 3600
|
454
|
-
|
455
|
-
def monthdays
|
456
|
-
|
457
|
-
date = to_time
|
458
|
-
|
459
|
-
pos = 1
|
460
|
-
d = self.dup
|
461
|
-
|
462
|
-
loop do
|
463
|
-
d.add(-WEEK_S)
|
464
|
-
break if d.month != date.month
|
465
|
-
pos = pos + 1
|
466
|
-
end
|
467
|
-
|
468
|
-
neg = -1
|
469
|
-
d = self.dup
|
470
|
-
|
471
|
-
loop do
|
472
|
-
d.add(WEEK_S)
|
473
|
-
break if d.month != date.month
|
474
|
-
neg = neg - 1
|
475
|
-
end
|
476
|
-
|
477
|
-
[ "#{date.wday}##{pos}", "#{date.wday}##{neg}" ]
|
478
|
-
end
|
479
|
-
|
480
|
-
def to_s
|
481
|
-
|
482
|
-
strftime('%Y-%m-%d %H:%M:%S %z')
|
483
|
-
end
|
484
|
-
|
485
|
-
def to_zs
|
486
|
-
|
487
|
-
strftime('%Y-%m-%d %H:%M:%S %/Z')
|
488
|
-
end
|
489
|
-
|
490
|
-
def iso8601(fraction_digits=0)
|
491
|
-
|
492
|
-
s = (fraction_digits || 0) > 0 ? ".%#{fraction_digits}N" : ''
|
493
|
-
strftime("%Y-%m-%dT%H:%M:%S#{s}%:z")
|
494
|
-
end
|
495
|
-
|
496
|
-
# Debug current time by showing local time / delta / utc time
|
497
|
-
# for example: "0120-7(0820)"
|
498
|
-
#
|
499
|
-
def to_utc_comparison_s
|
500
|
-
|
501
|
-
per = @zone.period_for_utc(utc)
|
502
|
-
off = per.utc_total_offset
|
503
|
-
|
504
|
-
off = off / 3600
|
505
|
-
off = off >= 0 ? "+#{off}" : off.to_s
|
506
|
-
|
507
|
-
strftime('%H%M') + off + utc.strftime('(%H%M)')
|
508
|
-
end
|
509
|
-
|
510
|
-
def to_time_s
|
511
|
-
|
512
|
-
strftime("%H:%M:%S.#{'%06d' % usec}")
|
513
|
-
end
|
514
|
-
|
515
|
-
def inc(t, dir=1)
|
516
|
-
|
517
|
-
case t
|
518
|
-
when Numeric
|
519
|
-
nt = self.dup
|
520
|
-
nt.seconds += dir * t.to_f
|
521
|
-
nt
|
522
|
-
when ::Time, ::EtOrbi::EoTime
|
523
|
-
fail ArgumentError.new(
|
524
|
-
"Cannot add #{t.class} to EoTime") if dir > 0
|
525
|
-
@seconds + dir * t.to_f
|
526
|
-
else
|
527
|
-
fail ArgumentError.new(
|
528
|
-
"Cannot call add or subtract #{t.class} to EoTime instance")
|
529
|
-
end
|
530
|
-
end
|
531
|
-
|
532
|
-
def localtime(zone=nil)
|
207
|
+
ZONES_ISO8601 =
|
208
|
+
%r{
|
209
|
+
(?<=:\d\d)\s*
|
210
|
+
(?:
|
211
|
+
[-+]
|
212
|
+
(?:[0-1][0-9]|2[0-4])
|
213
|
+
(?:(?::)?(?:[0-5][0-9]|60))?
|
214
|
+
(?![-+])
|
215
|
+
|Z
|
216
|
+
)
|
217
|
+
}x
|
533
218
|
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
alias translate localtime
|
538
|
-
|
539
|
-
def wday_in_month
|
540
|
-
|
541
|
-
[ count_weeks(-1), - count_weeks(1) ]
|
542
|
-
end
|
543
|
-
|
544
|
-
def reach(points)
|
545
|
-
|
546
|
-
t = EoTime.new(self.to_f, @zone)
|
547
|
-
step = 1
|
548
|
-
|
549
|
-
s = points[:second] || points[:sec] || points[:s]
|
550
|
-
m = points[:minute] || points[:min] || points[:m]
|
551
|
-
h = points[:hour] || points[:hou] || points[:h]
|
552
|
-
|
553
|
-
fail ArgumentError.new("missing :second, :minute, and :hour") \
|
554
|
-
unless s || m || h
|
555
|
-
|
556
|
-
if !s && !m
|
557
|
-
step = 60 * 60
|
558
|
-
t -= t.sec
|
559
|
-
t -= t.min * 60
|
560
|
-
elsif !s
|
561
|
-
step = 60
|
562
|
-
t -= t.sec
|
563
|
-
end
|
564
|
-
|
565
|
-
loop do
|
566
|
-
t += step
|
567
|
-
next if s && t.sec != s
|
568
|
-
next if m && t.min != m
|
569
|
-
next if h && t.hour != h
|
570
|
-
break
|
571
|
-
end
|
572
|
-
|
573
|
-
t
|
574
|
-
end
|
575
|
-
|
576
|
-
protected
|
577
|
-
|
578
|
-
# Returns a Ruby Time instance.
|
579
|
-
#
|
580
|
-
# Warning: the timezone of that Time instance will be UTC when used with
|
581
|
-
# TZInfo < 2.0.0.
|
219
|
+
# https://en.wikipedia.org/wiki/ISO_8601
|
220
|
+
# Postel's law applies
|
582
221
|
#
|
583
|
-
def
|
584
|
-
|
585
|
-
@time ||= @zone.utc_to_local(utc)
|
586
|
-
end
|
587
|
-
|
588
|
-
def count_weeks(dir)
|
589
|
-
|
590
|
-
c = 0
|
591
|
-
t = self
|
592
|
-
until t.month != self.month
|
593
|
-
c += 1
|
594
|
-
t += dir * (7 * 24 * 3600)
|
595
|
-
end
|
222
|
+
def list_iso8601_zones(s)
|
596
223
|
|
597
|
-
|
224
|
+
s.scan(ZONES_ISO8601).collect(&:strip)
|
598
225
|
end
|
599
226
|
|
600
|
-
|
227
|
+
ZONES_OLSON = (
|
228
|
+
TZInfo::Timezone.all.collect { |z| z.name }.sort +
|
229
|
+
(0..12).collect { |i| [ "UTC-#{i}", "UTC+#{i}" ] })
|
230
|
+
.flatten
|
231
|
+
.sort_by(&:size)
|
232
|
+
.reverse
|
601
233
|
|
602
|
-
|
603
|
-
|
604
|
-
per = @zone.period_for_utc(utc)
|
234
|
+
def list_olson_zones(s)
|
605
235
|
|
606
|
-
|
236
|
+
s = s.dup
|
607
237
|
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
if @zone.name == 'UTC'
|
616
|
-
'Z' # align on Ruby ::Time#iso8601
|
617
|
-
elsif code == '%z'
|
618
|
-
'%s%02d%02d' % [ sn, hr, mn ]
|
619
|
-
elsif code == '%:z'
|
620
|
-
'%s%02d:%02d' % [ sn, hr, mn ]
|
621
|
-
else
|
622
|
-
'%s%02d:%02d:%02d' % [ sn, hr, mn, sc ]
|
623
|
-
end
|
238
|
+
ZONES_OLSON
|
239
|
+
.inject([]) { |a, z|
|
240
|
+
i = s.index(z); next a unless i
|
241
|
+
s[i, z.length] = ''
|
242
|
+
a << z
|
243
|
+
a }
|
624
244
|
end
|
625
245
|
|
626
|
-
def
|
627
|
-
|
628
|
-
fail ArgumentError(
|
629
|
-
"Comparison of EoTime with #{o.inspect} failed"
|
630
|
-
) unless o.is_a?(EoTime) || o.is_a?(Time)
|
246
|
+
def find_olson_zone(str)
|
631
247
|
|
632
|
-
|
248
|
+
list_olson_zones(str).each { |s| z = get_tzone(s); return z if z }
|
249
|
+
nil
|
633
250
|
end
|
634
|
-
end
|
635
251
|
|
636
|
-
|
252
|
+
def extract_zone(str)
|
637
253
|
|
638
|
-
|
639
|
-
# extra public methods
|
254
|
+
s = str.dup
|
640
255
|
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
256
|
+
zs = ZONES_OLSON
|
257
|
+
.inject([]) { |a, z|
|
258
|
+
i = s.index(z); next a unless i
|
259
|
+
a << z
|
260
|
+
s[i, z.length] = ''
|
261
|
+
a }
|
645
262
|
|
646
|
-
s
|
647
|
-
.scan(
|
648
|
-
%r{
|
649
|
-
(?<=:\d\d)
|
650
|
-
\s*
|
651
|
-
(?:
|
652
|
-
[-+]
|
653
|
-
(?:[0-1][0-9]|2[0-4])
|
654
|
-
(?:(?::)?(?:[0-5][0-9]|60))?
|
655
|
-
(?![-+])
|
656
|
-
|
|
657
|
-
Z
|
658
|
-
)
|
659
|
-
}x)
|
660
|
-
.collect(&:strip)
|
661
|
-
end
|
263
|
+
s.gsub!(ZONES_ISO8601) { |m| zs << m.strip; '' } #if zs.empty?
|
662
264
|
|
663
|
-
|
265
|
+
zs = zs.sort_by { |z| str.index(z) }
|
664
266
|
|
665
|
-
s
|
666
|
-
.scan(
|
667
|
-
%r{
|
668
|
-
(?<=\s|\A)
|
669
|
-
(?:[A-Z][A-Za-z0-9+_-]+)
|
670
|
-
(?:\/(?:[A-Z][A-Za-z0-9+_-]+)){0,2}
|
671
|
-
}x)
|
672
|
-
end
|
673
|
-
|
674
|
-
def find_olson_zone(str)
|
675
|
-
|
676
|
-
list_olson_zones(str).each { |s| z = get_tzone(s); return z if z }
|
677
|
-
nil
|
267
|
+
[ s.strip, zs.last ]
|
678
268
|
end
|
679
269
|
|
680
270
|
def determine_local_tzone
|
681
271
|
|
272
|
+
# ENV has the priority
|
273
|
+
|
682
274
|
etz = ENV['TZ']
|
683
275
|
|
684
|
-
tz = etz && (
|
276
|
+
tz = etz && get_tzone(etz)
|
685
277
|
return tz if tz
|
686
278
|
|
279
|
+
# then Rails/ActiveSupport has the priority
|
280
|
+
|
687
281
|
if Time.respond_to?(:zone) && Time.zone.respond_to?(:tzinfo)
|
688
282
|
tz = Time.zone.tzinfo
|
689
283
|
return tz if tz
|
690
284
|
end
|
691
285
|
|
286
|
+
# then the operating system is queried
|
287
|
+
|
692
288
|
tz = ::TZInfo::Timezone.get(os_tz) rescue nil
|
693
289
|
return tz if tz
|
694
290
|
|
291
|
+
# then Ruby's time zone abbs are looked at CST, JST, CEST, ... :-(
|
292
|
+
|
695
293
|
tzs = determine_local_tzones
|
696
294
|
tz = (etz && tzs.find { |z| z.name == etz }) || tzs.first
|
697
295
|
return tz if tz
|
698
296
|
|
297
|
+
# then, fall back to GMT offest :-(
|
298
|
+
|
699
299
|
n = Time.now
|
700
300
|
|
701
301
|
get_tzone(n.zone) ||
|
702
302
|
get_tzone(n.strftime('%Z%z'))
|
703
303
|
end
|
304
|
+
alias zone determine_local_tzone
|
704
305
|
|
705
306
|
attr_accessor :_os_zone # test tool
|
706
307
|
|
@@ -712,7 +313,9 @@ module EtOrbi
|
|
712
313
|
@os_tz ||= (debian_tz || centos_tz || osx_tz)
|
713
314
|
end
|
714
315
|
|
715
|
-
|
316
|
+
# Semi-helpful, since it requires the current time
|
317
|
+
#
|
318
|
+
def windows_zone_name(zone_name, time)
|
716
319
|
|
717
320
|
twin = Time.utc(time.year, 1, 1) # winter
|
718
321
|
tsum = Time.utc(time.year, 7, 1) # summer
|
@@ -729,7 +332,13 @@ module EtOrbi
|
|
729
332
|
tz.period_for_utc(tsum).abbreviation.to_s ]
|
730
333
|
.uniq
|
731
334
|
|
732
|
-
|
335
|
+
if abbs[0].match(/\A[A-Z]/)
|
336
|
+
[ abbs[0], tzop, tzoh, tzos, abbs[1] ]
|
337
|
+
.compact.join
|
338
|
+
else
|
339
|
+
[ windows_zone_code_x(zone_name), tzop, tzoh, tzos || ':00', zone_name ]
|
340
|
+
.collect(&:to_s).join
|
341
|
+
end
|
733
342
|
end
|
734
343
|
|
735
344
|
#
|
@@ -737,6 +346,31 @@ module EtOrbi
|
|
737
346
|
|
738
347
|
protected
|
739
348
|
|
349
|
+
def windows_zone_code_x(zone_name)
|
350
|
+
|
351
|
+
a = [ '_' ]
|
352
|
+
a.concat(zone_name.split('/')[0, 2].collect { |s| s[0, 1].upcase })
|
353
|
+
a << '_' if a.size < 3
|
354
|
+
|
355
|
+
a.join
|
356
|
+
end
|
357
|
+
|
358
|
+
def get_local_tzone(t)
|
359
|
+
|
360
|
+
l = Time.local(t.year, t.month, t.day, t.hour, t.min, t.sec, t.usec)
|
361
|
+
|
362
|
+
(t.zone == l.zone) ? determine_local_tzone : nil
|
363
|
+
end
|
364
|
+
|
365
|
+
# https://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html
|
366
|
+
#
|
367
|
+
# If it responds to #time_zone, then return that time zone.
|
368
|
+
#
|
369
|
+
def get_as_tzone(t)
|
370
|
+
|
371
|
+
t.respond_to?(:time_zone) ? t.time_zone : nil
|
372
|
+
end
|
373
|
+
|
740
374
|
def to_offset(n)
|
741
375
|
|
742
376
|
i = n.to_i
|
@@ -748,17 +382,20 @@ module EtOrbi
|
|
748
382
|
'%s%02d:%02d' % [ sn, hr, mn ]
|
749
383
|
end
|
750
384
|
|
385
|
+
# custom timezones, no DST, just an offset, like "+08:00" or "-01:30"
|
386
|
+
#
|
751
387
|
def get_offset_tzone(str)
|
752
388
|
|
753
|
-
|
754
|
-
|
755
|
-
m = str.match(/\A([+-][0-1][0-9]):?([0-5][0-9])?\z/) rescue nil
|
389
|
+
m = str.match(/\A([+-][0-1]?[0-9]):?([0-5][0-9])?\z/) rescue nil
|
756
390
|
#
|
757
391
|
# On Windows, the real encoding could be something other than UTF-8,
|
758
392
|
# and make the match fail
|
759
393
|
#
|
760
394
|
return nil unless m
|
761
395
|
|
396
|
+
tz = custom_tzs[str]
|
397
|
+
return tz if tz
|
398
|
+
|
762
399
|
hr = m[1].to_i
|
763
400
|
mn = m[2].to_i
|
764
401
|
|
@@ -766,15 +403,12 @@ module EtOrbi
|
|
766
403
|
hr = nil if mn > 59
|
767
404
|
mn = -mn if hr && hr < 0
|
768
405
|
|
769
|
-
|
770
|
-
(
|
771
|
-
|
772
|
-
) if hr
|
773
|
-
|
774
|
-
nil
|
406
|
+
hr ?
|
407
|
+
custom_tzs[str] = create_offset_tzone(hr * 3600 + mn * 60, str) :
|
408
|
+
nil
|
775
409
|
end
|
776
410
|
|
777
|
-
if defined?
|
411
|
+
if defined?(TZInfo::DataSources::ConstantOffsetDataTimezoneInfo)
|
778
412
|
# TZInfo >= 2.0.0
|
779
413
|
|
780
414
|
def create_offset_tzone(utc_off, id)
|
@@ -795,6 +429,16 @@ module EtOrbi
|
|
795
429
|
end
|
796
430
|
end
|
797
431
|
|
432
|
+
def get_x_offset_tzone(str)
|
433
|
+
|
434
|
+
m = str.match(/\A_..-?[0-1]?\d:?(?:[0-5]\d)?(.+)\z/) rescue nil
|
435
|
+
#
|
436
|
+
# On Windows, the real encoding could be something other than UTF-8,
|
437
|
+
# and make the match fail (as in .get_offset_tzone above)
|
438
|
+
|
439
|
+
m ? ::TZInfo::Timezone.get(m[1]) : nil
|
440
|
+
end
|
441
|
+
|
798
442
|
def determine_local_tzones
|
799
443
|
|
800
444
|
tabbs = (-6..5)
|
@@ -811,10 +455,9 @@ module EtOrbi
|
|
811
455
|
twin = Time.local(t.year, 1, 1) # winter
|
812
456
|
tsum = Time.local(t.year, 7, 1) # summer
|
813
457
|
|
814
|
-
@tz_all ||= ::TZInfo::Timezone.all
|
815
458
|
@tz_winter_summer ||= {}
|
816
459
|
|
817
|
-
@tz_winter_summer[tabbs] ||=
|
460
|
+
@tz_winter_summer[tabbs] ||= tz_all
|
818
461
|
.select { |tz|
|
819
462
|
pw = tz.period_for_local(twin)
|
820
463
|
ps = tz.period_for_local(tsum)
|
@@ -826,6 +469,9 @@ module EtOrbi
|
|
826
469
|
@tz_winter_summer[tabbs]
|
827
470
|
end
|
828
471
|
|
472
|
+
def custom_tzs; @custom_tzs ||= {}; end
|
473
|
+
def tz_all; @tz_all ||= ::TZInfo::Timezone.all; end
|
474
|
+
|
829
475
|
#
|
830
476
|
# system tz determination
|
831
477
|
|
@@ -863,19 +509,5 @@ module EtOrbi
|
|
863
509
|
{ :debian => debian_tz, :centos => centos_tz, :osx => osx_tz }
|
864
510
|
end
|
865
511
|
end
|
866
|
-
|
867
|
-
#def in_zone(&block)
|
868
|
-
#
|
869
|
-
# current_timezone = ENV['TZ']
|
870
|
-
# ENV['TZ'] = @zone
|
871
|
-
#
|
872
|
-
# block.call
|
873
|
-
#
|
874
|
-
#ensure
|
875
|
-
#
|
876
|
-
# ENV['TZ'] = current_timezone
|
877
|
-
#end
|
878
|
-
#
|
879
|
-
# kept around as a (thread-unsafe) relic
|
880
512
|
end
|
881
513
|
|
@@ -0,0 +1,409 @@
|
|
1
|
+
|
2
|
+
module EtOrbi
|
3
|
+
|
4
|
+
# Our EoTime class (which quacks like a ::Time).
|
5
|
+
#
|
6
|
+
# An EoTime instance should respond to most of the methods ::Time instances
|
7
|
+
# respond to. If a method is missing, feel free to open an issue to
|
8
|
+
# ask (politely) for it. If it makes sense, it'll get added, else
|
9
|
+
# a workaround will get suggested.
|
10
|
+
# The immediate workaround is to call #to_t on the EoTime instance to get
|
11
|
+
# equivalent ::Time instance in the local, current, timezone.
|
12
|
+
#
|
13
|
+
class EoTime
|
14
|
+
|
15
|
+
#
|
16
|
+
# class methods
|
17
|
+
|
18
|
+
class << self
|
19
|
+
|
20
|
+
def now(zone=nil)
|
21
|
+
|
22
|
+
EtOrbi.now(zone)
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse(str, opts={})
|
26
|
+
|
27
|
+
EtOrbi.parse(str, opts)
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_tzone(o)
|
31
|
+
|
32
|
+
EtOrbi.get_tzone(o)
|
33
|
+
end
|
34
|
+
|
35
|
+
def local_tzone
|
36
|
+
|
37
|
+
EtOrbi.determine_local_tzone
|
38
|
+
end
|
39
|
+
|
40
|
+
def platform_info
|
41
|
+
|
42
|
+
EtOrbi.platform_info
|
43
|
+
end
|
44
|
+
|
45
|
+
def make(o)
|
46
|
+
|
47
|
+
EtOrbi.make_time(o)
|
48
|
+
end
|
49
|
+
|
50
|
+
def utc(*a)
|
51
|
+
|
52
|
+
EtOrbi.make_from_array(a, EtOrbi.get_tzone('UTC'))
|
53
|
+
end
|
54
|
+
|
55
|
+
def local(*a)
|
56
|
+
|
57
|
+
EtOrbi.make_from_array(a, local_tzone)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# instance methods
|
63
|
+
|
64
|
+
attr_reader :seconds
|
65
|
+
attr_reader :zone
|
66
|
+
|
67
|
+
def initialize(s, zone)
|
68
|
+
|
69
|
+
z = zone
|
70
|
+
z = nil if zone.is_a?(String) && zone.strip == ''
|
71
|
+
#
|
72
|
+
# happens with JRuby (and offset tzones like +04:00)
|
73
|
+
#
|
74
|
+
# $ jruby -r time -e "p Time.parse('2012-1-1 12:00 +04:00').zone"
|
75
|
+
# # => ""
|
76
|
+
# ruby -r time -e "p Time.parse('2012-1-1 12:00 +04:00').zone"
|
77
|
+
# # => nil
|
78
|
+
|
79
|
+
@seconds = s.to_f
|
80
|
+
@zone = self.class.get_tzone(z || :local)
|
81
|
+
|
82
|
+
fail ArgumentError.new(
|
83
|
+
"Cannot determine timezone from #{zone.inspect}" +
|
84
|
+
"\n#{EtOrbi.render_nozone_time(@seconds)}" +
|
85
|
+
"\n#{EtOrbi.platform_info.sub(',debian:', ",\ndebian:")}" +
|
86
|
+
"\nTry setting `ENV['TZ'] = 'Continent/City'` in your script " +
|
87
|
+
"(see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)" +
|
88
|
+
(defined?(TZInfo::Data) ? '' : "\nand adding gem 'tzinfo-data'")
|
89
|
+
) unless @zone
|
90
|
+
|
91
|
+
@time = nil
|
92
|
+
# cache for #to_time result
|
93
|
+
end
|
94
|
+
|
95
|
+
def seconds=(f)
|
96
|
+
|
97
|
+
@time = nil
|
98
|
+
@seconds = f
|
99
|
+
end
|
100
|
+
|
101
|
+
def zone=(z)
|
102
|
+
|
103
|
+
@time = nil
|
104
|
+
@zone = self.class.get_tzone(zone || :current)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns true if this EoTime instance corresponds to 2 different UTC
|
108
|
+
# times.
|
109
|
+
# It happens when transitioning from DST to winter time.
|
110
|
+
#
|
111
|
+
# https://www.timeanddate.com/time/change/usa/new-york?year=2018
|
112
|
+
#
|
113
|
+
def ambiguous?
|
114
|
+
|
115
|
+
@zone.local_to_utc(@zone.utc_to_local(utc))
|
116
|
+
|
117
|
+
false
|
118
|
+
|
119
|
+
rescue TZInfo::AmbiguousTime
|
120
|
+
|
121
|
+
true
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns this ::EtOrbi::EoTime as a ::Time instance
|
125
|
+
# in the current UTC timezone.
|
126
|
+
#
|
127
|
+
def utc
|
128
|
+
|
129
|
+
Time.utc(1970) + @seconds
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns true if this ::EtOrbi::EoTime instance timezone is UTC.
|
133
|
+
# Returns false else.
|
134
|
+
#
|
135
|
+
def utc?
|
136
|
+
|
137
|
+
%w[ gmt utc zulu etc/gmt etc/utc ].include?(
|
138
|
+
@zone.canonical_identifier.downcase)
|
139
|
+
end
|
140
|
+
|
141
|
+
alias getutc utc
|
142
|
+
alias getgm utc
|
143
|
+
alias to_utc_time utc
|
144
|
+
|
145
|
+
def to_f
|
146
|
+
|
147
|
+
@seconds
|
148
|
+
end
|
149
|
+
|
150
|
+
def to_i
|
151
|
+
|
152
|
+
@seconds.to_i
|
153
|
+
end
|
154
|
+
|
155
|
+
def strftime(format)
|
156
|
+
|
157
|
+
format = format.gsub(/%(\/?Z|:{0,2}z)/) { |f| strfz(f) }
|
158
|
+
|
159
|
+
to_time.strftime(format)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns this ::EtOrbi::EoTime as a ::Time instance
|
163
|
+
# in the current timezone.
|
164
|
+
#
|
165
|
+
# Has a #to_t alias.
|
166
|
+
#
|
167
|
+
def to_local_time
|
168
|
+
|
169
|
+
Time.at(@seconds)
|
170
|
+
end
|
171
|
+
|
172
|
+
alias to_t to_local_time
|
173
|
+
|
174
|
+
def is_dst?
|
175
|
+
|
176
|
+
@zone.period_for_utc(utc).std_offset != 0
|
177
|
+
end
|
178
|
+
alias isdst is_dst?
|
179
|
+
|
180
|
+
def to_debug_s
|
181
|
+
|
182
|
+
uo = self.utc_offset
|
183
|
+
uos = uo < 0 ? '-' : '+'
|
184
|
+
uo = uo.abs
|
185
|
+
uoh, uom = [ uo / 3600, uo % 3600 ]
|
186
|
+
|
187
|
+
[
|
188
|
+
'ot',
|
189
|
+
self.strftime('%Y-%m-%d %H:%M:%S'),
|
190
|
+
"%s%02d:%02d" % [ uos, uoh, uom ],
|
191
|
+
"dst:#{self.isdst}"
|
192
|
+
].join(' ')
|
193
|
+
end
|
194
|
+
|
195
|
+
def utc_offset
|
196
|
+
|
197
|
+
@zone.period_for_utc(utc).utc_total_offset
|
198
|
+
end
|
199
|
+
|
200
|
+
%w[
|
201
|
+
year month day wday hour min sec usec asctime
|
202
|
+
].each do |m|
|
203
|
+
define_method(m) { to_time.send(m) }
|
204
|
+
end
|
205
|
+
|
206
|
+
def ==(o)
|
207
|
+
|
208
|
+
o.is_a?(EoTime) &&
|
209
|
+
o.seconds == @seconds &&
|
210
|
+
(o.zone == @zone || o.zone.current_period == @zone.current_period)
|
211
|
+
end
|
212
|
+
#alias eql? == # FIXME see Object#== (ri)
|
213
|
+
|
214
|
+
def >(o); @seconds > _to_f(o); end
|
215
|
+
def >=(o); @seconds >= _to_f(o); end
|
216
|
+
def <(o); @seconds < _to_f(o); end
|
217
|
+
def <=(o); @seconds <= _to_f(o); end
|
218
|
+
def <=>(o); @seconds <=> _to_f(o); end
|
219
|
+
|
220
|
+
def add(t); @time = nil; @seconds += t.to_f; self; end
|
221
|
+
def subtract(t); @time = nil; @seconds -= t.to_f; self; end
|
222
|
+
|
223
|
+
def +(t); inc(t, 1); end
|
224
|
+
def -(t); inc(t, -1); end
|
225
|
+
|
226
|
+
WEEK_S = 7 * 24 * 3600
|
227
|
+
|
228
|
+
def monthdays
|
229
|
+
|
230
|
+
date = to_time
|
231
|
+
|
232
|
+
pos = 1
|
233
|
+
d = self.dup
|
234
|
+
|
235
|
+
loop do
|
236
|
+
d.add(-WEEK_S)
|
237
|
+
break if d.month != date.month
|
238
|
+
pos = pos + 1
|
239
|
+
end
|
240
|
+
|
241
|
+
neg = -1
|
242
|
+
d = self.dup
|
243
|
+
|
244
|
+
loop do
|
245
|
+
d.add(WEEK_S)
|
246
|
+
break if d.month != date.month
|
247
|
+
neg = neg - 1
|
248
|
+
end
|
249
|
+
|
250
|
+
[ "#{date.wday}##{pos}", "#{date.wday}##{neg}" ]
|
251
|
+
end
|
252
|
+
|
253
|
+
def to_s
|
254
|
+
|
255
|
+
strftime('%Y-%m-%d %H:%M:%S %z')
|
256
|
+
end
|
257
|
+
|
258
|
+
def to_zs
|
259
|
+
|
260
|
+
strftime('%Y-%m-%d %H:%M:%S %/Z')
|
261
|
+
end
|
262
|
+
|
263
|
+
def iso8601(fraction_digits=0)
|
264
|
+
|
265
|
+
s = (fraction_digits || 0) > 0 ? ".%#{fraction_digits}N" : ''
|
266
|
+
strftime("%Y-%m-%dT%H:%M:%S#{s}%:z")
|
267
|
+
end
|
268
|
+
|
269
|
+
# Debug current time by showing local time / delta / utc time
|
270
|
+
# for example: "0120-7(0820)"
|
271
|
+
#
|
272
|
+
def to_utc_comparison_s
|
273
|
+
|
274
|
+
per = @zone.period_for_utc(utc)
|
275
|
+
off = per.utc_total_offset
|
276
|
+
|
277
|
+
off = off / 3600
|
278
|
+
off = off >= 0 ? "+#{off}" : off.to_s
|
279
|
+
|
280
|
+
strftime('%H%M') + off + utc.strftime('(%H%M)')
|
281
|
+
end
|
282
|
+
|
283
|
+
def to_time_s
|
284
|
+
|
285
|
+
strftime("%H:%M:%S.#{'%06d' % usec}")
|
286
|
+
end
|
287
|
+
|
288
|
+
def inc(t, dir=1)
|
289
|
+
|
290
|
+
case t
|
291
|
+
when Numeric
|
292
|
+
nt = self.dup
|
293
|
+
nt.seconds += dir * t.to_f
|
294
|
+
nt
|
295
|
+
when ::Time, ::EtOrbi::EoTime
|
296
|
+
fail ArgumentError.new(
|
297
|
+
"Cannot add #{t.class} to EoTime") if dir > 0
|
298
|
+
@seconds + dir * t.to_f
|
299
|
+
else
|
300
|
+
fail ArgumentError.new(
|
301
|
+
"Cannot call add or subtract #{t.class} to EoTime instance")
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def localtime(zone=nil)
|
306
|
+
|
307
|
+
EoTime.new(self.to_f, zone)
|
308
|
+
end
|
309
|
+
|
310
|
+
alias translate localtime
|
311
|
+
|
312
|
+
def wday_in_month
|
313
|
+
|
314
|
+
[ count_weeks(-1), - count_weeks(1) ]
|
315
|
+
end
|
316
|
+
|
317
|
+
def reach(points)
|
318
|
+
|
319
|
+
t = EoTime.new(self.to_f, @zone)
|
320
|
+
step = 1
|
321
|
+
|
322
|
+
s = points[:second] || points[:sec] || points[:s]
|
323
|
+
m = points[:minute] || points[:min] || points[:m]
|
324
|
+
h = points[:hour] || points[:hou] || points[:h]
|
325
|
+
|
326
|
+
fail ArgumentError.new("missing :second, :minute, and :hour") \
|
327
|
+
unless s || m || h
|
328
|
+
|
329
|
+
if !s && !m
|
330
|
+
step = 60 * 60
|
331
|
+
t -= t.sec
|
332
|
+
t -= t.min * 60
|
333
|
+
elsif !s
|
334
|
+
step = 60
|
335
|
+
t -= t.sec
|
336
|
+
end
|
337
|
+
|
338
|
+
loop do
|
339
|
+
t += step
|
340
|
+
next if s && t.sec != s
|
341
|
+
next if m && t.min != m
|
342
|
+
next if h && t.hour != h
|
343
|
+
break
|
344
|
+
end
|
345
|
+
|
346
|
+
t
|
347
|
+
end
|
348
|
+
|
349
|
+
protected
|
350
|
+
|
351
|
+
# Returns a Ruby Time instance.
|
352
|
+
#
|
353
|
+
# Warning: the timezone of that Time instance will be UTC when used with
|
354
|
+
# TZInfo < 2.0.0.
|
355
|
+
#
|
356
|
+
def to_time
|
357
|
+
|
358
|
+
@time ||= @zone.utc_to_local(utc)
|
359
|
+
end
|
360
|
+
|
361
|
+
def count_weeks(dir)
|
362
|
+
|
363
|
+
c = 0
|
364
|
+
t = self
|
365
|
+
until t.month != self.month
|
366
|
+
c += 1
|
367
|
+
t += dir * (7 * 24 * 3600)
|
368
|
+
end
|
369
|
+
|
370
|
+
c
|
371
|
+
end
|
372
|
+
|
373
|
+
def strfz(code)
|
374
|
+
|
375
|
+
return @zone.name if code == '%/Z'
|
376
|
+
|
377
|
+
per = @zone.period_for_utc(utc)
|
378
|
+
|
379
|
+
return per.abbreviation.to_s if code == '%Z'
|
380
|
+
|
381
|
+
off = per.utc_total_offset
|
382
|
+
#
|
383
|
+
sn = off < 0 ? '-' : '+'; off = off.abs
|
384
|
+
hr = off / 3600
|
385
|
+
mn = (off % 3600) / 60
|
386
|
+
sc = 0
|
387
|
+
|
388
|
+
if @zone.name == 'UTC'
|
389
|
+
'Z' # align on Ruby ::Time#iso8601
|
390
|
+
elsif code == '%z'
|
391
|
+
'%s%02d%02d' % [ sn, hr, mn ]
|
392
|
+
elsif code == '%:z'
|
393
|
+
'%s%02d:%02d' % [ sn, hr, mn ]
|
394
|
+
else
|
395
|
+
'%s%02d:%02d:%02d' % [ sn, hr, mn, sc ]
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
def _to_f(o)
|
400
|
+
|
401
|
+
fail ArgumentError(
|
402
|
+
"Comparison of EoTime with #{o.inspect} failed"
|
403
|
+
) unless o.is_a?(EoTime) || o.is_a?(Time)
|
404
|
+
|
405
|
+
o.to_f
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
data/lib/et-orbi/zone_aliases.rb
CHANGED
@@ -37,6 +37,10 @@ module EtOrbi
|
|
37
37
|
nil
|
38
38
|
end
|
39
39
|
|
40
|
+
# https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones
|
41
|
+
# https://support.microsoft.com/en-ca/help/973627/microsoft-time-zone-index-values
|
42
|
+
# https://ss64.com/nt/timezones.html
|
43
|
+
|
40
44
|
ZONE_ALIASES = {
|
41
45
|
'Coordinated Universal Time' => 'UTC',
|
42
46
|
'Afghanistan Standard Time' => 'Asia/Kabul',
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: et-orbi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Mettraux
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tzinfo
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '3.4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: chronic
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.10'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.10'
|
41
55
|
description: Time zones for fugit and rufus-scheduler. Urbi et Orbi.
|
42
56
|
email:
|
43
57
|
- jmettraux+flor@gmail.com
|
@@ -52,6 +66,7 @@ files:
|
|
52
66
|
- README.md
|
53
67
|
- et-orbi.gemspec
|
54
68
|
- lib/et-orbi.rb
|
69
|
+
- lib/et-orbi/eo_time.rb
|
55
70
|
- lib/et-orbi/zone_aliases.rb
|
56
71
|
- lib/et_orbi.rb
|
57
72
|
- lib/etorbi.rb
|