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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b3e0133c5181167fb43ca7228ce7b76d154f402a
4
- data.tar.gz: 3ff631d838f4ea26b0af2c11701356f71e9518f4
3
+ metadata.gz: c52b936431c2d332e82c316fb52d7e786c577027
4
+ data.tar.gz: 8d82d60d76b8f041771852d688364eab445f2575
5
5
  SHA512:
6
- metadata.gz: cb8cfb83bb07a6289bc86b24a1c9c26510a807734a1f81b8f132f032c7015081320408515f606696a4fea03fa00bc6c7d2278d0ad6390fc3c8a277034e627683
7
- data.tar.gz: b1ec3168fd15980353048e3261e848bf63c7e740f47388e20f0f3c8e3b2bd4a9c1cab324a7663d60b5a71aba7ecc473b9453f0d2ee9eab859a4aa5f017df3d87
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
- * introduce et-orbi dependency (1.0.5 or better)
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
- Many thanks to all the rufus-scheduler contributors and people who gave feedback.
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-2017, John Mettraux, jmettraux+flor@gmail.com
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
- '*.gemspec', '*.txt', '*.md'
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.5'
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
@@ -1,7 +1,7 @@
1
1
 
2
2
  module Fugit
3
3
 
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
6
6
 
7
7
  require 'time'
@@ -14,5 +14,6 @@ require 'fugit/misc'
14
14
  require 'fugit/cron'
15
15
  require 'fugit/duration'
16
16
  require 'fugit/nat'
17
+ require 'fugit/at'
17
18
  require 'fugit/parse'
18
19
 
data/lib/fugit/at.rb ADDED
@@ -0,0 +1,17 @@
1
+
2
+ module Fugit
3
+
4
+ module At
5
+
6
+ def self.parse(s)
7
+
8
+ ::EtOrbi.make_time(s) rescue nil
9
+ end
10
+
11
+ def self.do_parse(s)
12
+
13
+ ::EtOrbi.make_time(s)
14
+ end
15
+ end
16
+ end
17
+
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
- attr_reader :minutes, :hours, :monthdays, :months, :weekdays
20
+ class << self
20
21
 
21
- def self.new(original)
22
+ def new(original)
22
23
 
23
- parse(original)
24
- end
24
+ parse(original)
25
+ end
25
26
 
26
- def to_cron_s
27
+ def parse(s)
27
28
 
28
- @cron_s ||=
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
- def self.parse(s)
31
+ original = s
32
+ s = SPECIALS[s] || s
40
33
 
41
- return s if s.is_a?(self)
34
+ return nil unless s.is_a?(String)
42
35
 
43
- original = s
44
- s = SPECIALS[s] || s
36
+ #p s; Raabro.pp(Parser.parse(s, debug: 3), colors: true)
37
+ h = Parser.parse(s)
45
38
 
46
- return nil unless s.is_a?(String)
39
+ return nil unless h
47
40
 
48
- #p s; Raabro.pp(Parser.parse(s, debug: 3))
49
- h = Parser.parse(s)
41
+ self.allocate.send(:init, s, h)
42
+ end
50
43
 
51
- return nil unless h
44
+ def do_parse(s)
52
45
 
53
- self.allocate.send(:init, s, h)
46
+ parse(s) ||
47
+ fail(ArgumentError.new("not a cron string #{s.inspect}"))
48
+ end
54
49
  end
55
50
 
56
- def self.do_parse(s)
51
+ def to_cron_s
57
52
 
58
- parse(s) || fail(ArgumentError.new("not a cron string #{s.inspect}"))
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 : ::EtOrbi.make_time(t)
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
- t = TimeCursor.new(from)
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
- t = TimeCursor.new(from)
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 a leap second)
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
- occurences = deltas.size
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| h[tt.name] = rewrite_entry(tt); h }
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
@@ -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.to_s if s.is_a?(Numeric)
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
- s = s + 's' if s.match(/\A-?(\d*\.)?\d+\z/)
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 to_plain_s
56
+ def _to_s(key)
57
57
 
58
- KEYS.inject(StringIO.new) { |s, (k, a)|
59
- v = @h[k]; next s unless v; s << v.to_s; s << a[:a]
60
- }.string
61
- end
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
- def deflate
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
- INFLA_KEYS.each do |k, v|
155
+ keys = INFLA_KEYS
136
156
 
137
- n = s / v[:s]; next if n == 0
138
- m = s % v[:s]
157
+ mon = options[:month]
158
+ yea = options[:year]
159
+ keys = keys.dup if mon || yea
139
160
 
140
- h[k] = (h[k] || 0) + n
141
- s = m
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
- self.class.allocate.init(@original, h)
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 substract(a)
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 substract #{a.class} instance to a Fugit::Duration")
262
+ "cannot subtract #{a.class} instance to a Fugit::Duration")
225
263
  end
226
264
  end
227
- alias - substract
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
- @h = h.reject { |k, v| v == 0 }
252
- # which copies h btw
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, /-?\d+ *y(ears?)?/i); end
276
- def mon(i); rex(:mon, i, /-?\d+ *(M|months?)/); end
277
- def wee(i); rex(:wee, i, /-?\d+ *(weeks?|w)/i); end
278
- def day(i); rex(:day, i, /-?\d+ *(days?|d)/i); end
279
- def hou(i); rex(:hou, i, /-?\d+ *(hours?|h)/i); end
280
- def min(i); rex(:min, i, /-?\d+ *(mins?|minutes?|m)/); end
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 sec(i); rex(:sec, i, /-?((\d*\.)?\d+) *(secs?|seconds?|s)/i); end
283
- # always last!
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 elt(i); alt(nil, i, :yea, :mon, :wee, :day, :hou, :min, :sec); end
342
+ def sdur(i); seq(:sdur, i, :sign, '?', :elt, '+'); end
286
343
 
287
- def dur(i); jseq(:dur, i, :elt, :sep); end
344
+ def dur(i); jseq(:dur, i, :sdur, :sep); end
288
345
 
289
346
  # rewrite parsed tree
290
347
 
291
- def rewrite_dur(t); Fugit::Duration.common_rewrite_dur(t); end
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.0.0
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: 2017-06-23 00:00:00.000000000 Z
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.5
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.5
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.5.2
96
+ rubygems_version: 2.6.13
96
97
  signing_key:
97
98
  specification_version: 4
98
99
  summary: time tools for flor