rufus-scheduler 3.0.0 → 3.0.9

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.txt CHANGED
@@ -2,6 +2,65 @@
2
2
  = rufus-scheduler CHANGELOG.txt
3
3
 
4
4
 
5
+ == rufus-scheduler - 3.0.9 released 2014/08/30
6
+
7
+ - fix TZ with underscores, thanks https://github.com/gnilrets
8
+ - integrate https://github.com/ecin Lock mecha
9
+
10
+
11
+ == rufus-scheduler - 3.0.8 released 2014/06/09
12
+
13
+ - handle TZInfo errors on DST transitions, thanks https://github.com/junhanamaki
14
+ - implement Scheduler#up?
15
+ - let schedule and schedule_at use Chronic if present
16
+ - let Rufus::Scheduler.parse use Chronic if present
17
+
18
+
19
+ == rufus-scheduler - 3.0.7 released 2014/03/18
20
+
21
+ - implement Scheduler #occurrences and #timeline, inspired by kreynolds
22
+ - implement Job #last_work_time and #mean_work_time
23
+ - implement Job#count
24
+ - add more info to the stderr error output (scheduler/tz info)
25
+ - prevent skipping a day on switch to summertime, gh-114, thanks Matteo
26
+
27
+
28
+ == rufus-scheduler - 3.0.6 released 2014/02/14
29
+
30
+ - avoid "can't be called from trap context" on Ruby 2.0, gh-98
31
+
32
+
33
+ == rufus-scheduler - 3.0.5 released 2014/01/30
34
+
35
+ - implement Job#call(do_rescue=false), gh-97
36
+ - implement :first => :now for repeat jobs, gh-96
37
+
38
+
39
+ == rufus-scheduler - 3.0.4 released 2014/01/19
40
+
41
+ - make CronLine#frequency faster (to avoid 20s schedule_cron times)
42
+
43
+
44
+ == rufus-scheduler - 3.0.3 released 2013/12/12
45
+
46
+ - CronLine#previous_time fix by Yassen Bantchev (https://github.com/yassenb)
47
+ - introduce ZookeptScheduler example in the readme
48
+ - rename #consider_lockfile to #lock and introduce #unlock
49
+
50
+
51
+ == rufus-scheduler - 3.0.2 released 2013/10/22
52
+
53
+ - default :max_work_threads to 28
54
+ - fix "rufus rushes to create work threads" issue, thanks Gatis Tomsons
55
+ - introduce Rufus::Scheduler::NotRunningError, thanks Gatis Tomsons
56
+
57
+
58
+ == rufus-scheduler - 3.0.1 released 2013/10/19
59
+
60
+ - fix post_install_message, thanks Ted Pennings
61
+ - bring back .parse_time_string and .parse_duration_string
62
+
63
+
5
64
  == rufus-scheduler - 3.0.0 released 2013/10/02
6
65
 
7
66
  - complete rewrite.
@@ -148,5 +207,9 @@
148
207
  == rufus-scheduler - 2.0.1 released 2009/05/07
149
208
  == rufus-scheduler - 2.0.0 released 2009/05/07
150
209
 
210
+ ...
211
+
151
212
  - initial release
152
213
 
214
+ (was openwferu-scheduler before that)
215
+
data/CREDITS.txt CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  == Contributors
6
6
 
