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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/config/locales/en.yml +178 -0
  3. data/config/locales/es.yml +176 -0
  4. data/config/locales/ja.yml +107 -0
  5. data/lib/ice_cube.rb +92 -0
  6. data/lib/ice_cube/builders/hash_builder.rb +27 -0
  7. data/lib/ice_cube/builders/ical_builder.rb +59 -0
  8. data/lib/ice_cube/builders/string_builder.rb +76 -0
  9. data/lib/ice_cube/deprecated.rb +36 -0
  10. data/lib/ice_cube/errors/count_exceeded.rb +7 -0
  11. data/lib/ice_cube/errors/until_exceeded.rb +7 -0
  12. data/lib/ice_cube/flexible_hash.rb +40 -0
  13. data/lib/ice_cube/i18n.rb +24 -0
  14. data/lib/ice_cube/null_i18n.rb +28 -0
  15. data/lib/ice_cube/occurrence.rb +101 -0
  16. data/lib/ice_cube/parsers/hash_parser.rb +91 -0
  17. data/lib/ice_cube/parsers/ical_parser.rb +91 -0
  18. data/lib/ice_cube/parsers/yaml_parser.rb +19 -0
  19. data/lib/ice_cube/rule.rb +123 -0
  20. data/lib/ice_cube/rules/daily_rule.rb +16 -0
  21. data/lib/ice_cube/rules/hourly_rule.rb +16 -0
  22. data/lib/ice_cube/rules/minutely_rule.rb +16 -0
  23. data/lib/ice_cube/rules/monthly_rule.rb +16 -0
  24. data/lib/ice_cube/rules/secondly_rule.rb +15 -0
  25. data/lib/ice_cube/rules/weekly_rule.rb +16 -0
  26. data/lib/ice_cube/rules/yearly_rule.rb +16 -0
  27. data/lib/ice_cube/schedule.rb +529 -0
  28. data/lib/ice_cube/single_occurrence_rule.rb +28 -0
  29. data/lib/ice_cube/time_util.rb +328 -0
  30. data/lib/ice_cube/validated_rule.rb +184 -0
  31. data/lib/ice_cube/validations/count.rb +61 -0
  32. data/lib/ice_cube/validations/daily_interval.rb +54 -0
  33. data/lib/ice_cube/validations/day.rb +71 -0
  34. data/lib/ice_cube/validations/day_of_month.rb +55 -0
  35. data/lib/ice_cube/validations/day_of_week.rb +77 -0
  36. data/lib/ice_cube/validations/day_of_year.rb +61 -0
  37. data/lib/ice_cube/validations/fixed_value.rb +95 -0
  38. data/lib/ice_cube/validations/hour_of_day.rb +55 -0
  39. data/lib/ice_cube/validations/hourly_interval.rb +54 -0
  40. data/lib/ice_cube/validations/lock.rb +95 -0
  41. data/lib/ice_cube/validations/minute_of_hour.rb +54 -0
  42. data/lib/ice_cube/validations/minutely_interval.rb +54 -0
  43. data/lib/ice_cube/validations/month_of_year.rb +54 -0
  44. data/lib/ice_cube/validations/monthly_interval.rb +53 -0
  45. data/lib/ice_cube/validations/schedule_lock.rb +46 -0
  46. data/lib/ice_cube/validations/second_of_minute.rb +54 -0
  47. data/lib/ice_cube/validations/secondly_interval.rb +51 -0
  48. data/lib/ice_cube/validations/until.rb +57 -0
  49. data/lib/ice_cube/validations/weekly_interval.rb +67 -0
  50. data/lib/ice_cube/validations/yearly_interval.rb +53 -0
  51. data/lib/ice_cube/version.rb +5 -0
  52. data/spec/spec_helper.rb +64 -0
  53. 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