fugit 1.8.0 → 1.10.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fugit might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe369cec5c3fb690ead6f7638fcd430db71fd6525070c45aa48a8c7b3c3d4171
4
- data.tar.gz: ce486e692afb9bf796122005314c89185373b07d596684b8c7a3f395bf36d1b4
3
+ metadata.gz: dfedcc6bdc1657d09a8c7a4dc23ad0cdb7abe7a78d5ec007477794361f678aaf
4
+ data.tar.gz: 617b8fb21468bb4a6c17294a9c856b2975dd67f5cb6add6569047545a92305ee
5
5
  SHA512:
6
- metadata.gz: 94b9b504e574dbff1f6b0abab21028c97c0303533a8b45747ae6250c6d866c83c7b1999c5cce4680f5015f62946e9ba2f0a2e28af0faf5dc6194c3649b615b97
7
- data.tar.gz: a70bdcd045774d4290cc7d014c62793e28f4a322bf8a3f93d7ee5ce2b2d37744481d32896ce5c88ff89cd3fddfe7ec37bf41c5d3b480dafea45a0d246e1636bd
6
+ metadata.gz: c22968c95e4986b091ed498a848a1f18800cb3217af2e1a1a999ac15a979c8fce8243672f33704e125e09c4dde1adb38b33c0ee3c639f355003d00e734e097a1
7
+ data.tar.gz: 99f6e2170a616663f536ddf9d1b627130ff0e4f20277a2b7011b24ecaa50e43ad2a9ce944c51ace296a6151df71583d7f6e921023a91e9aa82c7820389b39abb
data/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ## fugit 1.10.1 released 2024-02-29
6
+
7
+ * Fix on Ruby 2.2.6 thanks to @aunghtain, gh-93
8
+
9
+
10
+ ## fugit 1.10.0 released 2024-02-22
11
+
12
+ * Implement `Fugit::Cron#within(time_start, time_end)`
13
+ * Implement `Fugit::Cron#within(time_range)`
14
+ * Implement iterator-returning `Fugit::Cron#next` and `#prev`
15
+
16
+
17
+ ## fugit 1.9.0 released 2023-10-24
18
+
19
+ * Let nat parse "last", gh-88
20
+ * Change that I am not sure about, gh-86
21
+
22
+
23
+ ## fugit 1.8.1 released 2023-01-20
24
+
25
+ * Fix for month subtraction, gh-84, @mreinsch
26
+ * Fix duration - time, gh-85, @mreinsch
27
+
28
+
5
29
  ## fugit 1.8.0 released 2022-12-06
6
30
 
7
31
  * Introduce Fugit.parse_cronish and .do_parse_cronish, gh-70
data/CREDITS.md CHANGED
@@ -1,6 +1,10 @@
1
1
 
2
2
  # fugit credits
3
3
 
4
+ * https://hithub.com/aunghtain, gh-93, include oneliner vs Ruby 2.6.6
5
+ * Marcos Belluci, https://github.com/delbetu, gh-88, 1st and last nat
6
+ * Michael Reinsch, https://github.com/mreinsch, gh-84 and gh-85
7
+ * Marc Anguera, https://github.com/markets, gh-70 and Sidekiq-Cron
4
8
  * ski-nine, https://github.com/ski-nine, gh-81
5
9
  * Joseph Halter, https://github.com/JosephHalter, gh-79
6
10
  * James Healy, https://github.com/yob, gh-76
data/LICENSE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- Copyright (c) 2017-2022, John Mettraux, jmettraux+flor@gmail.com
2
+ Copyright (c) 2017-2024, 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
@@ -3,7 +3,6 @@
3
3
 