7
+ - Sterling Paramore (https://github.com/gnilrets) underscore TZ fix
8
+ - ecin (https://github.com/ecin) new lock mecha
9
+ - Adam Jonas (https://github.com/adamjonas) migrate specs to "expect"
10
+ - Yassen Bantchev (https://github.com/yassenb) CronLine#previous_time rewrite
11
+ - Eric Lindvall (https://github.com/eric) Zookeeper locked example
12
+ - Ted Pennings (https://github.com/tedpennings) typo in post_install_message
7
13
  - Tobias Kraze (https://github.com/kratob) timeout vs mutex fix
8
14
  - Patrick Farrell (https://github.com/pfarrell) pointing at deprecated start_new
9
15
  - Thomas Sevestre (https://github.com/thomassevestre) :exception option
@@ -27,6 +33,15 @@
27
33
 
28
34
  == Feedback
29
35
 
36
+ - junhanamaki - https://github.com/junhanamaki - #next_time and dst ambiguities
37
+ - kreynolds (tossrock) - inspiration for #occurrences
38
+ - Matteo - https://github.com/m4ce - dst and cron issue
39
+ - Tobias Bielohlawek - https://github.com/rngtng - missing assertion
40
+ - Joe Taylor and Agis Anastasopoulos -
41
+ http://stackoverflow.com/questions/21280870 - :first => :now and Job#call
42
+ - Gatis Tomsons - https://github.io/gacha - heavy work threads and lock errors
43
+ - https://github.com/joast - missing .to_time_string alias (code and doc)
44
+ - Tamir Duberstein - https://github.com/tamird - rdoc inaccuracies
30
45
  - Kevin Bouwkamp - https://github.com/bmxpert1 - first_at issues
31
46
  - Daniel Beauchamp - https://github.com/pushmatrix - pre/post trigger callbacks
32
47
  - Arthur Maltson - https://github.com/amaltson - readme fixes
@@ -60,5 +75,6 @@
60
75
 
61
76
  == and finally
62
77
 
78
+ - many thanks to the author and contributors of the tzinfo gem (http://tzinfo.github.io/)
63
79
  - many thanks to the EventMachine team (especially Aman Gupta)
64
80
 
data/LICENSE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- Copyright (c) 2005-2013, John Mettraux, jmettraux@gmail.com
2
+ Copyright (c) 2005-2014, John Mettraux, jmettraux@gmail.com
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
  # rufus-scheduler
3
3
 
4
4
  [![Build Status](https://secure.travis-ci.org/jmettraux/rufus-scheduler.png)](http://travis-ci.org/jmettraux/rufus-scheduler)
5
+ [![Gem Version](https://badge.fury.io/rb/rufus-scheduler.png)](http://badge.fury.io/rb/rufus-scheduler)
5
6
 
6
7
  Job scheduler for Ruby (at, cron, in and every jobs).
7
8
 
@@ -78,7 +79,7 @@ There is no EventMachine-based scheduler anymore.
78
79
  * The scheduler isn't catching the whole of Exception anymore, only StandardError
79
80
  * The error_handler is #on_error (instead of #on_exception), by default it now prints the details of the error to $stderr (used to be $stdout)
80
81
  * Rufus::Scheduler::TimeOutError renamed to Rufus::Scheduler::TimeoutError
81
- * Introduction of "interval" jobs. Whereas "every" jobs are like "every 10 minuts, do this", interval jobs are like "do that, then wait for 10 minutes, then do that again, and so on"
82
+ * Introduction of "interval" jobs. Whereas "every" jobs are like "every 10 minutes, do this", interval jobs are like "do that, then wait for 10 minutes, then do that again, and so on"
82
83
  * Introduction of a :lockfile => true/filename mechanism to prevent multiple schedulers from executing
83
84
  * "discard_past" is on by default. If the scheduler (its host) sleeps for 1 hour and a ```every '10m'``` job is on, it will trigger once at wakeup, not 6 times (discard_past was false by default in rufus-scheduler 2.x). No intention to re-introduce ```:discard_past => false``` in 3.0 for now.
84
85
  * Introduction of Scheduler #on_pre_trigger and #on_post_trigger callback points
@@ -88,6 +89,8 @@ There is no EventMachine-based scheduler anymore.
88
89
 
89
90
  So you need help. People can help you, but first help them help you, and don't waste their time. Provide a complete description of the issue. If it works on A but not on B and others have to ask you: "so what is different between A and B" you are wasting everyone's time.
90
91
 
92
+ "hello" and "thanks" are not swear words.
93
+
91
94
  Go read [how to report bugs effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html), twice.
92
95
 
93
96
  Update: [help_help.md](https://gist.github.com/jmettraux/310fed75f568fd731814) might help help you.
@@ -113,6 +116,8 @@ Yes, issues can be reported in [rufus-scheduler issues](https://github.com/jmett
113
116
  * [It doesn't work...](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html)
114
117
  * [I want a refund](http://blog.nodejitsu.com/getting-refunds-on-open-source-projects)
115
118
  * [Passenger and rufus-scheduler](http://stackoverflow.com/questions/18108719/debugging-rufus-scheduler/18156180#18156180)
119
+ * [Passenger and rufus-scheduler (2)](http://stackoverflow.com/questions/21861387/rufus-cron-job-not-working-in-apache-passenger#answer-21868555)
120
+ * [Unicorn and rufus-scheduler](https://jkraemer.net/running-rufus-scheduler-in-a-unicorn-rails-app)
116
121
 
117
122
 
118
123
  ## scheduling
@@ -249,6 +254,25 @@ The second argument is "time", it's the time when the job got cleared for trigge
249
254
 
250
255
  Note that time is the time when the job got cleared for triggering. If there are mutexes involved, now = mutex_wait_time + time...
251
256
 
257
+ #### "every" jobs and changing the next_time in-flight
258
+
259
+ It's OK to change the next_time of an every job in-flight:
260
+
261
+ ```ruby
262
+ scheduler.every '10m' do |job|
263
+
264
+ # ...
265
+
266
+ status = determine_pie_status
267
+
268
+ job.next_time = Time.now + 30 * 60 if status == 'burnt'
269
+ #
270
+ # if burnt, wait 30 minutes for the oven to cool a bit
271
+ end
272
+ ```
273
+
274
+ It should work as well with cron jobs, not so with interval jobs whose next_time is computed after their block ends its current run.
275
+
252
276
  ### scheduling handler instances
253
277
 
254
278
  It's OK to pass any object, as long as it respond to #call(), when scheduling:
@@ -366,14 +390,14 @@ end
366
390
 
367
391
  The :timeout option accepts either a duration (like "1d" or "2w3d") or a point in time (like "2013/12/12 12:00").
368
392
 
369
- ### :first_at, :first_in, :first
393
+ ### :first_at, :first_in, :first, :first_time
370
394
 
371
395
  This option is for repeat jobs (cron / every) only.
372
396
 
373
397
  It's used to specify the first time after which the repeat job should trigger for the first time.
374
398
 
375
- In the case of an "every" job, this will be the first time (module the scheduler frequency) the job triggers.
376
- For a "cron" job, it's the time after which the first schedule will trigger.
399
+ In the case of an "every" job, this will be the first time (modulo the scheduler frequency) the job triggers.
400
+ For a "cron" job, it's the time *after* which the first schedule will trigger.
377
401
 
378
402
  ```ruby
379
403
  scheduler.every '2d', :first_at => Time.now + 10 * 3600 do
@@ -383,6 +407,10 @@ end
383
407
  scheduler.every '2d', :first_in => '10h' do
384
408
  # ... every two days, but start in 10 hours
385
409
  end
410
+
411
+ scheduler.cron '00 14 * * *', :first_in => '3d' do
412
+ # ... every day at 14h00, but start after 3 * 24 hours
413
+ end
386
414
  ```
387
415
 
388
416
  :first, :first_at and :first_in all accept a point in time or a duration (number or time string). Use the symbol you think make your schedule more readable.
@@ -393,6 +421,34 @@ job.first_at = Time.now + 10
393
421
  job.first_at = Rufus::Scheduler.parse('2029-12-12')
394
422
  ```
395
423
 
424
+ The first argument (in all its flavours) accepts a :now or :immediately value. That schedules the first occurence for immediate triggering. Consider:
425
+
426
+ ```ruby
427
+ require 'rufus-scheduler'
428
+
429
+ s = Rufus::Scheduler.new
430
+
431
+ n = Time.now; p [ :scheduled_at, n, n.to_f ]
432
+
433
+ s.every '3s', :first => :now do
434
+ n = Time.now; p [ :in, n, n.to_f ]
435
+ end
436
+
437
+ s.join
438
+
439
+ ```
440
+
441
+ that'll output something like:
442
+
443
+ ```
444
+ [:scheduled_at, 2014-01-22 22:21:21 +0900, 1390396881.344438]
445
+ [:in, 2014-01-22 22:21:21 +0900, 1390396881.6453865]
446
+ [:in, 2014-01-22 22:21:24 +0900, 1390396884.648807]
447
+ [:in, 2014-01-22 22:21:27 +0900, 1390396887.651686]
448
+ [:in, 2014-01-22 22:21:30 +0900, 1390396890.6571937]
449
+ ...
450
+ ```
451
+
396
452
  ### :last_at, :last_in, :last
397
453
 
398
454
  This option is for repeat jobs (cron / every) only.
@@ -563,7 +619,6 @@ job.callable
563
619
  # => #<MyHandler:0x0000000163ae88 @counter=0>
564
620
  ```
565
621
 
566
-
567
622
  ### scheduled_at
568
623
 
569
624
  Returns the Time instance when the job got created.
@@ -585,6 +640,12 @@ job.scheduled_at
585
640
  # => 2013-07-17 23:48:54 +0900
586
641
  ```
587
642
 
643
+ ### last_work_time, mean_work_time
644
+
645
+ The job keeps track of how long its work was in the `last_work_time` attribute. For a one time job (in, at) it's probably not very useful.
646
+
647
+ The attribute `mean_work_time` contains a computed mean work time. It's recomputed after every run (if it's a repeat job).
648
+
588
649
  ### unschedule
589
650
 
590
651
  Unschedule the job, preventing it from firing again and removing it from the schedule. This doesn't prevent a running thread for this job to run until its end.
@@ -671,6 +732,44 @@ job.keys
671
732
 
672
733
  Job-local variables are thread-safe.
673
734
 
735
+ ### call
736
+
737
+ Job instances have a #call method. It simply calls the scheduled block or callable immediately.
738
+
739
+ ```ruby
740
+ job =
741
+ @scheduler.schedule_every '10m' do |job|
742
+ # ...
743
+ end
744
+
745
+ job.call
746
+ ```
747
+
748
+ Warning: the Scheduler#on_error handler is not involved. Error handling is the responsibility of the caller.
749
+
750
+ If the call has to be rescued by the error handler of the scheduler, ```call(true)``` might help:
751
+
752
+ ```ruby
753
+ require 'rufus-scheduler'
754
+
755
+ s = Rufus::Scheduler.new
756
+
757
+ def s.on_error(job, err)
758
+ p [ 'error in scheduled job', job.class, job.original, err.message ]
759
+ rescue
760
+ p $!
761
+ end
762
+
763
+ job =
764
+ s.schedule_in('1d') do
765
+ fail 'again'
766
+ end
767
+
768
+ job.call(true)
769
+ #
770
+ # true lets the error_handler deal with error in the job call
771
+ ```
772
+
674
773
  ## AtJob and InJob methods
675
774
 
676
775
  ### time
@@ -687,6 +786,10 @@ An alias to time.
687
786
 
688
787
  Returns the next time the job will trigger (hopefully).
689
788
 
789
+ ### count
790
+
791
+ Returns how many times the job fired.
792
+
690
793
  ## EveryJob methods
691
794
 
692
795
  ### frequency
@@ -717,16 +820,22 @@ Rufus::Scheduler.parse('* * * * * *').frequency # ==> 1
717
820
  Rufus::Scheduler.parse('5 23 * * *').frequency # ==> 24 * 3600
718
821
  Rufus::Scheduler.parse('5 * * * *').frequency # ==> 3600
719
822
  Rufus::Scheduler.parse('10,20,30 * * * *').frequency # ==> 600
823
+
824
+ Rufus::Scheduler.parse('10,20,30 * * * * *').frequency # ==> 10
720
825
  ```
721
826
 
722
827
  It's used to determine if the job frequency is higher than the scheduler frequency (it raises an ArgumentError if that is the case).
723
828
 
829
+ ### brute_frequency
830
+
831
+ Cron jobs also have a ```#brute_frequency``` method that looks a one year of intervals to determine the shortest delta for the cron. This method can take between 20 to 50 seconds for cron lines that go the second level. ```#frequency``` above, when encountering second level cron lines will take a shortcut to answer as quickly as possible with a usable value.
832
+
724
833
 
725
834
  ## looking up jobs
726
835
 
727
836
  ### Scheduler#job(job_id)
728
837
 
729
- The scheduler #job(job_id) method can be used to lookup Job instances.
838
+ The scheduler ```#job(job_id)``` method can be used to lookup Job instances.
730
839
 
731
840
  ```ruby
732
841
  require 'rufus-scheduler'
@@ -802,6 +911,20 @@ Shuts down the scheduler, waits (blocks) until all the jobs cease running.
802
911
 
803
912
  Kills all the job (threads) and then shuts the scheduler down. Radical.
804
913
 
914
+ ### Scheduler#down?
915
+
916
+ Returns true if the scheduler has been shut down.
917
+
918
+ ### Scheduler#started_at
919
+
920
+ Returns the Time instance at which the scheduler got started.
921
+
922
+ ### Scheduler #uptime / #uptime_s
923
+
924
+ Returns since the count of seconds for which the scheduler has been running.
925
+
926
+ ```#uptime_s``` returns this count in a String easier to grasp for humans, like ```"3d12m45s123"```.
927
+
805
928
  ### Scheduler#join
806
929
 
807
930
  Let's the current thread join the scheduling thread in rufus-scheduler. The thread comes back when the scheduler gets shut down.
@@ -824,6 +947,16 @@ Note that the main schedule thread will be returned if it is currently running a
824
947
 
825
948
  Returns true if the arg is a currently scheduled job (see Job#scheduled?).
826
949
 
950
+ ### Scheduler#occurrences(time0, time1)
951
+
952
+ Returns a hash ```{ job => [ t0, t1, ... ] }``` mapping jobs to their potential trigger time within the ```[ time0, time1 ]``` span.
953
+
954
+ Please note that, for interval jobs, the ```#mean_work_time``` is used, so the result is only a prediction.
955
+
956
+ ### Scheduler#timeline(time0, time1)
957
+
958
+ Like `#occurrences` but returns a list ```[ [ t0, job0 ], [ t1, job1 ], ... ]``` of time + job pairs.
959
+
827
960
 
828
961
  ## dealing with job errors
829
962
 
@@ -964,9 +1097,62 @@ The idea is to guarantee only one scheduler (in a group of scheduler sharing the
964
1097
 
965
1098
  This is useful in environments where the Ruby process holding the scheduler gets started multiple times.
966
1099
 
1100
+ If the lockfile mechanism here is not sufficient, you can plug your custom mechanism. It's explained in [advanced lock schemes](#advanced-lock-schemes) below.
1101
+
1102
+ ### :scheduler_lock
1103
+
1104
+ (since rufus-scheduler 3.0.9)
1105
+
1106
+ The scheduler lock is an object that responds to `#lock` and `#unlock`. The scheduler calls `#lock` when starting up. If the answer is `false`, the scheduler stops its initialization work and won't schedule anything.
1107
+
1108
+ Here is a sample of a scheduler lock that only lets the scheduler on host "coffee.example.com" start:
1109
+ ```ruby
1110
+ class HostLock
1111
+ def initialize(lock_name)
1112
+ @lock_name = lock_name
1113
+ end
1114
+ def lock
1115
+ @lock_name == `hostname -f`.strip
1116
+ end
1117
+ def unlock
1118
+ true
1119
+ end
1120
+ end
1121
+
1122
+ scheduler =
1123
+ Rufus::Scheduler.new(:scheduler_lock => HostLock.new('coffee.example.com'))
1124
+ ```
1125
+
1126
+ By default, the scheduler_lock is an instance of `Rufus::Scheduler::NullLock`, with a `#lock` that returns true.
1127
+
1128
+ ### :trigger_lock
1129
+
1130
+ (since rufus-scheduler 3.0.9)
1131
+
1132
+ The trigger lock in an object that responds to `#lock`. The scheduler calls that method on the job lock right before triggering any job. If the answer is false, the trigger doesn't happen, the job is not done (at least not in this scheduler).
1133
+
1134
+ Here is a (stupid) PingLock example, it'll only trigger if an "other host" is not responding to ping. Do not use that in production, you don't want to fork a ping process for each trigger attempt...
1135
+ ```ruby
1136
+ class PingLock
1137
+ def initialize(other_host)
1138
+ @other_host = other_host
1139
+ end
1140
+ def lock
1141
+ ! system("ping -c 1 #{@other_host}")
1142
+ end
1143
+ end
1144
+
1145
+ scheduler =
1146
+ Rufus::Scheduler.new(:trigger_lock => PingLock.new('main.example.com'))
1147
+ ```
1148
+
1149
+ By default, the trigger_lock is an instance of `Rufus::Scheduler::NullLock`, with a `#lock` that always returns true.
1150
+
1151
+ As explained in [advanced lock schemes](#advanced-lock-schemes), another way to tune that behaviour is by overriding the scheduler's `#confirm_lock` method. (You could also do that with an `#on_pre_trigger` callback).
1152
+
967
1153
  ### :max_work_threads
968
1154
 
969
- In rufus-scheduler 2.x, by default, each job triggering received its own, new, hthread of execution. In rufus-scheduler 3.x, execution happens in a work thread and the max work thread count defaults to 35.
1155
+ In rufus-scheduler 2.x, by default, each job triggering received its own, brand new, thread of execution. In rufus-scheduler 3.x, execution happens in a pooled work thread. The max work thread count (the pool size) defaults to 28.
970
1156
 
971
1157
  One can set this maximum value when starting the scheduler.
972
1158
 
@@ -1004,6 +1190,67 @@ Rufus::Scheduler.s.every '10s' { puts "hello, world!" }
1004
1190
  ```
1005
1191
 
1006
1192
 
1193
+ ## advanced lock schemes
1194
+
1195
+ As seen above, rufus-scheduler proposes the [:lockfile](#lockfile--mylockfiletxt) system out of the box. If in a group of schedulers only one is supposed to run, the lockfile mecha prevents schedulers that have not set/created the lockfile from running.
1196
+
1197
+ There are situation where this is not sufficient.
1198
+
1199
+ By overriding #lock and #unlock, one can customize how his schedulers lock.
1200
+
1201
+ This example was provided by [Eric Lindvall](https://github.com/eric):
1202
+
1203
+ ```ruby
1204
+ class ZookeptScheduler < Rufus::Scheduler
1205
+
1206
+ def initialize(zookeeper, opts={})
1207
+ @zk = zookeeper
1208
+ super(opts)
1209
+ end
1210
+
1211
+ def lock
1212
+ @zk_locker = @zk.exclusive_locker('scheduler')
1213
+ @zk_locker.lock # returns true if the lock was acquired, false else
1214
+ end
1215
+
1216
+ def unlock
1217
+ @zk_locker.unlock
1218
+ end
1219
+
1220
+ def confirm_lock
1221
+ return false if down?
1222
+ @zk_locker.assert!
1223
+ rescue ZK::Exceptions::LockAssertionFailedError => e
1224
+ # we've lost the lock, shutdown (and return false to at least prevent
1225
+ # this job from triggering
1226
+ shutdown
1227
+ false
1228
+ end
1229
+ end
1230
+ ```
1231
+
1232
+ This uses a [zookeeper](http://zookeeper.apache.org/) to make sure only one scheduler in a group of distributed schedulers runs.
1233
+
1234
+ The methods #lock and #unlock are overriden and #confirm_lock is provided,
1235
+ to make sure that the lock is still valid.
1236
+
1237
+ The #confirm_lock method is called right before a job triggers (if it is provided). The more generic callback #on_pre_trigger is called right after #confirm_lock.
1238
+
1239
+ ### :scheduler_lock and :trigger_lock
1240
+
1241
+ (introduced in rufus-scheduler 3.0.9).
1242
+
1243
+ Another way of prodiving `#lock`, `#unlock` and `#confirm_lock` to a rufus-scheduler is by using the `:scheduler_lock` and `:trigger_lock` options.
1244
+
1245
+ See [:trigger_lock](#trigger_lock) and [:scheduler_lock](#scheduler_lock).
1246
+
1247
+ The scheduler lock may be used to prevent a scheduler from starting, while a trigger lock prevents individual jobs from triggering (the scheduler goes on scheduling).
1248
+
1249
+ One has to be careful with what goes in `#confirm_lock` or in a trigger lock, as it gets called before each trigger.
1250
+
1251
+ Warning: you may think you're heading towards "high availability" by using a trigger lock and having lots of schedulers at hand. It may be so if you limit yourself to scheduling the same set of jobs at scheduler startup. But if you add schedules at runtime, they stay local to their scheduler. There is no magic that propagates the jobs to all the schedulers in your pack.
1252
+
1253
+
1007
1254
  ## parsing cronlines and time strings
1008
1255
 
1009
1256
  Rufus::Scheduler provides a class method ```.parse``` to parse time durations and cron strings. It's what it's using when receiving schedules. One can use it diectly (no need to instantiate a Scheduler).
@@ -1052,6 +1299,141 @@ Rufus::Scheduler.to_duration_hash(62.127, :drop_seconds => true)
1052
1299
  # => { :m => 1 }
1053
1300
  ```
1054
1301
 
1302
+ ### cronline notations specific to rufus-scheduler
1303
+
1304
+ #### first Monday, last Sunday et al
1305
+
1306
+ To schedule something at noon every first Monday of the month:
1307
+
1308
+ ```ruby
1309
+ scheduler.cron('00 12 * * mon#1') do
1310
+ # ...
1311
+ end
1312
+ ```
1313
+
1314
+ To schedule something at noon the last Sunday of every month:
1315
+
1316
+ ```ruby
1317
+ scheduler.cron('00 12 * * sun#-1') do
1318
+ # ...
1319
+ end
1320
+ #
1321
+ # OR
1322
+ #
1323
+ scheduler.cron('00 12 * * sun#L') do
1324
+ # ...
1325
+ end
1326
+ ```
1327
+
1328
+ Such cronlines can be tested with scripts like:
1329
+
1330
+ ```ruby
1331
+ require 'rufus-scheduler'
1332
+
1333
+ Time.now
1334
+ # => 2013-10-26 07:07:08 +0900
1335
+ Rufus::Scheduler.parse('* * * * mon#1').next_time
1336
+ # => 2013-11-04 00:00:00 +0900
1337
+ ```
1338
+
1339
+ #### L (last day of month)
1340
+
1341
+ L can be used in the "day" slot:
1342
+
1343
+ In this example, the cronline is supposed to trigger every last day of the month at noon:
1344
+
1345
+ ```ruby
1346
+ require 'rufus-scheduler'
1347
+ Time.now
1348
+ # => 2013-10-26 07:22:09 +0900
1349
+ Rufus::Scheduler.parse('00 12 L * *').next_time
1350
+ # => 2013-10-31 12:00:00 +0900
1351
+ ```
1352
+
1353
+
1354
+ ## a note about timezones
1355
+
1356
+ Cron schedules and at schedules support the specification of a timezone.
1357
+
1358
+ ```ruby
1359
+ scheduler.cron '0 22 * * 1-5 America/Chicago' do
1360
+ # the job...
1361
+ end
1362
+
1363
+ scheduler.at '2013-12-12 14:00 Pacific/Samoa' do
1364
+ puts "it's tea time!"
1365
+ end
1366
+
1367
+ # or even
1368
+
1369
+ Rufus::Scheduler.parse("2013-12-12 14:00 Pacific/Saipan")
1370
+ # => 2013-12-12 04:00:00 UTC
1371
+ ```
1372
+
1373
+ Behind the scenes, rufus-scheduler uses [tzinfo](http://tzinfo.github.io/) to deal with timezones.
1374
+
1375
+ Here is a [list of timezones](misc/tz_all.txt) known to my Debian GNU/Linux 7. It was generated with this script:
1376
+
1377
+ ```ruby
1378
+ require 'tzinfo'
1379
+ TZInfo::Timezone.all.each { |tz| puts tz.name }
1380
+ ```
1381
+
1382
+ Unknown timezones, typos, will be rejected by tzinfo thus rufus-scheduler.
1383
+
1384
+ On its own tzinfo derives the timezones from the system's information. On some system it needs some help, one can install the 'tzinfo-data' gem to provide the missing information.
1385
+
1386
+
1387
+ ## so Rails?
1388
+
1389
+ Yes, I know, all of the above is boring and you're only looking for a snippet to paste in your Ruby-on-Rails application to schedule...
1390
+
1391
+ Here is an example initializer:
1392
+
1393
+ ```ruby
1394
+ #
1395
+ # config/initializers/scheduler.rb
1396
+
1397
+ require 'rufus-scheduler'
1398
+
1399
+ # Let's use the rufus-scheduler singleton
1400
+ #
1401
+ s = Rufus::Scheduler.singleton
1402
+
1403
+
1404
+ # Stupid recurrent task...
1405
+ #
1406
+ s.every '1m' do
1407
+
1408
+ Rails.logger.info "hello, it's #{Time.now}"
1409
+ end
1410
+ ```
1411
+
1412
+ And now you tell me that this is good, but you want to schedule stuff from your controller.
1413
+
1414
+ Maybe:
1415
+
1416
+ ```ruby
1417
+ class ScheController < ApplicationController
1418
+
1419
+ # GET /sche/
1420
+ #
1421
+ def index
1422
+
1423
+ job_id =
1424
+ Rufus::Scheduler.singleton.in '5s' do
1425
+ Rails.logger.info "time flies, it's now #{Time.now}"
1426
+ end
1427
+
1428
+ render :text => "scheduled job #{job_id}"
1429
+ end
1430
+ end
1431
+ ```
1432
+
1433
+ The rufus-scheduler singleton is instantiated in the ```config/initializers/scheduler.rb``` file, it's then available throughout the webapp via ```Rufus::Scheduler.singleton```.
1434
+
1435
+ *Warning*: this works well with single-process Ruby servers like Webrick and Thin. Using rufus-scheduler with Passenger or Unicorn requires a bit more knowledge and tuning, gently provided by a bit of googling and reading, see [Faq](#faq) above.
1436
+
1055
1437
 
1056
1438
  ## support
1057
1439
 
data/Rakefile CHANGED
@@ -3,7 +3,6 @@ require 'rubygems'
3
3
 
4
4
  require 'rake'
5
5
  require 'rake/clean'
6
- #require 'rake/rdoctask'
7
6
  require 'rdoc/task'
8
7
 
9
8
 
data/TODO.txt CHANGED
@@ -145,3 +145,7 @@
145
145
  end
146
146
  end
147
147
 
148
+ ~~~
149
+
150
+ [ ] scheduler.at('chronic string', chronic_options...)
151
+