A summary of data about the Ruby ecosystem.

https://github.com/floraison/fugit

time tools (cron, parsing, durations, ...) for Ruby, rufus-scheduler, and flor
https://github.com/floraison/fugit

Keywords

at cron every in ruby time

Keywords from Contributors

crash-reporting rubygems activerecord feature-flag sidekiq rubocop activejob mvc oauth2 rack

Last synced: about 11 hours ago
JSON representation

Repository metadata

time tools (cron, parsing, durations, ...) for Ruby, rufus-scheduler, and flor

README.md

fugit

tests
Gem Version

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.

Related projects

Sister projects

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

Similar, sometimes overlapping projects

  • 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
  • chrono — a chain of logics about chronology
  • CronCalc — calculates cron job occurrences
  • Recurrence — a simple library to handle recurring events
  • CronConfigParser — parse the cron configuration for readability
  • ...

Projects using fugit

  • arask — "Automatic RAils taSKs" uses fugit to parse cron strings
  • sidekiq-cron — uses fugit to parse cron strings since version 1.0.0, it was using rufus-scheduler previously
  • rufus-scheduler — as seen above
  • flor — used in the cron procedure
  • que-scheduler — a reliable job scheduler for que
  • serial_scheduler — ruby task scheduler without threading
  • delayed_cron_job — an extension to Delayed::Job that allows you to set cron expressions for your jobs
  • GoodJob — a multithreaded, Postgres-based, Active Job backend for Ruby on Rails
  • Solid Queue — a DB-based queuing backend for Active Job, designed with simplicity and performance in mind
  • qron — stupid cron thread that wakes up from time to time to do what's in its crontab
  • ...

Fugit.parse(s)

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)

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)

parse_cron, parse_in, parse_at, parse_duration, and parse_nat

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

do_parse_cron, do_parse_in, do_parse_at, do_parse_duration, and do_parse_nat

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.

parse_cronish and do_parse_cronish

Sometimes you know a cron expression or an "every" natural expression will come in and you want to discard the rest.

require 'fugit'

Fugit.parse_cronish('0 0 1 jan *').class             # ==> ::Fugit::Cron
Fugit.parse_cronish('every saturday at noon').class  # ==> ::Fugit::Cron

Fugit.parse_cronish('12y12M')        # ==> nil

.parse_cronish(s) will return a Fugit::Cron instance or else nil.

.do_parse_cronish(s) will return a Fugit::Cron instance or else fail with an ArgumentError.

Introduced in fugit 1.8.0.

Fugit::Cron

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.to_s      # => 2017-01-08 00:00:00 +0900
p c.previous_time.to_s  # => 2017-01-01 00:00:00 +0900

p c.next_time(Time.parse('2024-06-01')).to_s
  # => "2024-06-02 00:00:00 +0900"
p c.previous_time(Time.parse('2024-06-01')).to_s
  # => "2024-05-26 00:00:00 +0900"
    #
    # `Fugit::Cron#next_time` and `#previous_time` accept a "start time"

c = Fugit.parse_cron('0 12 * * mon#2')

  # `#next` and `#prev` return Enumerable instances
  #
  # These two methods are available since fugit 1.10.0.
  #
c.next(Time.parse('2024-02-16 12:00:00'))
  .take(3)
  .map(&:to_s)
    # => [ '2024-03-11 12:00:00',
    #      '2024-04-08 12:00:00',
    #      '2024-05-13 12:00:00' ]
c.prev(Time.parse('2024-02-16 12:00:00'))
  .take(3)
  .map(&:to_s)
    # => [ '2024-02-12 12:00:00',
    #      '2024-01-08 12:00:00',
    #      '2023-12-11 12:00:00' ]

  # `#within` accepts a time range and returns an array of Eo::EoTime
  # instances that correspond to the occurrences of the cron within
  # the time range
  #
  # This method is available since fugit 1.10.0.
  #
c.within(Time.parse('2024-02-16 12:00')..Time.parse('2024-08-01 12:00'))
  .map(&:to_s)
    # => [ '2024-03-11 12:00:00',
    #      '2024-04-08 12:00:00',
    #      '2024-05-13 12:00:00',
    #      '2024-06-10 12:00:00',
    #      '2024-07-08 12:00:00' ]

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...

