perfectsched 0.7.6 → 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,4 +1,9 @@
1
1
 
2
+ == 2011-12-15 version 0.7.7
3
+
4
+ * Added timezone support
5
+
6
+
2
7
  == 2011-12-08 version 0.7.6
3
8
 
4
9
  * RDBBackend doesn't create table automatically
@@ -3,15 +3,16 @@ module PerfectSched
3
3
 
4
4
 
5
5
  class Task
6
- def initialize(id, time, cron, delay, data)
6
+ def initialize(id, time, cron, delay, data, timezone=nil)
7
7
  @id = id
8
8
  @time = time
9
9
  @cron = cron
10
10
  @delay = delay
11
11
  @data = data
12
+ @timezone = timezone
12
13
  end
13
14
 
14
- attr_reader :id, :time, :cron, :delay, :data
15
+ attr_reader :id, :time, :cron, :delay, :data, :timezone
15
16
  end
16
17
 
17
18
 
@@ -33,14 +34,15 @@ class Backend
33
34
  end
34
35
 
35
36
  # => true (success) or nil (already exists)
36
- def add(id, cron, delay, data, start_time)
37
- first_time = @croncalc.next_time(cron, start_time.to_i)
37
+ def add(id, cron, delay, data, start_time, timezone=nil)
38
+ timezone = TZInfo::Timezone.get(timezone).name if timezone # normalize
39
+ first_time = @croncalc.next_time(cron, start_time.to_i, timezone)
38
40
  timeout = first_time + delay
39
- add_checked(id, cron, delay, data, first_time, timeout)
41
+ add_checked(id, cron, delay, data, first_time, timeout, timezone)
40
42
  end
41
43
 
42
44
  # => true (success) or nil (already exists)
43
- def add_checked(id, cron, delay, data, next_time, timeout)
45
+ def add_checked(id, cron, delay, data, next_time, timeout, timezone)
44
46
  end
45
47
 
46
48
  # => true (success) or false (not found, canceled or finished)
@@ -48,19 +50,20 @@ class Backend
48
50
  end
49
51
 
50
52
  # => true (success) or false (not found)
51
- def modify(id, cron, delay, data)
53
+ def modify(id, cron, delay, data, timezone)
52
54
  cron = cron.strip
53
- @croncalc.next_time(cron, 0)
54
- modify_checked(id, cron, delay, data)
55
+ @croncalc.next_time(cron, 0, timezone)
56
+ modify_checked(id, cron, delay, data, timezone)
55
57
  end
56
58
 
57
- def modify_checked(id, cron, delay, data)
59
+ def modify_checked(id, cron, delay, data, timezone)
58
60
  end
59
61
 
60
62
  # => true (success) or false (not found)
61
63
  def modify_sched(id, cron, delay)
64
+ cron_, delay_, data_, timezone = get(id)
62
65
  cron = cron.strip
63
- @croncalc.next_time(cron, 0)
66
+ @croncalc.next_time(cron, 0, timezone)
64
67
  modify_sched_checked(id, cron, delay)
65
68
  end
66
69
 
@@ -15,37 +15,25 @@ class RDBBackend < Backend
15
15
  }
16
16
  end
17
17
 
