perfectsched 0.7.6 → 0.7.7

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