Please note that '15/30 * * * *' is interpreted as '15-59/30 * * * *' since fugit 1.4.6.

time zones

Fugit accepts a IANA timezone identifier right after a cron string:

'5 0 * * *  Europe/Rome'      # 5 minutes after midnight, every day, Rome tz
'0 22 * * 1-5  Asia/Tbilisi'  # at 2200 on weekdays in Georgia

'@yearly Asia/Kuala_Lumpur'  # turns into '0 0 1 1 * Asia/Kuala_Lumpur'
'@monthly Asia/Jakarta'      # turns into '0 0 1 * * Asia/Jakarta'
  #
  # those two "ats" and friends since fugit 1.11.2...

When no time zone is specified, fugit uses Ruby's provided timezone.

the first Monday of the month

Fugit tries to follow the man 5 crontab documentation.

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 *).

Many people (me included) are surprised 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.

The man page says:

Note: The day of a command's execution can be specified by
two fields -- day of month, and day of week. If both fields
are restricted (ie, are not *), the command will be run when
either field matches the current time.
For example, ``30 4 1,15 * 5'' would cause a command to be run
at 4:30 am on the 1st and 15th of each month, plus every Friday.

Fugit follows this specification.

Since fugit 1.7.0, by adding & right after a day specifier, the day-of-month OR day-of-week becomes day-of-month AND day-of-week.

# standard cron

p Fugit.parse_cron('0 0 */2 * 1-5').next_time('2022-08-09').to_s
  # ==> "2022-08-10 00:00:00 +0900"

# with an &

p Fugit.parse_cron('0 0 */2 * 1-5&').next_time('2022-08-09').to_s # or
p Fugit.parse_cron('0 0 */2& * 1-5').next_time('2022-08-09').to_s
p Fugit.parse_cron('0 0 */2& * 1-5&').next_time('2022-08-09').to_s
  # ==> "2022-08-11 00:00:00 +0900"


# standard cron

p Fugit.parse_cron('59 6 1-7 * 2').next_time('2020-03-15').to_s
  # ==> "2020-03-17 06:59:00 +0900"

# with an &

p Fugit.parse_cron('59 6 1-7 * 2&').next_time('2020-03-15').to_s
p Fugit.parse_cron('59 6 1-7& * 2').next_time('2020-03-15').to_s
p Fugit.parse_cron('59 6 1-7& * 2&').next_time('2020-03-15').to_s
  # ==> "2020-04-07 06:59:00 +0900"

the hash extension

Fugit understands 0 5 * * 1#1 or 0 5 * * mon#1 as "each first Monday of the month, at 05:00".

The hash extension can only be used in the day-of-week field.

'0 5 * * 1#1'    #
'0 5 * * mon#1'  # the first Monday of the month at 05:00

'0 6 * * 5#4,5#5'      #
'0 6 * * fri#4,fri#5'  # the 4th and 5th Fridays of the month at 06:00

'0 7 * * 5#-1'    #
'0 7 * * fri#-1'  # the last Friday of the month at 07:00

'0 7 * * 5#L'       #
'0 7 * * fri#L'     #
'0 7 * * 5#last'    #
'0 7 * * fri#last'  # the last Friday of the month at 07:00

'0 23 * * mon#2,tue'  # the 2nd Monday of the month and every Tuesday, at 23:00

the modulo extension

Since 1.1.10, fugit 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".

The modulo extension can only be used in the day-of-week field.

For odd Sundays, one can write 9 0 * * sun%2+1.

It can be combined, as in 9 0 * * sun%2,tue%3+2, which will match every other Sunday and 1 in 3 Tuesdays (with an offset of 2).

What does sun%2 actually mean?

t.wday == 0 && t.rweek % 2 == 0

What does tue%3+2 mean?

t.wday == 2 && t.rweek % 3 == 2

et-orbi < 1.4.0 : reference set on Tuesday 2019-01-01

The original implementation of #rweek (and #rday) found in et-orbi was initially pointing to "Tuesday 2019-01-01" and it was set as rday 1 and rweek 1.

Consider this iteration through the days around 2019-01-01.

require 'fugit'

t = EtOrbi.parse('2018-12-28 12:00')

