ice_cube_chosko 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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