chronic_cron 0.1.1

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.
Files changed (2) hide show
  1. data/lib/chronic_cron.rb +164 -0
  2. metadata +56 -0
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # file: chronic_cron.rb
4
+
5
+ require 'date'
6
+ require 'time'
7
+
8
+ class Array
9
+
10
+ def inflate()
11
+ Array.new(self.max_by {|x| x.length}.length).map do |x|
12
+ self.map{|x| x.length <= 1 ? x.first : x.shift}
13
+ end
14
+ end
15
+ end
16
+
17
+
18
+ class ChronicCron
19
+
20
+ def self.day_valid?(date)
21
+ year, month, day = date
22
+ last_day = DateTime.parse("%s-%s-%s" % [year, month.succ, 1]) - 1
23
+ day.to_i <= last_day.day
24
+ end
25
+
26
+ def self.parse(s)
27
+
28
+ raw_a = s.split
29
+ raw_a << '*' if raw_a.length <= 5 # add the year?
30
+
31
+ units = Time.now.to_a.values_at(1..4) + [nil, Time.now.year]
32
+
33
+ procs = {
34
+ min: lambda{|x, interval| x += (interval * 60).to_i},
35
+ hour: lambda{|x, interval| x += (interval * 60 * 60).to_i},
36
+ day: lambda{|x, interval| x += (interval * 60 * 60 * 24).to_i},
37
+ month: lambda{|x, interval|
38
+ date = x.to_a.values_at(1..5)
39
+ interval.times { date[3].succ! }
40
+ Time.parse("%s-%s-%s %s:%s" % date.reverse)},
41
+ year: lambda{|x, interval|
42
+ date = x.to_a.values_at(1..5)
43
+ interfval.times { date[4].succ! }
44
+ Time.parse("%s-%s-%s %s:%s" % date.reverse)}
45
+ }
46
+
47
+ dt = units.map do |start|
48
+ # convert * to time unit
49
+ lambda do |x| v2 = x.sub('*', start.to_s)
50
+ # split any times
51
+ multiples = v2.split(/,/)
52
+ range = multiples.map do |time|
53
+ s1, s2 = time.split(/-/)
54
+ s2 ? (s1..s2).to_a : s1
55
+ end
56
+ range.flatten
57
+ end
58
+
59
+ end
60
+
61
+ # take any repeater out of the unit value
62
+ raw_units, repeaters = [], []
63
+
64
+ raw_a.each do |x|
65
+ v1, v2 = x.split('/')
66
+ raw_units << v1
67
+ repeaters << v2
68
+ end
69
+
70
+ raw_date = raw_units.map.with_index {|x,i| dt[i].call(x) }
71
+
72
+ # expand the repeater
73
+
74
+ ceil = {min: 60, hour: 23, day: 31, month: 12}.values
75
+
76
+ if repeaters.any? then
77
+ repeaters.each_with_index do |x,i|
78
+ if x and not raw_a[i][/^\*/] then
79
+ raw_date[i] = raw_date[i].map {|y|
80
+ (y.to_i...ceil[i]).step(x.to_i).to_a.map(&:to_s)
81
+ }.flatten
82
+ else
83
+ raw_date[i]
84
+ end
85
+ end
86
+ end
87
+
88
+ dates = raw_date.inflate
89
+
90
+ a = dates.map do |date|
91
+ d = date.map{|x| x ? x.clone : nil}
92
+ wday, year = d.pop(2)
93
+ d << year
94
+
95
+ next unless day_valid? d.reverse.take 3
96
+ t = Time.parse("%s-%s-%s %s:%s" % d.reverse)
97
+
98
+ if t < Time.now and wday and wday != t.wday then
99
+ d[2], d[3] = Time.now.to_a.values_at(3,4).map(&:to_s)
100
+ t = Time.parse("%s-%s-%s %s:%s" % d.reverse)
101
+ t += (60 * 60 * 24) until t.wday == wday.to_i
102
+ end
103
+
104
+ i = 3
105
+ while t < Time.now and i >= 0 and raw_a[i][/\*/]
106
+ d[i] = Time.now.to_a[i+1].to_s
107
+ t = Time.parse("%s-%s-%s %s:%s" % d.reverse)
108
+ i -= 1
109
+ end
110
+
111
+ if t < Time.now then
112
+
113
+ if t.month < Time.now.month and raw_a[4] == '*' then
114
+ # increment the year
115
+ d[4].succ!
116
+ t = Time.parse("%s-%s-%s %s:%s" % d.reverse)
117
+
118
+ if repeaters[4] then
119
+ d[4].succ!
120
+ t = Time.parse("%s-%s-%s %s:%s" % d.reverse)
121
+ end
122
+ elsif t.day < Time.now.day and raw_a[3] == '*' then
123
+ # increment the month
124
+ if d[3].to_i <= 11 then
125
+ d[3].succ!
126
+ else
127
+ d[3] = '1'
128
+ d[4].succ!
129
+ end
130
+ t = Time.parse("%s-%s-%s %s:%s" % d.reverse)
131
+ elsif t.hour < Time.now.hour and raw_a[2] == '*' then
132
+ # increment the day
133
+ t += 60 * 60 * 24 * ((Time.now.day - d[2].to_i) + 1)
134
+ elsif t.min < Time.now.min and raw_a[1] == '*' then
135
+ # increment the hour
136
+ t += 60 * 60 * ((Time.now.hour - d[1].to_i) + 1)
137
+ elsif raw_a[0] == '*' then
138
+ # increment the minute
139
+ t += 60 * ((Time.now.min - d[0].to_i) + 1)
140
+ t = procs.values[i].call(t, repeaters[i].to_i) if repeaters[i]
141
+ end
142
+
143
+ end
144
+
145
+ if wday then
146
+ t += (60 * 60 * 24) until t.wday == wday.to_i
147
+ end
148
+
149
+ if t <= Time.now and repeaters.any? then
150
+
151
+ repeaters.each_with_index do |x,i|
152
+ if x then
153
+ t = procs.values[i].call(t, x.to_i)
154
+ end
155
+ end
156
+ end
157
+
158
+ t
159
+ end
160
+
161
+ a.compact.min
162
+ end
163
+
164
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chronic_cron
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.1
6
+ platform: ruby
7
+ authors: []
8
+
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-24 00:00:00 +01:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description:
18
+ email:
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - lib/chronic_cron.rb
27
+ has_rdoc: true
28
+ homepage:
29
+ licenses: []
30
+
31
+ post_install_message:
32
+ rdoc_options: []
33
+
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: "0"
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ requirements: []
49
+
50
+ rubyforge_project:
51
+ rubygems_version: 1.5.2
52
+ signing_key:
53
+ specification_version: 3
54
+ summary: chronic_cron
55
+ test_files: []
56
+