15.times do |i|

  puts " * %14s / rday: %5d / rweek: %5d" % [
    t.strftime('%F %a'), t.rday, t.rweek ]

  w = t.rweek
  t = t.add(24 * 3600)
  puts if t.rweek != w

  if i == 7
    puts "\n  (...)\n\n"
    t = EtOrbi.parse('2025-10-04 12:00')
  end
end

For et-orbi 1.2.11, it yields:

 * 2018-12-28 Fri / rday:    -3 / rweek:     0
 * 2018-12-29 Sat / rday:    -2 / rweek:     0
 * 2018-12-30 Sun / rday:    -1 / rweek:     0
 * 2018-12-31 Mon / rday:     0 / rweek:     0

 * 2019-01-01 Tue / rday:     1 / rweek:     1
 * 2019-01-02 Wed / rday:     2 / rweek:     1
 * 2019-01-03 Thu / rday:     3 / rweek:     1
 * 2019-01-04 Fri / rday:     4 / rweek:     1
 * 2019-01-05 Sat / rday:     5 / rweek:     1

  (...)

 * 2025-10-04 Sat / rday:  2469 / rweek:   353
 * 2025-10-05 Sun / rday:  2470 / rweek:   353
 * 2025-10-06 Mon / rday:  2471 / rweek:   353

 * 2025-10-07 Tue / rday:  2472 / rweek:   354
 * 2025-10-08 Wed / rday:  2473 / rweek:   354
 * 2025-10-09 Thu / rday:  2474 / rweek:   354

This was problematic, since the week started on, well, Tuesday.

et-orbi >= 1.4.0 : reference set on Monday 2018-12-31

Since 1.4.0, et-orbi starts by default on Monday (2018-12-31), as rday 0 with rweek 0.

Thus, the above code yields:

 * 2018-12-28 Fri / rday:    -3 / rweek:    -1
 * 2018-12-29 Sat / rday:    -2 / rweek:    -1
 * 2018-12-30 Sun / rday:    -1 / rweek:    -1

 * 2018-12-31 Mon / rday:     0 / rweek:     0
 * 2019-01-01 Tue / rday:     1 / rweek:     0
 * 2019-01-02 Wed / rday:     2 / rweek:     0
 * 2019-01-03 Thu / rday:     3 / rweek:     0
 * 2019-01-04 Fri / rday:     4 / rweek:     0
 * 2019-01-05 Sat / rday:     5 / rweek:     0

  (...)

 * 2025-10-04 Sat / rday:  2469 / rweek:   352
 * 2025-10-05 Sun / rday:  2470 / rweek:   352

 * 2025-10-06 Mon / rday:  2471 / rweek:   353
 * 2025-10-07 Tue / rday:  2472 / rweek:   353
 * 2025-10-08 Wed / rday:  2473 / rweek:   353
 * 2025-10-09 Thu / rday:  2474 / rweek:   353

modulo and et-orbi >= 1.4.0 sanity check

Given the cron "0 12 * * mon%2,wed%3+1", here is a piece of code that considers a range of 44 days and tells in its last column if YES or no if each of the days matches the cron.

require 'fugit'

c = Fugit.parse_cron('0 12 * * mon%2,wed%3+1')

t = EtOrbi.parse('2025-09-20 12:00')

44.times do

  wd = t.strftime('%a')
  wd = %w[ Mon Wed ].include?(wd) ? '*' + wd.upcase : ' ' + wd.downcase

  puts "%14s | rweek: %3d | %%2: %d == 0 | %%3: %d == 1 | ? %3s" % [
    t.strftime('%F') + ' ' + wd,
    t.rweek,
    t.rweek % 2, t.rweek % 3,
    c.match?(t)
      ].map { |e| e == true ? 'YES' : e == false ? 'no' : e }

  w = t.rweek
  t = t.add(24 * 3600)
  puts if t.rweek != w
end

Here's the output:

2025-09-20  sat | rweek: 350 | %2: 0 == 0 | %3: 2 == 1 | ?  no
2025-09-21  sun | rweek: 350 | %2: 0 == 0 | %3: 2 == 1 | ?  no