18
- private
19
- #def init_db(type)
20
- # sql = ''
21
- # case type
22
- # when /mysql/i
23
- # sql << "CREATE TABLE IF NOT EXISTS `#{@table}` ("
24
- # sql << " id VARCHAR(256) NOT NULL,"
25
- # sql << " timeout INT NOT NULL,"
26
- # sql << " next_time INT NOT NULL,"
27
- # sql << " cron VARCHAR(128) NOT NULL,"
28
- # sql << " delay INT NOT NULL,"
29
- # sql << " data BLOB NOT NULL,"
30
- # sql << " PRIMARY KEY (id)"
31
- # sql << ") ENGINE=INNODB;"
32
- # else
33
- # sql << "CREATE TABLE IF NOT EXISTS `#{@table}` ("
34
- # sql << " id VARCHAR(256) NOT NULL,"
35
- # sql << " timeout INT NOT NULL,"
36
- # sql << " next_time INT NOT NULL,"
37
- # sql << " cron VARCHAR(128) NOT NULL,"
38
- # sql << " delay INT NOT NULL,"
39
- # sql << " data BLOB NOT NULL,"
40
- # sql << " PRIMARY KEY (id)"
41
- # sql << ");"
42
- # end
43
- # # TODO index
44
- # connect {
45
- # @db.run sql
46
- # }
47
- #end
18
+ def create_tables
19
+ sql = ''
20
+ sql << "CREATE TABLE IF NOT EXISTS `#{@table}` ("
21
+ sql << " id VARCHAR(256) NOT NULL,"
22
+ sql << " timeout INT NOT NULL,"
23
+ sql << " next_time INT NOT NULL,"
24
+ sql << " cron VARCHAR(128) NOT NULL,"
25
+ sql << " delay INT NOT NULL,"
26
+ sql << " data BLOB NOT NULL,"
27
+ sql << " timezone VARCHAR(256) NULL,"
28
+ sql << " PRIMARY KEY (id)"
29
+ sql << ");"
30
+ # TODO index
31
+ connect {
32
+ @db.run sql
33
+ }
34
+ end
48
35
 
36
+ private
49
37
  def connect(&block)
50
38
  begin
51
39
  block.call
@@ -56,8 +44,8 @@ class RDBBackend < Backend
56
44
 
57
45
  public
58
46
  def list(&block)
