fugit 1.11.1 → 1.12.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: da93feee9bdfd44a793ce2b45b81109c0f7cd08ff9a930d15629a29c6a381e07
4
- data.tar.gz: 32d9b447734805d3b58ae64c70a6699b931ef4fb4e6a76d8f279e66d4b5cb7df
3
+ metadata.gz: 31f13ce15ee0d51e6029539634fd55e14401f60ba03cdc6ad90c8e47bd4b711e
4
+ data.tar.gz: 91db0bb6fd6aa714e44e4c6d2b67fdbffc70495da77bf9946cda8d859bdf510a
5
5
  SHA512:
6
- metadata.gz: e7ac7afec4cfdcfdac22e5d2113892a0c7cb1f790d205bc32ecaea75c30f6ceb73af811824712c6781b480831f2a09479a6344af0d9b3c0ee92ae5cff7ef6c38
7
- data.tar.gz: d87ecc490a49f19680891a82979903987843b5853ecfdc9ebee57d0b047fa4dffa96dfbbde43ef5d3011dbd07d492ac8f7a151fa6354478050c18efae9b911e4
6
+ metadata.gz: 604a819a90770a746f33f2d93cdab4524f036a71ac901d311fb74c438f20eaa01a4fb13187f5eca96b7a449354d7864ef3b515674088b3bc60b36710afd8c9df
7
+ data.tar.gz: c83c544fa51e1a3e8bc2f76ff163276caa61a93927db6bbfe47642d91ffa85281c8522e1b36a2e9c988c85963c34edc86b47fab98dc1ee516a4495df63839ac8
data/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ## fugit 1.12.0 released 2025-09-30
6
+
7
+ * Upgrade et-orbi to ~> 1.4.0 for EtOrbi.rweek_ref=, gh-114
8
+ this changes the rweek reference point to 2018-12-31 (Monday)
9
+ * Fix `12 0 * * wed%4+1,wed%4` issue where 1 wed was ignored, gh-114
10
+
11
+
12
+ ## fugit 1.11.2 released 2025-08-22
13
+
14
+ * Fix "every day at midnight America/Los_Angeles", gh-113, Mark R. James
15
+ * Avoid Hash#partition https://bugs.ruby-lang.org/issues/16252
16
+ * Fix Fugit::Nat "zero dark forty", gh-107
17
+ * Ensure @yearly and other specials accept a timezone
18
+
19
+
5
20
  ## fugit 1.11.1 released 2024-08-15
6
21
 
7
22
  * Prevent nat parsing chocking on long input (> 256 chars), gh-104
data/CREDITS.md CHANGED
@@ -1,6 +1,11 @@
1
1
 
2
2
  # fugit credits
3
3
 
4
+ * Hugh Kelsey https://github.com/hughkelsey gh-94 rweek/rday / wed%4+1,wed%4
5
+ * Mark R. James, https://github.com/mrj, AM vs America/Los_Angeles, gh-113
6
+ * Tejas Bubane, https://github.com/tejasbubane, r3.4 in test matrix, gh-109
7
+ * Luis Castillo, https://github.com/lekastillo, nice_hash gh-108
8
+ * Geremia Taglialatela, https://github.com/tagliala, gh-105 gh-107
4
9
  * https://github.com/personnumber3377, gh-104 Fugit.parse choke on long input
5
10
  * Michael Scrivo, https://github.com/mscrivo, gh-103
6
11
  * Benjamin Darcet, https://github.com/bdarcet gh-95 gh-96 et-orbi #rweek