2025-09-22 *MON | rweek: 351 | %2: 1 == 0 | %3: 0 == 1 | ?  no
2025-09-23  tue | rweek: 351 | %2: 1 == 0 | %3: 0 == 1 | ?  no
2025-09-24 *WED | rweek: 351 | %2: 1 == 0 | %3: 0 == 1 | ?  no
2025-09-25  thu | rweek: 351 | %2: 1 == 0 | %3: 0 == 1 | ?  no
2025-09-26  fri | rweek: 351 | %2: 1 == 0 | %3: 0 == 1 | ?  no
2025-09-27  sat | rweek: 351 | %2: 1 == 0 | %3: 0 == 1 | ?  no
2025-09-28  sun | rweek: 351 | %2: 1 == 0 | %3: 0 == 1 | ?  no

2025-09-29 *MON | rweek: 352 | %2: 0 == 0 | %3: 1 == 1 | ? YES
2025-09-30  tue | rweek: 352 | %2: 0 == 0 | %3: 1 == 1 | ?  no
2025-10-01 *WED | rweek: 352 | %2: 0 == 0 | %3: 1 == 1 | ? YES
2025-10-02  thu | rweek: 352 | %2: 0 == 0 | %3: 1 == 1 | ?  no
2025-10-03  fri | rweek: 352 | %2: 0 == 0 | %3: 1 == 1 | ?  no
2025-10-04  sat | rweek: 352 | %2: 0 == 0 | %3: 1 == 1 | ?  no
2025-10-05  sun | rweek: 352 | %2: 0 == 0 | %3: 1 == 1 | ?  no

2025-10-06 *MON | rweek: 353 | %2: 1 == 0 | %3: 2 == 1 | ?  no
2025-10-07  tue | rweek: 353 | %2: 1 == 0 | %3: 2 == 1 | ?  no
2025-10-08 *WED | rweek: 353 | %2: 1 == 0 | %3: 2 == 1 | ?  no
2025-10-09  thu | rweek: 353 | %2: 1 == 0 | %3: 2 == 1 | ?  no
2025-10-10  fri | rweek: 353 | %2: 1 == 0 | %3: 2 == 1 | ?  no
2025-10-11  sat | rweek: 353 | %2: 1 == 0 | %3: 2 == 1 | ?  no
2025-10-12  sun | rweek: 353 | %2: 1 == 0 | %3: 2 == 1 | ?  no

2025-10-13 *MON | rweek: 354 | %2: 0 == 0 | %3: 0 == 1 | ? YES
2025-10-14  tue | rweek: 354 | %2: 0 == 0 | %3: 0 == 1 | ?  no
2025-10-15 *WED | rweek: 354 | %2: 0 == 0 | %3: 0 == 1 | ?  no
2025-10-16  thu | rweek: 354 | %2: 0 == 0 | %3: 0 == 1 | ?  no
2025-10-17  fri | rweek: 354 | %2: 0 == 0 | %3: 0 == 1 | ?  no
2025-10-18  sat | rweek: 354 | %2: 0 == 0 | %3: 0 == 1 | ?  no
2025-10-19  sun | rweek: 354 | %2: 0 == 0 | %3: 0 == 1 | ?  no

2025-10-20 *MON | rweek: 355 | %2: 1 == 0 | %3: 1 == 1 | ?  no
2025-10-21  tue | rweek: 355 | %2: 1 == 0 | %3: 1 == 1 | ?  no
2025-10-22 *WED | rweek: 355 | %2: 1 == 0 | %3: 1 == 1 | ? YES
2025-10-23  thu | rweek: 355 | %2: 1 == 0 | %3: 1 == 1 | ?  no
2025-10-24  fri | rweek: 355 | %2: 1 == 0 | %3: 1 == 1 | ?  no
2025-10-25  sat | rweek: 355 | %2: 1 == 0 | %3: 1 == 1 | ?  no
2025-10-26  sun | rweek: 355 | %2: 1 == 0 | %3: 1 == 1 | ?  no

2025-10-27 *MON | rweek: 356 | %2: 0 == 0 | %3: 2 == 1 | ? YES
2025-10-28  tue | rweek: 356 | %2: 0 == 0 | %3: 2 == 1 | ?  no
2025-10-29 *WED | rweek: 356 | %2: 0 == 0 | %3: 2 == 1 | ?  no
2025-10-30  thu | rweek: 356 | %2: 0 == 0 | %3: 2 == 1 | ?  no
2025-10-31  fri | rweek: 356 | %2: 0 == 0 | %3: 2 == 1 | ?  no
2025-11-01  sat | rweek: 356 | %2: 0 == 0 | %3: 2 == 1 | ?  no
2025-11-02  sun | rweek: 356 | %2: 0 == 0 | %3: 2 == 1 | ?  no