4
4
  [![tests](https://github.com/floraison/fugit/workflows/test/badge.svg)](https://github.com/floraison/fugit/actions)
5
5
  [![Gem Version](https://badge.fury.io/rb/fugit.svg)](http://badge.fury.io/rb/fugit)
6
- [![Join the chat at https://gitter.im/floraison/fugit](https://badges.gitter.im/floraison/fugit.svg)](https://gitter.im/floraison/fugit?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7
6
 
8
7
  Time tools for [flor](https://github.com/floraison/flor) and the floraison group.
9
8
 
@@ -27,17 +26,21 @@ The intersection of those two projects is where fugit is born:
27
26
  * [parse-cron](https://github.com/siebertm/parse-cron) - parses cron expressions and calculates the next occurrence after a given date
28
27
  * [ice_cube](https://github.com/seejohnrun/ice_cube) - Ruby date recurrence library
29
28
  * [ISO8601](https://github.com/arnau/ISO8601) - Ruby parser to work with ISO8601 dateTimes and durations
29
+ * [chrono](https://github.com/r7kamura/chrono) - a chain of logics about chronology
30
+ * [CronCalc](https://github.com/mizinsky/cron_calc) - calculates cron job occurrences
31
+ * [Recurrence](https://github.com/fnando/recurrence) - a simple library to handle recurring events
30
32
  * ...
31
33
 
32
34
  ### Projects using fugit
33
35
 
34
36
  * [arask](https://github.com/Ebbe/arask) - "Automatic RAils taSKs" uses fugit to parse cron strings
35
37
  * [sidekiq-cron](https://github.com/ondrejbartas/sidekiq-cron) - recent versions of Sidekiq-Cron use fugit to parse cron strings
36
- * [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) -
38
+ * [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) - as seen above
37
39
  * [flor](https://github.com/floraison/flor) - used in the [cron](https://github.com/floraison/flor/blob/master/doc/procedures/cron.md) procedure
38
40
  * [que-scheduler](https://github.com/hlascelles/que-scheduler) - a reliable job scheduler for [que](https://github.com/chanks/que)
39
41
  * [serial_scheduler](https://github.com/grosser/serial_scheduler) - ruby task scheduler without threading
40
42
  * [delayed_cron_job](https://github.com/codez/delayed_cron_job) - an extension to Delayed::Job that allows you to set cron expressions for your jobs
43
+ * [GoodJob](https://github.com/bensheldon/good_job) - a multithreaded, Postgres-based, Active Job backend for Ruby on Rails
41
44
  * ...
42
45
 
43
46
  ## `Fugit.parse(s)`
@@ -93,7 +96,7 @@ As `Fugit.parse(s)` returns nil when it doesn't grok its input, and `Fugit.do_pa
93
96
 
94
97
  Sometimes you know a cron expression or an "every" natural expression will come in and you want to discard the rest.
95
98
 
96
- ```
99
+ ```ruby
97
100
  require 'fugit'
98
101
 
99
102
  Fugit.parse_cronish('0 0 1 jan *').class # ==> ::Fugit::Cron
@@ -123,8 +126,44 @@ c = Fugit::Cron.new('0 0 * * sun')
123
126
 
124
127
  p Time.now # => 2017-01-03 09:53:27 +0900
125
128
 
126
- p c.next_time # => 2017-01-08 00:00:00 +0900
127
- p c.previous_time # => 2017-01-01 00:00:00 +0900
129
+ p c.next_time.to_s # => 2017-01-08 00:00:00 +0900
130
+ p c.previous_time.to_s # => 2017-01-01 00:00:00 +0900
131
+
132
+ p c.next_time(Time.parse('2024-06-01')).to_s
133
+ # => "2024-06-02 00:00:00 +0900"
134
+ p c.previous_time(Time.parse('2024-06-01')).to_s
135
+ # => "2024-05-26 00:00:00 +0900"
136
+ #
137
+ # `Fugit::Cron#next_time` and `#previous_time` accept a "start time"
138
+
139
+ c = Fugit.parse_cron('0 12 * * mon#2')
140
+
141
+ # `#next` and `#prev` return Enumerable instances
142
+ #
143
+ c.next(Time.parse('2024-02-16 12:00:00'))
144
+ .take(3)
145
+ .map(&:to_s)
146
+ # => [ '2024-03-11 12:00:00',
147
+ # '2024-04-08 12:00:00',
148
+ # '2024-05-13 12:00:00' ]
149
+ c.prev(Time.parse('2024-02-16 12:00:00'))
150
+ .take(3)
151
+ .map(&:to_s)
152
+ # => [ '2024-02-12 12:00:00',
153
+ # '2024-01-08 12:00:00',
154
+ # '2023-12-11 12:00:00' ]
155
+
156
+ # `#within` accepts a time range and returns an array of Eo::EoTime
157
+ # instances that correspond to the occurrences of the cron within
158
+ # the time range
159
+ #
160
+ c.within(Time.parse('2024-02-16 12:00')..Time.parse('2024-08-01 12:00'))
161
+ .map(&:to_s)
162
+ # => [ '2024-03-11 12:00:00',
163
+ # '2024-04-08 12:00:00',
164
+ # '2024-05-13 12:00:00',
165
+ # '2024-06-10 12:00:00',
166
+ # '2024-07-08 12:00:00' ]
128
167
 
129
168
  p c.brute_frequency # => [ 604800, 604800, 53 ]
130
169
  # [ delta min, delta max, occurrence count ]
@@ -269,6 +308,18 @@ c.match?('2019-01-08') # => false, since (rweek + 1) % 2 == 1
269
308
  `tue%x+y` matches if Tuesday and `current_date.rweek + y % x == 0`
270
309
 
271
310
 
311
+ ### the second extension
312
+
313
+ Fugit accepts cron strings with five elements, `minute hour day-of-month month day-of-week`, the standard cron format or six elements `second minute hour day-of-month month day-of-week`.
314
+
315
+ ```ruby
316
+ c = Fugit.parse('* * * * *') # every minute
317
+ c = Fugit.parse('5 * * * *') # every hour at minute 5
318
+ c = Fugit.parse('* * * * * *') # every second
319
+ c = Fugit.parse('5 * * * * *') # every minute at second 5
320
+ ```
321
+
322
+
272
323
  ## `Fugit::Duration`
273
324
 
274
325
  A class `Fugit::Duration` to parse duration strings (vanilla [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) ones and [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) ones).
@@ -402,7 +453,8 @@ Fugit::Nat.parse('every day at 16:15 and 18:30', multi: true)
402
453
  # ==> [ '15 16 * * *', '30 18 * * *' ] (two Fugit::Cron instances)
403
454
 
404
455
  Fugit::Nat.parse('every day at 16:15 and 18:30', multi: :fail)
405
- # ==> ArgumentError: multiple crons in "every day at 16:15 and 18:30" (15 16 * * * | 30 18 * * *)
456
+ # ==> ArgumentError: multiple crons in "every day at 16:15 and 18:30"
457
+ # (15 16 * * * | 30 18 * * *)
406
458
  Fugit::Nat.parse('every day at 16:15 nada 18:30', multi: true)
407
459
  # ==> nil
408
460
  ```
data/lib/fugit/cron.rb CHANGED
@@ -40,8 +40,6 @@ module Fugit
40
40
  #p s; Raabro.pp(Parser.parse(s, debug: 3), colors: true)
41
41
  h = Parser.parse(s.strip)
42
42
 
43
- return nil unless h
44
-
45
43
  self.allocate.send(:init, s, h)
46
44
  end
47
45
 
@@ -320,6 +318,60 @@ module Fugit
320
318
  t.time.translate(from.zone)
321
319
  end
322
320
 
321
+ # Used by Fugit::Cron#next and Fugit::Cron#prev
322
+ #
323
+ class CronIterator
324
+ include ::Enumerable
325
+
326
+ attr_reader :cron, :start, :current, :direction
327
+
328
+ def initialize(cron, direction, start)
329
+
330
+ @cron = cron
331
+ @start = start
332
+ @current = start.dup
333
+ @direction = direction
334
+ end
335
+
336
+ def each
337
+
338
+ loop do
339
+
340
+ yield(@current = @cron.send(@direction, @current))
341
+ end
342
+ end
343
+ end
344
+
345
+ # Returns an ::Enumerable instance that yields each "next time" in
346
+ # succession
347
+ #
348
+ def next(from=::EtOrbi::EoTime.now)
349
+
350
+ CronIterator.new(self, :next_time, from)
351
+ end
352
+
353
+ # Returns an ::Enumerable instance that yields each "previous time" in
354
+ # succession
355
+ #
356
+ def prev(from=::EtOrbi::EoTime.now)
357
+
358
+ CronIterator.new(self, :previous_time, from)
359
+ end
360
+
361
+ # Returns an array of EtOrbi::EoTime instances that correspond to
362
+ # the occurrences of the cron within the given time range
363
+ #
364
+ def within(time_range, time_end=nil)
365
+
366
+ sta, ned =
367
+ time_range.is_a?(::Range) ? [ time_range.begin, time_range.end ] :
368
+ [ ::EtOrbi.make_time(time_range), ::EtOrbi.make_time(time_end) ]
369
+
370
+ CronIterator
371
+ .new(self, :next_time, sta)
372
+ .take_while { |eot| eot.to_t < ned }
373
+ end
374
+
323
375
  # Mostly used as a #next_time sanity check.
324
376
  # Avoid for "business" use, it's slow.
325
377
  #
@@ -488,18 +540,22 @@ module Fugit
488
540
 
489
541
  def init(original, h)
490
542
 
543
+ return nil unless h
544
+
491
545
  @original = original
492
546
  @cron_s = nil # just to be sure
493
547
  @day_and = h[:&]
494
548
 
495
- determine_seconds(h[:sec])
496
- determine_minutes(h[:min])
497
- determine_hours(h[:hou])
498
- determine_monthdays(h[:dom])
499
- determine_months(h[:mon])
500
- determine_weekdays(h[:dow])
501
- determine_timezone(h[:tz])
549
+ valid =
550
+ determine_seconds(h[:sec]) &&
551
+ determine_minutes(h[:min]) &&
552
+ determine_hours(h[:hou]) &&
553
+ determine_monthdays(h[:dom]) &&
554
+ determine_months(h[:mon]) &&
555
+ determine_weekdays(h[:dow]) &&
556
+ determine_timezone(h[:tz])
502
557
 
558
+ return nil unless valid
503
559
  return nil unless compact_month_days
504
560
 
505
561
  self
@@ -509,10 +565,12 @@ module Fugit
509
565
 
510
566
  sta, edn, sla = r
511
567
 
568
+ return false if sla && sla > max
569
+
512
570
  edn = max if sla && edn.nil?
513
571
 
514
- return [ nil ] if sta.nil? && edn.nil? && sla.nil?
515
- return [ sta ] if sta && edn.nil?
572
+ return nil if sta.nil? && edn.nil? && sla.nil?
573
+ return sta if sta && edn.nil?
516
574
 
517
575
  sla = 1 if sla == nil
518
576
  sta = min if sta == nil
@@ -563,42 +621,41 @@ module Fugit
563
621
  .uniq
564
622
  end
565
623
 
566
- def compact(key)
624
+ def do_determine(key, arr, min, max)
567
625
 
568
- arr = instance_variable_get(key)
626
+ null = false
569
627
 
570
- return instance_variable_set(key, nil) if arr.include?(nil)
571
- # reductio ad astrum
628
+ r = arr
629
+ .collect { |v|
630
+ expand(min, max, v) }
631
+ .flatten(1)
632
+ .collect { |e|
633
+ return false if e == false
634
+ null = null || e == nil
635
+ (key == :hours && e == 24) ? 0 : e }
572
636
 
573
- arr.uniq!
574
- arr.sort!
637
+ return nil if null
638
+ r.uniq.sort
575
639
  end
576
640
 
577
641
  def determine_seconds(arr)
578
- @seconds = (arr || [ 0 ]).inject([]) { |a, s| a.concat(expand(0, 59, s)) }
579
- compact(:@seconds)
642
+ (@seconds = do_determine(:seconds, arr || [ 0 ], 0, 59)) != false
580
643
  end
581
644
 
582
645
  def determine_minutes(arr)
583
- @minutes = arr.inject([]) { |a, m| a.concat(expand(0, 59, m)) }
584
- compact(:@minutes)
646
+ (@minutes = do_determine(:minutes, arr, 0, 59)) != false
585
647
  end
586
648
 
587
649
  def determine_hours(arr)
588
- @hours = arr
589
- .inject([]) { |a, h| a.concat(expand(0, 23, h)) }
590
- .collect { |h| h == 24 ? 0 : h }
591
- compact(:@hours)
650
+ (@hours = do_determine(:hours, arr, 0, 23)) != false
592
651
  end
593
652
 
594
653
  def determine_monthdays(arr)
595
- @monthdays = arr.inject([]) { |a, d| a.concat(expand(1, 31, d)) }
596
- compact(:@monthdays)
654
+ (@monthdays = do_determine(:monthdays, arr, 1, 31)) != false
597
655
  end
598
656
 
599
657
  def determine_months(arr)
600
- @months = arr.inject([]) { |a, m| a.concat(expand(1, 12, m)) }
601
- compact(:@months)
658
+ (@months = do_determine(:months, arr, 1, 12)) != false
602
659
  end
603
660
 
604
661
  def determine_weekdays(arr)
@@ -624,11 +681,15 @@ module Fugit
624
681
  @weekdays.uniq!
625
682
  @weekdays.sort!
626
683
  @weekdays = nil if @weekdays.empty?
684
+
685
+ true
627
686
  end
628
687
 
629
688
  def determine_timezone(z)
630
689
 
631
690
  @zone, @timezone = z
691
+
692
+ true
632
693
  end
633
694
 
634
695
  def weekdays_to_cron_s
@@ -68,8 +68,10 @@ module Fugit
68
68
  hou: { a: 'h', r: 'h', i: 'H', s: 3600, I: true, l: 'hour' },
69
69
  min: { a: 'm', r: 'm', i: 'M', s: 60, I: true, l: 'minute' },
70
70
  sec: { a: 's', r: 's', i: 'S', s: 1, I: true, l: 'second' } }.freeze
71
- INFLA_KEYS, NON_INFLA_KEYS =
72
- KEYS.partition { |k, v| v[:I] }.freeze
71
+
72
+ INFLA_KEYS, NON_INFLA_KEYS = KEYS
73
+ .partition { |k, v| v[:I] }
74
+ .collect(&:freeze)
73
75
 
74
76
  def _to_s(key)
75
77
 
@@ -240,7 +242,7 @@ module Fugit
240
242
  n, m = at[1] / 12, at[1] % 12
241
243
  at[0], at[1] = at[0] + n, m
242
244
  elsif at[1] < 1
243
- n, m = -at[1] / 12, -at[1] % 12
245
+ n, m = (-at[1]) / 12 + 1, (11+at[1]) % 12 + 1
244
246
  at[0], at[1] = at[0] - n, m
245
247
  end
246
248
 
@@ -269,7 +271,7 @@ module Fugit
269
271
  when Numeric then add_numeric(-a)
270
272
  when Fugit::Duration then add_duration(-a)
271
273
  when String then add_duration(-self.class.parse(a))
272
- when ::Time, ::EtOrbi::EoTime then add_to_time(a)
274
+ when ::Time, ::EtOrbi::EoTime then opposite.add_to_time(a)
273
275
  else fail ArgumentError.new(
274
276
  "cannot subtract #{a.class} instance to a Fugit::Duration")
275
277
  end
data/lib/fugit/nat.rb CHANGED
@@ -642,9 +642,9 @@ module Fugit
642
642
  fail(ArgumentError.new(
643
643
  "multiple crons in #{opts[:_s].inspect} - #{@slots.inspect}"))
644
644
  elsif multi == true
645
- hms.collect { |hm| parse_cron(hm) }
645
+ hms.collect { |hm| parse_cron(hm, opts) }
646
646
  else
647
- parse_cron(hms.first)
647
+ parse_cron(hms.first, opts)
648
648
  end
649
649
  end
650
650
 
@@ -678,7 +678,7 @@ module Fugit
678
678
  .to_a
679
679
  end
680
680
 
681
- def parse_cron(hm)
681
+ def parse_cron(hm, opts)
682
682
 
683
683
  a = [
684
684
  slot(:second, '0'),
@@ -691,11 +691,39 @@ module Fugit
691
691
  a << tz.data0 if tz
692
692
  a.shift if a.first == [ '0' ]
693
693
 
694
+ letters_last = lambda { |x| x.is_a?(Numeric) ? x : 999_999 }
695
+
694
696
  s = a
695
- .collect { |e| e.uniq.sort.collect(&:to_s).join(',') }
697
+ .collect { |e|
698
+ e.uniq.sort_by(&letters_last).collect(&:to_s).join(',') }
696
699
  .join(' ')
697
700
 
698
- Fugit::Cron.parse(s)
701
+ c = Fugit::Cron.parse(s)
702
+
703
+ if opts[:strict]
704
+ restrict(a, c)
705
+ else
706
+ c
707
+ end
708
+ end
709
+
710
+ # Return nil if the cron is "not strict"
711
+ #
712
+ # For example, "0 0/17 * * *" (gh-86) is a perfectly valid
713
+ # cron string, but makes not much sense when derived via `.parse_nat`
714
+ # from "every 17 hours".
715
+ #
716
+ # It happens here because it's nat being strict, not cron.
717
+ #
718
+ def restrict(a, cron)
719
+
720
+ if m = ((a[1] && a[1][0]) || '').match(/^(\d+|\*)\/(\d+)$/)
721
+ #p m
722
+ sla = m[1].to_i
723
+ return nil unless [ 1, 2, 3, 4, 5, 6, 8, 12 ].include?(sla)
724
+ end
725
+
726
+ cron
699
727
  end
700
728
 
701
729
  def slot(key, default)
data/lib/fugit.rb CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  module Fugit
5
5
 
6
- VERSION = '1.8.0'
6
+ VERSION = '1.10.1'
7
7
  end
8
8
 
9
9
  require 'time'
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.8.0
4
+ version: 1.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Mettraux
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-06 00:00:00.000000000 Z
11
+ date: 2024-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: raabro
@@ -116,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  requirements: []
119
- rubygems_version: 3.1.6
119
+ rubygems_version: 3.4.10
120
120
  signing_key:
121
121
  specification_version: 4
122
122
  summary: time tools for flor