fugit 1.12.1 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15a3ea665c020e35ce51dcbc82be9a74e6f4abcaa2441316b514c630ffdbc094
4
- data.tar.gz: f99246722de255325b57ea46f7b3f24bbfc11d65f1eb98fde9a2a43a3c249df0
3
+ metadata.gz: 11380138d493b9ed80a43bffc6a61f74de5a2c52132ae2b9746c8097b3f8015a
4
+ data.tar.gz: 40ec86bcc48074cc6d3a5bc02b7c2ae84ba3f9fc505104373fc83c7c984ec04b
5
5
  SHA512:
6
- metadata.gz: cc55e7332f5f111ddf27d145fed7fbfeaeed30f492ba343d1f4067e776c96874b42bad9488b9683203040cc50333f74bab56fa6e61744ef92fb059f2a2c9f076
7
- data.tar.gz: 5ee65240dd04fddb7777bee327f8fee0cfef5260ed55db9ec71ece64c5e5e6b53283aa7d26ba52facbca3882aee0920ff0dd3715fea5ad9153caca82ea712450
6
+ metadata.gz: dbd303863f53f7fbf39544fe914e48226952ff4e5983769e7bf932d8a92fdae67514460363f0a1921c77d9dfd7431539935ea18383e3187e103770f46eeeba78
7
+ data.tar.gz: b558ec5b070e2119d5ed0b21f840afd375f2b1eed95ef4542bd78d17dc026402d17c890163f8f9b3e08f8899d01ff08f2d4d3b1baece4d0c5af0e56d72e6d3d9
data/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ## fugit 1.14.0 released 2026-09-14
6
+
7
+ * Reject input when length > 256
8
+
9
+
10
+ ## fugit 1.13.0 released 2026-07-10
11
+
12
+ * Random values support in cron string `"10 ~ * * *"` gh-121 Nicols Bachschmidt
13
+
14
+
15
+ ## fugit 1.12.3 released 2026-07-01
16
+
17
+ * Wrap-around for weekday ranges with step value, gh-119 Nicolas Bachschmidt
18
+
19
+
20
+ ## fugit 1.12.2 released 2026-05-28
21
+
22
+ * Fix "divide by zero" gh-117
23
+ * Let at parse 'now', gh-111
24
+ * Let Fugit::At pass options to Chronic, gh-116
25
+
26
+
5
27
  ## fugit 1.12.1 released 2025-10-14
6
28
 
7
29
  * Fix Fugit::Cron#to_cron_s to include & if @day_and
data/CREDITS.md CHANGED
@@ -1,12 +1,15 @@
1
1
 
2
2
  # fugit credits
3
3
 
4
+ * Yaroslav Kurbatov https://github.com/viralpraxis input max length
5
+ * Nicolas Bachschmidt https://github.com/baarde gh-120 weekday wrap fix
6
+ * JebeenLee https://github.com/JebeenLee gh-117 divide by zero
4
7
  * Eric Claerhout https://github.com/swebra gh-114 rweek readme clarity
5
8
  * Hugh Kelsey https://github.com/hughkelsey gh-114 rweek/rday / wed%4+1,wed%4
6
9
  * Mark R. James, https://github.com/mrj, AM vs America/Los_Angeles, gh-113
7
10
  * Tejas Bubane, https://github.com/tejasbubane, r3.4 in test matrix, gh-109
8
11
  * Luis Castillo, https://github.com/lekastillo, nice_hash gh-108
9
- * Geremia Taglialatela, https://github.com/tagliala, gh-105 gh-107
12
+ * Geremia Taglialatela, https://github.com/tagliala, gh-105 gh-107 gh-122
10
13
  * https://github.com/personnumber3377, gh-104 Fugit.parse choke on long input
11
14
  * Michael Scrivo, https://github.com/mscrivo, gh-103
12
15
  * Benjamin Darcet, https://github.com/bdarcet gh-95 gh-96 et-orbi #rweek
@@ -32,7 +35,7 @@
32
35
  * Niklas https://github.com/gr8bit gh-49, Fugit::Cron.parse('')
33
36
  * Matsuda Akira https://github.com/amatsuda gh-46, warning suppression
34
37
  * Honglooker https://github.com/honglooker gh-43, New York cron skip
