ice_cube_chosko 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/config/locales/en.yml +178 -0
- data/config/locales/es.yml +176 -0
- data/config/locales/ja.yml +107 -0
- data/lib/ice_cube.rb +92 -0
- data/lib/ice_cube/builders/hash_builder.rb +27 -0
- data/lib/ice_cube/builders/ical_builder.rb +59 -0
- data/lib/ice_cube/builders/string_builder.rb +76 -0
- data/lib/ice_cube/deprecated.rb +36 -0
- data/lib/ice_cube/errors/count_exceeded.rb +7 -0
- data/lib/ice_cube/errors/until_exceeded.rb +7 -0
- data/lib/ice_cube/flexible_hash.rb +40 -0
- data/lib/ice_cube/i18n.rb +24 -0
- data/lib/ice_cube/null_i18n.rb +28 -0
- data/lib/ice_cube/occurrence.rb +101 -0
- data/lib/ice_cube/parsers/hash_parser.rb +91 -0
- data/lib/ice_cube/parsers/ical_parser.rb +91 -0
- data/lib/ice_cube/parsers/yaml_parser.rb +19 -0
- data/lib/ice_cube/rule.rb +123 -0
- data/lib/ice_cube/rules/daily_rule.rb +16 -0
- data/lib/ice_cube/rules/hourly_rule.rb +16 -0
- data/lib/ice_cube/rules/minutely_rule.rb +16 -0
- data/lib/ice_cube/rules/monthly_rule.rb +16 -0
- data/lib/ice_cube/rules/secondly_rule.rb +15 -0
- data/lib/ice_cube/rules/weekly_rule.rb +16 -0
- data/lib/ice_cube/rules/yearly_rule.rb +16 -0
- data/lib/ice_cube/schedule.rb +529 -0
- data/lib/ice_cube/single_occurrence_rule.rb +28 -0
- data/lib/ice_cube/time_util.rb +328 -0
- data/lib/ice_cube/validated_rule.rb +184 -0
- data/lib/ice_cube/validations/count.rb +61 -0
- data/lib/ice_cube/validations/daily_interval.rb +54 -0
- data/lib/ice_cube/validations/day.rb +71 -0
- data/lib/ice_cube/validations/day_of_month.rb +55 -0
- data/lib/ice_cube/validations/day_of_week.rb +77 -0
- data/lib/ice_cube/validations/day_of_year.rb +61 -0
- data/lib/ice_cube/validations/fixed_value.rb +95 -0
- data/lib/ice_cube/validations/hour_of_day.rb +55 -0
- data/lib/ice_cube/validations/hourly_interval.rb +54 -0
- data/lib/ice_cube/validations/lock.rb +95 -0
- data/lib/ice_cube/validations/minute_of_hour.rb +54 -0
- data/lib/ice_cube/validations/minutely_interval.rb +54 -0
- data/lib/ice_cube/validations/month_of_year.rb +54 -0
- data/lib/ice_cube/validations/monthly_interval.rb +53 -0
- data/lib/ice_cube/validations/schedule_lock.rb +46 -0
- data/lib/ice_cube/validations/second_of_minute.rb +54 -0
- data/lib/ice_cube/validations/secondly_interval.rb +51 -0
- data/lib/ice_cube/validations/until.rb +57 -0
- data/lib/ice_cube/validations/weekly_interval.rb +67 -0
- data/lib/ice_cube/validations/yearly_interval.rb +53 -0
- data/lib/ice_cube/version.rb +5 -0
- data/spec/spec_helper.rb +64 -0
- metadata +166 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::Count
|
4
|
+
|
5
|
+
# Value reader for limit
|
6
|
+
def occurrence_count
|
7
|
+
@count
|
8
|
+
end
|
9
|
+
|
10
|
+
def count(max)
|
11
|
+
unless max.nil? || max.is_a?(Fixnum)
|
12
|
+
raise ArgumentError, "Expecting Fixnum or nil value for count, got #{max.inspect}"
|
13
|
+
end
|
14
|
+
@count = max
|
15
|
+
replace_validations_for(:count, max && [Validation.new(max, self)])
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
class Validation
|
20
|
+
|
21
|
+
attr_reader :rule, :count
|
22
|
+
|
23
|
+
def initialize(count, rule)
|
24
|
+
@count = count
|
25
|
+
@rule = rule
|
26
|
+
end
|
27
|
+
|
28
|
+
def type
|
29
|
+
:limit
|
30
|
+
end
|
31
|
+
|
32
|
+
def dst_adjust?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate(time, schedule)
|
37
|
+
raise CountExceeded if rule.uses && rule.uses >= count
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_s(builder)
|
41
|
+
builder.piece(:count) << count
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_hash(builder)
|
45
|
+
builder[:count] = count
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_ical(builder)
|
49
|
+
builder['COUNT'] << count
|
50
|
+
end
|
51
|
+
|
52
|
+
StringBuilder.register_formatter(:count) do |segments|
|
53
|
+
count = segments.first
|
54
|
+
IceCube::I18n.t('ice_cube.times', count: count)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::DailyInterval
|
4
|
+
|
5
|
+
# Add a new interval validation
|
6
|
+
def interval(interval)
|
7
|
+
@interval = normalized_interval(interval)
|
8
|
+
replace_validations_for(:interval, [Validation.new(@interval)])
|
9
|
+
clobber_base_validations(:wday, :day)
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
class Validation
|
14
|
+
|
15
|
+
attr_reader :interval
|
16
|
+
|
17
|
+
def initialize(interval)
|
18
|
+
@interval = interval
|
19
|
+
end
|
20
|
+
|
21
|
+
def type
|
22
|
+
:day
|
23
|
+
end
|
24
|
+
|
25
|
+
def dst_adjust?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate(step_time, schedule)
|
30
|
+
t0, t1 = schedule.start_time, step_time
|
31
|
+
days = Date.new(t1.year, t1.month, t1.day) -
|
32
|
+
Date.new(t0.year, t0.month, t0.day)
|
33
|
+
offset = (days % interval).nonzero?
|
34
|
+
interval - offset if offset
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_s(builder)
|
38
|
+
builder.base = IceCube::I18n.t('ice_cube.each_day', count: interval)
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_hash(builder)
|
42
|
+
builder[:interval] = interval
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_ical(builder)
|
46
|
+
builder['FREQ'] << 'DAILY'
|
47
|
+
builder['INTERVAL'] << interval unless interval == 1
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module IceCube
|
4
|
+
|
5
|
+
module Validations::Day
|
6
|
+
|
7
|
+
def day(*days)
|
8
|
+
days.flatten.each do |day|
|
9
|
+
unless day.is_a?(Fixnum) || day.is_a?(Symbol)
|
10
|
+
raise ArgumentError, "expecting Fixnum or Symbol value for day, got #{day.inspect}"
|
11
|
+
end
|
12
|
+
day = TimeUtil.sym_to_wday(day)
|
13
|
+
validations_for(:day) << Validation.new(day)
|
14
|
+
end
|
15
|
+
clobber_base_validations(:wday, :day)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
class Validation < Validations::FixedValue
|
20
|
+
|
21
|
+
attr_reader :day
|
22
|
+
alias :value :day
|
23
|
+
|
24
|
+
def initialize(day)
|
25
|
+
@day = day
|
26
|
+
end
|
27
|
+
|
28
|
+
def type
|
29
|
+
:wday
|
30
|
+
end
|
31
|
+
|
32
|
+
def dst_adjust?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_s(builder)
|
37
|
+
builder.piece(:day) << day
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_hash(builder)
|
41
|
+
builder.validations_array(:day) << day
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_ical(builder)
|
45
|
+
ical_day = IcalBuilder.fixnum_to_ical_day(day)
|
46
|
+
# Only add if there aren't others from day_of_week that override
|
47
|
+
if builder['BYDAY'].none? { |b| b.end_with?(ical_day) }
|
48
|
+
builder['BYDAY'] << ical_day
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
StringBuilder.register_formatter(:day) do |validation_days|
|
53
|
+
# sort the days
|
54
|
+
validation_days.sort!
|
55
|
+
# pick the right shortening, if applicable
|
56
|
+
if validation_days == [0, 6]
|
57
|
+
IceCube::I18n.t('ice_cube.on_weekends')
|
58
|
+
elsif validation_days == (1..5).to_a
|
59
|
+
IceCube::I18n.t('ice_cube.on_weekdays')
|
60
|
+
else
|
61
|
+
day_names = ->(d){ "#{IceCube::I18n.t("ice_cube.days_on")[d]}" }
|
62
|
+
segments = validation_days.map(&day_names)
|
63
|
+
IceCube::I18n.t('ice_cube.on_days', days: StringBuilder.sentence(segments))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::DayOfMonth
|
4
|
+
|
5
|
+
def day_of_month(*days)
|
6
|
+
days.flatten.each do |day|
|
7
|
+
unless day.is_a?(Fixnum)
|
8
|
+
raise ArgumentError, "expecting Fixnum value for day, got #{day.inspect}"
|
9
|
+
end
|
10
|
+
validations_for(:day_of_month) << Validation.new(day)
|
11
|
+
end
|
12
|
+
clobber_base_validations(:day, :wday)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
class Validation < Validations::FixedValue
|
17
|
+
|
18
|
+
attr_reader :day
|
19
|
+
alias :value :day
|
20
|
+
|
21
|
+
def initialize(day)
|
22
|
+
@day = day
|
23
|
+
end
|
24
|
+
|
25
|
+
def type
|
26
|
+
:day
|
27
|
+
end
|
28
|
+
|
29
|
+
def dst_adjust?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_s(builder)
|
34
|
+
builder.piece(:day_of_month) << StringBuilder.nice_number(day)
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_hash(builder)
|
38
|
+
builder.validations_array(:day_of_month) << day
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_ical(builder)
|
42
|
+
builder['BYMONTHDAY'] << day
|
43
|
+
end
|
44
|
+
|
45
|
+
StringBuilder.register_formatter(:day_of_month) do |entries|
|
46
|
+
sentence = StringBuilder.sentence(entries)
|
47
|
+
str = IceCube::I18n.t('ice_cube.days_of_month', count: entries.size, segments: sentence)
|
48
|
+
IceCube::I18n.t('ice_cube.on', sentence: str)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::DayOfWeek
|
4
|
+
|
5
|
+
def day_of_week(dows)
|
6
|
+
dows.each do |day, occs|
|
7
|
+
occs.each do |occ|
|
8
|
+
day = TimeUtil.sym_to_wday(day)
|
9
|
+
validations_for(:day_of_week) << Validation.new(day, occ)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
clobber_base_validations :day, :wday
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
class Validation
|
17
|
+
|
18
|
+
attr_reader :day, :occ
|
19
|
+
|
20
|
+
def initialize(day, occ)
|
21
|
+
@day = day
|
22
|
+
@occ = occ
|
23
|
+
end
|
24
|
+
|
25
|
+
def type
|
26
|
+
:day
|
27
|
+
end
|
28
|
+
|
29
|
+
def dst_adjust?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate(step_time, schedule)
|
34
|
+
wday = step_time.wday
|
35
|
+
offset = (day < wday) ? (7 - wday + day) : (day - wday)
|
36
|
+
wrapper = TimeUtil::TimeWrapper.new(step_time)
|
37
|
+
wrapper.add :day, offset
|
38
|
+
loop do
|
39
|
+
which_occ, num_occ = TimeUtil.which_occurrence_in_month(wrapper.to_time, day)
|
40
|
+
this_occ = (occ < 0) ? (num_occ + occ + 1) : (occ)
|
41
|
+
break offset if which_occ == this_occ
|
42
|
+
wrapper.add :day, 7
|
43
|
+
offset += 7
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_s(builder)
|
48
|
+
builder.piece(:day_of_week) << IceCube::I18n.t(
|
49
|
+
'ice_cube.days_of_week',
|
50
|
+
segments: StringBuilder.nice_number(occ),
|
51
|
+
day: IceCube::I18n.t('ice_cube.date.day_names')[day]
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_hash(builder)
|
56
|
+
builder.validations[:day_of_week] ||= {}
|
57
|
+
arr = (builder.validations[:day_of_week][day] ||= [])
|
58
|
+
arr << occ
|
59
|
+
end
|
60
|
+
|
61
|
+
def build_ical(builder)
|
62
|
+
ical_day = IcalBuilder.fixnum_to_ical_day(day)
|
63
|
+
# Delete any with this day and no occ first
|
64
|
+
builder['BYDAY'].delete_if { |d| d == ical_day }
|
65
|
+
builder['BYDAY'] << "#{occ}#{ical_day}"
|
66
|
+
end
|
67
|
+
|
68
|
+
StringBuilder.register_formatter(:day_of_week) do |segments|
|
69
|
+
sentence = segments.join(IceCube::I18n.t('ice_cube.array.two_words_connector'))
|
70
|
+
IceCube::I18n.t('ice_cube.on', sentence: sentence)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::DayOfYear
|
4
|
+
|
5
|
+
def day_of_year(*days)
|
6
|
+
days.flatten.each do |day|
|
7
|
+
unless day.is_a?(Fixnum)
|
8
|
+
raise ArgumentError, "expecting Fixnum value for day, got #{day.inspect}"
|
9
|
+
end
|
10
|
+
validations_for(:day_of_year) << Validation.new(day)
|
11
|
+
end
|
12
|
+
clobber_base_validations(:month, :day, :wday)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
class Validation
|
17
|
+
|
18
|
+
attr_reader :day
|
19
|
+
|
20
|
+
def initialize(day)
|
21
|
+
@day = day
|
22
|
+
end
|
23
|
+
|
24
|
+
def type
|
25
|
+
:day
|
26
|
+
end
|
27
|
+
|
28
|
+
def dst_adjust?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate(step_time, schedule)
|
33
|
+
days_in_year = TimeUtil.days_in_year(step_time)
|
34
|
+
yday = day < 0 ? day + days_in_year : day
|
35
|
+
offset = yday - step_time.yday
|
36
|
+
offset >= 0 ? offset : offset + days_in_year
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_s(builder)
|
40
|
+
builder.piece(:day_of_year) << StringBuilder.nice_number(day)
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_hash(builder)
|
44
|
+
builder.validations_array(:day_of_year) << day
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_ical(builder)
|
48
|
+
builder['BYYEARDAY'] << day
|
49
|
+
end
|
50
|
+
|
51
|
+
StringBuilder.register_formatter(:day_of_year) do |entries|
|
52
|
+
str = StringBuilder.sentence(entries)
|
53
|
+
sentence = IceCube::I18n.t('ice_cube.days_of_year', count: entries.size, segments: str)
|
54
|
+
IceCube::I18n.t('ice_cube.on', sentence: sentence)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
# This abstract validation class is used by the various "fixed-time" (e.g.
|
4
|
+
# day, day_of_month, hour_of_day) Validation and ScheduleLock::Validation
|
5
|
+
# modules. It is not a standalone rule validation module like the others.
|
6
|
+
#
|
7
|
+
# Given the including Validation's defined +type+ field, it will lock to the
|
8
|
+
# specified +value+ or else the corresponding time unit from the schedule's
|
9
|
+
# start_time
|
10
|
+
#
|
11
|
+
class Validations::FixedValue
|
12
|
+
|
13
|
+
INTERVALS = {:min => 60, :sec => 60, :hour => 24, :month => 12, :wday => 7}
|
14
|
+
|
15
|
+
def validate(time, schedule)
|
16
|
+
case type
|
17
|
+
when :day then validate_day_lock(time, schedule)
|
18
|
+
when :hour then validate_hour_lock(time, schedule)
|
19
|
+
else validate_interval_lock(time, schedule)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Validate if the current time unit matches the same unit from the schedule
|
26
|
+
# start time, returning the difference to the interval
|
27
|
+
#
|
28
|
+
def validate_interval_lock(time, schedule)
|
29
|
+
t0 = starting_unit(schedule.start_time)
|
30
|
+
t1 = time.send(type)
|
31
|
+
t0 >= t1 ? t0 - t1 : INTERVALS[type] - t1 + t0
|
32
|
+
end
|
33
|
+
|
34
|
+
# Lock the hour if explicitly set by hour_of_day, but allow for the nearest
|
35
|
+
# hour during DST start to keep the correct interval.
|
36
|
+
#
|
37
|
+
def validate_hour_lock(time, schedule)
|
38
|
+
h0 = starting_unit(schedule.start_time)
|
39
|
+
h1 = time.hour
|
40
|
+
if h0 >= h1
|
41
|
+
h0 - h1
|
42
|
+
else
|
43
|
+
if dst_offset = TimeUtil.dst_change(time)
|
44
|
+
h0 - h1 + dst_offset
|
45
|
+
else
|
46
|
+
24 - h1 + h0
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# For monthly rules that have no specified day value, the validation relies
|
52
|
+
# on the schedule start time and jumps to include every month even if it
|
53
|
+
# has fewer days than the schedule's start day.
|
54
|
+
#
|
55
|
+
# Negative day values (from month end) also include all months.
|
56
|
+
#
|
57
|
+
# Positive day values are taken literally so months with fewer days will
|
58
|
+
# be skipped.
|
59
|
+
#
|
60
|
+
def validate_day_lock(time, schedule)
|
61
|
+
days_in_month = TimeUtil.days_in_month(time)
|
62
|
+
date = Date.new(time.year, time.month, time.day)
|
63
|
+
|
64
|
+
if value && value < 0
|
65
|
+
start = TimeUtil.day_of_month(value, date)
|
66
|
+
month_overflow = days_in_month - TimeUtil.days_in_next_month(time)
|
67
|
+
elsif value && value > 0
|
68
|
+
start = value
|
69
|
+
month_overflow = 0
|
70
|
+
else
|
71
|
+
start = TimeUtil.day_of_month(schedule.start_time.day, date)
|
72
|
+
month_overflow = 0
|
73
|
+
end
|
74
|
+
|
75
|
+
sleeps = start - date.day
|
76
|
+
|
77
|
+
if value && value > 0
|
78
|
+
until_next_month = days_in_month + sleeps
|
79
|
+
else
|
80
|
+
until_next_month = start < 28 ? days_in_month : TimeUtil.days_to_next_month(date)
|
81
|
+
until_next_month += sleeps - month_overflow
|
82
|
+
end
|
83
|
+
|
84
|
+
sleeps >= 0 ? sleeps : until_next_month
|
85
|
+
end
|
86
|
+
|
87
|
+
def starting_unit(start_time)
|
88
|
+
start = value || start_time.send(type)
|
89
|
+
start = start % INTERVALS[type] if start < 0
|
90
|
+
start
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|