data/LICENSE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- Copyright (c) 2017-2024, John Mettraux, jmettraux+flor@gmail.com
2
+ Copyright (c) 2017-2025, 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
@@ -17,31 +17,33 @@ Fugit is a core dependency of [rufus-scheduler](https://github.com/jmettraux/ruf
17
17
 
18
18
  The intersection of those two projects is where fugit is born:
19
19
 
20
- * [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) - a cron/at/in/every/interval in-process scheduler, in fact, it's the father project to this fugit project
21
- * [flor](https://github.com/floraison/flor) - a Ruby workflow engine, fugit provides the foundation for its time scheduling capabilities
20
+ * [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) a cron/at/in/every/interval in-process scheduler, in fact, it's the father project to this fugit project
21
+ * [flor](https://github.com/floraison/flor) a Ruby workflow engine, fugit provides the foundation for its time scheduling capabilities
22
22
 
23
23
  ### Similar, sometimes overlapping projects
24
24
 
25
- * [chronic](https://github.com/mojombo/chronic) - a pure Ruby natural language date parser
26
- * [parse-cron](https://github.com/siebertm/parse-cron) - parses cron expressions and calculates the next occurrence after a given date
27
- * [ice_cube](https://github.com/seejohnrun/ice_cube) - Ruby date recurrence library
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
25
+ * [chronic](https://github.com/mojombo/chronic) a pure Ruby natural language date parser
26
+ * [parse-cron](https://github.com/siebertm/parse-cron) parses cron expressions and calculates the next occurrence after a given date
27
+ * [ice_cube](https://github.com/seejohnrun/ice_cube) Ruby date recurrence library
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
32
+ * [CronConfigParser](https://github.com/madogiwa0124/cron_config_parser) — parse the cron configuration for readability
32
33
  * ...
33
34
 
34
35
  ### Projects using fugit
35
36
 
36
- * [arask](https://github.com/Ebbe/arask) - "Automatic RAils taSKs" uses fugit to parse cron strings
37
- * [sidekiq-cron](https://github.com/ondrejbartas/sidekiq-cron) - uses fugit to parse cron strings since version 1.0.0, it was using rufus-scheduler previously
38
- * [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) - as seen above
39
- * [flor](https://github.com/floraison/flor) - used in the [cron](https://github.com/floraison/flor/blob/master/doc/procedures/cron.md) procedure
40
- * [que-scheduler](https://github.com/hlascelles/que-scheduler) - a reliable job scheduler for [que](https://github.com/chanks/que)
41
- * [serial_scheduler](https://github.com/grosser/serial_scheduler) - ruby task scheduler without threading
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
44
- * [Solid Queue](https://github.com/rails/solid_queue) - a DB-based queuing backend for Active Job, designed with simplicity and performance in mind
37
+ * [arask](https://github.com/Ebbe/arask) "Automatic RAils taSKs" uses fugit to parse cron strings
38
+ * [sidekiq-cron](https://github.com/ondrejbartas/sidekiq-cron) uses fugit to parse cron strings since version 1.0.0, it was using rufus-scheduler previously
39
+ * [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) as seen above
40
+ * [flor](https://github.com/floraison/flor) used in the [cron](https://github.com/floraison/flor/blob/master/doc/procedures/cron.md) procedure
41
+ * [que-scheduler](https://github.com/hlascelles/que-scheduler) a reliable job scheduler for [que](https://github.com/chanks/que)
42
+ * [serial_scheduler](https://github.com/grosser/serial_scheduler) ruby task scheduler without threading
43
+ * [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
44
+ * [GoodJob](https://github.com/bensheldon/good_job) a multithreaded, Postgres-based, Active Job backend for Ruby on Rails
45
+ * [Solid Queue](https://github.com/rails/solid_queue) a DB-based queuing backend for Active Job, designed with simplicity and performance in mind
46
+ * [qron](https://github.com/floraison/qron) — stupid cron thread that wakes up from time to time to do what's in its crontab
45
47
  * ...
46
48
 
47
49
  ## `Fugit.parse(s)`
@@ -112,6 +114,7 @@ Fugit.parse_cronish('12y12M') # ==> nil
112
114
 
113
115
  Introduced in fugit 1.8.0.
114
116
 
117
+
115
118
  ## `Fugit::Cron`
116
119
 
117
120
  A class `Fugit::Cron` to parse cron strings and then `#next_time` and `#previous_time` to compute the next or the previous occurrence respectively.
@@ -204,13 +207,28 @@ Example of cron strings understood by fugit:
204
207
 
205
208
  Please note that `'15/30 * * * *'` is interpreted as `'15-59/30 * * * *'` since fugit 1.4.6.
206
209
 
210
+ ### time zones
211
+
212
+ Fugit accepts a IANA timezone identifier right after a cron string:
213
+ ```ruby
214
+ '5 0 * * * Europe/Rome' # 5 minutes after midnight, every day, Rome tz
215
+ '0 22 * * 1-5 Asia/Tbilisi' # at 2200 on weekdays in Georgia
216
+
217
+ '@yearly Asia/Kuala_Lumpur' # turns into '0 0 1 1 * Asia/Kuala_Lumpur'
218
+ '@monthly Asia/Jakarta' # turns into '0 0 1 * * Asia/Jakarta'
219
+ #
220
+ # those two "ats" and friends since fugit 1.11.2...
221
+ ```
222
+
223
+ When no time zone is specified, fugit uses Ruby's provided timezone.
224
+
207
225
  ### the first Monday of the month
208
226
 
209
227
  Fugit tries to follow the `man 5 crontab` documentation.
210
228
 
211
229
  There is a surprising thing about this canon, all the columns are joined by ANDs, except for monthday and weekday which are joined together by OR if they are both set (they are not `*`).
212
230
 
213
- Many people (me included) [are suprised](https://superuser.com/questions/428807/run-a-cron-job-on-the-first-monday-of-every-month) when they try to specify "at 05:00 on the first Monday of the month" as `0 5 1-7 * 1` or `0 5 1-7 * mon` and the results are off.
231
+ Many people (me included) [are surprised](https://superuser.com/questions/428807/run-a-cron-job-on-the-first-monday-of-every-month) when they try to specify "at 05:00 on the first Monday of the month" as `0 5 1-7 * 1` or `0 5 1-7 * mon` and the results are off.
214
232
 
215
233
  The man page says:
216
234
 
@@ -278,7 +296,7 @@ The hash extension can only be used in the day-of-week field.
278
296
 
279
297
  ### the modulo extension
280
298
 
281
- Fugit, since 1.1.10, also understands cron strings like "`9 0 * * sun%2`" which can be read as "every other Sunday at 9am".
299
+ Fugit, since 1.1.10, also understands cron strings like `9 0 * * sun%2` which can be read as "every other Sunday at 9am" or `12 0 * * mon%4` for "every fourth monday at noon".
282
300
 
283
301
  The modulo extension can only be used in the day-of-week field.
284
302
 
@@ -286,131 +304,131 @@ For odd Sundays, one can write `9 0 * * sun%2+1`.
286
304
 
287
305
  It can be combined, as in `9 0 * * sun%2,tue%3+2`
288
306
 
289
- But what does it reference to? It starts at 1 on 2019-01-01.
307
+ But what does it reference to? It starts at 1 on 2019-01-01 (in the EtOrbi instance timezone, not the UTC "timezone").
308
+
309
+ Since [et-orbi](https://github.com/floraison/et-orbi) 1.4.0, the reference has been switched to make `Monday 2018-12-31` as the reference, so that weeks start on Monday (read a bit below on how to make it start on another day).
290
310
 
291
311
  ```ruby
292
- require 'et-orbi' # >= 1.1.8
312
+ require 'et-orbi' # >= 1.1.8 and < 1.4.0
313
+
314
+ class EtOrbi::EoTime
315
+ def d # debug
316
+ "%14s | rday: %4d | rweek: %3d" % [ strftime('%a'), rday, rweek ]
317
+ end
318
+ end
293
319
 
294
320
  # the reference
295
- p EtOrbi.parse('2019-01-01').wday # => 2
296
- p EtOrbi.parse('2019-01-01').rweek # => 1
321
+ puts EtOrbi.parse('2019-01-01').d # => Tue | rday: 1 | rweek: 1
297
322
  p EtOrbi.parse('2019-01-01').rweek % 2 # => 1
298
323
 
299
324
  # today (as of this coding...)
300
- p EtOrbi.parse('2019-04-11').wday # => 4
301
- p EtOrbi.parse('2019-04-11').rweek # => 15
325
+ puts EtOrbi.parse('2019-04-11').d # => Thu | rday: 101 | rweek: 15
302
326
  p EtOrbi.parse('2019-04-11').rweek % 2 # => 1
303
327
 
304
328
  c = Fugit.parse('* * * * tue%2')
305
- c.match?('2019-01-01') # => false, since rweek % 2 == 1
306
- c.match?('2019-01-08') # => true, since rweek % 2 == 0
329
+ p c.match?('2019-01-01') # => false, since rweek % 2 == 1
330
+ p c.match?('2019-01-08') # => true, since rweek % 2 == 0
307
331
 
308
332
  c = Fugit.parse('* * * * tue%2+1')
309
- c.match?('2019-01-01') # => true, since (rweek + 1) % 2 == 0
310
- c.match?('2019-01-08') # => false, since (rweek + 1) % 2 == 1
333
+ p c.match?('2019-01-01') # => true, since (rweek + 1) % 2 == 0
334
+ p c.match?('2019-01-08') # => false, since (rweek + 1) % 2 == 1
311
335
 
312
336
  # ...
337
+
338
+ class EtOrbi::EoTime
339
+ def d # debug
340
+ "%14s | rday: %4d | rweek: %3d" % [ strftime('%F %a'), rday, rweek ]
341
+ end
342
+ end
343
+
344
+ c = Fugit.parse_cron('20 0 * * mon%2,wed%3+1')
345
+ puts c.next('2025-09-21').take(10).map(&:d)
346
+ #
347
+ # => 2025-09-24 Wed | rday: 2459 | rweek: 352
348
+ # 2025-09-29 Mon | rday: 2464 | rweek: 352
349
+ # 2025-10-13 Mon | rday: 2478 | rweek: 354
350
+ # 2025-10-15 Wed | rday: 2480 | rweek: 355
351
+ # 2025-10-27 Mon | rday: 2492 | rweek: 356
352
+ # 2025-11-05 Wed | rday: 2501 | rweek: 358
353
+ # 2025-11-10 Mon | rday: 2506 | rweek: 358
354
+ # 2025-11-24 Mon | rday: 2520 | rweek: 360
355
+ # 2025-11-26 Wed | rday: 2522 | rweek: 361
356
+ # 2025-12-08 Mon | rday: 2534 | rweek: 362
313
357
  ```
314
358
 
315
359
  `sun%2` matches if Sunday and `current_date.rweek % 2 == 0`
316
360
  `tue%3+2` matches if Tuesday and `current_date.rweek + 2 % 3 == 0`
317
361
  `tue%x+y` matches if Tuesday and `current_date.rweek + y % x == 0`
318
362
 
319
-
320
- ### the second extension
321
-
322
- 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`.
323
-
324
- ```ruby
325
- c = Fugit.parse('* * * * *') # every minute
326
- c = Fugit.parse('5 * * * *') # every hour at minute 5
327
- c = Fugit.parse('* * * * * *') # every second
328
- c = Fugit.parse('5 * * * * *') # every minute at second 5
329
- ```
330
-
331
-
332
- ## `Fugit::Duration`
333
-
334
- 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).
335
-
336
- Provides duration arithmetic tools.
337
-
338
363
  ```ruby
339
- require 'fugit'
364
+ require 'et-orbi' # >= 1.4.0
340
365
 
341
- d = Fugit::Duration.parse('1y2M1d4h')
366
+ class EtOrbi::EoTime
367
+ def d # debug
368
+ "%14s | rday: %4d | rweek: %3d" % [ strftime('%a'), rday, rweek ]
369
+ end
370
+ end
342
371
 
343
- p d.to_plain_s # => "1Y2M1D4h"
344
- p d.to_iso_s # => "P1Y2M1DT4H" ISO 8601 duration
345
- p d.to_long_s # => "1 year, 2 months, 1 day, and 4 hours"
372
+ puts EtOrbi.parse('2018-12-30').d # => Sun | rday: -1 | rweek: -1
373
+ puts EtOrbi.parse('2018-12-31').d # => Mon | rday: 0 | rweek: 0 >REF<
374
+ puts EtOrbi.parse('2019-01-01').d # => Tue | rday: 1 | rweek: 0
375
+ puts EtOrbi.parse('2019-01-02').d # => Wed | rday: 2 | rweek: 0
376
+ puts EtOrbi.parse('2019-01-31').d # => Thu | rday: 31 | rweek: 4
346
377
 
347
- d += Fugit::Duration.parse('1y1h')
378
+ puts EtOrbi.parse('2019-04-11').d # => Thu | rday: 101 | rweek: 14
379
+ puts EtOrbi.parse('2025-09-30').d # => Tue | rday: 2465 | rweek: 352
348
380
 
349
- p d.to_long_s # => "2 years, 2 months, 1 day, and 5 hours"
381
+ class EtOrbi::EoTime
382
+ def d # debug
383
+ "%14s | rday: %4d | rweek: %3d" % [ strftime('%F %a'), rday, rweek ]
384
+ end
385
+ end
350
386
 
351
- d += 3600
387
+ p EtOrbi.rweek_ref # => "2018-12-31"
352
388
 
353
- p d.to_plain_s # => "2Y2M1D5h3600s"
354
-
355
- p Fugit::Duration.parse('1y2M1d4h').to_sec # => 36820800
389
+ c = Fugit.parse_cron('20 0 * * mon%2,wed%3+1')
390
+ puts c.next('2025-09-21').take(10).map(&:d)
391
+ #
392
+ # => 2025-09-29 Mon | rday: 2464 | rweek: 352
393
+ # 2025-10-01 Wed | rday: 2466 | rweek: 352
394
+ # 2025-10-13 Mon | rday: 2478 | rweek: 354
395
+ # 2025-10-22 Wed | rday: 2487 | rweek: 355
396
+ # 2025-10-27 Mon | rday: 2492 | rweek: 356
397
+ # 2025-11-10 Mon | rday: 2506 | rweek: 358
398
+ # 2025-11-12 Wed | rday: 2508 | rweek: 358
399
+ # 2025-11-24 Mon | rday: 2520 | rweek: 360
400
+ # 2025-12-03 Wed | rday: 2529 | rweek: 361
401
+ # 2025-12-08 Mon | rday: 2534 | rweek: 362
356
402
  ```
357
403
 
358
- There is a `#deflate` method
359
-
404
+ One can tell et-orbi >= 1.4.0 which day to take as reference:
360
405
  ```ruby
361
- Fugit::Duration.parse(1000).to_plain_s # => "1000s"
362
- Fugit::Duration.parse(3600).to_plain_s # => "3600s"
363
- Fugit::Duration.parse(1000).deflate.to_plain_s # => "16m40s"
364
- Fugit::Duration.parse(3600).deflate.to_plain_s # => "1h"
406
+ EtOrbi.rweek_ref = :us # or
407
+ EtOrbi.rweek_ref = :sunday # to start on a Sunday (2018-12-30)
365
408
 
366
- # or event shorter
367
- Fugit.parse(1000).deflate.to_plain_s # => "16m40s"
368
- Fugit.parse(3600).deflate.to_plain_s # => "1h"
369
- ```
409
+ EtOrbi.rweek_ref = :iso # or
410
+ EtOrbi.rweek_ref = :monday # or
411
+ EtOrbi.rweek_ref = :default # to start on a Monday (2019-12-31)
370
412
 
371
- There is also an `#inflate` method
413
+ EtOrbi.rweek_ref = :saturday # to start on, well, a Saturday (2019-01-05)
372
414
 
373
- ```ruby
374
- Fugit::Duration.parse('1h30m12').inflate.to_plain_s # => "5412s"
375
- Fugit.parse('1h30m12').inflate.to_plain_s # => "5412s"
415
+ EtOrbi.rweek_ref = '2025-09-30' # to start on this very Tuesday...
376
416
 
377
- Fugit.parse('1h30m12').to_sec # => 5412
378
- Fugit.parse('1h30m12').to_sec.to_s + 's' # => "5412s"
417
+ p EtOrbi.rweek_ref # to determine what the current setting is...
379
418
  ```
380
419
 
381
- The `to_*_s` methods are also available as class methods:
382
- ```ruby
383
- p Fugit::Duration.to_plain_s('1y2M1d4h')
384
- # => "1Y2M1D4h"
385
- p Fugit::Duration.to_iso_s('1y2M1d4h')
386
- # => "P1Y2M1DT4H" ISO 8601 duration
387
- p Fugit::Duration.to_long_s('1y2M1d4h')
388
- # => "1 year, 2 months, 1 day, and 4 hours"
389
- ```
390
420
 
391
- ## `Fugit::At`
421
+ ### the second extension
392
422
 
393
- Points in time are parsed and given back as EtOrbi::EoTime instances.
423
+ 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`.
394
424
 
395
425
  ```ruby
396
- Fugit::At.parse('2017-12-12').to_s
397
- # ==> "2017-12-12 00:00:00 +0900" (at least here in Hiroshima)
398
-
399
- Fugit::At.parse('2017-12-12 12:00:00 America/New_York').to_s
400
- # ==> "2017-12-12 12:00:00 -0500"
426
+ c = Fugit.parse('* * * * *') # every minute
427
+ c = Fugit.parse('5 * * * *') # every hour at minute 5
428
+ c = Fugit.parse('* * * * * *') # every second
429
+ c = Fugit.parse('5 * * * * *') # every minute at second 5
401
430
  ```
402
431
 
403
- Directly with `Fugit.parse_at(s)` is OK too:
404
- ```ruby
405
- Fugit.parse_at('2017-12-12 12:00:00 America/New_York').to_s
406
- # ==> "2017-12-12 12:00:00 -0500"
407
- ```
408
-
409
- Directly with `Fugit.parse(s)` is OK too:
410
- ```ruby
411
- Fugit.parse('2017-12-12 12:00:00 America/New_York').to_s
412
- # ==> "2017-12-12 12:00:00 -0500"
413
- ```
414
432
 
415
433
  ## `Fugit::Nat`
416
434
 
@@ -511,6 +529,96 @@ p Fugit.parse('every day at 12:15 midnight').original # ==> "15 24 * * *"
511
529
  ```
512
530
 
513
531
 
532
+ ## `Fugit::Duration`
533
+
534
+ 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).
535
+
536
+ Provides duration arithmetic tools.
537
+
538
+ ```ruby
539
+ require 'fugit'
540
+
541
+ d = Fugit::Duration.parse('1y2M1d4h')
542
+
543
+ p d.to_plain_s # => "1Y2M1D4h"
544
+ p d.to_iso_s # => "P1Y2M1DT4H" ISO 8601 duration
545
+ p d.to_long_s # => "1 year, 2 months, 1 day, and 4 hours"
546
+
547
+ d += Fugit::Duration.parse('1y1h')
548
+
549
+ p d.to_long_s # => "2 years, 2 months, 1 day, and 5 hours"
550
+
551
+ d += 3600
552
+
553
+ p d.to_plain_s # => "2Y2M1D5h3600s"
554
+
555
+ p Fugit::Duration.parse('1y2M1d4h').to_sec # => 36820800
556
+ ```
557
+
558
+ There is a `#deflate` method
559
+
560
+ ```ruby
561
+ Fugit::Duration.parse(1000).to_plain_s # => "1000s"
562
+ Fugit::Duration.parse(3600).to_plain_s # => "3600s"
563
+ Fugit::Duration.parse(1000).deflate.to_plain_s # => "16m40s"
564
+ Fugit::Duration.parse(3600).deflate.to_plain_s # => "1h"
565
+
566
+ # or event shorter
567
+ Fugit.parse(1000).deflate.to_plain_s # => "16m40s"
568
+ Fugit.parse(3600).deflate.to_plain_s # => "1h"
569
+ ```
570
+
571
+ There is also an `#inflate` method
572
+
573
+ ```ruby
574
+ Fugit::Duration.parse('1h30m12').inflate.to_plain_s # => "5412s"
575
+ Fugit.parse('1h30m12').inflate.to_plain_s # => "5412s"
576
+
577
+ Fugit.parse('1h30m12').to_sec # => 5412
578
+ Fugit.parse('1h30m12').to_sec.to_s + 's' # => "5412s"
579
+ ```
580
+
581
+ The `to_*_s` methods are also available as class methods:
582
+ ```ruby
583
+ p Fugit::Duration.to_plain_s('1y2M1d4h')
584
+ # => "1Y2M1D4h"
585
+ p Fugit::Duration.to_iso_s('1y2M1d4h')
586
+ # => "P1Y2M1DT4H" ISO 8601 duration
587
+ p Fugit::Duration.to_long_s('1y2M1d4h')
588
+ # => "1 year, 2 months, 1 day, and 4 hours"
589
+ ```
590
+
591
+
592
+ ## `Fugit::At`
593
+
594
+ Points in time are parsed and given back as EtOrbi::EoTime instances.
595
+
596
+ ```ruby
597
+ Fugit::At.parse('2017-12-12').to_s
598
+ # ==> "2017-12-12 00:00:00 +0900" (at least here in Hiroshima)
599
+
600
+ Fugit::At.parse('2017-12-12 12:00:00 America/New_York').to_s
601
+ # ==> "2017-12-12 12:00:00 -0500"
602
+ ```
603
+
604
+ Directly with `Fugit.parse_at(s)` is OK too:
605
+ ```ruby
606
+ Fugit.parse_at('2017-12-12 12:00:00 America/New_York').to_s
607
+ # ==> "2017-12-12 12:00:00 -0500"
608
+ ```
609
+
610
+ Directly with `Fugit.parse(s)` is OK too:
611
+ ```ruby
612
+ Fugit.parse('2017-12-12 12:00:00 America/New_York').to_s
613
+ # ==> "2017-12-12 12:00:00 -0500"
614
+ ```
615
+
616
+
617
+ ## KNOWN ISSUES
618
+
619
+ The gem [nice_hash](https://github.com/MarioRuiz/nice_hash) gets in the way of `fugit`, as seen in [issue 108](https://github.com/floraison/fugit/issues/108). It prevents `fugit` from correctly parsing cron strings.
620
+
621
+
514
622
  ## LICENSE
515
623
 
516
624
  MIT, see [LICENSE.txt](LICENSE.txt)
data/fugit.gemspec CHANGED
@@ -20,18 +20,18 @@ Time tools for flor and the floraison project. Cron parsing and occurrence compu
20
20
 
21
21
  s.metadata = {
22
22
  'changelog_uri' => s.homepage + '/blob/master/CHANGELOG.md',
23
- 'documentation_uri' => s.homepage,
24
23
  'bug_tracker_uri' => s.homepage + '/issues',
25
- #'mailing_list_uri' => 'https://groups.google.com/forum/#!forum/floraison',
24
+ 'documentation_uri' => s.homepage,
26
25
  'homepage_uri' => s.homepage,
27
26
  'source_code_uri' => s.homepage,
27
+ #'mailing_list_uri' => 'https://groups.google.com/forum/#!forum/floraison',
28
28
  #'wiki_uri' => s.homepage + '/wiki',
29
+ 'rubygems_mfa_required' => 'true',
29
30
  }
30
31
 
31
32
  #s.files = `git ls-files`.split("\n")
32
33
  s.files = Dir[
33
- 'README.{md,txt}',
34
- 'CHANGELOG.{md,txt}', 'CREDITS.{md,txt}', 'LICENSE.{md,txt}',
34
+ '{README,CHANGELOG,CREDITS,LICENSE}.{md,txt}',
35
35
  #'Makefile',
36
36
  'lib/**/*.rb', #'spec/**/*.rb', 'test/**/*.rb',
37
37
  "#{s.name}.gemspec",
@@ -41,7 +41,7 @@ Time tools for flor and the floraison project. Cron parsing and occurrence compu
41
41
  # this dependency appears in 'et-orbi'
42
42
 
43
43
  s.add_runtime_dependency 'raabro', '~> 1.4'
44
- s.add_runtime_dependency 'et-orbi', '~> 1', '>= 1.2.11'
44
+ s.add_runtime_dependency 'et-orbi', '~> 1.4'
45
45
 
46
46
  s.add_development_dependency 'rspec', '~> 3.8'
47
47
  s.add_development_dependency 'chronic', '~> 0.10'
data/lib/fugit/cron.rb CHANGED
@@ -32,15 +32,23 @@ module Fugit
32
32
  def parse(s)
33
33
 
34
34
  return s if s.is_a?(self)
35
+ return nil unless s.is_a?(String)
35
36
 
36
- s = SPECIALS[s] || s
37
+ s0 = s
38
+ s = s.strip
37
39
 
38
- return nil unless s.is_a?(String)
40
+ s =
41
+ if s[0, 1] == '@'
42
+ ss = s.split(/\s+/, 2)
43
+ [ SPECIALS[ss[0]] || ss, *ss[1..-1] ].join(' ')
44
+ else
45
+ s
46
+ end
39
47
 
40
48
  #p s; Raabro.pp(Parser.parse(s, debug: 3), colors: true)
41
- h = Parser.parse(s.strip)
49
+ h = Parser.parse(s)
42
50
 
43
- self.allocate.send(:init, s, h)
51
+ self.allocate.send(:init, s0, h)
44
52
  end
45
53
 
46
54
  def do_parse(s)
@@ -167,34 +175,30 @@ module Fugit
167
175
 
168
176
  def weekday_hash_match?(nt, hsh)
169
177
 
178
+ return false unless hsh.is_a?(Integer)
179
+
170
180
  phsh, nhsh = nt.wday_in_month
181
+ #
182
+ # positive wday, from the beginning of the month
183
+ # negative wday, from the end of the month, -1 == last
171
184
 
172
- if hsh > 0
173
- hsh == phsh # positive wday, from the beginning of the month
174
- else
175
- hsh == nhsh # negative wday, from the end of the month, -1 == last
176
- end
185
+ (hsh == phsh) || (hsh == nhsh)
177
186
  end
178
187
 
179
188
  def weekday_modulo_match?(nt, mod)
180
189
 
181
- (nt.rweek % mod[0]) == (mod[1] % mod[0])
190
+ mod.is_a?(Array) &&
191
+ ((nt.rweek % mod[0]) == (mod[1] % mod[0]))
182
192
  end
183
193
 
184
194
  def weekday_match?(nt)
185
195
 
186
- return true if @weekdays.nil?
187
-
188
- wd, hom = @weekdays.find { |d, _| d == nt.wday }
189
-
190
- return false unless wd
191
- return true if hom.nil?
192
-
193
- if hom.is_a?(Array)
194
- weekday_modulo_match?(nt, hom)
195
- else
196
- weekday_hash_match?(nt, hom)
197
- end
196
+ @weekdays.nil? ||
197
+ @weekdays.find { |wd, hom|
198
+ (nt.wday == wd) &&
199
+ (hom.nil? ||
200
+ weekday_modulo_match?(nt, hom) ||
201
+ weekday_hash_match?(nt, hom)) }
198
202
  end
199
203
 
200
204
  def monthday_match?(nt)
@@ -67,11 +67,20 @@ module Fugit
67
67
  day: { a: 'D', r: 'd', i: 'D', s: DAY_S, I: true, l: 'day' },
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
- sec: { a: 's', r: 's', i: 'S', s: 1, I: true, l: 'second' } }.freeze
71
-
72
- INFLA_KEYS, NON_INFLA_KEYS = KEYS
73
- .partition { |k, v| v[:I] }
74
- .collect(&:freeze)
70
+ sec: { a: 's', r: 's', i: 'S', s: 1, I: true, l: 'second' }
71
+ }.freeze
72
+
73
+ #INFLA_KEYS, NON_INFLA_KEYS = KEYS
74
+ # .partition { |k, v| v[:I] }
75
+ # .collect(&:freeze)
76
+ #
77
+ # https://bugs.ruby-lang.org/issues/16252
78
+ #
79
+ kes = KEYS.entries
80
+ INFLA_KEYS,
81
+ NON_INFLA_KEYS =
82
+ kes.select { |_, v| v[:I] }.freeze,
83
+ kes.select { |_, v| ! v[:I] }.freeze
75
84
 
76
85
  def _to_s(key)
77
86
 
data/lib/fugit/nat.rb CHANGED
@@ -54,7 +54,7 @@ module Fugit
54
54
  %w[ zero ] + one_to_nine +
55
55
  %w[ ten eleven twelve thirteen fourteen fifteen sixteen seventeen
56
56
  eighteen nineteen ] +
57
- %w[ twenty thirty fourty fifty ]
57
+ %w[ twenty thirty forty fifty ]
58
58
  .collect { |a|
59
59
  ([ nil ] + one_to_nine)
60
60
  .collect { |b| [ a, b ].compact.join('-') } }
@@ -210,7 +210,7 @@ module Fugit
210
210
  end
211
211
 
212
212
  def ampm(i)
213
- rex(:ampm, i, /[ \t]*(am|pm|noon|midday|midnight)/i)
213
+ rex(:ampm, i, /[ \t]*(am|AM|pm|PM|[Nn]oon|[Mm]idday|[Mm]idnight)/)
214
214
  end
215
215
  def dark(i)
216
216
  rex(:dark, i, /[ \t]*dark/i)
data/lib/fugit/parse.rb CHANGED
@@ -63,6 +63,24 @@ module Fugit
63
63
  fail(ArgumentError.new("not cron or 'natural' cron string: #{s.inspect}"))
64
64
  end
65
65
 
66
+ def parse_max(s, opts={})
67
+
68
+ s0 = s.lines.first
69
+
70
+ (0..[ ::Fugit::Nat::MAX_INPUT_LENGTH, s0.length - 1 ].min).each do |i|
71
+
72
+ s1 =
73
+ s0[0, s0.length - i].rstrip
74
+ f =
75
+ opts[:cronish] ? parse_cronish(s1, opts) :
76
+ parse(s1, opts)
77
+
78
+ return [ s1, f ] if f
79
+ end
80
+
81
+ nil
82
+ end
83
+
66
84
  def determine_type(s)
67
85
 
68
86
  case self.parse(s)
data/lib/fugit.rb CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  module Fugit
5
5
 
6
- VERSION = '1.11.1'
6
+ VERSION = '1.12.0'
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.11.1
4
+ version: 1.12.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: 2024-08-15 00:00:00.000000000 Z
11
+ date: 2025-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: raabro
@@ -30,20 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1'
34
- - - ">="
35
- - !ruby/object:Gem::Version
36
- version: 1.2.11
33
+ version: '1.4'
37
34
  type: :runtime
38
35
  prerelease: false
39
36
  version_requirements: !ruby/object:Gem::Requirement
40
37
  requirements:
41
38
  - - "~>"
42
39
  - !ruby/object:Gem::Version
43
- version: '1'
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: 1.2.11
40
+ version: '1.4'
47
41
  - !ruby/object:Gem::Dependency
48
42
  name: rspec
49
43
  requirement: !ruby/object:Gem::Requirement
@@ -97,10 +91,11 @@ licenses:
97
91
  - MIT
98
92
  metadata:
99
93
  changelog_uri: https://github.com/floraison/fugit/blob/master/CHANGELOG.md
100
- documentation_uri: https://github.com/floraison/fugit
101
94
  bug_tracker_uri: https://github.com/floraison/fugit/issues
95
+ documentation_uri: https://github.com/floraison/fugit
102
96
  homepage_uri: https://github.com/floraison/fugit
103
97
  source_code_uri: https://github.com/floraison/fugit
98
+ rubygems_mfa_required: 'true'
104
99
  post_install_message:
105
100
  rdoc_options: []
106
101
  require_paths:
@@ -116,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
111
  - !ruby/object:Gem::Version
117
112
  version: '0'
118
113
  requirements: []
119
- rubygems_version: 3.4.10
114
+ rubygems_version: 3.4.19
120
115
  signing_key:
121
116
  specification_version: 4
122
117
  summary: time tools for flor