parse-cron 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/cron_parser.rb CHANGED
@@ -15,9 +15,12 @@ class CronParser
15
15
  end
16
16
 
17
17
  def to_time
18
- Time.utc(@year, @month, @day, @hour, @min, 0)
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
- next_value.nil? ? nudge_year(t) : t
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 nudge_date(t)
127
- spec = interpolate_weekdays(t.year, t.month)
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 nudge_hour(t)
134
- spec = time_specs[:hour]
135
- next_value = find_best_next(t.hour, spec)
136
- t.hour = next_value || spec.first
137
- next_value.nil? ? nudge_date(t) : t
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
- next_value.nil? ? nudge_hour(t) : t
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
- allowed.sort.find { |val| val > current }
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
@@ -1,5 +1,5 @@
1
1
  module Parse
2
2
  module Cron
3
- VERSION = "0.0.1"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
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
 
@@ -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-15T12:00", "2011-08-15T12:01"],
24
- ["* * * * *", "2011-08-15T02:25", "2011-08-15T02:26"],
25
- ["* * * * *", "2011-08-15T02:59", "2011-08-15T03:00"],
26
- ["*/15 * * * *", "2011-08-15T02:02", "2011-08-15T02:15"],
27
- ["*/15,25 * * * *", "2011-08-15T02:15", "2011-08-15T02:25"],
28
- ["30 3,6,9 * * *", "2011-08-15T02:15", "2011-08-15T03:30"],
29
- ["30 9 * * *", "2011-08-15T10:15", "2011-08-16T09:30"],
30
- ["30 9 * * *", "2011-08-31T10:15", "2011-09-01T09:30"],
31
- ["30 9 * * *", "2011-09-30T10:15", "2011-10-01T09:30"],
32
- ["0 9 * * *", "2011-12-31T10:15", "2012-01-01T09:00"],
33
- ["* * 12 * *", "2010-04-15T10:15", "2010-05-12T00:00"],
34
- ["* * * * 1,3", "2010-04-15T10:15", "2010-04-19T00:00"],
35
- ["0 0 1 1 *", "2010-04-15T10:15", "2011-01-01T00:00"],
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 = Time.xmlschema(now + ":00+00:00")
39
- expected_next = Time.xmlschema(expected_next + ":00+00:00")
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
- segments:
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-17 00:00:00 +02:00
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
- has_rdoc: true
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.4.2
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