et-orbi 1.1.6 → 1.1.7
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/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
|