cron.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913
  1. /*
  2. * Public Domain Software
  3. *
  4. * I (Matthias Ladkau) am the author of the source code in this file.
  5. * I have placed the source code in this file in the public domain.
  6. *
  7. * For further information see: http://creativecommons.org/publicdomain/zero/1.0/
  8. */
  9. /*
  10. Package timeutil contains common function for time related operations.
  11. */
  12. package timeutil
  13. import (
  14. "bytes"
  15. "fmt"
  16. "sort"
  17. "strconv"
  18. "strings"
  19. "sync"
  20. "time"
  21. "devt.de/krotik/common/errorutil"
  22. )
  23. /*
  24. Cron is an object which implements cron-like functionality. It can be
  25. used to schedule jobs at certain time intervals based on local time.
  26. Each Cron object runs a single scheduling thread. Very time consuming
  27. tasks which are triggered by this object are expected to run in a
  28. separate thread otherwise other registered tasks might not be
  29. triggered on time.
  30. Time can be speed up for testing purposes by changing the NowFunc and Tick
  31. properties. See NewTestingCron for details.
  32. Example code:
  33. c := NewCron()
  34. c.Register("0 0 12 1 * *", func() {
  35. // Do something at the beginning of hour 12:00 on 1st of every month
  36. })
  37. c.Start() // Start cron thread
  38. ...
  39. c.Stop() // Shutdown cron thread
  40. */
  41. type Cron struct {
  42. newNowFunc func() time.Time // Function to get the current local time
  43. NowFunc func() time.Time // Function to get the current local time
  44. Tick time.Duration // Cron check interval
  45. cronLock *sync.Mutex // Lock for data operations
  46. handlerMap map[string][]func() // Map of spec to handler functions
  47. specMap map[string]*CronSpec // Map of spec to spec object
  48. stopChan chan bool // Channel for the kill command
  49. }
  50. /*
  51. NewCron creates a new Cron object.
  52. */
  53. func NewCron() *Cron {
  54. return &Cron{
  55. time.Now,
  56. time.Now,
  57. time.Second * 1, // Cron check interval is a second by default
  58. &sync.Mutex{},
  59. make(map[string][]func()),
  60. make(map[string]*CronSpec),
  61. nil,
  62. }
  63. }
  64. /*
  65. run is the actual cron thread.
  66. */
  67. func (c *Cron) run() {
  68. t := c.NowFunc()
  69. Mainloop:
  70. for {
  71. select {
  72. case <-c.stopChan:
  73. break Mainloop
  74. case <-time.After(c.Tick):
  75. break
  76. }
  77. c.cronLock.Lock()
  78. for specString, cs := range c.specMap {
  79. if cs.MatchesTime(t) {
  80. // Execute all handlers if the current time matches a cron spec
  81. for _, handler := range c.handlerMap[specString] {
  82. handler()
  83. }
  84. }
  85. }
  86. c.cronLock.Unlock()
  87. if t = c.newNowFunc(); t == TestingEndOfTime {
  88. // We are a testing cron and stop after the given testing time range
  89. // has passed
  90. c.cronLock.Lock()
  91. close(c.stopChan)
  92. c.stopChan = nil
  93. c.cronLock.Unlock()
  94. return
  95. }
  96. }
  97. c.stopChan <- true
  98. }
  99. /*
  100. Start starts the cron thread. Start is a NOP if cron is already running.
  101. */
  102. func (c *Cron) Start() {
  103. c.cronLock.Lock()
  104. defer c.cronLock.Unlock()
  105. if c.stopChan == nil {
  106. c.stopChan = make(chan bool)
  107. go c.run()
  108. }
  109. }
  110. /*
  111. Stop stops the cron thread. Stop is a NOP if cron is not running.
  112. */
  113. func (c *Cron) Stop() {
  114. c.cronLock.Lock()
  115. defer c.cronLock.Unlock()
  116. if c.stopChan != nil {
  117. // Do the closing handshake
  118. c.stopChan <- true
  119. <-c.stopChan
  120. // Dispose of the stop channel
  121. close(c.stopChan)
  122. c.stopChan = nil
  123. }
  124. }
  125. /*
  126. Register registers a new handler to be called every interval, defined
  127. by the given spec.
  128. */
  129. func (c *Cron) Register(specString string, handler func()) error {
  130. spec, err := NewCronSpec(specString)
  131. if err == nil {
  132. c.RegisterSpec(spec, handler)
  133. }
  134. return err
  135. }
  136. /*
  137. RegisterSpec registers a new handler to be called every interval, defined
  138. by the given spec.
  139. */
  140. func (c *Cron) RegisterSpec(spec *CronSpec, handler func()) {
  141. specString := spec.SpecString()
  142. c.cronLock.Lock()
  143. defer c.cronLock.Unlock()
  144. if _, ok := c.specMap[spec.SpecString()]; !ok {
  145. c.specMap[specString] = spec
  146. }
  147. handlers, _ := c.handlerMap[specString]
  148. c.handlerMap[specString] = append(handlers, handler)
  149. }
  150. /*
  151. CronSpec is a data structure which is used to specify time schedules.
  152. A CronSpec can be stated as a single text string which must have the
  153. following 6 entries separated by whitespace:
  154. Field Valid values
  155. ----- ------------
  156. second * or 0-59 or *%1-59
  157. minute * or 0-59 or *%1-59
  158. hour * or 0-23 or *%1-23
  159. day of month * or 1-31 or *%1-31
  160. month * or 1-12 or *%1-12
  161. day of week * or 0-6 (0 is Sunday) or *%1-7
  162. Multiple values for an entry can be separated by commas e.g. 1,3,5,7.
  163. A * in any field matches all values i.e. execute every minute, every
  164. day, etc. A *%<number> in any field entry matches when the time is a
  165. multiple of <number>.
  166. Example code:
  167. ss, _ := NewCronSpec("0 0 12 1 * *")
  168. fmt.Println(ss.String())
  169. Output:
  170. at the beginning of hour 12:00 on 1st of every month
  171. */
  172. type CronSpec struct {
  173. Second []string
  174. Minute []string
  175. Hour []string
  176. DayOfMonth []string
  177. Month []string
  178. DayOfWeek []string
  179. }
  180. /*
  181. NewCronSpec creates a new CronSpec from a given spec string.
  182. */
  183. func NewCronSpec(spec string) (*CronSpec, error) {
  184. sspec := strings.Split(spec, " ")
  185. if len(sspec) != 6 {
  186. return nil, fmt.Errorf("Cron spec must have 6 entries separated by space")
  187. }
  188. checkNumberRange := func(num, min, max int) string {
  189. if num < min {
  190. return fmt.Sprintf("must be greater or equal than %v", min)
  191. } else if num > max {
  192. return fmt.Sprintf("must be smaller or equal than %v", max)
  193. }
  194. return ""
  195. }
  196. entries := make([][]string, 6)
  197. for i, entry := range sspec {
  198. field := fields[i]
  199. vals := strings.Split(entry, ",")
  200. valsLoop:
  201. for _, val := range vals {
  202. // Auto convert pointless things like *%1 -> *
  203. if val == "*%1" || (val == "*" && len(vals) > 1) {
  204. val = "*"
  205. vals = []string{val}
  206. break valsLoop
  207. }
  208. if strings.HasPrefix(val, "*%") {
  209. var res string
  210. // Deal with multiple-of entries
  211. num, err := strconv.Atoi(val[2:])
  212. if err != nil {
  213. return nil, fmt.Errorf("Cron %v entry needs a number after '*%%'", field)
  214. }
  215. // Check number range
  216. switch i {
  217. case 0: // Second
  218. res = checkNumberRange(num, 1, 59)
  219. case 1: // Minute
  220. res = checkNumberRange(num, 1, 59)
  221. case 2: // Hour
  222. res = checkNumberRange(num, 1, 23)
  223. case 3: // Day of month
  224. res = checkNumberRange(num, 1, 31)
  225. case 4: // Month
  226. res = checkNumberRange(num, 1, 12)
  227. case 5: // Day of week
  228. res = checkNumberRange(num, 1, 7)
  229. }
  230. if res != "" {
  231. return nil, fmt.Errorf("Cron %v entry %v", field, res)
  232. }
  233. } else if val != "*" {
  234. var res string
  235. num, err := strconv.Atoi(val)
  236. if err != nil {
  237. return nil, fmt.Errorf("Cron entries must be a number, '*' or *%% and a number")
  238. }
  239. // Check number range
  240. switch i {
  241. case 0: // Second
  242. res = checkNumberRange(num, 0, 59)
  243. case 1: // Minute
  244. res = checkNumberRange(num, 0, 59)
  245. case 2: // Hour
  246. res = checkNumberRange(num, 0, 23)
  247. case 3: // Day of month
  248. res = checkNumberRange(num, 1, 31)
  249. case 4: // Month
  250. res = checkNumberRange(num, 1, 12)
  251. case 5: // Day of week
  252. res = checkNumberRange(num, 0, 6)
  253. }
  254. if res != "" {
  255. return nil, fmt.Errorf("Cron %v entry %v", field, res)
  256. }
  257. }
  258. }
  259. entries[i] = vals
  260. }
  261. ret := &CronSpec{entries[0], entries[1], entries[2], entries[3],
  262. entries[4], entries[5]}
  263. // Sort all entries
  264. sort.Sort(numberStringSlice(ret.Second))
  265. sort.Sort(numberStringSlice(ret.Minute))
  266. sort.Sort(numberStringSlice(ret.Hour))
  267. sort.Sort(numberStringSlice(ret.DayOfMonth))
  268. sort.Sort(numberStringSlice(ret.Month))
  269. sort.Sort(numberStringSlice(ret.DayOfWeek))
  270. return ret, nil
  271. }
  272. /*
  273. MatchesTime checks if a given time object matches this CronSpec.
  274. */
  275. func (cs *CronSpec) MatchesTime(t time.Time) bool {
  276. matchItem := func(timeItem int, specItems []string) bool {
  277. for _, specItem := range specItems {
  278. if specItem == "*" {
  279. return true
  280. }
  281. if strings.HasPrefix(specItem, "*%") {
  282. interval, err := strconv.Atoi(specItem[2:])
  283. if err == nil && interval != 0 && timeItem%interval == 0 {
  284. return true
  285. }
  286. } else {
  287. item, err := strconv.Atoi(specItem)
  288. if err == nil && item == timeItem {
  289. return true
  290. }
  291. }
  292. }
  293. return false
  294. }
  295. return matchItem(t.Second(), cs.Second) &&
  296. matchItem(t.Minute(), cs.Minute) &&
  297. matchItem(t.Hour(), cs.Hour) &&
  298. matchItem(t.Day(), cs.DayOfMonth) &&
  299. matchItem(int(t.Month()), cs.Month) &&
  300. matchItem(int(t.Weekday()), cs.DayOfWeek)
  301. }
  302. /*
  303. Generate2000Examples generates matching time examples from the year 2000
  304. for this CronSpec. This function returns the first n examples starting
  305. from 01. January 2000 00:00:00.
  306. */
  307. func (cs *CronSpec) Generate2000Examples(n int) []string {
  308. var t time.Time
  309. res := make([]string, 0, n)
  310. // Create reference time boundaries for the year 2000
  311. startTime, err := time.Parse(time.RFC3339, "2000-01-01T00:00:00Z")
  312. errorutil.AssertOk(err)
  313. startNano := startTime.UnixNano()
  314. // Loop over all seconds of the year (2000 had 366 days)
  315. timerange := int64(60 * 60 * 24 * 366)
  316. for i := int64(0); i < timerange; i++ {
  317. if t = time.Unix(i, startNano).In(time.UTC); cs.MatchesTime(t) {
  318. res = append(res, t.String())
  319. }
  320. if len(res) > n-1 {
  321. break
  322. }
  323. }
  324. return res
  325. }
  326. /*
  327. SpecString returns the spec object as a spec string. This string can
  328. be used to construct the object again using NewCronSpec.
  329. */
  330. func (cs *CronSpec) SpecString() string {
  331. var res bytes.Buffer
  332. res.WriteString(strings.Join(cs.Second, ","))
  333. res.WriteString(" ")
  334. res.WriteString(strings.Join(cs.Minute, ","))
  335. res.WriteString(" ")
  336. res.WriteString(strings.Join(cs.Hour, ","))
  337. res.WriteString(" ")
  338. res.WriteString(strings.Join(cs.DayOfMonth, ","))
  339. res.WriteString(" ")
  340. res.WriteString(strings.Join(cs.Month, ","))
  341. res.WriteString(" ")
  342. res.WriteString(strings.Join(cs.DayOfWeek, ","))
  343. return res.String()
  344. }
  345. /*
  346. Constants for pretty printing
  347. */
  348. var (
  349. fields = []string{"second", "minute", "hour", "day of month",
  350. "month", "day of week"}
  351. days = []string{"Sunday", "Monday", "Tuesday",
  352. "Wednesday", "Thursday", "Friday", "Saturday"}
  353. months = []string{"January", "February", "March", "April",
  354. "May", "June", "July", "August", "September", "October",
  355. "November", "December"}
  356. )
  357. /*
  358. TimeString returns on which time during the day this spec will trigger.
  359. */
  360. func (cs *CronSpec) TimeString() string {
  361. secondString := func() []string {
  362. ret := make([]string, len(cs.Second))
  363. for i, s := range cs.Second {
  364. if strings.HasPrefix(s, "*%") {
  365. ret[i] = fmt.Sprintf("every %v second",
  366. ordinalNumberString(s[2:]))
  367. } else {
  368. if s == "0" {
  369. ret[i] = "the beginning"
  370. } else if s == "59" {
  371. ret[i] = "the end"
  372. } else {
  373. ret[i] = fmt.Sprintf("second %v", s)
  374. }
  375. }
  376. }
  377. return ret
  378. }
  379. minuteString := func() []string {
  380. ret := make([]string, len(cs.Minute))
  381. for i, m := range cs.Minute {
  382. if strings.HasPrefix(m, "*%") {
  383. ret[i] = fmt.Sprintf("every %v minute",
  384. ordinalNumberString(m[2:]))
  385. } else {
  386. if m == "0" {
  387. ret[i] = "the beginning"
  388. } else if m == "59" {
  389. ret[i] = "the end"
  390. } else {
  391. ret[i] = fmt.Sprintf("minute %v", m)
  392. }
  393. }
  394. }
  395. return ret
  396. }
  397. hourString := func() []string {
  398. ret := make([]string, len(cs.Hour))
  399. for i, h := range cs.Hour {
  400. if strings.HasPrefix(h, "*%") {
  401. ret[i] = fmt.Sprintf("every %v hour",
  402. ordinalNumberString(h[2:]))
  403. } else {
  404. hour, _ := strconv.Atoi(h)
  405. ret[i] = fmt.Sprintf("hour %02d:00", hour)
  406. }
  407. }
  408. return ret
  409. }
  410. if len(cs.Hour) == 1 && cs.Hour[0] == "*" {
  411. if len(cs.Minute) == 1 && cs.Minute[0] == "*" {
  412. if len(cs.Second) == 1 && cs.Second[0] == "*" {
  413. return "every second"
  414. }
  415. // Specs of the format [Seconds] * * ? ? ?
  416. return fmt.Sprintf("at %v of every minute", andJoin(secondString()))
  417. }
  418. if len(cs.Second) == 1 {
  419. if cs.Second[0] == "*" {
  420. // Specs of the format * [Minutes] * ? ? ?
  421. return fmt.Sprintf("every second of %v of every hour", andJoin(minuteString()))
  422. } else if cs.Second[0] == "0" {
  423. // Specs of the format 0 [Minutes] * ? ? ?
  424. return fmt.Sprintf("at %v of every hour", andJoin(minuteString()))
  425. }
  426. }
  427. // Specs of the format [Seconds] [Minutes] * ? ? ?
  428. return fmt.Sprintf("at %v of %v of every hour",
  429. andJoin(secondString()), andJoin(minuteString()))
  430. }
  431. if len(cs.Minute) == 1 && cs.Minute[0] == "*" {
  432. if len(cs.Second) == 1 {
  433. if cs.Second[0] == "*" {
  434. // Specs of the format * * [Hours] ? ? ?
  435. return fmt.Sprintf("every second of %v", andJoin(hourString()))
  436. } else if cs.Second[0] == "0" {
  437. // Specs of the format 0 * [Hours] ? ? ?
  438. return fmt.Sprintf("every minute of %v", andJoin(hourString()))
  439. }
  440. }
  441. // Specs of the format [Seconds] * [Hours] ? ? ?
  442. return fmt.Sprintf("at %v of every minute of %v",
  443. andJoin(secondString()), andJoin(hourString()))
  444. }
  445. if len(cs.Second) == 1 {
  446. if cs.Second[0] == "*" {
  447. // Specs of the format * [Minutes] [Hours] ? ? ?
  448. return fmt.Sprintf("every second of %v of %v", andJoin(minuteString()),
  449. andJoin(hourString()))
  450. } else if cs.Second[0] == "0" {
  451. // Specs of the format 0 [Minutes] [Hours] ? ? ?
  452. return fmt.Sprintf("at %v of %v", andJoin(minuteString()),
  453. andJoin(hourString()))
  454. }
  455. }
  456. // Specs of the format [Seconds] [Minutes] [Hours] ? ? ?
  457. return fmt.Sprintf("at %v of %v of %v",
  458. andJoin(secondString()), andJoin(minuteString()),
  459. andJoin(hourString()))
  460. }
  461. /*
  462. DaysString returns on which days this spec will trigger.
  463. */
  464. func (cs *CronSpec) DaysString() string {
  465. dayOfWeekToString := func() []string {
  466. ret := make([]string, len(cs.DayOfWeek))
  467. for i, dow := range cs.DayOfWeek {
  468. if strings.HasPrefix(dow, "*%") {
  469. ret[i] = fmt.Sprintf("every %v day of the week",
  470. ordinalNumberString(dow[2:]))
  471. } else {
  472. idx, _ := strconv.Atoi(dow)
  473. ret[i] = days[idx]
  474. }
  475. }
  476. return ret
  477. }
  478. dayOfMonthString := func() []string {
  479. ret := make([]string, len(cs.DayOfMonth))
  480. for i, dom := range cs.DayOfMonth {
  481. if strings.HasPrefix(dom, "*%") {
  482. // No need to write here "of the month" since a cron spec
  483. // addresses months specifically i.e. the month(s) will always
  484. // be included in every DaysString.
  485. ret[i] = fmt.Sprintf("every %v day",
  486. ordinalNumberString(dom[2:]))
  487. } else {
  488. ret[i] = ordinalNumberString(dom)
  489. }
  490. }
  491. return ret
  492. }
  493. monthString := func() []string {
  494. ret := make([]string, len(cs.Month))
  495. for i, m := range cs.Month {
  496. if strings.HasPrefix(m, "*%") {
  497. ret[i] = fmt.Sprintf("every %v month",
  498. ordinalNumberString(m[2:]))
  499. } else {
  500. idx, _ := strconv.Atoi(m)
  501. ret[i] = months[idx-1]
  502. }
  503. }
  504. return ret
  505. }
  506. if len(cs.Month) == 1 && cs.Month[0] == "*" {
  507. if len(cs.DayOfMonth) == 1 && cs.DayOfMonth[0] == "*" {
  508. if len(cs.DayOfWeek) == 1 && cs.DayOfWeek[0] == "*" {
  509. return "every day"
  510. }
  511. // Specs of the format ? ? ? * * [Days of week]
  512. return fmt.Sprintf("on %v", andJoin(dayOfWeekToString()))
  513. }
  514. if len(cs.DayOfWeek) == 1 && cs.DayOfWeek[0] == "*" {
  515. // Specs of the format ? ? ? [Days of month] * *
  516. return fmt.Sprintf("on %v of every month", andJoin(dayOfMonthString()))
  517. }
  518. // Specs of the format ? ? ? [Days of month] * [Days of week]
  519. return fmt.Sprintf("on %v and %v of every month",
  520. andJoin(dayOfWeekToString()), andJoin(dayOfMonthString()))
  521. }
  522. if len(cs.DayOfMonth) == 1 && cs.DayOfMonth[0] == "*" {
  523. if len(cs.DayOfWeek) == 1 && cs.DayOfWeek[0] == "*" {
  524. return fmt.Sprintf("in %v", andJoin(monthString()))
  525. }
  526. // Specs of the format ? ? ? * [Months] [Days of week]
  527. return fmt.Sprintf("on %v in %v", andJoin(dayOfWeekToString()),
  528. andJoin(monthString()))
  529. }
  530. if len(cs.DayOfWeek) == 1 && cs.DayOfWeek[0] == "*" {
  531. // Specs of the format ? ? ? [Days of month] [Months] *
  532. return fmt.Sprintf("on %v of %v", andJoin(dayOfMonthString()),
  533. andJoin(monthString()))
  534. }
  535. // Specs of the format ? ? ? [Days of month] [Months] [Days of week]
  536. return fmt.Sprintf("on %v and %v of %v",
  537. andJoin(dayOfWeekToString()), andJoin(dayOfMonthString()),
  538. andJoin(monthString()))
  539. }
  540. /*
  541. String returns a human readable string representing the spec.
  542. */
  543. func (cs *CronSpec) String() string {
  544. return fmt.Sprintf("%v %v", cs.TimeString(), cs.DaysString())
  545. }
  546. // Testing functions
  547. // =================
  548. /*
  549. NewTestingCronMonth creates a new test Cron object which goes through a full month.
  550. */
  551. func NewTestingCronMonth() *Cron {
  552. startTime, _ := time.Parse(time.RFC3339, "2000-01-01T00:00:00Z")
  553. endTime, _ := time.Parse(time.RFC3339, "2000-01-31T23:59:59Z")
  554. return NewTestingCron(startTime, endTime)
  555. }
  556. /*
  557. NewTestingCronWeek creates a new test Cron object which goes through a full week.
  558. */
  559. func NewTestingCronWeek() *Cron {
  560. startTime, _ := time.Parse(time.RFC3339, "2000-01-01T00:00:00Z")
  561. endTime, _ := time.Parse(time.RFC3339, "2000-01-07T23:59:59Z")
  562. return NewTestingCron(startTime, endTime)
  563. }
  564. /*
  565. NewTestingCronDay creates a new test Cron object which goes through a full day.
  566. */
  567. func NewTestingCronDay() *Cron {
  568. startTime, _ := time.Parse(time.RFC3339, "2000-01-01T00:00:00Z")
  569. endTime, _ := time.Parse(time.RFC3339, "2000-01-01T23:59:59Z")
  570. return NewTestingCron(startTime, endTime)
  571. }
  572. /*
  573. NewTestingCron creates a new Cron object which can be used for testing.
  574. When started it goes through all seconds of the the given time range in
  575. a fraction of the time (seconds are instantly increased). All handler functions
  576. are still called the exact number of times as if they would be running
  577. during the given time range in normal time. Use the NowFunc of the cron
  578. object to get the current testing time - the testing time will always
  579. return the same time unless advanced by the cron thread.
  580. Example code:
  581. c = NewTestingCronDay()
  582. c.Register("0 12 12,8 * * *", func() {
  583. // Do something at minute 12 of hour 08:00 and hour 12:00 every day
  584. })
  585. c.Start()
  586. WaitTestingCron(c)
  587. */
  588. func NewTestingCron(startTime, endTime time.Time) *Cron {
  589. tn, _ := NewTestingNow(startTime, endTime)
  590. ret := NewCron()
  591. ret.newNowFunc = tn.NewNow
  592. ret.NowFunc = tn.Now
  593. ret.Tick = 0 // A normal second is instantly increased
  594. return ret
  595. }
  596. /*
  597. WaitTestingCron waits for a testing cron object to end. No need to call
  598. stop afterwards.
  599. */
  600. func WaitTestingCron(c *Cron) {
  601. <-c.stopChan
  602. }
  603. /*
  604. TestingEndOfTime is a special time returned by TestingNow objects to indicate
  605. that the end time has been reached.
  606. */
  607. var TestingEndOfTime = time.Time{}
  608. /*
  609. TestingNow is a testing object which can provide a specialized Now function
  610. which runs from a given start to a given end time.
  611. */
  612. type TestingNow struct {
  613. start time.Time
  614. end time.Time
  615. tick int64
  616. }
  617. /*
  618. NewTestingNow creates a new TestingNow object with a given start and end time.
  619. */
  620. func NewTestingNow(start, end time.Time) (*TestingNow, error) {
  621. if !end.After(start) {
  622. return nil, fmt.Errorf("End time %v is not after start time %v", end, start)
  623. }
  624. return &TestingNow{start, end, 0}, nil
  625. }
  626. /*
  627. Now returns the current testing time.
  628. */
  629. func (tn *TestingNow) Now() time.Time {
  630. return tn.testingTime(false)
  631. }
  632. /*
  633. NewNow returns the current testing time and advances the clock.
  634. */
  635. func (tn *TestingNow) NewNow() time.Time {
  636. return tn.testingTime(true)
  637. }
  638. /*
  639. testingTime returns the current testing time and optionally advances the clock.
  640. */
  641. func (tn *TestingNow) testingTime(advance bool) time.Time {
  642. if advance {
  643. tn.tick++
  644. }
  645. if newTime := time.Unix(tn.tick, tn.start.UnixNano()); newTime.Before(tn.end) {
  646. return newTime.UTC()
  647. }
  648. return TestingEndOfTime
  649. }
  650. // Helper functions
  651. // ================
  652. func andJoin(ss []string) string {
  653. var buf bytes.Buffer
  654. sslen := len(ss) - 1
  655. for i, s := range ss {
  656. buf.WriteString(s)
  657. if i == sslen-1 {
  658. buf.WriteString(" and ")
  659. } else if i < sslen {
  660. buf.WriteString(", ")
  661. }
  662. }
  663. return buf.String()
  664. }
  665. /*
  666. ordinalNumber produces an ordinal number string from a given number
  667. string (e.g. 1 -> 1st, 2 -> 2nd).
  668. */
  669. func ordinalNumberString(number string) string {
  670. var suffix = "th"
  671. if strings.HasSuffix(number, "1") && !strings.HasSuffix(number, "11") {
  672. suffix = "st"
  673. } else if strings.HasSuffix(number, "2") && !strings.HasSuffix(number, "12") {
  674. suffix = "nd"
  675. } else if strings.HasSuffix(number, "3") && !strings.HasSuffix(number, "13") {
  676. suffix = "rd"
  677. }
  678. return number + suffix
  679. }
  680. // Comparator for number strings
  681. type numberStringSlice []string
  682. func (p numberStringSlice) Len() int { return len(p) }
  683. func (p numberStringSlice) Less(i, j int) bool {
  684. val1 := p[i]
  685. val2 := p[j]
  686. convert := func(val string) string {
  687. if strings.HasPrefix(val, "*%") {
  688. if num, err := strconv.Atoi(val[2:]); err == nil && num < 10 {
  689. return fmt.Sprintf("*%%0%v", num)
  690. }
  691. } else if val != "*" {
  692. if num, err := strconv.Atoi(val); err == nil && num < 10 {
  693. return fmt.Sprintf("0%v", num)
  694. }
  695. }
  696. return val
  697. }
  698. return convert(val1) < convert(val2)
  699. }
  700. func (p numberStringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }