Time tools for flor and the floraison group.
It uses et-orbi to represent time instances and raabro as a basis for its parsers.
Fugit is a core dependency of rufus-scheduler 3.5.x.
The intersection of those two projects is where fugit is born:
- rufus-scheduler - a cron/at/in/every/interval in-process scheduler, in fact, it's the father project to this fugit project
- flor - a Ruby workflow engine, fugit provides the foundation for its time scheduling capabilities
- chronic - a pure Ruby natural language date parser
- parse-cron - parses cron expressions and calculates the next occurrence after a given date
- ice_cube - Ruby date recurrence library
- ISO8601 - Ruby parser to work with ISO8601 dateTimes and durations
- ...
- arask - "Automatic RAils taSKs" uses fugit to parse cron strings
- sideqik-cron - recent versions of Sideqik-Cron use fugit to parse cron strings
- rufus-scheduler -
- flor - used in the cron procedure
- que-scheduler - a reliable job scheduler for que
- ...
The simplest way to use fugit is via Fugit.parse(s)
.
require 'fugit'
Fugit.parse('0 0 1 jan *').class # ==> ::Fugit::Cron
Fugit.parse('12y12M').class # ==> ::Fugit::Duration
Fugit.parse('2017-12-12').class # ==> ::EtOrbi::EoTime
Fugit.parse('2017-12-12 UTC').class # ==> ::EtOrbi::EoTime
Fugit.parse('every day at noon').class # ==> ::Fugit::Cron
If fugit cannot extract a cron, duration or point in time out of the string, it will return nil.
Fugit.parse('nada')
# ==> nil
Fugit.do_parse(s)
is equivalent to Fugit.parse(s)
, but instead of returning nil, it raises an error if the given string contains no time information.
Fugit.do_parse('nada')
# ==> /home/jmettraux/w/fugit/lib/fugit/parse.rb:32
# :in `do_parse': found no time information in "nada" (ArgumentError)
require 'fugit'
Fugit.parse_cron('0 0 1 jan *').class # ==> ::Fugit::Cron
Fugit.parse_duration('12y12M').class # ==> ::Fugit::Duration
Fugit.parse_at('2017-12-12').class # ==> ::EtOrbi::EoTime
Fugit.parse_at('2017-12-12 UTC').class # ==> ::EtOrbi::EoTime
Fugit.parse_nat('every day at noon').class # ==> ::Fugit::Cron
As Fugit.parse(s)
returns nil when it doesn't grok its input, and Fugit.do_parse(s)
fails when it doesn't grok, each of the parse_
methods has its partner do_parse_
method.
A class Fugit::Cron
to parse cron strings and then #next_time
and #previous_time
to compute the next or the previous occurrence respectively.
There is also a #brute_frequency
method which returns an array [ shortest delta, longest delta, occurrence count ]
where delta is the time between two occurrences.
require 'fugit'
c = Fugit::Cron.parse('0 0 * * sun')
# or
c = Fugit::Cron.new('0 0 * * sun')
p Time.now # => 2017-01-03 09:53:27 +0900
p c.next_time # => 2017-01-08 00:00:00 +0900
p c.previous_time # => 2017-01-01 00:00:00 +0900
p c.brute_frequency # => [ 604800, 604800, 53 ]
# [ delta min, delta max, occurrence count ]
p c.rough_frequency # => 7 * 24 * 3600 (7d rough frequency)
p c.match?(Time.parse('2017-08-06')) # => true
p c.match?(Time.parse('2017-08-07')) # => false
p c.match?('2017-08-06') # => true
p c.match?('2017-08-06 12:00') # => false
Example of cron strings understood by fugit:
'5 0 * * *' # 5 minutes after midnight, every day
'15 14 1 * *' # at 1415 on the 1st of every month
'0 22 * * 1-5' # at 2200 on weekdays
'0 22 * * mon-fri' # idem
'23 0-23/2 * * *' # 23 minutes after 00:00, 02:00, 04:00, ...
'@yearly' # turns into '0 0 1 1 *'
'@monthly' # turns into '0 0 1 * *'
'@weekly' # turns into '0 0 * * 0'
'@daily' # turns into '0 0 * * *'
'@midnight' # turns into '0 0 * * *'
'@hourly' # turns into '0 * * * *'
'0 0 L * *' # last day of month at 00:00
'0 0 last * *' # idem
'0 0 -7-L * *' # from the seventh to last to the last day of month at 00:00
# and more...
Fugit, since 1.1.10, also understands cron strings like "9 0 * * sun%2
" which can be read as "every other Sunday at 9am".
For odd Sundays, one can write 9 0 * * sun%2+1
.
It can be combined, as in 9 0 * * sun%2,tue%3+2
But what does it references to? It starts at 1 on 2019-01-01.
require 'et-orbi' # >= 1.1.8
# the reference
p EtOrbi.parse('2019-01-01').wday # => 2
p EtOrbi.parse('2019-01-01').rweek # => 1
p EtOrbi.parse('2019-01-01').rweek % 2 # => 1
# today (as of this coding...)
p EtOrbi.parse('2019-04-11').wday # => 4
p EtOrbi.parse('2019-04-11').rweek # => 15
p EtOrbi.parse('2019-04-11').rweek % 2 # => 1
A class Fugit::Duration
to parse duration strings (vanilla rufus-scheduler ones and ISO 8601 ones).
Provides duration arithmetic tools.
require 'fugit'
d = Fugit::Duration.parse('1y2M1d4h')
p d.to_plain_s # => "1Y2M1D4h"
p d.to_iso_s # => "P1Y2M1DT4H" ISO 8601 duration
p d.to_long_s # => "1 year, 2 months, 1 day, and 4 hours"
d += Fugit::Duration.parse('1y1h')
p d.to_long_s # => "2 years, 2 months, 1 day, and 5 hours"
d += 3600
p d.to_plain_s # => "2Y2M1D5h3600s"
p Fugit::Duration.parse('1y2M1d4h').to_sec # => 36820800
The to_*_s
methods are also available as class methods:
p Fugit::Duration.to_plain_s('1y2M1d4h')
# => "1Y2M1D4h"
p Fugit::Duration.to_iso_s('1y2M1d4h')
# => "P1Y2M1DT4H" ISO 8601 duration
p Fugit::Duration.to_long_s('1y2M1d4h')
# => "1 year, 2 months, 1 day, and 4 hours"
Points in time are parsed and given back as EtOrbi::EoTime instances.
Fugit::At.parse('2017-12-12').to_s
# ==> "2017-12-12 00:00:00 +0900" (at least here in Hiroshima)
Fugit::At.parse('2017-12-12 12:00:00 America/New_York').to_s
# ==> "2017-12-12 12:00:00 -0500"
Directly with Fugit.parse_at(s)
is OK too:
Fugit.parse_at('2017-12-12 12:00:00 America/New_York').to_s
# ==> "2017-12-12 12:00:00 -0500"
Directly with Fugit.parse(s)
is OK too:
Fugit.parse('2017-12-12 12:00:00 America/New_York').to_s
# ==> "2017-12-12 12:00:00 -0500"
Fugit understand some kind of "natural" language:
For example, those "every" get turned into Fugit::Cron
instances:
Fugit::Nat.parse('every day at five') # ==> '0 5 * * *'
Fugit::Nat.parse('every weekday at five') # ==> '0 5 * * 1,2,3,4,5'
Fugit::Nat.parse('every day at 5 pm') # ==> '0 17 * * *'
Fugit::Nat.parse('every tuesday at 5 pm') # ==> '0 17 * * 2'
Fugit::Nat.parse('every wed at 5 pm') # ==> '0 17 * * 3'
Fugit::Nat.parse('every day at 16:30') # ==> '30 16 * * *'
Fugit::Nat.parse('every day at 16:00 and 18:00') # ==> '0 16,18 * * *'
Fugit::Nat.parse('every day at noon') # ==> '0 12 * * *'
Fugit::Nat.parse('every day at midnight') # ==> '0 0 * * *'
Fugit::Nat.parse('every tuesday and monday at 5pm') # ==> '0 17 * * 1,2'
Fugit::Nat.parse('every wed or Monday at 5pm and 11') # ==> '0 11,17 * * 1,3'
Fugit::Nat.parse('every day at 5 pm on America/Los_Angeles') # ==> '0 17 * * * America/Los_Angeles'
Fugit::Nat.parse('every day at 6 pm in Asia/Tokyo') # ==> '0 18 * * * Asia/Tokyo'
Fugit::Nat.parse('every 3 hours') # ==> '0 */3 * * *'
Fugit::Nat.parse('every 4 months') # ==> '0 0 1 */4 *'
Fugit::Nat.parse('every 5 minutes') # ==> '*/5 * * * *'
Fugit::Nat.parse('every 15s') # ==> '*/15 * * * * *'
Directly with Fugit.parse(s)
is OK too:
Fugit.parse('every day at five') # ==> Fugit::Cron instance '0 5 * * *'
Not all strings result in a clean, single, cron expression.
Fugit::Nat.parse('every day at 16:00 and 18:00', multi: true)
# ==> [ '0 16,18 * * *' ]
Fugit::Nat.parse('every day at 16:15 and 18:30')
# ==> [ '15 16 * * *' ]
Fugit::Nat.parse('every day at 16:15 and 18:30', multi: true)
# ==> [ '15 16 * * *', '30 18 * * *' ]
Fugit::Nat.parse('every day at 16:15 and 18:30', multi: :fail)
# ==> ArgumentError: multiple crons in "every day at 16:15 and 18:30" (15 16 * * * | 30 18 * * *)
MIT, see LICENSE.txt