2018-10-26 00:12:43 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-04-04 05:44:25 -04:00
|
|
|
module Gitlab
|
|
|
|
module Ci
|
|
|
|
class CronParser
|
2019-08-31 15:25:25 -04:00
|
|
|
VALID_SYNTAX_SAMPLE_TIME_ZONE = 'UTC'
|
|
|
|
VALID_SYNTAX_SAMPLE_CRON = '* * * * *'
|
2017-04-04 05:44:25 -04:00
|
|
|
|
2021-08-31 05:08:57 -04:00
|
|
|
class << self
|
|
|
|
def parse_natural(expression, cron_timezone = 'UTC')
|
|
|
|
new(Fugit::Nat.parse(expression)&.original, cron_timezone)
|
|
|
|
end
|
|
|
|
|
|
|
|
# This method generates compatible expressions that can be
|
|
|
|
# parsed by Fugit::Nat.parse to generate a cron line.
|
|
|
|
# It takes start date of the cron and cadence in the following format:
|
|
|
|
# cadence = {
|
|
|
|
# unit: 'day/week/month/year'
|
|
|
|
# duration: 1
|
|
|
|
# }
|
|
|
|
def parse_natural_with_timestamp(starts_at, cadence)
|
|
|
|
case cadence[:unit]
|
|
|
|
when 'day' # Currently supports only 'every 1 day'.
|
|
|
|
"#{starts_at.min} #{starts_at.hour} * * *"
|
|
|
|
when 'week' # Currently supports only 'every 1 week'.
|
|
|
|
"#{starts_at.min} #{starts_at.hour} * * #{starts_at.wday}"
|
|
|
|
when 'month'
|
|
|
|
unless [1, 3, 6, 12].include?(cadence[:duration])
|
|
|
|
raise NotImplementedError, "The cadence #{cadence} is not supported"
|
|
|
|
end
|
|
|
|
|
|
|
|
"#{starts_at.min} #{starts_at.hour} #{starts_at.mday} #{fall_in_months(cadence[:duration], starts_at)} *"
|
|
|
|
when 'year' # Currently supports only 'every 1 year'.
|
|
|
|
"#{starts_at.min} #{starts_at.hour} #{starts_at.mday} #{starts_at.month} *"
|
|
|
|
else
|
|
|
|
raise NotImplementedError, "The cadence unit #{cadence[:unit]} is not implemented"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def fall_in_months(offset, start_date)
|
|
|
|
(1..(12 / offset)).map { |i| start_date.next_month(offset * i).month }.join(',')
|
|
|
|
end
|
2021-06-11 14:10:13 -04:00
|
|
|
end
|
|
|
|
|
2017-04-04 12:54:45 -04:00
|
|
|
def initialize(cron, cron_timezone = 'UTC')
|
2017-04-04 05:44:25 -04:00
|
|
|
@cron = cron
|
2018-04-28 19:10:36 -04:00
|
|
|
@cron_timezone = timezone_name(cron_timezone)
|
2017-04-04 05:44:25 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def next_time_from(time)
|
2020-05-14 08:08:21 -04:00
|
|
|
cron_line.next_time(time).utc.in_time_zone(Time.zone) if cron_line.present?
|
|
|
|
end
|
|
|
|
|
|
|
|
def previous_time_from(time)
|
|
|
|
cron_line.previous_time(time).utc.in_time_zone(Time.zone) if cron_line.present?
|
2017-04-04 05:44:25 -04:00
|
|
|
end
|
|
|
|
|
2017-04-04 06:14:49 -04:00
|
|
|
def cron_valid?
|
|
|
|
try_parse_cron(@cron, VALID_SYNTAX_SAMPLE_TIME_ZONE).present?
|
|
|
|
end
|
|
|
|
|
2017-04-04 12:54:45 -04:00
|
|
|
def cron_timezone_valid?
|
|
|
|
try_parse_cron(VALID_SYNTAX_SAMPLE_CRON, @cron_timezone).present?
|
2017-04-04 05:44:25 -04:00
|
|
|
end
|
|
|
|
|
2021-06-11 14:10:13 -04:00
|
|
|
def match?(time)
|
|
|
|
cron_line.match?(time)
|
|
|
|
end
|
|
|
|
|
2017-04-04 05:44:25 -04:00
|
|
|
private
|
|
|
|
|
2018-04-28 19:10:36 -04:00
|
|
|
def timezone_name(timezone)
|
|
|
|
ActiveSupport::TimeZone.find_tzinfo(timezone).name
|
|
|
|
rescue TZInfo::InvalidTimezoneIdentifier
|
|
|
|
timezone
|
|
|
|
end
|
|
|
|
|
2017-05-03 11:23:20 -04:00
|
|
|
# NOTE:
|
|
|
|
# cron_timezone can only accept timezones listed in TZInfo::Timezone.
|
|
|
|
# Aliases of Timezones from ActiveSupport::TimeZone are NOT accepted,
|
2019-01-08 10:36:38 -05:00
|
|
|
# because Fugit::Cron only supports TZInfo::Timezone.
|
2017-05-03 11:23:20 -04:00
|
|
|
#
|
|
|
|
# For example, those codes have the same effect.
|
|
|
|
# Time.zone = 'Pacific Time (US & Canada)' (ActiveSupport::TimeZone)
|
|
|
|
# Time.zone = 'America/Los_Angeles' (TZInfo::Timezone)
|
|
|
|
#
|
|
|
|
# However, try_parse_cron only accepts the latter format.
|
|
|
|
# try_parse_cron('* * * * *', 'Pacific Time (US & Canada)') -> Doesn't work
|
|
|
|
# try_parse_cron('* * * * *', 'America/Los_Angeles') -> Works
|
|
|
|
# If you want to know more, please take a look
|
|
|
|
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/values/time_zone.rb
|
2017-04-04 12:54:45 -04:00
|
|
|
def try_parse_cron(cron, cron_timezone)
|
2019-01-08 10:36:38 -05:00
|
|
|
Fugit::Cron.parse("#{cron} #{cron_timezone}")
|
2017-04-04 05:44:25 -04:00
|
|
|
end
|
2020-05-14 08:08:21 -04:00
|
|
|
|
|
|
|
def cron_line
|
|
|
|
@cron_line ||= try_parse_cron(@cron, @cron_timezone)
|
|
|
|
end
|
2017-04-04 05:44:25 -04:00
|
|
|
end
|
|
|
|
end
|
2017-04-04 12:59:24 -04:00
|
|
|
end
|