the second extension

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.

c = Fugit.parse('* * * * *') # every minute
c = Fugit.parse('5 * * * *') # every hour at minute 5
c = Fugit.parse('* * * * * *') # every second
c = Fugit.parse('5 * * * * *') # every minute at second 5

Fugit::Nat

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 * * *'

Ambiguous nats

Not all strings result in a clean, single, cron expression. The multi: false|true|:fail argument to Fugit::Nat.parse could help.

Fugit::Nat.parse('every day at 16:00 and 18:00')
  .to_cron_s
    # ==> '0 16,18 * * *' (a single Fugit::Cron instances)
Fugit::Nat.parse('every day at 16:00 and 18:00', multi: true)
  .collect(&:to_cron_s)
    # ==> [ '0 16,18 * * *' ] (array of Fugit::Cron instances, here only one)

Fugit::Nat.parse('every day at 16:15 and 18:30')
  .to_cron_s
    # ==> '15 16 * * *' (a single of Fugit::Cron instances)
Fugit::Nat.parse('every day at 16:15 and 18:30', multi: true)
  .collect(&:to_cron_s)
    # ==> [ '15 16 * * *', '30 18 * * *' ] (two Fugit::Cron instances)

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 * * *)
Fugit::Nat.parse('every day at 16:15 nada 18:30', multi: true)
  # ==> nil

multi: true indicates to Fugit::Nat that an array of Fugit::Cron instances is expected as a result.

multi: :fail tells Fugit::Nat.parse to fail if the result is more than 1 Fugit::Cron instances.

multi: false is the default behaviour, return a single Fugit::Cron instance or nil when it cannot parse.

Please note that "nat" input is limited to 256 characters (fugit 1.11.1).

Nat Midnight

"Every day at midnight" is supported, but "Every monday at midnight" will be interpreted (as of Fugit <= 1.4.x) as "Every monday at 00:00". Sorry about that.

12 AM and PM

How does fugit react with "12 am", "12 pm", "12 midnight", etc?

require 'fugit'

p Fugit.parse('every day at 12am').original  # ==> "0 0 * * *"
p Fugit.parse('every day at 12pm').original  # ==> "0 12 * * *"

p Fugit.parse('every day at 12:00am').original   # ==> "0 0 * * *"
p Fugit.parse('every day at 12:00pm').original   # ==> "0 12 * * *"
p Fugit.parse('every day at 12:00 am').original  # ==> "0 0 * * *"
p Fugit.parse('every day at 12:00 pm').original  # ==> "0 12 * * *"
p Fugit.parse('every day at 12:15am').original   # ==> "15 0 * * *"
p Fugit.parse('every day at 12:15pm').original   # ==> "15 12 * * *"
p Fugit.parse('every day at 12:15 am').original  # ==> "15 0 * * *"
p Fugit.parse('every day at 12:15 pm').original  # ==> "15 12 * * *"

p Fugit.parse('every day at 12 noon').original         # ==> "0 12 * * *"
p Fugit.parse('every day at 12 midnight').original     # ==> "0 24 * * *"
p Fugit.parse('every day at 12:00 noon').original      # ==> "0 12 * * *"
p Fugit.parse('every day at 12:00 midnight').original  # ==> "0 24 * * *"
p Fugit.parse('every day at 12:15 noon').original      # ==> "15 12 * * *"
p Fugit.parse('every day at 12:15 midnight').original  # ==> "15 24 * * *"

  # as of fugit 1.7.2

Fugit::Duration

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

There is a #deflate method

Fugit::Duration.parse(1000).to_plain_s # => "1000s"
Fugit::Duration.parse(3600).to_plain_s # => "3600s"
Fugit::Duration.parse(1000).deflate.to_plain_s # => "16m40s"
Fugit::Duration.parse(3600).deflate.to_plain_s # => "1h"

# or event shorter
Fugit.parse(1000).deflate.to_plain_s # => "16m40s"
Fugit.parse(3600).deflate.to_plain_s # => "1h"

There is also an #inflate method

Fugit::Duration.parse('1h30m12').inflate.to_plain_s # => "5412s"
Fugit.parse('1h30m12').inflate.to_plain_s # => "5412s"