35
- * Jérôme Dalbert https://github.com/jeromedalbert gh-41, gh-42
38
+ * Jérôme Dalbert https://github.com/jeromedalbert gh-41, gh-42, gh-123
36
39
  * Danny Ben Shitrit https://github.com/DannyBen nat variants, gh-38
37
40
  * Dominik Sander https://github.com/dsander #rough_frequency 0, gh-36
38
41
  * Milovan Zogovic https://github.com/assembler Cron#match? vs TZ, gh-31
data/LICENSE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- Copyright (c) 2017-2025, John Mettraux, jmettraux+flor@gmail.com
2
+ Copyright (c) 2017-2026, 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
@@ -1,6 +1,12 @@
1
1
 
2
2
  # fugit
3
3
 
4
+ > Sed fugit interea, fugit irreparabile tempus
5
+
6
+ Virgil, _Georgics_ Book III
7
+
8
+ ---
9
+
4
10
  [![tests](https://github.com/floraison/fugit/workflows/test/badge.svg)](https://github.com/floraison/fugit/actions)
5
11
  [![Gem Version](https://badge.fury.io/rb/fugit.svg)](http://badge.fury.io/rb/fugit)
6
12
 
@@ -480,6 +486,29 @@ Here's the output:
480
486
  ```
481
487
 
482
488
 
489
+ ### the random extension
490
+
491
+ Fugit accepts the `~` character to specify a range from which a random value is picked. Either or both the start and the end of the range may be omitted.
492
+
493
+ ```ruby
494
+ '~ * * * *' # every hour on a random minute
495
+ '~29 * * * *' # every hour on a random minute between 0 and 29
496
+ '30~ * * * *' # every hour on a random minute between 30 and 59
497
+ '~/10 * * * *' # every 10 minutes starting on a random minute between 0 and 9
498
+ '0 12 * * 1~5' # every week at noon on a random workday
499
+ ```
500
+
501
+ You may customize how random values are generated by adding the `:random` option with a boolean or an object that implements the `#rand` method.
502
+
503
+ ```ruby
504
+ Fugit.parse('~ * * * *', random: false) # on a non-random minute ==> '0 * * * *'
505
+ Fugit.parse('~ * * * *', random: true) # on a random minute (default)
506
+ Fugit.parse('~ * * * *', random: SecureRandom) # on a cryptographically secure random minute
507
+ ```
508
+
509
+ Please note that the random values are determined at parse time. So `"~29 * * * *"` at parse time could become `"17 * * * *"` for example and go on triggering every hour at minute 17, until a new parsing occurs and another (or the same) minute is picked. This aligns with the OpenBSD [`man 5 crontab`](https://man.openbsd.org/crontab.5), OpenBSD `crond` rolling the dice at load time.
510
+
511
+
483
512
  ### the second extension
484
513
 
485
514
  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`.
data/fugit.gemspec CHANGED
@@ -43,8 +43,11 @@ Time tools for flor and the floraison project. Cron parsing and occurrence compu
43
43
  s.add_runtime_dependency 'raabro', '~> 1.4'
44
44
  s.add_runtime_dependency 'et-orbi', '~> 1.4'
45
45
 
46
- s.add_development_dependency 'rspec', '~> 3.8'
46
+ #s.add_development_dependency 'rspec', '~> 3.8'
47
+ s.add_development_dependency 'probatio', '~> 1.6'
48
+
47
49
  s.add_development_dependency 'chronic', '~> 0.10'
50
+ s.add_development_dependency 'ostruct'
48
51
 
49
52
  s.require_path = 'lib'
50
53
  end
data/lib/fugit/at.rb CHANGED
@@ -6,14 +6,19 @@ module Fugit
6
6
 
7
7
  class << self
8
8
 
9
- def parse(s)
9
+ def parse(s, opts={})
10
10
 
11
- ::EtOrbi.make_time(s) rescue nil
11
+ do_parse(s, opts || {}) rescue nil
12
12
  end
13
13
 
14
- def do_parse(s)
14
+ def do_parse(s, opts={})
15
15
 
16
- ::EtOrbi.make_time(s)
16
+ case s
17
+ when /^now$/i then ::EtOrbi::EoTime.now
18
+ when String then ::EtOrbi.parse(s, opts || {})
19
+ when ::EtOrbi::EoTime then s
20
+ else ::EtOrbi.make_time(s)
21
+ end
17
22
  end
18
23
  end
19
24
  end
data/lib/fugit/cron.rb CHANGED
@@ -29,7 +29,7 @@ module Fugit
29
29
  parse(original)
30
30
  end
31
31
 
32
- def parse(s)
32
+ def parse(s, opts={})
33
33
 
34
34
  return s if s.is_a?(self)
35
35
  return nil unless s.is_a?(String)
@@ -37,6 +37,16 @@ module Fugit
37
37
  s0 = s
38
38
  s = s.strip
39
39
 
40
+ if s.length > ::Fugit::MAX_INPUT_LENGTH
41
+
42
+ fail ArgumentError.new(
43
+ 'input too long for a cron string, ' +
44
+ "#{s.length} > #{MAX_INPUT_LENGTH}"
45
+ ) if opts[:do_parse]
46
+
47
+ return nil
48
+ end
49
+
40
50
  s =
41
51
  if s[0, 1] == '@'
42
52
  ss = s.split(/\s+/, 2)
@@ -48,12 +58,12 @@ module Fugit
48
58
  #p s; Raabro.pp(Parser.parse(s, debug: 3), colors: true)
49
59
  h = Parser.parse(s)
50
60
 
51
- self.allocate.send(:init, s0, h)
61
+ self.allocate.send(:init, s0, h, opts)
52
62
  end
53
63
 
54
- def do_parse(s)
64
+ def do_parse(s, opts={})
55
65
 
56
- parse(s) ||
66
+ parse(s, opts.merge(do_parse: true)) ||
57
67
  fail(ArgumentError.new("invalid cron string #{trunc(s)}"))
58
68
  end
59
69
 
@@ -559,7 +569,7 @@ module Fugit
559
569
 
560
570
  FREQUENCY_CACHE = {}
561
571
 
562
- def init(original, h)
572
+ def init(original, h, opts)
563
573
 
564
574
  return nil unless h
565
575
 
@@ -568,12 +578,12 @@ module Fugit
568
578
  @day_and = h[:&]
569
579
 
570
580
  valid =
571
- determine_seconds(h[:sec]) &&
572
- determine_minutes(h[:min]) &&
573
- determine_hours(h[:hou]) &&
574
- determine_monthdays(h[:dom]) &&
575
- determine_months(h[:mon]) &&
576
- determine_weekdays(h[:dow]) &&
581
+ determine_seconds(h[:sec], opts) &&
582
+ determine_minutes(h[:min], opts) &&
583
+ determine_hours(h[:hou], opts) &&
584
+ determine_monthdays(h[:dom], opts) &&
585
+ determine_months(h[:mon], opts) &&
586
+ determine_weekdays(h[:dow], opts) &&
577
587
  determine_timezone(h[:tz])
578
588
 
579
589
  return nil unless valid
@@ -582,15 +592,22 @@ module Fugit
582
592
  self
583
593
  end
584
594
 
585
- def expand(min, max, r)
595
+ def expand(min, max, opts, r)
586
596
 
587
- sta, edn, sla = r
597
+ sta, edn, sep, sla = r
588
598
 
589
599
  #return false if sla && sla > max
590
600
  #
591
601
  # let it go, "* */24 * * *" and "* */27 * * *" are okay
592
602
  # gh-86 and gh-103
593
603
 
604
+ return false if sla && sla < 1
605
+
606
+ if sep == :tilde
607
+ sta = random(min, max, sta, edn, sla, opts)
608
+ edn = nil unless sla
609
+ end
610
+
594
611
  edn = max if sla && edn.nil?
595
612
 
596
613
  return nil if sta.nil? && edn.nil? && sla.nil?
@@ -603,6 +620,24 @@ module Fugit
603
620
  range(min, max, sta, edn, sla)
604
621
  end
605
622
 
623
+ def random(min, max, sta, edn, sla, opts)
624
+
625
+ sta = min if sta.nil?
626
+ edn = max if edn.nil?
627
+
628
+ random = opts.fetch(:random, true)
629
+ random = Random if random == true
630
+ return sta unless random
631
+
632
+ n = edn - sta + 1
633
+ n = max - min + 1 + n if n <= 0
634
+ n = sla if sla && sla < n
635
+
636
+ r = sta + random.rand(n)
637
+ r = r + min - max - 1 if r > max
638
+ r
639
+ end
640
+
606
641
  def range(min, max, sta, edn, sla)
607
642
 
608
643
  return [ nil ] if sta == min && edn == max && sla == 1
@@ -645,13 +680,13 @@ module Fugit
645
680
  .uniq
646
681
  end
647
682
 
648
- def do_determine(key, arr, min, max)
683
+ def do_determine(key, arr, min, max, opts)
649
684
 
650
685
  null = false
651
686
 
652
687
  r = arr
653
688
  .collect { |v|
654
- expand(min, max, v) }
689
+ expand(min, max, opts, v) }
655
690
  .flatten(1)
656
691
  .collect { |e|
657
692
  return false if e == false
@@ -662,36 +697,44 @@ module Fugit
662
697
  r.uniq.sort
663
698
  end
664
699
 
665
- def determine_seconds(arr)
666
- (@seconds = do_determine(:seconds, arr || [ 0 ], 0, 59)) != false
700
+ def determine_seconds(arr, opts)
701
+ (@seconds = do_determine(:seconds, arr || [ 0 ], 0, 59, opts)) != false
667
702
  end
668
703
 
669
- def determine_minutes(arr)
670
- (@minutes = do_determine(:minutes, arr, 0, 59)) != false
704
+ def determine_minutes(arr, opts)
705
+ (@minutes = do_determine(:minutes, arr, 0, 59, opts)) != false
671
706
  end
672
707
 
673
- def determine_hours(arr)
674
- (@hours = do_determine(:hours, arr, 0, 23)) != false
708
+ def determine_hours(arr, opts)
709
+ (@hours = do_determine(:hours, arr, 0, 23, opts)) != false
675
710
  end
676
711
 
677
- def determine_monthdays(arr)
678
- (@monthdays = do_determine(:monthdays, arr, 1, 31)) != false
712
+ def determine_monthdays(arr, opts)
713
+ (@monthdays = do_determine(:monthdays, arr, 1, 31, opts)) != false
679
714
  end
680
715
 
681
- def determine_months(arr)
682
- (@months = do_determine(:months, arr, 1, 12)) != false
716
+ def determine_months(arr, opts)
717
+ (@months = do_determine(:months, arr, 1, 12, opts)) != false
683
718
  end
684
719
 
685
- def determine_weekdays(arr)
720
+ def determine_weekdays(arr, opts)
686
721
 
687
722
  @weekdays = []
688
723
 
689
- arr.each do |a, z, sl, ha, mo| # a to z, slash, hash, and mod
724
+ arr.each do |a, z, sp, sl, ha, mo| # a to z, seperator, slash, hash, and mod
725
+ if sp == :tilde
726
+ a = random(0, 6, a, z, sl, opts)
727
+ z = nil unless sl
728
+ end
729
+
690
730
  if ha || mo
691
731
  @weekdays << [ a, ha || mo ]
692
732
  elsif sl
693
- ((a || 0)..(z || (a ? a : 6))).step(sl < 1 ? 1 : sl)
694
- .each { |i| @weekdays << [ i ] }
733
+ z ||= (a ? a : 6)
734
+ a ||= 0
735
+ z = z + 7 if a > z
736
+ (a..z).step(sl < 1 ? 1 : sl)
737
+ .each { |i| @weekdays << [ (i > 6) ? i - 7 : i ] }
695
738
  elsif z
696
739
  z = z + 7 if a > z
697
740
  (a..z).each { |i| @weekdays << [ (i > 6) ? i - 7 : i ] }
@@ -754,7 +797,8 @@ module Fugit
754
797
 
755
798
  def s(i); rex(nil, i, /[ \t]+/); end
756
799
  def star(i); str(nil, i, '*'); end
757
- def hyphen(i); str(nil, i, '-'); end
800
+ def hyphen(i); str(:hyphen, i, '-'); end
801
+ def tilde(i); str(:tilde, i, '~'); end
758
802
  def comma(i); rex(nil, i, /,([ \t]*,)*/); end
759
803
  def comma?(i); rex(nil, i, /([ \t]*,)*/); end
760
804
  def and?(i); rex(nil, i, /&?/); end
@@ -782,12 +826,19 @@ module Fugit
782
826
  def r_mon(i); seq(nil, i, :mon, :_mon, '?'); end
783
827
  def r_dow(i); seq(nil, i, :dow, :_dow, '?'); end
784
828
 
829
+ # t: tilde range
830
+ def t_mos(i); seq(nil, i, :mos, '?', :tilde, :mos, '?'); end
831
+ def t_hou(i); seq(nil, i, :hou, '?', :tilde, :hou, '?'); end
832
+ def t_dom(i); seq(nil, i, :dom, '?', :tilde, :dom, '?'); end
833
+ def t_mon(i); seq(nil, i, :mon, '?', :tilde, :mon, '?'); end
834
+ def t_dow(i); seq(nil, i, :dow, '?', :tilde, :dow, '?'); end
835
+
785
836
  # sor: star or range
786
- def sor_mos(i); alt(nil, i, :star, :r_mos); end
787
- def sor_hou(i); alt(nil, i, :star, :r_hou); end
788
- def sor_dom(i); alt(nil, i, :star, :r_dom); end
789
- def sor_mon(i); alt(nil, i, :star, :r_mon); end
790
- def sor_dow(i); alt(nil, i, :star, :r_dow); end
837
+ def sor_mos(i); alt(nil, i, :star, :t_mos, :r_mos); end
838
+ def sor_hou(i); alt(nil, i, :star, :t_hou, :r_hou); end
839
+ def sor_dom(i); alt(nil, i, :star, :t_dom, :r_dom); end
840
+ def sor_mon(i); alt(nil, i, :star, :t_mon, :r_mon); end
841
+ def sor_dow(i); alt(nil, i, :star, :t_dow, :r_dow); end
791
842
 
792
843
  # sorws: star or range with[out] slash
793
844
  def sorws_mos(i); seq(nil, i, :sor_mos, :slash, '?'); end
@@ -867,12 +918,16 @@ module Fugit
867
918
 
868
919
  def rewrite_elt(k, t)
869
920
 
870
- at, zt, slt, hat, mot = nil; t.subgather(nil).each do |tt|
921
+ at, zt, spt, slt, hat, mot = nil
922
+
923
+ t.subgather(nil).each do |tt|
871
924
  case tt.name
872
925
  when :slash then slt = tt
873
926
  when :hash then hat = tt
874
927
  when :mod then mot = tt
875
- else if at; zt ||= tt; else; at = tt; end
928
+ when :hyphen then spt = tt
929
+ when :tilde then spt = tt
930
+ else if spt; zt ||= tt; else; at = tt; end
876
931
  end
877
932
  end
878
933
 
@@ -886,11 +941,12 @@ module Fugit
886
941
 
887
942
  a = at ? rewrite_bound(k, at) : nil
888
943
  z = zt ? rewrite_bound(k, zt) : nil
944
+ sp = spt ? spt.name : nil
889
945
 
890
946
  #a, z = z, a if a && z && a > z
891
947
  # handled downstream since gh-27
892
948
 
893
- [ a, z, sl, ha, mo ]
949
+ [ a, z, sp, sl, ha, mo ]
894
950
  end
895
951
 
896
952
  def rewrite_entry(t)
@@ -26,6 +26,16 @@ module Fugit
26
26
  s = s.strip
27
27
  #p [ original, s ]; Raabro.pp(Parser.parse(s, debug: 3), colours: true)
28
28
 
29
+ if s.length > ::Fugit::MAX_INPUT_LENGTH
30
+
31
+ fail ArgumentError.new(
32
+ 'input too long for a duration string, ' +
33
+ "#{s.length} > #{MAX_INPUT_LENGTH}"
34
+ ) if opts[:do_parse]
35
+
36
+ return nil
37
+ end
38
+
29
39
  h =
30
40
  if opts[:iso]
31
41
  IsoParser.parse(opts[:stricter] ? s : s.upcase)
@@ -40,7 +50,7 @@ module Fugit
40
50
 
41
51
  def do_parse(s, opts={})
42
52
 
43
- parse(s, opts) ||
53
+ parse(s, opts.merge(do_parse: true)) ||
44
54
  fail(ArgumentError.new("not a duration #{s.inspect}"))
45
55
  end
46
56
 
data/lib/fugit/misc.rb CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Fugit
4
4
 
5
- DAY_S = (24 * 3600).freeze
6
- YEAR_S = (365 * DAY_S).freeze
5
+ DAY_S = 24 * 3600
6
+ YEAR_S = 365 * DAY_S
7
7
 
8
8
  class << self
9
9
 
data/lib/fugit/nat.rb CHANGED
@@ -7,8 +7,6 @@ module Fugit
7
7
  #
8
8
  module Nat
9
9
 
10
- MAX_INPUT_LENGTH = 256
11
-
12
10
  class << self
13
11
 
14
12
  def parse(s, opts={})
@@ -19,10 +17,10 @@ module Fugit
19
17
 
20
18
  s = s.strip
21
19
 
22
- if s.length > MAX_INPUT_LENGTH
20
+ if s.length > ::Fugit::MAX_INPUT_LENGTH
23
21
 
24
22
  fail ArgumentError.new(
25
- "input too long for a nat string, " +
23
+ 'input too long for a nat string, ' +
26
24
  "#{s.length} > #{MAX_INPUT_LENGTH}"
27
25
  ) if opts[:do_parse]
28
26
 
@@ -278,10 +276,27 @@ module Fugit
278
276
  rex(:interval, i, INTERVAL_REX)
279
277
  end
280
278
 
279
+ def _starting_at(i)
280
+ rex(nil, i, /[ \t]*(starting[ \t]+at|from)[ \t]+/i)
281
+ end
282
+ def _offset_unit(i)
283
+ rex(nil, i, /(second|minute|hour|day|month)s?[ \t]+/i)
284
+ end
285
+ def offset_count(i)
286
+ rex(:offset_count, i, /\d+/)
287
+ end
288
+
289
+ # starting at minute 1
290
+ # from minute 1
291
+ def offset(i)
292
+ seq(:offset, i, :_starting_at, :_offset_unit, :offset_count)
293
+ end
294
+
281
295
  # every day
282
296
  # every 1 minute
297
+ # every minute starting at minute 10
283
298
  def every_interval(i)
284
- seq(:every_interval, i, :count, '?', :interval)
299
+ seq(:every_interval, i, :count, '?', :interval, :offset, '?')
285
300
  end
286
301
 
287
302
  def every_single_interval(i)
@@ -380,6 +395,7 @@ module Fugit
380
395
  end
381
396
 
382
397
  def rewrite_on_minutes(t)
398
+
383
399
  #Raabro.pp(t, colours: true)
384
400
  mins = t.subgather(:dmin).collect(&:strinp)
385
401
  #slot(:m, mins.join(','))
@@ -387,6 +403,7 @@ module Fugit
387
403
  end
388
404
 
389
405
  def rewrite_on_thex(t)
406
+
390
407
  case s = t.string
391
408
  #when /hour/i then slot(:h, 0)
392
409
  #else slot(:m, '*')
@@ -415,10 +432,12 @@ module Fugit
415
432
  end
416
433
 
417
434
  def rewrite_at_p(t)
435
+
418
436
  pt = t.sublookup(:point).strinpd
419
437
  pt = pt.start_with?('mon') ? 'M' : pt[0, 1]
420
438
  pts = t.subgather(:count).collect { |e| e.string.to_i }
421
439
  #p [ pt, pts ]
440
+
422
441
  case pt
423
442
  #when 'm' then slot(:m, pts)
424
443
  when 'm' then slot(:hm, '*', pts, strong: 1)
@@ -428,6 +447,7 @@ module Fugit
428
447
  end
429
448
 
430
449
  def rewrite_every_single_interval(t)
450
+
431
451
  case t.string
432
452
  when /year/i then [ slot(:month, 1, :weak), slot(:monthday, 1, :weak) ]
433
453
  #when /week/i then xxx...
@@ -438,11 +458,19 @@ module Fugit
438
458
  def rewrite_every_interval(t)
439
459
 
440
460
  #Raabro.pp(t, colours: true)
441
- ci = t.subgather(nil).collect(&:string)
442
- i = ci.pop.strip[0, 3]
443
- c = (ci.pop || '1').strip
461
+ ct = t.sublookup(:count)
462
+ it = t.sublookup(:interval)
463
+ ot = t.sublookup(:offset)
464
+
465
+ c = (ct ? ct.string : '1').strip
466
+ i = it.string.strip[0, 3]
444
467
  i = (i == 'M' || i.downcase == 'mon') ? 'M' : i[0, 1].downcase
445
- cc = c == '1' ? '*' : "*/#{c}"
468
+ off = ot ? ot.sublookup(:offset_count).string.to_i : nil
469
+ cc =
470
+ if off then "#{off}/#{c}"
471
+ elsif c == '1' then '*'
472
+ else "*/#{c}"
473
+ end
446
474
 
447
475
  case i
448
476
  when 'M' then slot(:month, cc)
@@ -465,17 +493,14 @@ module Fugit
465
493
  end
466
494
 
467
495
  def rewrite_tz(t)
468
-
469
496
  slot(:tz, t.string)
470
497
  end
471
498
 
472
499
  def rewrite_weekday(t)
473
-
474
500
  Fugit::Cron::Parser::WEEKDS.index(t.string[0, 3].downcase)
475
501
  end
476
502
 
477
503
  def rewrite_weekdays(t)
478
-
479
504
  #Raabro.pp(t, colours: true)
480
505
  slot(:weekday, _rewrite_subs(t, :weekday))
481
506
  end
@@ -542,12 +567,15 @@ module Fugit
542
567
  end
543
568
 
544
569
  def rewrite_to_hour(t)
570
+
545
571
  #Raabro.pp(t, colours: true)
546
572
  ht0, ht1 = t.subgather(nil)
547
573
  h0, h1 = rewrite(ht0), rewrite(ht1)
574
+
548
575
  fail ArgumentError.new(
549
576
  "cannot deal with #{ht0.strinp} to #{ht1.strinp}, minutes diverge"
550
577
  ) if h0.data1 != h1.data1
578
+
551
579
  slot(:hm, "#{h0._data0}-#{h1._data0}", 0, strong: 0)
552
580
  end
553
581
 
@@ -710,7 +738,7 @@ module Fugit
710
738
  e.uniq.sort_by(&letters_last).collect(&:to_s).join(',') }
711
739
  .join(' ')
712
740
 
713
- c = Fugit::Cron.parse(s)
741
+ c = Fugit::Cron.parse(s, opts)
714
742
 
715
743
  if opts[:strict]
716
744
  restrict(a, c)
data/lib/fugit/parse.rb CHANGED
@@ -6,15 +6,15 @@ module Fugit
6
6
 
7
7
  class << self
8
8
 
9
- def parse_cron(s); ::Fugit::Cron.parse(s); end
9
+ def parse_cron(s, opts={}); ::Fugit::Cron.parse(s, opts || {}); end
10
10
  def parse_duration(s); ::Fugit::Duration.parse(s); end
11
- def parse_nat(s, opts={}); ::Fugit::Nat.parse(s, opts); end
12
- def parse_at(s); ::Fugit::At.parse(s); end
11
+ def parse_nat(s, opts={}); ::Fugit::Nat.parse(s, opts || {}); end
12
+ def parse_at(s, opts={}); ::Fugit::At.parse(s, opts || {}); end
13
13
  def parse_in(s); parse_duration(s); end
14
14
 
15
- def do_parse_cron(s); ::Fugit::Cron.do_parse(s); end
15
+ def do_parse_cron(s, opts={}); ::Fugit::Cron.do_parse(s, opts || {}); end
16
16
  def do_parse_duration(s); ::Fugit::Duration.do_parse(s); end
17
- def do_parse_nat(s, opts={}); ::Fugit::Nat.do_parse(s, opts); end
17
+ def do_parse_nat(s, opts={}); ::Fugit::Nat.do_parse(s, opts || {}); end
18
18
  def do_parse_at(s); ::Fugit::At.do_parse(s); end
19
19
  def do_parse_in(s); do_parse_duration(s); end
20
20
 
@@ -22,10 +22,10 @@ module Fugit
22
22
 
23
23
  opts[:at] = opts[:in] if opts.has_key?(:in)
24
24
 
25
- (opts[:cron] != false && parse_cron(s)) || # 542ms 616ms
25
+ (opts[:cron] != false && parse_cron(s, opts || {})) || # 542ms 616ms
26
26
  (opts[:duration] != false && parse_duration(s)) || # 645ms # 534ms
27
- (opts[:nat] != false && parse_nat(s, opts)) || # 2s # 35s
28
- (opts[:at] != false && parse_at(s)) || # 568ms 622ms
27
+ (opts[:nat] != false && parse_nat(s, opts || {})) || # 2s # 35s
28
+ (opts[:at] != false && parse_at(s, opts || {})) || # 568ms 622ms
29
29
  nil
30
30
  end
31
31
 
@@ -54,22 +54,24 @@ module Fugit
54
54
 
55
55
  def parse_cronish(s, opts={})
56
56
 
57
- r = parse_cron(s) || parse_nat(s, opts)
57
+ r = parse_cron(s, opts) || parse_nat(s, opts)
58
58
 
59
59
  r.is_a?(::Fugit::Cron) ? r : nil
60
60
  end
61
61
 
62
62
  def do_parse_cronish(s, opts={})
63
63
 
64
- parse_cronish(s) ||
64
+ parse_cronish(s, opts) ||
65
65
  fail(ArgumentError.new("not cron or 'natural' cron string: #{s.inspect}"))
66
66
  end
67
67
 
68
+ # Still an exploration, but what was the goal? 2026-09-14
69
+ #
68
70
  def parse_max(s, opts={})
69
71
 
70
72
  s0 = s.lines.first
71
73
 
72
- (0..[ ::Fugit::Nat::MAX_INPUT_LENGTH, s0.length - 1 ].min).each do |i|
74
+ (0..[ ::Fugit::MAX_INPUT_LENGTH, s0.length - 1 ].min).each do |i|
73
75
 
74
76
  s1 =
75
77
  s0[0, s0.length - i].rstrip
data/lib/fugit.rb CHANGED
@@ -1,9 +1,9 @@
1
- # frozen_string_literal: true
2
-
3
1
 
4
2
  module Fugit
5
3
 
6
- VERSION = '1.12.1'
4
+ VERSION = '1.14.0'.freeze
5
+
6
+ MAX_INPUT_LENGTH = 256
7
7
  end
8
8
 
9
9
  require 'time'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fugit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.1
4
+ version: 1.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Mettraux
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-10-14 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: raabro
@@ -39,19 +38,19 @@ dependencies:
39
38
  - !ruby/object:Gem::Version
40
39
  version: '1.4'
41
40
  - !ruby/object:Gem::Dependency
42
- name: rspec
41
+ name: probatio
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
44
  - - "~>"
46
45
  - !ruby/object:Gem::Version
47
- version: '3.8'
46
+ version: '1.6'
48
47
  type: :development
49
48
  prerelease: false
50
49
  version_requirements: !ruby/object:Gem::Requirement
51
50
  requirements:
52
51
  - - "~>"
53
52
  - !ruby/object:Gem::Version
54
- version: '3.8'
53
+ version: '1.6'
55
54
  - !ruby/object:Gem::Dependency
56
55
  name: chronic
57
56
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +65,20 @@ dependencies:
66
65
  - - "~>"
67
66
  - !ruby/object:Gem::Version
68
67
  version: '0.10'
68
+ - !ruby/object:Gem::Dependency
69
+ name: ostruct
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
69
82
  description: Time tools for flor and the floraison project. Cron parsing and occurrence
70
83
  computing. Timestamps and more.
71
84
  email:
@@ -96,7 +109,6 @@ metadata:
96
109
  homepage_uri: https://github.com/floraison/fugit
97
110
  source_code_uri: https://github.com/floraison/fugit
98
111
  rubygems_mfa_required: 'true'
99
- post_install_message:
100
112
  rdoc_options: []
101
113
  require_paths:
102
114
  - lib
@@ -111,8 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
123
  - !ruby/object:Gem::Version
112
124
  version: '0'
113
125
  requirements: []
114
- rubygems_version: 3.4.19
115
- signing_key:
126
+ rubygems_version: 3.6.9
116
127
  specification_version: 4
117
128
  summary: time tools for flor
118
129
  test_files: []