Class: Wurk::Cron::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/wurk/cron.rb

Overview

5-field crontab + ‘@aliases`. No seconds field, no DOW name aliases (sidekiq-ent §2.2 uses numeric DOW only).

Constant Summary collapse

FIELDS =
[
  [0, 59],
  [0, 23],
  [1, 31],
  [1, 12],
  [0, 7]
].freeze
ALIASES =
{
  '@hourly' => '0 * * * *',
  '@daily' => '0 0 * * *',
  '@midnight' => '0 0 * * *',
  '@weekly' => '0 0 * * 0',
  '@monthly' => '0 0 1 * *',
  '@yearly' => '0 0 1 1 *',
  '@annually' => '0 0 1 1 *'
}.freeze
MAX_LOOKAHEAD_MINUTES =
366 * 24 * 60 * 4

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(expression) ⇒ Parser

Returns a new instance of Parser.



73
74
75
76
77
78
79
# File 'lib/wurk/cron.rb', line 73

def initialize(expression)
  parts = normalize_expression(expression)
  @expression = parts.join(' ')
  @fields = parts.each_with_index.map { |part, i| parse_field(part, *FIELDS[i]) }
  @dom_restricted = parts[2] != '*'
  @dow_restricted = parts[4] != '*'
end

Instance Attribute Details

#expressionObject (readonly)

Returns the value of attribute expression.



71
72
73
# File 'lib/wurk/cron.rb', line 71

def expression
  @expression
end

#fieldsObject (readonly)

Returns the value of attribute fields.



71
72
73
# File 'lib/wurk/cron.rb', line 71

def fields
  @fields
end

Class Method Details

.resolve_zone(name) ⇒ Object



230
231
232
233
234
235
236
237
# File 'lib/wurk/cron.rb', line 230

def resolve_zone(name)
  cached = @tz_cache[name]
  return cached || nil unless cached.nil?

  @tz_cache_mutex.synchronize do
    @tz_cache.fetch(name) { @tz_cache[name] = load_zone(name) } || nil
  end
end

Instance Method Details

#local_components(epoch, tz = nil) ⇒ Object

Local wall-clock [min, hour, day, mon, dow] for ‘epoch` in `tz`. Public so the Loop/Poller can detect a DST fall-back fold — two distinct UTC instants that share the same local minute — and avoid a double-fire.



112
113
114
# File 'lib/wurk/cron.rb', line 112

def local_components(epoch, tz = nil)
  wall_clock(epoch, tz)
end

#match?(time, tz = nil) ⇒ Boolean

Returns:

  • (Boolean)


105
106
107
# File 'lib/wurk/cron.rb', line 105

def match?(time, tz = nil)
  match_components?(wall_clock(time.to_i, tz))
end

#next(from = ::Time.now, tz = nil) ⇒ Object

Sidekiq Ent ‘Sidekiq::CronParser#next`: the next fire Time strictly after `from` (default now), or nil if the schedule never matches within the lookahead window. `tz` evaluates the schedule in a timezone (an AS::TimeZone / TZInfo::Tz / IANA String); default is UTC. Thin wrapper over `next_fire_at` so there’s a single crontab parser.



100
101
102
103
# File 'lib/wurk/cron.rb', line 100

def next(from = ::Time.now, tz = nil)
  epoch = next_fire_at(from.to_i, tz)
  epoch && ::Time.at(epoch)
end

#next_fire_at(from_epoch, tz = nil) ⇒ Object

Smallest UTC epoch strictly greater than ‘from_epoch` whose wall-clock components in `tz` match every field. Returns nil if no match within ~4 years (a malformed loop like Feb 29 in a non-leap span).



84
85
86
87
88
89
90
91
92
93
# File 'lib/wurk/cron.rb', line 84

def next_fire_at(from_epoch, tz = nil)
  t = ((from_epoch.to_i / 60) + 1) * 60
  MAX_LOOKAHEAD_MINUTES.times do
    wc = wall_clock(t, tz)
    return t if match_components?(wc)

    t += 60
  end
  nil
end