Fugit.parse('1h30m12').to_sec # => 5412
Fugit.parse('1h30m12').to_sec.to_s + 's' # => "5412s"

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"

Fugit::At

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"

KNOWN ISSUES

The gem nice_hash gets in the way of fugit, as seen in issue 108. It prevents fugit from correctly parsing cron strings.

LICENSE

MIT, see LICENSE.txt


Owner metadata


GitHub Events

Total
Last Year

Committers metadata

Last synced: 2 days ago

Total Commits: 689
Total Committers: 19
Avg Commits per committer: 36.263
Development Distribution Score (DDS): 0.042

Commits in past year: 35
Committers in past year: 3
Avg Commits per committer in past year: 11.667
Development Distribution Score (DDS) in past year: 0.086

Name Email Commits
John Mettraux j****x@g****m 660
Cristian Bica c****a@g****m 4
dependabot[bot] 4****] 4
Peter Goldstein p****n@g****m 3
Geremia Taglialatela t****v@g****m 2
Michael Reinsch m****l@x****o 2
Olle Jonsson o****n@g****m 2
Tero Marttila t****a@k****o 1
Akira Matsuda r****e@d****p 1
Chukwuemeka Ajah t****h@g****m 1
Harry Lascelles h****y@h****m 1
John W Higgins w****v@g****m 1
M. Bellucci d****u@g****m 1
Mark James m****j@a****u 1
Michael Grosser m****l@g****t 1
Tejas Bubane t****e@g****m 1
The Gitter Badger b****r@g****m 1
Vivek v****l@g****m 1
solteszad s****d@g****m 1

Committer domains:


Issue and Pull Request metadata

Last synced: 1 day ago

Total issues: 84
Total pull requests: 33
Average time to close issues: 15 days
Average time to close pull requests: about 4 hours
Total issue authors: 53
Total pull request authors: 21
Average comments per issue: 4.95
Average comments per pull request: 1.73
Merged pull request: 28
Bot issues: 0
Bot pull requests: 3

Past year issues: 3
Past year pull requests: 2
Past year average time to close issues: 8 days
Past year average time to close pull requests: about 6 hours
Past year issue authors: 3
Past year pull request authors: 2
Past year average comments per issue: 5.33
Past year average comments per pull request: 2.0
Past year merged pull request: 2
Past year bot issues: 0
Past year bot pull requests: 1

More stats: https://issues.ecosyste.ms/repositories/lookup?url=https://github.com/floraison/fugit

Top Issue Authors

  • jmettraux (16)
  • shaicoleman (6)
  • grosser (3)
  • ticky (2)
  • gee-forr (2)
  • trafium (2)
  • jeromedalbert (2)
  • gr8bit (2)
  • ozachun (2)
  • hlascelles (2)
  • mscrivo (2)
  • bdarcet (2)
  • Ggallardoh (1)
  • honglooker (1)
  • JosephHalter (1)

Top Pull Request Authors

  • dependabot[bot] (3)
  • petergoldstein (3)
  • tagliala (3)
  • delbetu (2)
  • mreinsch (2)
  • trafium (2)
  • tejasbubane (2)
  • olleolleolle (2)
  • ChukwuEmekaAjah (2)
  • vivekmiyani (1)
  • SpComb (1)
  • amatsuda (1)
  • wishdev (1)
  • hlascelles (1)
  • utilum (1)

Top Issue Labels

  • feature request (2)
  • bug (2)

Top Pull Request Labels

  • dependencies (3)
  • github_actions (1)

Package metadata

gem.coop: fugit

Time tools for flor and the floraison project. Cron parsing and occurrence computing. Timestamps and more.

  • Homepage: https://github.com/floraison/fugit
  • Documentation: http://www.rubydoc.info/gems/fugit/
  • Licenses: MIT
  • Latest release: 1.12.1 (published 5 months ago)
  • Last Synced: 2026-03-01T11:31:57.742Z (2 days ago)
  • Versions: 58
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Downloads: 179,877,644 Total
  • Docker Downloads: 723,345,191
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 0.05%
    • Downloads: 0.15%
  • Maintainers (1)
rubygems.org: fugit

