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 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