on_calendar 0.1.6 → 0.1.7
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 +4 -4
- data/lib/on_calendar/condition/base.rb +14 -2
- data/lib/on_calendar/condition/day_of_month.rb +4 -4
- data/lib/on_calendar/condition/day_of_week.rb +0 -6
- data/lib/on_calendar/condition/hour.rb +21 -0
- data/lib/on_calendar/condition/minute.rb +23 -0
- data/lib/on_calendar/condition/year.rb +20 -4
- data/lib/on_calendar/parser.rb +32 -10
- data/lib/on_calendar/version.rb +1 -1
- data/lib/on_calendar.rb +1 -0
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f0f1bc76c2e4b7726e431fd1966f3eeb249ac1d1a37a922d8d7faf146609e569
|
|
4
|
+
data.tar.gz: 80a788fc4eff681edca8761d07753d5a88a2254639a97682ab1cea150ca68b90
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8016805896b6eb2f46aae718954a1c5e7506e0473f6b34221b6f654434fdf3df6fec342a0c4167507da38a72679df756621a897ef44896657227252866544a38
|
|
7
|
+
data.tar.gz: e1b1b69f687f36f354ca69102ed2d4f8aba996ac8272bd850a03216c906e03d0caaa157b9b74730a87e80f70132043610e29cb4fca98cb8d78486f3c6054522b
|
|
@@ -19,10 +19,15 @@ module OnCalendar
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# Some subclasses need more context for RANGE
|
|
22
|
-
def range
|
|
22
|
+
def range(clamp: nil)
|
|
23
23
|
self.class::RANGE
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
# Provide min / max of range for validation
|
|
27
|
+
def self.range_bounds
|
|
28
|
+
self.const_get(:RANGE).minmax
|
|
29
|
+
end
|
|
30
|
+
|
|
26
31
|
# Match this condition
|
|
27
32
|
# - If wild card return true
|
|
28
33
|
# No step:
|
|
@@ -70,7 +75,7 @@ module OnCalendar
|
|
|
70
75
|
return 1 if wildcard
|
|
71
76
|
|
|
72
77
|
# Build array to find needle_index
|
|
73
|
-
arr =
|
|
78
|
+
arr = range(clamp: range_args).to_a
|
|
74
79
|
needle_index = arr.index(current)
|
|
75
80
|
|
|
76
81
|
return nil if needle_index.nil?
|
|
@@ -119,6 +124,13 @@ module OnCalendar
|
|
|
119
124
|
end
|
|
120
125
|
distance
|
|
121
126
|
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
# Determine if change in DST between start/end of day
|
|
131
|
+
def dst_day?(clamp:)
|
|
132
|
+
clamp.beginning_of_day.dst? != clamp.end_of_day.dst?
|
|
133
|
+
end
|
|
122
134
|
end
|
|
123
135
|
end
|
|
124
136
|
end
|
|
@@ -7,11 +7,11 @@ module OnCalendar
|
|
|
7
7
|
|
|
8
8
|
# NOTE: by default we validate number in default range but this needs to be context aware
|
|
9
9
|
# because not all months have the same number of days
|
|
10
|
-
def range(
|
|
11
|
-
if
|
|
12
|
-
RANGE
|
|
10
|
+
def range(clamp: nil)
|
|
11
|
+
if clamp.present?
|
|
12
|
+
(RANGE.min..Time.days_in_month(clamp.month, clamp.year))
|
|
13
13
|
else
|
|
14
|
-
|
|
14
|
+
RANGE
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
end
|
|
@@ -4,12 +4,6 @@ module OnCalendar
|
|
|
4
4
|
module Condition
|
|
5
5
|
class DayOfWeek < Base
|
|
6
6
|
RANGE = (0..6)
|
|
7
|
-
|
|
8
|
-
# Utility function to pass our min,max range to the segment parser
|
|
9
|
-
# this helps dealing with when the parser comes back with 6..0
|
|
10
|
-
def self.range_bounds
|
|
11
|
-
RANGE.minmax
|
|
12
|
-
end
|
|
13
7
|
end
|
|
14
8
|
end
|
|
15
9
|
end
|
|
@@ -4,6 +4,27 @@ module OnCalendar
|
|
|
4
4
|
module Condition
|
|
5
5
|
class Hour < Base
|
|
6
6
|
RANGE = (0..23)
|
|
7
|
+
|
|
8
|
+
# NOTE: by default we validate number in default range but this needs to be context aware
|
|
9
|
+
# because not all days have all hours (ie: DST changes)
|
|
10
|
+
def range(clamp: nil)
|
|
11
|
+
# If we are dealing with DST
|
|
12
|
+
if clamp.present? and dst_day?(clamp: clamp)
|
|
13
|
+
hours = []
|
|
14
|
+
cursor = clamp.beginning_of_day
|
|
15
|
+
day = clamp.day
|
|
16
|
+
zone = clamp.zone
|
|
17
|
+
# Record each hour of the day until we change day || zone
|
|
18
|
+
loop do
|
|
19
|
+
hours << cursor.hour
|
|
20
|
+
cursor = cursor + 1.hour
|
|
21
|
+
break if cursor.day != day or clamp.zone != zone
|
|
22
|
+
end
|
|
23
|
+
hours
|
|
24
|
+
else
|
|
25
|
+
RANGE
|
|
26
|
+
end
|
|
27
|
+
end
|
|
7
28
|
end
|
|
8
29
|
end
|
|
9
30
|
end
|
|
@@ -4,6 +4,29 @@ module OnCalendar
|
|
|
4
4
|
module Condition
|
|
5
5
|
class Minute < Base
|
|
6
6
|
RANGE = (0..59)
|
|
7
|
+
|
|
8
|
+
# NOTE: by default we validate number in default range but this needs to be context aware
|
|
9
|
+
# because not all days have all minutes (ie: DST changes with 30 mins)
|
|
10
|
+
# NOTE: With Condition::Hour we check dst_day? I haven't been able to work out how to check for DST change within an hour without jumping boundaries of DST. Therefore we check the entire day. This might have a small performance impact but we generally aren't looping over each hour when checking conditions.
|
|
11
|
+
def range(clamp: nil)
|
|
12
|
+
# If we are dealing with DST
|
|
13
|
+
if clamp.present? and dst_day?(clamp: clamp)
|
|
14
|
+
mins = []
|
|
15
|
+
# NOTE: We can't use Time.beginning_of_hour because we may jump across DST boundary
|
|
16
|
+
cursor = clamp.end_of_hour
|
|
17
|
+
hour = clamp.hour
|
|
18
|
+
zone = clamp.zone
|
|
19
|
+
# Record each minute of the hour until we change hour || zone
|
|
20
|
+
loop do
|
|
21
|
+
mins << cursor.min
|
|
22
|
+
cursor = cursor - 1.minute
|
|
23
|
+
break if cursor.hour != hour or cursor.zone != zone
|
|
24
|
+
end
|
|
25
|
+
mins.reverse
|
|
26
|
+
else
|
|
27
|
+
RANGE
|
|
28
|
+
end
|
|
29
|
+
end
|
|
7
30
|
end
|
|
8
31
|
end
|
|
9
32
|
end
|
|
@@ -8,14 +8,30 @@ module OnCalendar
|
|
|
8
8
|
def initialize(base: nil, step: nil, wildcard: false)
|
|
9
9
|
# Translate short year to long
|
|
10
10
|
unless base.nil?
|
|
11
|
-
if
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
if base.is_a?(Range)
|
|
12
|
+
values = [
|
|
13
|
+
translate_short_year(base.begin),
|
|
14
|
+
translate_short_year(base.end)
|
|
15
|
+
]
|
|
16
|
+
base = (values.min..values.max)
|
|
17
|
+
else
|
|
18
|
+
base = translate_short_year(base)
|
|
15
19
|
end
|
|
16
20
|
end
|
|
17
21
|
super
|
|
18
22
|
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def translate_short_year(base)
|
|
27
|
+
if (0..69).cover?(base)
|
|
28
|
+
base += 2000
|
|
29
|
+
elsif (70..99).cover?(base)
|
|
30
|
+
base += 1900
|
|
31
|
+
else
|
|
32
|
+
base
|
|
33
|
+
end
|
|
34
|
+
end
|
|
19
35
|
end
|
|
20
36
|
end
|
|
21
37
|
end
|
data/lib/on_calendar/parser.rb
CHANGED
|
@@ -28,7 +28,7 @@ module OnCalendar
|
|
|
28
28
|
@expression = expression
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def next(count=1, clamp: timezone.now)
|
|
31
|
+
def next(count=1, clamp: timezone.now, debug: false)
|
|
32
32
|
raise OnCalendar::Parser::Error, "Clamp must be instance of Time" unless clamp.is_a?(Time)
|
|
33
33
|
|
|
34
34
|
# Translate to correct timezone and add 1.second to ensure
|
|
@@ -37,7 +37,7 @@ module OnCalendar
|
|
|
37
37
|
|
|
38
38
|
results = []
|
|
39
39
|
count.times do
|
|
40
|
-
result = iterate(clamp: clamp)
|
|
40
|
+
result = iterate(clamp: clamp, debug: debug)
|
|
41
41
|
break if result.nil?
|
|
42
42
|
|
|
43
43
|
clamp = result + 1.second
|
|
@@ -55,8 +55,9 @@ module OnCalendar
|
|
|
55
55
|
|
|
56
56
|
private
|
|
57
57
|
|
|
58
|
-
def iterate(clamp:)
|
|
58
|
+
def iterate(clamp:, debug: false)
|
|
59
59
|
iterations = 0
|
|
60
|
+
output = [["-", clamp.to_s, "", ""]] if debug
|
|
60
61
|
|
|
61
62
|
while true
|
|
62
63
|
# Fail safe
|
|
@@ -84,8 +85,7 @@ module OnCalendar
|
|
|
84
85
|
days_of_month: {
|
|
85
86
|
base_method: :day,
|
|
86
87
|
changes: { hour: 0, min: 0, sec: 0 },
|
|
87
|
-
increment_method: :days
|
|
88
|
-
range_args: ->(clamp) { { year: clamp.year, month: clamp.month } }
|
|
88
|
+
increment_method: :days
|
|
89
89
|
},
|
|
90
90
|
days_of_week: {
|
|
91
91
|
base_method: :wday,
|
|
@@ -107,14 +107,18 @@ module OnCalendar
|
|
|
107
107
|
# Do we miss all condition matches - thus increment
|
|
108
108
|
next if matches_any_conditions?(field: field, base: clamp.send(values[:base_method]))
|
|
109
109
|
|
|
110
|
-
# Do we need any range arguments? If so calculate
|
|
111
|
-
range_args = values[:range_args].call(clamp) if values.key?(:range_args) || nil
|
|
112
110
|
# Determine distances required to jump to next match
|
|
113
111
|
distances = send(field).map do |condition|
|
|
114
|
-
condition.distance_to_next(clamp.send(values[:base_method]), range_args:
|
|
112
|
+
condition.distance_to_next(clamp.send(values[:base_method]), range_args: clamp)
|
|
115
113
|
end.sort!
|
|
116
114
|
# Check for only nil - if so impossible to compute bail
|
|
117
|
-
|
|
115
|
+
if distances.compact.empty?
|
|
116
|
+
if debug
|
|
117
|
+
output << [iterations, clamp.to_s, "impossible", ""]
|
|
118
|
+
debug_table(output)
|
|
119
|
+
end
|
|
120
|
+
return nil
|
|
121
|
+
end
|
|
118
122
|
|
|
119
123
|
# Increment by field method
|
|
120
124
|
method = values[:increment_method] || field
|
|
@@ -123,6 +127,13 @@ module OnCalendar
|
|
|
123
127
|
clamp = clamp.change(**values[:changes]) if values.key?(:changes)
|
|
124
128
|
# Force re-check everything by marking manipulation
|
|
125
129
|
field_manipulation = true
|
|
130
|
+
# Debug
|
|
131
|
+
output << [
|
|
132
|
+
iterations,
|
|
133
|
+
clamp.to_s,
|
|
134
|
+
field.to_s,
|
|
135
|
+
distances.min
|
|
136
|
+
] if debug
|
|
126
137
|
break
|
|
127
138
|
end
|
|
128
139
|
|
|
@@ -131,9 +142,20 @@ module OnCalendar
|
|
|
131
142
|
field_manipulation ? next : break
|
|
132
143
|
end
|
|
133
144
|
|
|
145
|
+
# Output debug table
|
|
146
|
+
debug_table(output) if debug
|
|
147
|
+
|
|
134
148
|
clamp
|
|
135
149
|
end
|
|
136
150
|
|
|
151
|
+
def debug_table(rows)
|
|
152
|
+
table = Terminal::Table.new do |t|
|
|
153
|
+
t.headings = ["Iteration", "Datetime", "Function", "Distance"]
|
|
154
|
+
t.rows = rows
|
|
155
|
+
end
|
|
156
|
+
puts table
|
|
157
|
+
end
|
|
158
|
+
|
|
137
159
|
def parse(expression)
|
|
138
160
|
raise OnCalendar::Parser::Error, "Expression must be a string" unless expression.is_a?(String)
|
|
139
161
|
raise OnCalendar::Parser::Error, "Expression cannot be empty" if expression.empty?
|
|
@@ -236,7 +258,7 @@ module OnCalendar
|
|
|
236
258
|
begin
|
|
237
259
|
parsed = OnCalendar::Segment.parse(segments.shift, max: max, min: min)
|
|
238
260
|
if parsed.nil?
|
|
239
|
-
# We are a
|
|
261
|
+
# We are a wildcard
|
|
240
262
|
conditions[idx] = [OnCalendar::Condition.const_get(klass).new(wildcard: true)]
|
|
241
263
|
else
|
|
242
264
|
# Lets build conditions with parsed
|
data/lib/on_calendar/version.rb
CHANGED
data/lib/on_calendar.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: on_calendar
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ben Passmore
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '8.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: terminal-table
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 4.0.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 4.0.0
|
|
26
40
|
email:
|
|
27
41
|
- contact@passbe.com
|
|
28
42
|
executables: []
|