parse-cron 0.0.1 → 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.
- data/lib/cron_parser.rb +83 -33
- data/lib/parse-cron/version.rb +1 -1
- data/parse-cron.gemspec +1 -1
- data/spec/cron_parser_spec.rb +49 -15
- metadata +4 -22
data/lib/cron_parser.rb
CHANGED
@@ -15,9 +15,12 @@ class CronParser
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def to_time
|
18
|
-
Time.
|
18
|
+
Time.local(@year, @month, @day, @hour, @min, 0)
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
[year, month, day, hour, min].inspect
|
19
23
|
end
|
20
|
-
alias :inspect :to_time
|
21
24
|
end
|
22
25
|
|
23
26
|
SYMBOLS = {
|
@@ -52,17 +55,17 @@ class CronParser
|
|
52
55
|
def next(now = Time.now)
|
53
56
|
t = InternalTime.new(now)
|
54
57
|
|
55
|
-
unless time_specs[:month].include?(t.month)
|
58
|
+
unless time_specs[:month][0].include?(t.month)
|
56
59
|
nudge_month(t)
|
57
60
|
t.day = 0
|
58
61
|
end
|
59
62
|
|
60
|
-
unless t.day == 0 || interpolate_weekdays(t.year, t.month).include?(t.day)
|
63
|
+
unless t.day == 0 || interpolate_weekdays(t.year, t.month)[0].include?(t.day)
|
61
64
|
nudge_date(t)
|
62
65
|
t.hour = -1
|
63
66
|
end
|
64
67
|
|
65
|
-
unless time_specs[:hour].include?(t.hour)
|
68
|
+
unless time_specs[:hour][0].include?(t.hour)
|
66
69
|
nudge_hour(t)
|
67
70
|
t.min = -1
|
68
71
|
end
|
@@ -72,9 +75,34 @@ class CronParser
|
|
72
75
|
t.to_time
|
73
76
|
end
|
74
77
|
|
78
|
+
# returns the last occurence before the given date
|
79
|
+
def last(now = Time.now)
|
80
|
+
t = InternalTime.new(now)
|
81
|
+
|
82
|
+
unless time_specs[:month][0].include?(t.month)
|
83
|
+
nudge_month(t, :last)
|
84
|
+
t.day = 32
|
85
|
+
end
|
86
|
+
|
87
|
+
if t.day == 32 || !interpolate_weekdays(t.year, t.month)[0].include?(t.day)
|
88
|
+
nudge_date(t, :last)
|
89
|
+
t.hour = 24
|
90
|
+
end
|
91
|
+
|
92
|
+
unless time_specs[:hour][0].include?(t.hour)
|
93
|
+
nudge_hour(t, :last)
|
94
|
+
t.min = 60
|
95
|
+
end
|
96
|
+
|
97
|
+
# always nudge the minute
|
98
|
+
nudge_minute(t, :last)
|
99
|
+
t.to_time
|
100
|
+
end
|
101
|
+
|
102
|
+
|
75
103
|
SUBELEMENT_REGEX = %r{^(\d+)(-(\d+)(/(\d+))?)?$}
|
76
104
|
def parse_element(elem, allowed_range)
|
77
|
-
elem.split(',').map do |subel|
|
105
|
+
values = elem.split(',').map do |subel|
|
78
106
|
if subel =~ /^\*/
|
79
107
|
step = subel.length > 1 ? subel[2..-1].to_i : 1
|
80
108
|
stepped_range(allowed_range, step)
|
@@ -92,6 +120,8 @@ class CronParser
|
|
92
120
|
end
|
93
121
|
end
|
94
122
|
end.flatten.sort
|
123
|
+
|
124
|
+
[Set.new(values), values]
|
95
125
|
end
|
96
126
|
|
97
127
|
|
@@ -99,9 +129,14 @@ class CronParser
|
|
99
129
|
|
100
130
|
# returns a list of days which do both match time_spec[:dom] and time_spec[:dow]
|
101
131
|
def interpolate_weekdays(year, month)
|
132
|
+
@_interpolate_weekdays_cache ||= {}
|
133
|
+
@_interpolate_weekdays_cache["#{year}-#{month}"] ||= interpolate_weekdays_without_cache(year, month)
|
134
|
+
end
|
135
|
+
|
136
|
+
def interpolate_weekdays_without_cache(year, month)
|
102
137
|
t = Date.new(year, month, 1)
|
103
|
-
valid_mday = time_specs[:dom]
|
104
|
-
valid_wday = time_specs[:dow]
|
138
|
+
valid_mday = time_specs[:dom][0]
|
139
|
+
valid_wday = time_specs[:dow][0]
|
105
140
|
|
106
141
|
result = []
|
107
142
|
while t.month == month
|
@@ -109,39 +144,50 @@ class CronParser
|
|
109
144
|
t = t.succ
|
110
145
|
end
|
111
146
|
|
112
|
-
result
|
147
|
+
[Set.new(result), result]
|
113
148
|
end
|
114
149
|
|
115
|
-
def nudge_year(t)
|
116
|
-
t.year = t.year + 1
|
150
|
+
def nudge_year(t, dir = :next)
|
151
|
+
t.year = t.year + (dir == :next ? 1 : -1)
|
117
152
|
end
|
118
153
|
|
119
|
-
def nudge_month(t)
|
120
|
-
spec = time_specs[:month]
|
121
|
-
next_value = find_best_next(t.month, spec)
|
122
|
-
t.month = next_value || spec.first
|
123
|
-
|
154
|
+
def nudge_month(t, dir = :next)
|
155
|
+
spec = time_specs[:month][1]
|
156
|
+
next_value = find_best_next(t.month, spec, dir)
|
157
|
+
t.month = next_value || (dir == :next ? spec.first : spec.last)
|
158
|
+
|
159
|
+
nudge_year(t, dir) if next_value.nil?
|
160
|
+
|
161
|
+
# we changed the month, so its likely that the date is incorrect now
|
162
|
+
t.day = interpolate_weekdays(t.year, t.month)[1].last if dir == :last
|
124
163
|
end
|
125
164
|
|
126
|
-
def
|
127
|
-
|
128
|
-
next_value = find_best_next(t.day, spec)
|
129
|
-
t.day = next_value || spec.first
|
130
|
-
next_value.nil? ? nudge_month(t) : t
|
165
|
+
def date_valid?(t, dir = :next)
|
166
|
+
interpolate_weekdays(t.year, t.month)[0].include?(t.day)
|
131
167
|
end
|
132
168
|
|
133
|
-
def
|
134
|
-
spec =
|
135
|
-
next_value = find_best_next(t.
|
136
|
-
t.
|
137
|
-
|
169
|
+
def nudge_date(t, dir = :next, can_nudge_month = true)
|
170
|
+
spec = interpolate_weekdays(t.year, t.month)[1]
|
171
|
+
next_value = find_best_next(t.day, spec, dir)
|
172
|
+
t.day = next_value || (dir == :next ? spec.first : spec.last)
|
173
|
+
|
174
|
+
nudge_month(t, dir) if next_value.nil? && can_nudge_month
|
175
|
+
end
|
176
|
+
|
177
|
+
def nudge_hour(t, dir = :next)
|
178
|
+
spec = time_specs[:hour][1]
|
179
|
+
next_value = find_best_next(t.hour, spec, dir)
|
180
|
+
t.hour = next_value || (dir == :next ? spec.first : spec.last)
|
181
|
+
|
182
|
+
nudge_date(t, dir) if next_value.nil?
|
138
183
|
end
|
139
184
|
|
140
|
-
def nudge_minute(t)
|
141
|
-
spec = time_specs[:minute]
|
142
|
-
next_value = find_best_next(t.min, spec)
|
143
|
-
t.min = next_value || spec.first
|
144
|
-
|
185
|
+
def nudge_minute(t, dir = :next)
|
186
|
+
spec = time_specs[:minute][1]
|
187
|
+
next_value = find_best_next(t.min, spec, dir)
|
188
|
+
t.min = next_value || (dir == :next ? spec.first : spec.last)
|
189
|
+
|
190
|
+
nudge_hour(t, dir) if next_value.nil?
|
145
191
|
end
|
146
192
|
|
147
193
|
def time_specs
|
@@ -178,7 +224,11 @@ class CronParser
|
|
178
224
|
|
179
225
|
# returns the smallest element from allowed which is greater than current
|
180
226
|
# returns nil if no matching value was found
|
181
|
-
def find_best_next(current, allowed)
|
182
|
-
|
227
|
+
def find_best_next(current, allowed, dir)
|
228
|
+
if dir == :next
|
229
|
+
allowed.sort.find { |val| val > current }
|
230
|
+
else
|
231
|
+
allowed.sort.reverse.find { |val| val < current }
|
232
|
+
end
|
183
233
|
end
|
184
234
|
end
|
data/lib/parse-cron/version.rb
CHANGED
data/parse-cron.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
s.authors = ["Michael Siebert"]
|
10
10
|
s.email = ["siebertm85@googlemail.com"]
|
11
|
-
s.homepage = ""
|
11
|
+
s.homepage = "https://github.com/siebertm/parse-cron"
|
12
12
|
s.summary = %q{Parses cron expressions and calculates the next occurence}
|
13
13
|
s.description = %q{Parses cron expressions and calculates the next occurence}
|
14
14
|
|
data/spec/cron_parser_spec.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
require "time"
|
2
2
|
require "./spec/spec_helper"
|
3
3
|
require "cron_parser"
|
4
|
+
require "time"
|
5
|
+
|
6
|
+
def parse_date(str)
|
7
|
+
dt = DateTime.strptime(str, "%Y-%m-%d %H:%M")
|
8
|
+
Time.local(dt.year, dt.month, dt.day, dt.hour, dt.min, 0)
|
9
|
+
end
|
4
10
|
|
5
11
|
describe "CronParser#parse_element" do
|
6
12
|
[
|
@@ -20,23 +26,23 @@ end
|
|
20
26
|
|
21
27
|
describe "CronParser#next" do
|
22
28
|
[
|
23
|
-
["* * * * *", "2011-08-
|
24
|
-
["* * * * *", "2011-08-
|
25
|
-
["* * * * *", "2011-08-
|
26
|
-
["*/15 * * * *", "2011-08-
|
27
|
-
["*/15,25 * * * *", "2011-08-
|
28
|
-
["30 3,6,9 * * *", "2011-08-
|
29
|
-
["30 9 * * *", "2011-08-
|
30
|
-
["30 9 * * *", "2011-08-
|
31
|
-
["30 9 * * *", "2011-09-
|
32
|
-
["0 9 * * *", "2011-12-
|
33
|
-
["* * 12 * *", "2010-04-
|
34
|
-
["* * * * 1,3", "2010-04-
|
35
|
-
["0 0 1 1 *", "2010-04-
|
29
|
+
["* * * * *", "2011-08-15 12:00", "2011-08-15 12:01"],
|
30
|
+
["* * * * *", "2011-08-15 02:25", "2011-08-15 02:26"],
|
31
|
+
["* * * * *", "2011-08-15 02:59", "2011-08-15 03:00"],
|
32
|
+
["*/15 * * * *", "2011-08-15 02:02", "2011-08-15 02:15"],
|
33
|
+
["*/15,25 * * * *", "2011-08-15 02:15", "2011-08-15 02:25"],
|
34
|
+
["30 3,6,9 * * *", "2011-08-15 02:15", "2011-08-15 03:30"],
|
35
|
+
["30 9 * * *", "2011-08-15 10:15", "2011-08-16 09:30"],
|
36
|
+
["30 9 * * *", "2011-08-31 10:15", "2011-09-01 09:30"],
|
37
|
+
["30 9 * * *", "2011-09-30 10:15", "2011-10-01 09:30"],
|
38
|
+
["0 9 * * *", "2011-12-31 10:15", "2012-01-01 09:00"],
|
39
|
+
["* * 12 * *", "2010-04-15 10:15", "2010-05-12 00:00"],
|
40
|
+
["* * * * 1,3", "2010-04-15 10:15", "2010-04-19 00:00"],
|
41
|
+
["0 0 1 1 *", "2010-04-15 10:15", "2011-01-01 00:00"],
|
36
42
|
].each do |line, now, expected_next|
|
37
43
|
it "should return #{expected_next} for '#{line}' when now is #{now}" do
|
38
|
-
now =
|
39
|
-
expected_next =
|
44
|
+
now = parse_date(now)
|
45
|
+
expected_next = parse_date(expected_next)
|
40
46
|
|
41
47
|
parser = CronParser.new(line)
|
42
48
|
|
@@ -44,3 +50,31 @@ describe "CronParser#next" do
|
|
44
50
|
end
|
45
51
|
end
|
46
52
|
end
|
53
|
+
|
54
|
+
describe "CronParser#last" do
|
55
|
+
[
|
56
|
+
["* * * * *", "2011-08-15 12:00", "2011-08-15 11:59"],
|
57
|
+
["* * * * *", "2011-08-15 02:25", "2011-08-15 02:24"],
|
58
|
+
["* * * * *", "2011-08-15 03:00", "2011-08-15 02:59"],
|
59
|
+
["*/15 * * * *", "2011-08-15 02:02", "2011-08-15 02:00"],
|
60
|
+
["*/15,45 * * * *", "2011-08-15 02:55", "2011-08-15 02:45"],
|
61
|
+
["*/15,25 * * * *", "2011-08-15 02:35", "2011-08-15 02:30"],
|
62
|
+
["30 3,6,9 * * *", "2011-08-15 02:15", "2011-08-14 09:30"],
|
63
|
+
["30 9 * * *", "2011-08-15 10:15", "2011-08-15 09:30"],
|
64
|
+
["30 9 * * *", "2011-09-01 08:15", "2011-08-31 09:30"],
|
65
|
+
["30 9 * * *", "2011-10-01 08:15", "2011-09-30 09:30"],
|
66
|
+
["0 9 * * *", "2012-01-01 00:15", "2011-12-31 09:00"],
|
67
|
+
["* * 12 * *", "2010-04-15 10:15", "2010-04-12 23:59"],
|
68
|
+
["* * * * 1,3", "2010-04-15 10:15", "2010-04-14 23:59"],
|
69
|
+
["0 0 1 1 *", "2010-04-15 10:15", "2010-01-01 00:00"],
|
70
|
+
].each do |line, now, expected_next|
|
71
|
+
it "should return #{expected_next} for '#{line}' when now is #{now}" do
|
72
|
+
now = parse_date(now)
|
73
|
+
expected_next = parse_date(expected_next)
|
74
|
+
|
75
|
+
parser = CronParser.new(line)
|
76
|
+
|
77
|
+
parser.last(now).should == expected_next
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parse-cron
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 29
|
5
4
|
prerelease:
|
6
|
-
|
7
|
-
- 0
|
8
|
-
- 0
|
9
|
-
- 1
|
10
|
-
version: 0.0.1
|
5
|
+
version: 0.1.0
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Michael Siebert
|
@@ -15,8 +10,7 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2011-08-
|
19
|
-
default_executable:
|
13
|
+
date: 2011-08-18 00:00:00 Z
|
20
14
|
dependencies:
|
21
15
|
- !ruby/object:Gem::Dependency
|
22
16
|
name: rspec
|
@@ -26,11 +20,6 @@ dependencies:
|
|
26
20
|
requirements:
|
27
21
|
- - ~>
|
28
22
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 23
|
30
|
-
segments:
|
31
|
-
- 2
|
32
|
-
- 6
|
33
|
-
- 0
|
34
23
|
version: 2.6.0
|
35
24
|
type: :development
|
36
25
|
version_requirements: *id001
|
@@ -54,8 +43,7 @@ files:
|
|
54
43
|
- parse-cron.gemspec
|
55
44
|
- spec/cron_parser_spec.rb
|
56
45
|
- spec/spec_helper.rb
|
57
|
-
|
58
|
-
homepage: ""
|
46
|
+
homepage: https://github.com/siebertm/parse-cron
|
59
47
|
licenses: []
|
60
48
|
|
61
49
|
post_install_message:
|
@@ -68,23 +56,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
56
|
requirements:
|
69
57
|
- - ">="
|
70
58
|
- !ruby/object:Gem::Version
|
71
|
-
hash: 3
|
72
|
-
segments:
|
73
|
-
- 0
|
74
59
|
version: "0"
|
75
60
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
61
|
none: false
|
77
62
|
requirements:
|
78
63
|
- - ">="
|
79
64
|
- !ruby/object:Gem::Version
|
80
|
-
hash: 3
|
81
|
-
segments:
|
82
|
-
- 0
|
83
65
|
version: "0"
|
84
66
|
requirements: []
|
85
67
|
|
86
68
|
rubyforge_project: parse-cron
|
87
|
-
rubygems_version: 1.
|
69
|
+
rubygems_version: 1.7.2
|
88
70
|
signing_key:
|
89
71
|
specification_version: 3
|
90
72
|
summary: Parses cron expressions and calculates the next occurence
|