Time tools for flor and the floraison project. Cron parsing and occurrence computing. Timestamps and more.

  • Homepage: https://github.com/floraison/fugit
  • Documentation: http://www.rubydoc.info/gems/fugit/
  • Licenses: MIT
  • Latest release: 1.12.1 (published 5 months ago)
  • Last Synced: 2026-03-01T17:34:48.264Z (2 days ago)
  • Versions: 58
  • Dependent Packages: 32
  • Dependent Repositories: 4,622
  • Downloads: 179,895,615 Total
  • Docker Downloads: 723,345,191
  • Rankings:
    • Docker downloads count: 0.156%
    • Downloads: 0.175%
    • Dependent repos count: 0.463%
    • Dependent packages count: 0.786%
    • Average: 1.605%
    • Stargazers count: 3.167%
    • Forks count: 4.882%
  • Maintainers (1)
  • Advisories:
proxy.golang.org: github.com/floraison/fugit

  • Homepage:
  • Documentation: https://pkg.go.dev/github.com/floraison/fugit#section-documentation
  • Licenses: mit
  • Latest release: v1.12.1 (published 5 months ago)
  • Last Synced: 2026-03-02T02:27:09.342Z (2 days ago)
  • Versions: 57
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Stargazers count: 2.878%
    • Forks count: 3.79%
    • Average: 4.508%
    • Dependent packages count: 5.497%
    • Dependent repos count: 5.866%
ubuntu-24.04: ruby-fugit

  • Homepage: https://github.com/floraison/fugit
  • Licenses:
  • Latest release: 1.8.1-3 (published 25 days ago)
  • Last Synced: 2026-02-06T15:13:09.309Z (25 days ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
debian-10: ruby-fugit

  • Homepage: https://github.com/floraison/fugit
  • Documentation: https://packages.debian.org/buster/ruby-fugit
  • Licenses:
  • Latest release: 1.1.7-1 (published 20 days ago)
  • Last Synced: 2026-02-13T04:21:27.596Z (19 days ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
ubuntu-20.04: ruby-fugit

  • Homepage: https://github.com/floraison/fugit
  • Licenses:
  • Latest release: 1.3.3+gh-1 (published 19 days ago)
  • Last Synced: 2026-02-13T07:14:05.807Z (19 days ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
debian-11: ruby-fugit

  • Homepage: https://github.com/floraison/fugit
  • Documentation: https://packages.debian.org/bullseye/ruby-fugit
  • Licenses:
  • Latest release: 1.3.8-1 (published 21 days ago)
  • Last Synced: 2026-02-13T08:20:28.845Z (19 days ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
ubuntu-23.10: ruby-fugit

  • Homepage: https://github.com/floraison/fugit
  • Licenses:
  • Latest release: 1.8.1-3 (published 18 days ago)
  • Last Synced: 2026-02-13T18:20:49.170Z (18 days ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
ubuntu-23.04: ruby-fugit

  • Homepage: https://github.com/floraison/fugit
  • Licenses:
  • Latest release: 1.5.2-1 (published 21 days ago)
  • Last Synced: 2026-02-11T06:39:44.080Z (21 days ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
ubuntu-24.10: ruby-fugit

  • Homepage: https://github.com/floraison/fugit
  • Licenses:
  • Latest release: 1.8.1-3 (published 22 days ago)
  • Last Synced: 2026-02-09T16:35:35.350Z (22 days ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
debian-12: ruby-fugit

  • Homepage: https://github.com/floraison/fugit
  • Documentation: https://packages.debian.org/bookworm/ruby-fugit
  • Licenses:
  • Latest release: 1.5.2-1 (published 19 days ago)
  • Last Synced: 2026-02-12T23:29:54.344Z (19 days ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%
ubuntu-22.04: ruby-fugit

  • Homepage: https://github.com/floraison/fugit
  • Licenses:
  • Latest release: 1.5.2-1 (published 18 days ago)
  • Last Synced: 2026-02-13T13:17:22.645Z (18 days ago)
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Rankings:
    • Dependent repos count: 0.0%
    • Dependent packages count: 0.0%
    • Average: 100%

Dependencies

fugit.gemspec rubygems
  • chronic ~> 0.10 development
  • rspec ~> 3.8 development
  • et-orbi ~> 1, >= 1.2.7
  • raabro ~> 1.4
.github/workflows/test.yaml actions
  • actions/checkout v3 composite
  • ruby/setup-ruby v1 composite
Gemfile rubygems

Score: 30.4636367474561