fugit

repository·master·Indexed 19 days ago

https://github.com/floraison/fugit

A Ruby time parsing library used for scheduling. It provides tools to parse cron expressions (including 5 and 6-element strings), durations (ISO 8601 and rufus-scheduler formats), natural language time strings, and specific points in time. Key features include support for IANA timezones, AND operators in cron strings, hash and modulo extensions for day-of-week matching, and random jitter support.

Tokens
8.9K
Snippets
40
Records
41
Agent score
66%

What's inside fugit

  1. Use the AND operator (&) in cron strings

    master

    By default, standard cron treats the day-of-month and day-of-week fields as an OR relationship (the job runs if either matches).

    Since Fugit 1.7.0, you can force an AND relationship by adding an ampersand (&) immediately after a day specifier. This is useful for specifying requirements like "the first Monday of the month".

    Example: 0 5 1-7 * 1& means "at 05:00, on days 1 through 7 AND on Monday".

    # Standard cron (OR logic): runs on 1st-7th OR every Monday
    p Fugit.parse_cron('59 6 1-7 * 2').next_time('2020-03-15')
    
    # With & (AND logic): runs on 1st-7th AND on Tuesday
    p Fugit.parse_cron('59 6 1-7* 2&').next_time('2020-03-15')
  2. Understand 12 AM/PM and Midnight behavior in Fugit

    master

    Fugit has specific behaviors for 12 AM/PM and midnight strings:

    12 AM/PM:

    • 12am or 12:00am is parsed as 00:00 (midnight).
    • 12pm or 12:00pm is parsed as 12:00 (noon).
    • 12 noon is parsed as 12:00.
    • 12 midnight is parsed as 24:00 (which effectively represents the end of the day/start of the next).

    Midnight Note: While "Every day at midnight" is supported, note that in versions $\le$ 1.4.x, "Every monday at midnight" is interpreted as "Every monday at 00:00".

    # Examples of 12 AM/PM parsing
    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 noon').original # ==> "0 12 * * *"
    p Fugit.parse('every day at 12 midnight').original # ==> "0 24 * * *"
  3. Use the Hash extension for specific days of the month

    master

    The hash extension allows you to target specific occurrences within a month (e.g., the first Monday, the last Friday). This can only be used in the day-of-week field.

    Syntax patterns:

    • mon#1: The first Monday of the month.
    • fri#4,sat#5: The 4th Friday and 5th Saturday of the month.
    • fri#-1: The last Friday of the month.
    • fri#L: The last Friday of the month.
    • mon#2,tue: The 2nd Monday and every Tuesday.
    # First Monday of the month at 05:00
    '0 5 * * mon#1'
    
    # Last Friday of the month at 07:00
    '0 7 * * fri#-1'
    
    # 2nd Monday and every Tuesday at 23:00
    '0 23 * * mon#2,tue'
  4. Use the Modulo extension for periodic day-of-week matching

    master

    Since version 1.1.10, Fugit supports a modulo extension in the day-of-week field to match occurrences based on the week number (rweek).

    Syntax patterns:

    • sun%2: Every other Sunday (where rweek % 2 == 0).
    • mon%2,wed%3+1: Every other Monday AND every 3rd Wednesday with an offset of 1 (rweek % 3 == 1).

    Note: The behavior of rweek depends on the et-orbi version. Since et-orbi 1.4.0, the week starts on Monday (rday 0, rweek 0).

    # Every other Sunday at 9am
    '9 0 * * sun%2'
    
    # Every 3rd Wednesday with an offset of 1 at noon
    '12 0 * * wed%3+1'
  5. Configure time zones in cron strings

    master

    Fugit supports IANA timezone identifiers appended to the end of a cron string. If no timezone is specified, Fugit defaults to the system's provided timezone.

    Example formats:

    • '5 0 * * * Europe/Rome'
    • '@yearly Asia/Kuala_Lumpur'
    # 5 minutes after midnight, Rome time
    c = Fugit.parse('5 0 * * * Europe/Rome')
    
    # @yearly with specific timezone
    c = Fugit.parse('@yearly Asia/Jakarta')
  6. Use the Random extension for jitter

    master

    The ~ character allows you to specify a range for a random value to be picked at parse time. This is useful for adding jitter to schedules.

    Syntax patterns:

    • ~ * * * *: A random minute every hour.
    • ~29 * * * *: A random minute between 0 and 29 every hour.
    • 30~ * * * *: A random minute between 30 and 59 every hour.
    • ~/10 * * * *: A random minute in a 10-minute block (0-9, 10-19, etc.).
    • 0 12 * * 1~5: A random workday (Mon-Fri) at noon.

    Important: Random values are determined at parse time. The value remains fixed until the string is parsed again.

    You can customize the randomness using the :random option:

    • random: false: Disables randomness (effectively treats ~ as 0).
    • random: true: Uses default random generator.
    • random: SecureRandom: Uses a cryptographically secure generator.
    # Every hour on a random minute
    Fugit.parse('~ * * * *')
    
    # Every hour on a random minute between 0 and 29
    Fugit.parse('~29 * * * *')
    
    # Using a secure random generator
    Fugit.parse('~ * * * *', random: SecureRandom)
  7. Use the Fugit module

    master

    Fugit is a Ruby library for parsing various time-related strings, including cron expressions, durations, natural language time, and specific timestamps. The main entrypoint is the Fugit module, which provides high-level parsing methods like Fugit.parse(s) for general parsing, or specialized methods like Fugit.parse_cron(s), Fugit.parse_in(s), Fugit.parse_at(s), Fugit.parse_duration(s), and Fugit.parse_nat(s) for specific formats.

    require 'fugit'
    
    # General parsing
    Fugit.parse('tomorrow at 10:00')
    
    # Specialized parsing
    Fugit.parse_cron('0 0 * * *')
    Fugit.parse_in('2 days')
    Fugit.parse_at('2023-01-01 12:00:00')
    Fugit.parse_duration('1h 30m')
    Fugit.parse_nat('next Tuesday')
  8. Normalize durations with inflate and deflate

    master

    Fugit::Duration objects can be manipulated between two states: inflated (where components like weeks, days, hours are separate) and deflated (where components are collapsed into the largest possible units, typically seconds).

    • inflate: Converts the duration into a state where all time components are expanded into their base second equivalents (e.g., converting '1 hour' into 3600 seconds).
    • deflate(options={}): Collapses the duration into the largest possible units (e.g., converting 3660 seconds into '1 hour and 1 minute').

    Deflate Options:

    • options[:month]: If true, treats months as 30 days. If an integer, treats it as a specific number of days.
    • options[:year]: If true, treats years as 365 days. If an integer, treats it as a specific number of days.

    Warning: Year and month calculations are approximations (365 days/year, 30 days/month).

    d = Fugit::Duration.parse("90m")
    
    # Deflate collapses 90m into 1h 30m
    deflated = d.deflate
    deflected.to_plain_s # => "1h 30m"
    
    # Deflate with custom month/year handling
    d.deflate(month: true)
  9. Parse specific time types with specialized methods

    master

    Fugit provides specialized parsing methods if you know exactly what type of time object you are expecting. Each parse_ method has a corresponding do_parse_ method that raises an ArgumentError instead of returning nil on failure.

    Available specialized methods:

    • parse_cron(s): Parses cron expressions.
    • parse_duration(s): Parses durations.
    • parse_at(s): Parses specific points in time.
    • parse_nat(s): Parses natural language expressions (e.g., 'every day at noon').
    • parse_in(s): Parses relative time/durations (implied by naming convention).
    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
  10. Use Fugit::Cron to parse and calculate cron occurrences

    master

    The Fugit::Cron class allows you to parse cron strings and compute the next or previous occurrences of a schedule. You can initialize it using Fugit::Cron.parse(string) or Fugit::Cron.new(string).

    Key methods include:

    • #next_time(start_time): Returns the next occurrence after the provided start_time. If no argument is given, it uses the current time.
    • #previous_time(start_time): Returns the previous occurrence before the provided start_time.
    • #brute_frequency: Returns an array [shortest_delta, longest_delta, occurrence_count] representing the time between occurrences.
    • #rough_frequency: Returns a rough estimate of the frequency in seconds.
    • #match?(time): Returns true if the provided time (or time string) matches the cron schedule.
    require 'fugit'
    
    c = Fugit::Cron.parse('0 0 * *  sun')
    
    p c.next_time.to_s      # => next occurrence
    p c.previous_time.to_s  # => previous occurrence
    
    # With a specific start time
    p c.next_time(Time.parse('2024-06-01')).to_s
    
    # Frequency analysis
    p c.brute_frequency  # => [ shortest_delta, longest_delta, count ]
    
    # Matching
    p c.match?(Time.parse('2017-08-06')) # => true
  11. Parse specific points in time with Fugit::At

    master

    Use Fugit::At or the convenience methods Fugit.parse_at(s) and Fugit.parse(s) to parse specific points in time. These return EtOrbi::EoTime instances.

    You can include timezones in your input strings to ensure correct parsing.

    # Using Fugit::At
    Fugit::At.parse('2017-12-12 12:00:00 America/New_York').to_s
      # ==> "2017-12-12 12:00:00 -0500"
    
    # Using Fugit.parse_at
    Fugit.parse_at('2017-12-12').to_s
    
    # Using Fugit.parse
    Fugit.parse('2017-12-12 12:00:00 America/New_York').to_s
  12. Extract cron expressions using parse_cronish

    master

    Introduced in fugit 1.8.0, parse_cronish(s) and do_parse_cronish(s) are used when you expect a cron or 'every' natural expression but want to discard any trailing non-cron information.

    Unlike the general parse method, parse_cronish specifically targets Fugit::Cron instances. It will return nil (or raise an error for do_parse_cronish) if the input is not a cron/natural expression, even if it is a valid duration or date.

    require 'fugit'
    
    Fugit.parse_cronish('0 0 1 jan *').class             # ==> ::Fugit::Cron
    Fugit.parse_cronish('every saturday at noon').class  # ==> ::Fugit::Cron
    
    # Returns nil because it is a duration, not a cron expression
    Fugit.parse_cronish('12y12M')                        # ==> nil