fugit 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -1
- data/CREDITS.md +7 -1
- data/LICENSE.txt +4 -1
- data/README.md +65 -0
- data/fugit.gemspec +4 -2
- data/lib/fugit.rb +2 -1
- data/lib/fugit/at.rb +17 -0
- data/lib/fugit/cron.rb +103 -44
- data/lib/fugit/duration.rb +123 -45
- data/lib/fugit/parse.rb +4 -12
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c52b936431c2d332e82c316fb52d7e786c577027
|
4
|
+
data.tar.gz: 8d82d60d76b8f041771852d688364eab445f2575
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 24c0e708a7f42af660d14e3718ec395e7dd2e791cb7f2bb5fa2bb3f9d0d2f850a9c78712772854fd182f47bb479a89658513f7fa9eb6d66f55cdca46b788f34d
|
7
|
+
data.tar.gz: b8ba24ce4f558c3cd174bb1b70cf96b2e444999313aee67b0920e7616725cbfaeb98e75964351802b1a6487cb97044288636f450222ed6424dbb2e6833767de6
|
data/CHANGELOG.md
CHANGED
@@ -2,9 +2,26 @@
|
|
2
2
|
# fugit CHANGELOG.md
|
3
3
|
|
4
4
|
|
5
|
+
## fugit 1.1.0 released 2018-03-27
|
6
|
+
|
7
|
+
* Travel in Cron zone in #next_time and #previous_time, return from zone
|
8
|
+
* Parse and store timezone in Fugit::Cron
|
9
|
+
* Introduce Fugit::Duration#deflate month: d / year: d
|
10
|
+
* Introduce Fugit::Duration#drop_seconds
|
11
|
+
* Alias Fugit::Duration#to_h to Fugit::Duration#h
|
12
|
+
* Introduce to_rufus_s (1y2M3d) vs to_plain_s (1Y2M3D)
|
13
|
+
* Ensure Duration#deflate preserves at least `{ sec: 0 }`
|
14
|
+
* Stringify 0 seconds as "0s"
|
15
|
+
* Ignore "-5" and "-5.", only accept "-5s" and "-5.s"
|
16
|
+
* Introduce "signed durations", "-1Y+2Y-3m"
|
17
|
+
* Ensure `1.0d1.0w1.0d` gets parsed correctly
|
18
|
+
* Ensure Fugit::Cron.next_time returns plain seconds (.0, not .1234...)
|
19
|
+
* Introduce Fugit::Frequency for cron
|
20
|
+
|
21
|
+
|
5
22
|
## fugit 1.0.0 released 2017-06-23
|
6
23
|
|
7
|
-
*
|
24
|
+
* Introduce et-orbi dependency (1.0.5 or better)
|
8
25
|
* Wire #deflate into Duration.to_long_s / .to_iso_s / .to_plain_s
|
9
26
|
|
10
27
|
|
data/CREDITS.md
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
|
2
2
|
# fugit credits
|
3
3
|
|
4
|
-
|
4
|
+
* Harry Lascelles https://github.com/hlascelles timezone reminder
|
5
|
+
|
6
|
+
|
7
|
+
## rufus-scheduler credits
|
8
|
+
|
9
|
+
As fugit originates in rufus-scheduler, many thanks to all the
|
10
|
+
rufus-scheduler contributors and people who gave feedback.
|
5
11
|
|
6
12
|
https://github.com/jmettraux/rufus-scheduler/blob/master/CREDITS.txt
|
7
13
|
|
data/LICENSE.txt
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
|
2
|
-
Copyright (c) 2017-
|
2
|
+
Copyright (c) 2017-2018, 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
|
@@ -19,3 +19,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
19
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
20
|
THE SOFTWARE.
|
21
21
|
|
22
|
+
|
23
|
+
Made in Japan
|
24
|
+
|
data/README.md
CHANGED
@@ -6,6 +6,8 @@
|
|
6
6
|
|
7
7
|
Time tools for [flor](https://github.com/floraison/flor) and the floraison group.
|
8
8
|
|
9
|
+
It uses [et-orbi](https://github.com/floraison/et-orbi) to represent time instances and [raabro](https://github.com/floraison/raabro) as a basis for its parsers.
|
10
|
+
|
9
11
|
Fugit will probably become the foundation for [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) 4.x
|
10
12
|
|
11
13
|
|
@@ -25,6 +27,22 @@ Fugit will probably become the foundation for [rufus-scheduler](https://github.c
|
|
25
27
|
* ...
|
26
28
|
|
27
29
|
|
30
|
+
## `Fugit.parse(s)`
|
31
|
+
|
32
|
+
The simplest way to use fugit is via `Fugit.parse(s)`.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 'fugit'
|
36
|
+
|
37
|
+
Fugit.parse('0 0 1 jan *').class # ==> ::Fugit::Cron
|
38
|
+
Fugit.parse('12y12M').class # ==> ::Fugit::Duration
|
39
|
+
|
40
|
+
Fugit.parse('2017-12-12').class # ==> ::EtOrbi::EoTime
|
41
|
+
Fugit.parse('2017-12-12 UTC').class # ==> ::EtOrbi::EoTime
|
42
|
+
|
43
|
+
Fugit.parse('every day at noon').class # ==> ::Fugit::Cron
|
44
|
+
```
|
45
|
+
|
28
46
|
## `Fugit::Cron`
|
29
47
|
|
30
48
|
A class `Fugit::Cron` to parse cron strings and then `#next_time` and `#previous_time` to compute the next or the previous occurrence respectively.
|
@@ -108,6 +126,53 @@ p Fugit::Duration.to_long_s('1y2M1d4h')
|
|
108
126
|
# => "1 year, 2 months, 1 day, and 4 hours"
|
109
127
|
```
|
110
128
|
|
129
|
+
## `Fugit::At`
|
130
|
+
|
131
|
+
Points in time are parsed and given back as EtOrbi::EoTime instances.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
Fugit::At.parse('2017-12-12').to_s
|
135
|
+
# ==> "2017-12-12 00:00:00 +0900" (at least here in Hiroshima)
|
136
|
+
|
137
|
+
Fugit::At.parse('2017-12-12 12:00:00 America/New_York').to_s
|
138
|
+
# ==> "2017-12-12 12:00:00 -0500"
|
139
|
+
```
|
140
|
+
|
141
|
+
Directly with `Fugit.parse_at(s)` is OK too:
|
142
|
+
```ruby
|
143
|
+
Fugit.parse_at('2017-12-12 12:00:00 America/New_York').to_s
|
144
|
+
# ==> "2017-12-12 12:00:00 -0500"
|
145
|
+
```
|
146
|
+
|
147
|
+
Directly with `Fugit.parse(s)` is OK too:
|
148
|
+
```ruby
|
149
|
+
Fugit.parse('2017-12-12 12:00:00 America/New_York').to_s
|
150
|
+
# ==> "2017-12-12 12:00:00 -0500"
|
151
|
+
```
|
152
|
+
|
153
|
+
## `Fugit::Nat`
|
154
|
+
|
155
|
+
Fugit understand some kind of "natural" language:
|
156
|
+
|
157
|
+
For example, those "every" get turned into `Fugit::Cron` instances:
|
158
|
+
```ruby
|
159
|
+
Fugit::Nat.parse('every day at five') # ==> '0 5 * * *'
|
160
|
+
Fugit::Nat.parse('every weekday at five') # ==> '0 5 * * 1,2,3,4,5'
|
161
|
+
Fugit::Nat.parse('every day at 5 pm') # ==> '0 17 * * *'
|
162
|
+
Fugit::Nat.parse('every tuesday at 5 pm') # ==> '0 17 * * 2'
|
163
|
+
Fugit::Nat.parse('every wed at 5 pm') # ==> '0 17 * * 3'
|
164
|
+
Fugit::Nat.parse('every day at 16:30') # ==> '30 16 * * *'
|
165
|
+
Fugit::Nat.parse('every day at noon') # ==> '0 12 * * *'
|
166
|
+
Fugit::Nat.parse('every day at midnight') # ==> '0 0 * * *'
|
167
|
+
Fugit::Nat.parse('every tuesday and monday at 5pm') # ==> '0 17 * * 1,2'
|
168
|
+
Fugit::Nat.parse('every wed or Monday at 5pm and 11') # ==> '0 11,17 * * 1,3'
|
169
|
+
```
|
170
|
+
|
171
|
+
Directly with `Fugit.parse(s)` is OK too:
|
172
|
+
```ruby
|
173
|
+
Fugit.parse('every day at five') # ==> Fugit::Cron instance '0 5 * * *'
|
174
|
+
```
|
175
|
+
|
111
176
|
|
112
177
|
## LICENSE
|
113
178
|
|
data/fugit.gemspec
CHANGED
@@ -20,14 +20,16 @@ Time tools for flor and the floraison project. Cron parsing and occurence comput
|
|
20
20
|
|
21
21
|
#s.files = `git ls-files`.split("\n")
|
22
22
|
s.files = Dir[
|
23
|
+
'README.{md,txt}',
|
24
|
+
'CHANGELOG.{md,txt}', 'CREDITS.{md,txt}', 'LICENSE.{md,txt}',
|
23
25
|
'Makefile',
|
24
26
|
'lib/**/*.rb', #'spec/**/*.rb', 'test/**/*.rb',
|
25
|
-
|
27
|
+
"#{s.name}.gemspec",
|
26
28
|
]
|
27
29
|
|
28
30
|
#s.add_runtime_dependency 'tzinfo'
|
29
31
|
s.add_runtime_dependency 'raabro', '~> 1.1'
|
30
|
-
s.add_runtime_dependency 'et-orbi', '>= 1.0
|
32
|
+
s.add_runtime_dependency 'et-orbi', '>= 1.1.0'
|
31
33
|
|
32
34
|
s.add_development_dependency 'rspec', '~> 3.4'
|
33
35
|
|
data/lib/fugit.rb
CHANGED
data/lib/fugit/at.rb
ADDED
data/lib/fugit/cron.rb
CHANGED
@@ -14,54 +14,60 @@ module Fugit
|
|
14
14
|
'@hourly' => '0 * * * *',
|
15
15
|
}
|
16
16
|
|
17
|
-
attr_reader :original
|
17
|
+
attr_reader :original, :zone
|
18
|
+
attr_reader :minutes, :hours, :monthdays, :months, :weekdays, :timezone
|
18
19
|
|
19
|
-
|
20
|
+
class << self
|
20
21
|
|
21
|
-
|
22
|
+
def new(original)
|
22
23
|
|
23
|
-
|
24
|
-
|
24
|
+
parse(original)
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
+
def parse(s)
|
27
28
|
|
28
|
-
|
29
|
-
[
|
30
|
-
@seconds == [ 0 ] ? nil : (@seconds || [ '*' ]).join(','),
|
31
|
-
(@minutes || [ '*' ]).join(','),
|
32
|
-
(@hours || [ '*' ]).join(','),
|
33
|
-
(@monthdays || [ '*' ]).join(','),
|
34
|
-
(@months || [ '*' ]).join(','),
|
35
|
-
(@weekdays || [ [ '*' ] ]).map { |d| d.compact.join('#') }.join(',')
|
36
|
-
].compact.join(' ')
|
37
|
-
end
|
29
|
+
return s if s.is_a?(self)
|
38
30
|
|
39
|
-
|
31
|
+
original = s
|
32
|
+
s = SPECIALS[s] || s
|
40
33
|
|
41
|
-
|
34
|
+
return nil unless s.is_a?(String)
|
42
35
|
|
43
|
-
|
44
|
-
|
36
|
+
#p s; Raabro.pp(Parser.parse(s, debug: 3), colors: true)
|
37
|
+
h = Parser.parse(s)
|
45
38
|
|
46
|
-
|
39
|
+
return nil unless h
|
47
40
|
|
48
|
-
|
49
|
-
|
41
|
+
self.allocate.send(:init, s, h)
|
42
|
+
end
|
50
43
|
|
51
|
-
|
44
|
+
def do_parse(s)
|
52
45
|
|
53
|
-
|
46
|
+
parse(s) ||
|
47
|
+
fail(ArgumentError.new("not a cron string #{s.inspect}"))
|
48
|
+
end
|
54
49
|
end
|
55
50
|
|
56
|
-
def
|
51
|
+
def to_cron_s
|
57
52
|
|
58
|
-
|
53
|
+
@cron_s ||= begin
|
54
|
+
[
|
55
|
+
@seconds == [ 0 ] ? nil : (@seconds || [ '*' ]).join(','),
|
56
|
+
(@minutes || [ '*' ]).join(','),
|
57
|
+
(@hours || [ '*' ]).join(','),
|
58
|
+
(@monthdays || [ '*' ]).join(','),
|
59
|
+
(@months || [ '*' ]).join(','),
|
60
|
+
(@weekdays || [ [ '*' ] ]).map { |d| d.compact.join('#') }.join(','),
|
61
|
+
@timezone ? @timezone.to_s : nil
|
62
|
+
].compact.join(' ')
|
63
|
+
end
|
59
64
|
end
|
60
65
|
|
61
66
|
class TimeCursor
|
62
67
|
|
63
68
|
def initialize(t)
|
64
|
-
@t = t.is_a?(TimeCursor) ? t.time :
|
69
|
+
@t = t.is_a?(TimeCursor) ? t.time : t
|
70
|
+
@t.seconds = @t.seconds.to_i
|
65
71
|
end
|
66
72
|
|
67
73
|
def time; @t; end
|
@@ -163,7 +169,8 @@ module Fugit
|
|
163
169
|
|
164
170
|
def next_time(from=::EtOrbi::EoTime.now)
|
165
171
|
|
166
|
-
|
172
|
+
from = ::EtOrbi.make_time(from)
|
173
|
+
t = TimeCursor.new(from.translate(@timezone))
|
167
174
|
|
168
175
|
loop do
|
169
176
|
#p [ :l, Fugit.time_to_s(t.time) ]
|
@@ -176,12 +183,13 @@ module Fugit
|
|
176
183
|
break
|
177
184
|
end
|
178
185
|
|
179
|
-
t.time
|
186
|
+
t.time.translate(from.zone)
|
180
187
|
end
|
181
188
|
|
182
189
|
def previous_time(from=::EtOrbi::EoTime.now)
|
183
190
|
|
184
|
-
|
191
|
+
from = ::EtOrbi.make_time(from)
|
192
|
+
t = TimeCursor.new(from.translate(@timezone))
|
185
193
|
|
186
194
|
loop do
|
187
195
|
#p [ :l, Fugit.time_to_s(t.time) ]
|
@@ -194,13 +202,14 @@ module Fugit
|
|
194
202
|
break
|
195
203
|
end
|
196
204
|
|
197
|
-
t.time
|
205
|
+
t.time.translate(from.zone)
|
198
206
|
end
|
199
207
|
|
200
208
|
# Mostly used as a #next_time sanity check.
|
201
209
|
# Avoid for "business" use, it's slow.
|
202
210
|
#
|
203
|
-
# 2017 is non leap year (though it is preceded by
|
211
|
+
# 2017 is a non leap year (though it is preceded by
|
212
|
+
# a leap second on 2016-12-31)
|
204
213
|
#
|
205
214
|
# Nota bene: cron with seconds are not supported.
|
206
215
|
#
|
@@ -223,16 +232,38 @@ module Fugit
|
|
223
232
|
t = t1
|
224
233
|
end
|
225
234
|
|
226
|
-
|
227
|
-
span = t1 - t0
|
228
|
-
span_years = span / (365 * 24 * 3600)
|
229
|
-
yearly_occurences = occurences.to_f / span_years
|
230
|
-
|
231
|
-
[ deltas.min, deltas.max, occurences,
|
232
|
-
span.to_i, span_years.to_i, yearly_occurences.to_i ]
|
235
|
+
Frequency.new(deltas, t1 - t0)
|
233
236
|
end
|
234
237
|
end
|
235
238
|
|
239
|
+
class Frequency
|
240
|
+
|
241
|
+
attr_reader :span, :delta_min, :delta_max, :occurrences
|
242
|
+
attr_reader :span_years, :yearly_occurrences
|
243
|
+
|
244
|
+
def initialize(deltas, span)
|
245
|
+
|
246
|
+
@span = span
|
247
|
+
|
248
|
+
@delta_min = deltas.min; @delta_max = deltas.max
|
249
|
+
@occurrences = deltas.size
|
250
|
+
@span_years = span / (365 * 24 * 3600)
|
251
|
+
@yearly_occurrences = @occurrences.to_f / @span_years
|
252
|
+
end
|
253
|
+
|
254
|
+
def to_debug_s
|
255
|
+
|
256
|
+
{
|
257
|
+
dmin: Fugit::Duration.new(delta_min).deflate.to_plain_s,
|
258
|
+
dmax: Fugit::Duration.new(delta_max).deflate.to_plain_s,
|
259
|
+
ocs: occurrences,
|
260
|
+
spn: Fugit::Duration.new(span.to_i).deflate.to_plain_s,
|
261
|
+
spnys: span_years.to_i,
|
262
|
+
yocs: yearly_occurrences.to_i
|
263
|
+
}.collect { |k, v| "#{k}: #{v}" }.join(', ')
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
236
267
|
def to_a
|
237
268
|
|
238
269
|
[ @seconds, @minutes, @hours, @monthdays, @months, @weekdays ]
|
@@ -264,6 +295,7 @@ module Fugit
|
|
264
295
|
determine_monthdays(h[:dom])
|
265
296
|
determine_months(h[:mon])
|
266
297
|
determine_weekdays(h[:dow])
|
298
|
+
determine_timezone(h[:tz])
|
267
299
|
|
268
300
|
self
|
269
301
|
end
|
@@ -345,6 +377,11 @@ module Fugit
|
|
345
377
|
@weekdays = nil if @weekdays.empty?
|
346
378
|
end
|
347
379
|
|
380
|
+
def determine_timezone(z)
|
381
|
+
|
382
|
+
@zone, @timezone = z
|
383
|
+
end
|
384
|
+
|
348
385
|
module Parser include Raabro
|
349
386
|
|
350
387
|
WEEKDAYS = %w[ sunday monday tuesday wednesday thursday friday saturday ]
|
@@ -420,11 +457,19 @@ module Fugit
|
|
420
457
|
def lmon_(i); seq(nil, i, :list_mon, :s); end
|
421
458
|
alias ldow list_dow
|
422
459
|
|
460
|
+
def _tz_name(i)
|
461
|
+
rex(nil, i, / +[A-Z][a-zA-Z0-9]+(\/[A-Z][a-zA-Z0-9_]+){0,2}/)
|
462
|
+
end
|
463
|
+
def _tz_delta(i)
|
464
|
+
rex(nil, i, / +[-+]([01][0-9]|2[0-4]):?(00|15|30|45)/)
|
465
|
+
end
|
466
|
+
def _tz(i); alt(:tz, i, :_tz_delta, :_tz_name); end
|
467
|
+
|
423
468
|
def classic_cron(i)
|
424
|
-
seq(:ccron, i, :lmin_, :lhou_, :ldom_, :lmon_, :ldow)
|
469
|
+
seq(:ccron, i, :lmin_, :lhou_, :ldom_, :lmon_, :ldow, :_tz, '?')
|
425
470
|
end
|
426
471
|
def second_cron(i)
|
427
|
-
seq(:scron, i, :lsec_, :lmin_, :lhou_, :ldom_, :lmon_, :ldow)
|
472
|
+
seq(:scron, i, :lsec_, :lmin_, :lhou_, :ldom_, :lmon_, :ldow, :_tz, '?')
|
428
473
|
end
|
429
474
|
|
430
475
|
def cron(i)
|
@@ -469,12 +514,26 @@ module Fugit
|
|
469
514
|
.collect { |et| rewrite_elt(t.name, et) }
|
470
515
|
end
|
471
516
|
|
517
|
+
def rewrite_tz(t)
|
518
|
+
|
519
|
+
s = t.string.strip
|
520
|
+
z = EtOrbi.get_tzone(s)
|
521
|
+
|
522
|
+
[ s, z ]
|
523
|
+
end
|
524
|
+
|
472
525
|
def rewrite_cron(t)
|
473
526
|
|
474
|
-
t
|
527
|
+
hcron = t
|
475
528
|
.sublookup(nil) # go to :ccron or :scron
|
476
529
|
.subgather(nil) # list min, hou, mon, ...
|
477
|
-
.inject({}) { |h, tt|
|
530
|
+
.inject({}) { |h, tt|
|
531
|
+
h[tt.name] = tt.name == :tz ? rewrite_tz(tt) : rewrite_entry(tt)
|
532
|
+
h }
|
533
|
+
|
534
|
+
z, tz = hcron[:tz]; return nil if z && ! tz
|
535
|
+
|
536
|
+
hcron
|
478
537
|
end
|
479
538
|
end
|
480
539
|
end
|
data/lib/fugit/duration.rb
CHANGED
@@ -3,7 +3,7 @@ module Fugit
|
|
3
3
|
|
4
4
|
class Duration
|
5
5
|
|
6
|
-
attr_reader :original, :h
|
6
|
+
attr_reader :original, :h, :options
|
7
7
|
|
8
8
|
def self.new(s)
|
9
9
|
|
@@ -16,13 +16,12 @@ module Fugit
|
|
16
16
|
|
17
17
|
original = s
|
18
18
|
|
19
|
-
s = s
|
19
|
+
s = "#{s}s" if s.is_a?(Numeric)
|
20
20
|
|
21
21
|
return nil unless s.is_a?(String)
|
22
22
|
|
23
23
|
s = s.strip
|
24
|
-
|
25
|
-
#p [ original, s ]; Raabro.pp(Parser.parse(s, debug: 3))
|
24
|
+
#p [ original, s ]; Raabro.pp(Parser.parse(s, debug: 3), colours: true)
|
26
25
|
|
27
26
|
h =
|
28
27
|
if opts[:iso]
|
@@ -32,8 +31,9 @@ module Fugit
|
|
32
31
|
else
|
33
32
|
Parser.parse(s) || IsoParser.parse(opts[:stricter] ? s : s.upcase)
|
34
33
|
end
|
34
|
+
#p h
|
35
35
|
|
36
|
-
h ? self.allocate.send(:init, original, h) : nil
|
36
|
+
h ? self.allocate.send(:init, original, opts, h) : nil
|
37
37
|
end
|
38
38
|
|
39
39
|
def self.do_parse(s, opts={})
|
@@ -42,23 +42,30 @@ module Fugit
|
|
42
42
|
end
|
43
43
|
|
44
44
|
KEYS = {
|
45
|
-
yea: { a: 'Y', i: 'Y', s: 365 * 24 * 3600, x: 0, l: 'year' },
|
46
|
-
mon: { a: 'M', i: 'M', s: 30 * 24 * 3600, x: 1, l: 'month' },
|
47
|
-
wee: { a: 'W', i: 'W', s: 7 * 24 * 3600, I: true, l: 'week' },
|
48
|
-
day: { a: 'D', i: 'D', s: 24 * 3600, I: true, l: 'day' },
|
49
|
-
hou: { a: 'h', i: 'H', s: 3600, I: true, l: 'hour' },
|
50
|
-
min: { a: 'm', i: 'M', s: 60, I: true, l: 'minute' },
|
51
|
-
sec: { a: 's', i: 'S', s: 1, I: true, l: 'second' },
|
45
|
+
yea: { a: 'Y', r: 'y', i: 'Y', s: 365 * 24 * 3600, x: 0, l: 'year' },
|
46
|
+
mon: { a: 'M', r: 'M', i: 'M', s: 30 * 24 * 3600, x: 1, l: 'month' },
|
47
|
+
wee: { a: 'W', r: 'w', i: 'W', s: 7 * 24 * 3600, I: true, l: 'week' },
|
48
|
+
day: { a: 'D', r: 'd', i: 'D', s: 24 * 3600, I: true, l: 'day' },
|
49
|
+
hou: { a: 'h', r: 'h', i: 'H', s: 3600, I: true, l: 'hour' },
|
50
|
+
min: { a: 'm', r: 'm', i: 'M', s: 60, I: true, l: 'minute' },
|
51
|
+
sec: { a: 's', r: 's', i: 'S', s: 1, I: true, l: 'second' },
|
52
52
|
}
|
53
53
|
INFLA_KEYS, NON_INFLA_KEYS =
|
54
54
|
KEYS.partition { |k, v| v[:I] }
|
55
55
|
|
56
|
-
def
|
56
|
+
def _to_s(key)
|
57
57
|
|
58
|
-
KEYS.inject(StringIO.new) { |s, (k, a)|
|
59
|
-
v = @h[k]
|
60
|
-
|
61
|
-
|
58
|
+
KEYS.inject([ StringIO.new, '+' ]) { |(s, sign), (k, a)|
|
59
|
+
v = @h[k]
|
60
|
+
next [ s, sign ] unless v
|
61
|
+
sign1 = v < 0 ? '-' : '+'
|
62
|
+
s << (sign1 != sign ? sign1 : '') << v.abs.to_s << a[key]
|
63
|
+
[ s, sign1 ]
|
64
|
+
}[0].string
|
65
|
+
end; protected :_to_s
|
66
|
+
|
67
|
+
def to_plain_s; _to_s(:a); end
|
68
|
+
def to_rufus_s; _to_s(:r); end
|
62
69
|
|
63
70
|
def to_iso_s
|
64
71
|
|
@@ -102,6 +109,15 @@ module Fugit
|
|
102
109
|
def to_long_s(o, opts={}); do_parse(o).deflate.to_long_s(opts); end
|
103
110
|
end
|
104
111
|
|
112
|
+
# For now, let's alias to #h
|
113
|
+
#
|
114
|
+
def to_h; h; end
|
115
|
+
|
116
|
+
def to_rufus_h
|
117
|
+
|
118
|
+
KEYS.inject({}) { |h, (ks, kh)| v = @h[ks]; h[kh[:r].to_sym] = v if v; h }
|
119
|
+
end
|
120
|
+
|
105
121
|
# Warning: this is an "approximation", months are 30 days and years are
|
106
122
|
# 365 days, ...
|
107
123
|
#
|
@@ -123,32 +139,54 @@ module Fugit
|
|
123
139
|
h
|
124
140
|
}
|
125
141
|
|
126
|
-
self.class.allocate.init(@original, h)
|
142
|
+
self.class.allocate.init(@original, {}, h)
|
127
143
|
end
|
128
144
|
|
129
|
-
|
145
|
+
# Round float seconds to 9 decimals when deflating
|
146
|
+
#
|
147
|
+
SECOND_ROUND = 9
|
148
|
+
|
149
|
+
def deflate(options={})
|
130
150
|
|
131
151
|
id = inflate
|
132
152
|
h = id.h.dup
|
133
153
|
s = h.delete(:sec) || 0
|
134
154
|
|
135
|
-
|
155
|
+
keys = INFLA_KEYS
|
136
156
|
|
137
|
-
|
138
|
-
|
157
|
+
mon = options[:month]
|
158
|
+
yea = options[:year]
|
159
|
+
keys = keys.dup if mon || yea
|
139
160
|
|
140
|
-
|
141
|
-
|
161
|
+
if mon
|
162
|
+
mon = 30 if mon == true
|
163
|
+
mon = "#{mon}d" if mon.is_a?(Integer)
|
164
|
+
keys.unshift([ :mon, { s: Fugit::Duration.parse(mon).to_sec } ])
|
165
|
+
end
|
166
|
+
if yea
|
167
|
+
yea = 365 if yea == true
|
168
|
+
yea = "#{yea}d" if yea.is_a?(Integer)
|
169
|
+
keys.unshift([ :yea, { s: Fugit::Duration.parse(yea).to_sec } ])
|
170
|
+
end
|
171
|
+
|
172
|
+
keys[0..-2].each do |k, v|
|
173
|
+
|
174
|
+
vs = v[:s]; next if s < vs
|
175
|
+
|
176
|
+
h[k] = (h[k] || 0) + s.to_i / vs
|
177
|
+
s = s % vs
|
142
178
|
end
|
143
179
|
|
144
|
-
|
180
|
+
h[:sec] = s.is_a?(Integer) ? s : s.round(SECOND_ROUND)
|
181
|
+
|
182
|
+
self.class.allocate.init(@original, {}, h)
|
145
183
|
end
|
146
184
|
|
147
185
|
def opposite
|
148
186
|
|
149
187
|
h = @h.inject({}) { |h, (k, v)| h[k] = -v; h }
|
150
188
|
|
151
|
-
self.class.allocate.init(nil, h)
|
189
|
+
self.class.allocate.init(nil, {}, h)
|
152
190
|
end
|
153
191
|
|
154
192
|
alias -@ opposite
|
@@ -158,14 +196,14 @@ module Fugit
|
|
158
196
|
h = @h.dup
|
159
197
|
h[:sec] = (h[:sec] || 0) + n.to_i
|
160
198
|
|
161
|
-
self.class.allocate.init(nil, h)
|
199
|
+
self.class.allocate.init(nil,{}, h)
|
162
200
|
end
|
163
201
|
|
164
202
|
def add_duration(d)
|
165
203
|
|
166
204
|
h = d.h.inject(@h.dup) { |h, (k, v)| h[k] = (h[k] || 0) + v; h }
|
167
205
|
|
168
|
-
self.class.allocate.init(nil, h)
|
206
|
+
self.class.allocate.init(nil, {}, h)
|
169
207
|
end
|
170
208
|
|
171
209
|
def add_to_time(t)
|
@@ -213,7 +251,7 @@ module Fugit
|
|
213
251
|
end
|
214
252
|
alias + add
|
215
253
|
|
216
|
-
def
|
254
|
+
def subtract(a)
|
217
255
|
|
218
256
|
case a
|
219
257
|
when Numeric then add_numeric(-a)
|
@@ -221,10 +259,10 @@ module Fugit
|
|
221
259
|
when String then add_duration(-self.class.parse(a))
|
222
260
|
when ::Time, ::EtOrbi::EoTime then add_to_time(a)
|
223
261
|
else fail ArgumentError.new(
|
224
|
-
"cannot
|
262
|
+
"cannot subtract #{a.class} instance to a Fugit::Duration")
|
225
263
|
end
|
226
264
|
end
|
227
|
-
alias -
|
265
|
+
alias - subtract
|
228
266
|
|
229
267
|
def ==(o)
|
230
268
|
|
@@ -242,14 +280,30 @@ module Fugit
|
|
242
280
|
add(from)
|
243
281
|
end
|
244
282
|
|
283
|
+
# Returns a copy of this duration, omitting its seconds.
|
284
|
+
#
|
285
|
+
def drop_seconds
|
286
|
+
|
287
|
+
h = @h.dup
|
288
|
+
h.delete(:sec)
|
289
|
+
h[:min] = 0 if h.empty?
|
290
|
+
|
291
|
+
self.class.allocate.init(nil, { literal: true }, h)
|
292
|
+
end
|
293
|
+
|
245
294
|
protected
|
246
295
|
|
247
|
-
def init(original, h)
|
296
|
+
def init(original, options, h)
|
248
297
|
|
249
298
|
@original = original
|
299
|
+
@options = options
|
250
300
|
|
251
|
-
|
252
|
-
|
301
|
+
if options[:literal]
|
302
|
+
@h = h
|
303
|
+
else
|
304
|
+
@h = h.reject { |k, v| v == 0 }
|
305
|
+
@h[:sec] = 0 if @h.empty?
|
306
|
+
end
|
253
307
|
|
254
308
|
self
|
255
309
|
end
|
@@ -272,23 +326,47 @@ module Fugit
|
|
272
326
|
|
273
327
|
def sep(i); rex(nil, i, /([ \t,]+|and)*/i); end
|
274
328
|
|
275
|
-
def yea(i); rex(:yea, i,
|
276
|
-
def mon(i); rex(:mon, i,
|
277
|
-
def wee(i); rex(:wee, i,
|
278
|
-
def day(i); rex(:day, i,
|
279
|
-
def hou(i); rex(:hou, i,
|
280
|
-
def min(i); rex(:min, i,
|
329
|
+
def yea(i); rex(:yea, i, /(\d+\.\d*|(\d*\.)?\d+) *y(ears?)?/i); end
|
330
|
+
def mon(i); rex(:mon, i, /(\d+\.\d*|(\d*\.)?\d+) *(M|months?)/); end
|
331
|
+
def wee(i); rex(:wee, i, /(\d+\.\d*|(\d*\.)?\d+) *(weeks?|w)/i); end
|
332
|
+
def day(i); rex(:day, i, /(\d+\.\d*|(\d*\.)?\d+) *(days?|d)/i); end
|
333
|
+
def hou(i); rex(:hou, i, /(\d+\.\d*|(\d*\.)?\d+) *(hours?|h)/i); end
|
334
|
+
def min(i); rex(:min, i, /(\d+\.\d*|(\d*\.)?\d+) *(mins?|minutes?|m)/); end
|
335
|
+
|
336
|
+
def sec(i); rex(:sec, i, /(\d+\.\d*|(\d*\.)?\d+) *(secs?|seconds?|s)/i); end
|
337
|
+
def sek(i); rex(:sec, i, /(\d+\.\d*|\.\d+|\d+)$/); end
|
281
338
|
|
282
|
-
def
|
283
|
-
|
339
|
+
def elt(i); alt(nil, i, :yea, :mon, :wee, :day, :hou, :min, :sec, :sek); end
|
340
|
+
def sign(i); rex(:sign, i, /[-+]?/); end
|
284
341
|
|
285
|
-
def
|
342
|
+
def sdur(i); seq(:sdur, i, :sign, '?', :elt, '+'); end
|
286
343
|
|
287
|
-
def dur(i); jseq(:dur, i, :
|
344
|
+
def dur(i); jseq(:dur, i, :sdur, :sep); end
|
288
345
|
|
289
346
|
# rewrite parsed tree
|
290
347
|
|
291
|
-
def
|
348
|
+
def merge(h0, h1)
|
349
|
+
|
350
|
+
sign = h1.delete(:sign) || 1
|
351
|
+
|
352
|
+
h1.inject(h0) { |h, (k, v)| h.merge(k => (h[k] || 0) + sign * v) }
|
353
|
+
end
|
354
|
+
|
355
|
+
def rewrite_sdur(t)
|
356
|
+
|
357
|
+
h = Fugit::Duration.common_rewrite_dur(t)
|
358
|
+
|
359
|
+
sign = t.sublookup(:sign)
|
360
|
+
sign = (sign && sign.string == '-') ? -1 : 1
|
361
|
+
|
362
|
+
h.merge(sign: sign)
|
363
|
+
end
|
364
|
+
|
365
|
+
def rewrite_dur(t)
|
366
|
+
|
367
|
+
#Raabro.pp(t, colours: true)
|
368
|
+
t.children.inject({}) { |h, ct| merge(h, ct.name ? rewrite(ct) : {}) }
|
369
|
+
end
|
292
370
|
end
|
293
371
|
|
294
372
|
module IsoParser include Raabro
|
data/lib/fugit/parse.rb
CHANGED
@@ -1,25 +1,17 @@
|
|
1
1
|
|
2
2
|
module Fugit
|
3
3
|
|
4
|
-
def self.parse_at(s)
|
5
|
-
|
6
|
-
::EtOrbi.make_time(s) rescue nil
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.do_parse_at(s)
|
10
|
-
|
11
|
-
::EtOrbi.make_time(s)
|
12
|
-
end
|
13
|
-
|
14
4
|
def self.parse_cron(s); ::Fugit::Cron.parse(s); end
|
15
5
|
def self.parse_duration(s); ::Fugit::Duration.parse(s); end
|
16
|
-
def self.parse_in(s); parse_duration(s); end
|
17
6
|
def self.parse_nat(s); ::Fugit::Nat.parse(s); end
|
7
|
+
def self.parse_at(s); ::Fugit::At.parse(s); end
|
8
|
+
def self.parse_in(s); parse_duration(s); end
|
18
9
|
|
19
10
|
def self.do_parse_cron(s); ::Fugit::Cron.do_parse(s); end
|
20
11
|
def self.do_parse_duration(s); ::Fugit::Duration.do_parse(s); end
|
21
|
-
def self.do_parse_in(s); do_parse_duration(s); end
|
22
12
|
def self.do_parse_nat(s); ::Fugit::Nat.do_parse(s); end
|
13
|
+
def self.do_parse_at(s); ::Fugit::At.do_parse(s); end
|
14
|
+
def self.do_parse_in(s); do_parse_duration(s); end
|
23
15
|
|
24
16
|
def self.parse(s, opts={})
|
25
17
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fugit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
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: 2018-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: raabro
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.0
|
33
|
+
version: 1.1.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 1.0
|
40
|
+
version: 1.1.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,6 +67,7 @@ files:
|
|
67
67
|
- README.md
|
68
68
|
- fugit.gemspec
|
69
69
|
- lib/fugit.rb
|
70
|
+
- lib/fugit/at.rb
|
70
71
|
- lib/fugit/cron.rb
|
71
72
|
- lib/fugit/duration.rb
|
72
73
|
- lib/fugit/misc.rb
|
@@ -92,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
93
|
version: '0'
|
93
94
|
requirements: []
|
94
95
|
rubyforge_project:
|
95
|
-
rubygems_version: 2.
|
96
|
+
rubygems_version: 2.6.13
|
96
97
|
signing_key:
|
97
98
|
specification_version: 4
|
98
99
|
summary: time tools for flor
|