59
- @db.fetch("SELECT id, timeout, next_time, cron, delay, data FROM `#{@table}` ORDER BY timeout ASC") {|row|
60
- yield row[:id], row[:cron], row[:delay], row[:data], row[:next_time], row[:timeout]
47
+ @db.fetch("SELECT id, timeout, next_time, cron, delay, data, timezone FROM `#{@table}` ORDER BY timeout ASC") {|row|
48
+ yield row[:id], row[:cron], row[:delay], row[:data], row[:next_time], row[:timeout], row[:timezone]
61
49
  }
62
50
  end
63
51
 
@@ -67,12 +55,12 @@ class RDBBackend < Backend
67
55
  connect {
68
56
  while true
69
57
  rows = 0
70
- @db.fetch("SELECT id, timeout, next_time, cron, delay, data FROM `#{@table}` WHERE timeout <= ? ORDER BY timeout ASC LIMIT #{MAX_SELECT_ROW};", now) {|row|
58
+ @db.fetch("SELECT id, timeout, next_time, cron, delay, data, timezone FROM `#{@table}` WHERE timeout <= ? ORDER BY timeout ASC LIMIT #{MAX_SELECT_ROW};", now) {|row|
71
59
 
72
60
  n = @db["UPDATE `#{@table}` SET timeout=? WHERE id=? AND timeout=?;", timeout, row[:id], row[:timeout]].update
73
61
  salt = timeout
74
62
  if n > 0
75
- return [row[:id],salt], Task.new(row[:id], row[:next_time], row[:cron], row[:delay], row[:data])
63
+ return [row[:id],salt], Task.new(row[:id], row[:next_time], row[:cron], row[:delay], row[:data], row[:timezone])
76
64
  end
77
65
 
78
66
  rows += 1
@@ -92,10 +80,10 @@ class RDBBackend < Backend
92
80
  }
93
81
  end
94
82
 
95
- def add_checked(id, cron, delay, data, next_time, timeout)
83
+ def add_checked(id, cron, delay, data, next_time, timeout, timezone)
96
84
  connect {
97
85
  begin
98
- n = @db["INSERT INTO `#{@table}` (id, timeout, next_time, cron, delay, data) VALUES (?, ?, ?, ?, ?, ?);", id, timeout, next_time, cron, delay, data].insert
86
+ n = @db["INSERT INTO `#{@table}` (id, timeout, next_time, cron, delay, data, timezone) VALUES (?, ?, ?, ?, ?, ?, ?);", id, timeout, next_time, cron, delay, data, timezone].insert
99
87
  return true
100
88
  rescue Sequel::DatabaseError
101
89
  return nil
@@ -112,16 +100,16 @@ class RDBBackend < Backend
112
100
 
113
101
  def get(id)
114
102
  connect {
115
- @db.fetch("SELECT id, timeout, next_time, cron, delay, data FROM `#{@table}` WHERE id=?;", id) {|row|
116
- return row[:cron], row[:delay], row[:data]
103
+ @db.fetch("SELECT id, timeout, next_time, cron, delay, data, timezone FROM `#{@table}` WHERE id=?;", id) {|row|
104
+ return row[:cron], row[:delay], row[:data], row[:timezone]
117
105
  }
118
106
  return nil
119
107
  }
120
108
  end
121
109
 
122
- def modify_checked(id, cron, delay, data)
110
+ def modify_checked(id, cron, delay, data, timezone)
123
111
  connect {
124
- n = @db["UPDATE `#{@table}` SET cron=?, delay=?, data=? WHERE id=?;", cron, delay, data, id].update
112
+ n = @db["UPDATE `#{@table}` SET cron=?, delay=?, data=?, timezone=? WHERE id=?;", cron, delay, data, timezone, id].update
125
113
  return n > 0
126
114
  }
127
115
  end
@@ -28,7 +28,7 @@ class SimpleDBBackend < Backend
28
28
 
29
29
  def list(&block)
30
30
  rows = 0
31
- @domain.items.select('timeout', 'next_time', 'cron', 'delay', 'data',
31
+ @domain.items.select('timeout', 'next_time', 'cron', 'delay', 'data', 'timezone',
32
32
  :where => "timeout > '#{int_encode(0)}'", # required by SimpleDB
33
33
  :order => [:timeout, :asc],
34
34
  :consistent_read => @consistent_read,
@@ -40,9 +40,10 @@ class SimpleDBBackend < Backend
40
40
  cron = attrs['cron'].first
41
41
  delay = int_decode(attrs['delay'].first)
42
42
  data = attrs['data'].first
43
+ timezone = attrs['timezone'].first
43
44
  timeout = int_decode(attrs['timeout'].first)
44
45
 
45
- yield id, cron, delay, data, next_time, timeout
46
+ yield id, cron, delay, data, next_time, timeout, timezone
46
47
  }
47
48
  end
48
49
 
@@ -51,7 +52,7 @@ class SimpleDBBackend < Backend
51
52
  def acquire(timeout, now=Time.now.to_i)
52
53
  while true
53
54
  rows = 0
54
- @domain.items.select('timeout', 'next_time', 'cron', 'delay', 'data',
55
+ @domain.items.select('timeout', 'next_time', 'cron', 'delay', 'data', 'timezone',
55
56
  :where => "timeout <= '#{int_encode(now)}'",
56
57
  :order => [:timeout, :asc],
57
58
  :consistent_read => @consistent_read,
@@ -67,9 +68,10 @@ class SimpleDBBackend < Backend
67
68
  cron = attrs['cron'].first
68
69
  delay = int_decode(attrs['delay'].first)
69
70
  data = attrs['data'].first
71
+ timezone = attrs['timezone'].first
70
72
  salt = int_encode(timeout)
71
73
 
72
- return [id,salt], Task.new(id, next_time, cron, delay, data)
74
+ return [id,salt], Task.new(id, next_time, cron, delay, data, timezone)
73
75
 
74
76
  rescue AWS::SimpleDB::Errors::ConditionalCheckFailed, AWS::SimpleDB::Errors::AttributeDoesNotExist
75
77
  end
@@ -93,11 +95,17 @@ class SimpleDBBackend < Backend
93
95
  end
94
96
  end
95
97
 
96
- def add_checked(id, cron, delay, data, next_time, timeout)
98
+ def add_checked(id, cron, delay, data, next_time, timeout, timezone)
97
99
  begin
98
- @domain.items[id].attributes.replace('timeout'=>int_encode(timeout), 'next_time'=>int_encode(next_time),
99
- 'cron'=>cron, 'delay'=>int_encode(delay), 'data'=>data,
100
- :unless=>'timeout')
100
+ hash = {}
101
+ hash['timeout'] = int_encode(timeout)
102
+ hash['next_time'] = int_encode(next_time)
103
+ hash['cron'] = cron
104
+ hash['delay'] = int_encode(delay)
105
+ hash['data'] = data
106
+ hash['timezone'] = timezone if timezone
107
+ hash[:unless] = 'timeout'
108
+ @domain.items[id].attributes.replace()
101
109
  return true
102
110
  rescue AWS::SimpleDB::Errors::ConditionalCheckFailed, AWS::SimpleDB::Errors::ExistsAndExpectedValue
103
111
  return nil
@@ -122,14 +130,15 @@ class SimpleDBBackend < Backend
122
130
  end
123
131
  delay = int_decode(attrs['delay'].first)
124
132
  data = attrs['data'].first
125
- return cron, delay, data
133
+ timezone = attrs['timezone'].first
134
+ return cron, delay, data, timezone
126
135
  end
127
136
 
128
- def modify_checked(id, cron, delay, data)
137
+ def modify_checked(id, cron, delay, data, timezone)
129
138
  unless get(id)
130
139
  return false
131
140
  end
132
- @domain.items[id].attributes.replace('cron'=>cron, 'delay'=>int_encode(delay), 'data'=>data)
141
+ @domain.items[id].attributes.replace('cron'=>cron, 'delay'=>int_encode(delay), 'data'=>data, 'timezone'=>timezone)
133
142
  return true
134
143
  end
135
144
 
@@ -52,6 +52,10 @@ op.on('-d', '--delay SEC', 'Delay time before running a schedule (default: 0)',
52
52
  add_conf[:delay] = i
53
53
  }
54
54
 
55
+ op.on('-t', '--timezone NAME', 'Set timezone (default: localtime)') {|s|
56
+ add_conf[:timezone] = s
57
+ }
58
+
55
59
  op.on('-s', '--start UNIXTIME', 'Start time to run a schedule (default: now)', Integer) {|i|
56
60
  add_conf[:start] = i
57
61
  }
@@ -143,13 +147,13 @@ begin
143
147
  timeout: 300
144
148
  poll_interval: 1
145
149
  backend:
146
- database: "mysql://user:password@localhost/mydb"
150
+ database: "mysql2://user:password@localhost/mydb"
147
151
  table: "perfectsched"
148
152
  #simpledb: your-simpledb-domain-name-for-scheduler
149
153
  #aws_key_id: "AWS_ACCESS_KEY_ID"
150
154
  #aws_secret_key: "AWS_SECRET_ACCESS_KEY"
151
155
  queue:
152
- database: "mysql://user:password@localhost/mydb"
156
+ database: "mysql2://user:password@localhost/mydb"
153
157
  table: "perfectqueue"
154
158
  #simpledb: your-simpledb-domain-name-for-queue
155
159
  #aws_key_id: "AWS_ACCESS_KEY_ID"
@@ -228,12 +232,12 @@ require 'logger'
228
232
 
229
233
  case type
230
234
  when :list
231
- format = "%26s %20s %8s %26s %26s %s"
232
- puts format % ["id", "schedule", "delay", "next time", "next run", "data"]
233
- time_format = "%Y-%m-%d %H:%M:%S %z"
235
+ format = "%26s %18s %8s %20s %20s %20s %s"
236
+ puts format % ["id", "schedule", "delay", "next time", "next run", "timezone", "data"]
237
+ time_format = "%Y-%m-%d %H:%M:%S"
234
238
  n = 0
235
- backend.list {|id,cron,delay,data,next_time,timeout|
236
- puts format % [id, cron, delay, Time.at(next_time).strftime(time_format), Time.at(timeout).strftime(time_format), data]
239
+ backend.list {|id,cron,delay,data,next_time,timeout,timezone|
240
+ puts format % [id, cron, delay, Time.at(next_time).utc.strftime(time_format), Time.at(timeout).utc.strftime(time_format), timezone, data]
237
241
  n += 1
238
242
  }
239
243
  puts "#{n} entries."
@@ -252,8 +256,9 @@ when :add
252
256
  data = add_conf[:data]
253
257
  delay = add_conf[:delay]
254
258
  start = add_conf[:start] || Time.now.to_i
259
+ timezone = add_conf[:timezone]
255
260
 
256
- added = backend.add(id, cron, delay, data, start)
261
+ added = backend.add(id, cron, delay, data, start, timezone)
257
262
  if added
258
263
  puts "Schedule id=#{id} is added."
259
264
  else
@@ -262,7 +267,7 @@ when :add
262
267
  end
263
268
 
264
269
  when :modify_sched, :modify_delay, :modify_data
265
- cron, delay, data = backend.get(id)
270
+ cron, delay, data, timezone = backend.get(id)
266
271
  unless cron
267
272
  puts "Schedule id=#{id} does not exist."
268
273
  exit 1
@@ -5,16 +5,19 @@ module PerfectSched
5
5
  class CronCalc
6
6
  def initialize
7
7
  require 'cron-spec'
8
+ require 'tzinfo'
8
9
  # TODO optimize
9
10
  end
10
11
 
11
- def next_time(cron, time)
12
- t = Time.at(time)
12
+ def next_time(cron, time, timezone)
13
13
  tab = CronSpec::CronSpecification.new(cron)
14
+ tz = TZInfo::Timezone.get(timezone) if timezone
14
15
  while true
15
- t += 60
16
+ time += 60
17
+ t = Time.at(time)
18
+ t = tz.utc_to_local(t.utc) if tz
16
19
  if tab.is_specification_in_effect?(t)
17
- return t.to_i
20
+ return time
18
21
  end
19
22
  # FIXME break
20
23
  end
@@ -42,7 +42,7 @@ class Engine
42
42
  @queue.submit(id, task.data)
43
43
  # ignore already exists error
44
44
 
45
- next_time = @croncalc.next_time(task.cron, task.time)
45
+ next_time = @croncalc.next_time(task.cron, task.time, task.timezone)
46
46
  next_run = next_time + task.delay
47
47
  @backend.finish(token, next_time, next_run)
48
48
 
@@ -1,5 +1,5 @@
1
1
  module PerfectSched
2
2
 
3
- VERSION = '0.7.6'
3
+ VERSION = '0.7.7'
4
4
 
5
5
  end
data/test/backend_test.rb CHANGED
@@ -17,7 +17,9 @@ class BackendTest < Test::Unit::TestCase
17
17
 
18
18
  def open_backend
19
19
  #PerfectSched::SimpleDBBackend.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'], 'perfectsched-test-1').use_consistent_read
20
- PerfectSched::RDBBackend.new(DB_URI, "perfectdb_test")
20
+ db = PerfectSched::RDBBackend.new(DB_URI, "perfectdb_test")
21
+ db.create_tables
22
+ db
21
23
  end
22
24
 
23
25
  it 'acquire' do
@@ -176,7 +178,7 @@ class BackendTest < Test::Unit::TestCase
176
178
  assert_equal 10, delay
177
179
  assert_equal 'data2', data
178
180
 
179
- ok = db1.modify(key, "* * * * 2", 20, "data3")
181
+ ok = db1.modify(key, "* * * * 2", 20, "data3", nil)
180
182
  assert_equal true, ok
181
183
 
182
184
  cron, delay, data = db1.get(key)
@@ -184,5 +186,28 @@ class BackendTest < Test::Unit::TestCase
184
186
  assert_equal 20, delay
185
187
  assert_equal 'data3', data
186
188
  end
189
+
190
+ it 'timezone' do
191
+ clean_backend
192
+
193
+ db1 = open_backend
194
+ time = 1323820800 # 2011-12-14 00:00:00 UTC
195
+
196
+ ok = db1.add(@key_prefix+'test1', "0 0 * * *", 0, '', time-60, 'UTC')
197
+ assert_equal true, ok
198
+
199
+ ok = db1.add(@key_prefix+'test2', "0 0 * * *", 0, '', time-60, 'Asia/Tokyo')
200
+ assert_equal true, ok
201
+
202
+ token, task = db1.acquire(time+86400, time)
203
+ assert_not_equal nil, task
204
+ assert_equal @key_prefix+'test1', task.id
205
+ assert_equal time, task.time
206
+
207
+ token, task = db1.acquire(time+54000+86400, time+54000)
208
+ assert_not_equal nil, task
209
+ assert_equal @key_prefix+'test2', task.id
210
+ assert_equal time+54000, task.time
211
+ end
187
212
  end
188
213
 
data/test/test_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
1
3
  require 'test/unit'
2
4
  $LOAD_PATH << File.dirname(__FILE__)+"/../lib"
3
5
  require 'perfectsched'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perfectsched
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.6
4
+ version: 0.7.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-09 00:00:00.000000000Z
12
+ date: 2011-12-16 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: cron-spec
16
- requirement: &70298435645560 !ruby/object:Gem::Requirement
16
+ requirement: &70273403596080 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - <=
@@ -24,10 +24,10 @@ dependencies:
24
24
  version: 0.1.2
25
25
  type: :runtime
26
26
  prerelease: false
27
- version_requirements: *70298435645560
27
+ version_requirements: *70273403596080
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: sequel
30
- requirement: &70298435644720 !ruby/object:Gem::Requirement
30
+ requirement: &70273403594120 !ruby/object:Gem::Requirement
31
31
  none: false
32
32
  requirements:
33
33
  - - ~>
@@ -35,10 +35,10 @@ dependencies:
35
35
  version: 3.26.0
36
36
  type: :runtime
37
37
  prerelease: false
38
- version_requirements: *70298435644720
38
+ version_requirements: *70273403594120
39
39
  - !ruby/object:Gem::Dependency
40
40
  name: aws-sdk
41
- requirement: &70298435644200 !ruby/object:Gem::Requirement
41
+ requirement: &70273403591760 !ruby/object:Gem::Requirement
42
42
  none: false
43
43
  requirements:
44
44
  - - ~>
@@ -46,10 +46,10 @@ dependencies:
46
46
  version: 1.1.1
47
47
  type: :runtime
48
48
  prerelease: false
49
- version_requirements: *70298435644200
49
+ version_requirements: *70273403591760
50
50
  - !ruby/object:Gem::Dependency
51
51
  name: perfectqueue
52
- requirement: &70298435643640 !ruby/object:Gem::Requirement
52
+ requirement: &70273403589380 !ruby/object:Gem::Requirement
53
53
  none: false
54
54
  requirements:
55
55
  - - ~>
@@ -57,7 +57,18 @@ dependencies:
57
57
  version: 0.7.0
58
58
  type: :runtime
59
59
  prerelease: false
60
- version_requirements: *70298435643640
60
+ version_requirements: *70273403589380
61
+ - !ruby/object:Gem::Dependency
62
+ name: tzinfo
63
+ requirement: &70273403587440 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 0.3.29
69
+ type: :runtime
70
+ prerelease: false
71
+ version_requirements: *70273403587440
61
72
  description:
62
73
  email: frsyuki@gmail.com
63
74
  executables: