Code Monkey home page Code Monkey logo

cron's Introduction

GoDoc Build Status

cron

Cron V3 has been released!

To download the specific tagged release, run:

go get github.com/robfig/cron/[email protected]

Import it in your program as:

import "github.com/robfig/cron/v3"

It requires Go 1.11 or later due to usage of Go Modules.

Refer to the documentation here: http://godoc.org/github.com/robfig/cron

The rest of this document describes the the advances in v3 and a list of breaking changes for users that wish to upgrade from an earlier version.

Upgrading to v3 (June 2019)

cron v3 is a major upgrade to the library that addresses all outstanding bugs, feature requests, and rough edges. It is based on a merge of master which contains various fixes to issues found over the years and the v2 branch which contains some backwards-incompatible features like the ability to remove cron jobs. In addition, v3 adds support for Go Modules, cleans up rough edges like the timezone support, and fixes a number of bugs.

New features:

  • Support for Go modules. Callers must now import this library as github.com/robfig/cron/v3, instead of gopkg.in/...

  • Fixed bugs:

    • 0f01e6b parser: fix combining of Dow and Dom (#70)
    • dbf3220 adjust times when rolling the clock forward to handle non-existent midnight (#157)
    • eeecf15 spec_test.go: ensure an error is returned on 0 increment (#144)
    • 70971dc cron.Entries(): update request for snapshot to include a reply channel (#97)
    • 1cba5e6 cron: fix: removing a job causes the next scheduled job to run too late (#206)
  • Standard cron spec parsing by default (first field is "minute"), with an easy way to opt into the seconds field (quartz-compatible). Although, note that the year field (optional in Quartz) is not supported.

  • Extensible, key/value logging via an interface that complies with the https://github.com/go-logr/logr project.

  • The new Chain & JobWrapper types allow you to install "interceptors" to add cross-cutting behavior like the following:

    • Recover any panics from jobs
    • Delay a job's execution if the previous run hasn't completed yet
    • Skip a job's execution if the previous run hasn't completed yet
    • Log each job's invocations
    • Notification when jobs are completed

It is backwards incompatible with both v1 and v2. These updates are required:

  • The v1 branch accepted an optional seconds field at the beginning of the cron spec. This is non-standard and has led to a lot of confusion. The new default parser conforms to the standard as described by the Cron wikipedia page.

    UPDATING: To retain the old behavior, construct your Cron with a custom parser:

// Seconds field, required
cron.New(cron.WithSeconds())

// Seconds field, optional
cron.New(cron.WithParser(cron.NewParser(
	cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor,
)))
  • The Cron type now accepts functional options on construction rather than the previous ad-hoc behavior modification mechanisms (setting a field, calling a setter).

    UPDATING: Code that sets Cron.ErrorLogger or calls Cron.SetLocation must be updated to provide those values on construction.

  • CRON_TZ is now the recommended way to specify the timezone of a single schedule, which is sanctioned by the specification. The legacy "TZ=" prefix will continue to be supported since it is unambiguous and easy to do so.

    UPDATING: No update is required.

  • By default, cron will no longer recover panics in jobs that it runs. Recovering can be surprising (see issue #192) and seems to be at odds with typical behavior of libraries. Relatedly, the cron.WithPanicLogger option has been removed to accommodate the more general JobWrapper type.

    UPDATING: To opt into panic recovery and configure the panic logger:

cron.New(cron.WithChain(
  cron.Recover(logger),  // or use cron.DefaultLogger
))
  • In adding support for https://github.com/go-logr/logr, cron.WithVerboseLogger was removed, since it is duplicative with the leveled logging.

    UPDATING: Callers should use WithLogger and specify a logger that does not discard Info logs. For convenience, one is provided that wraps *log.Logger:

cron.New(
  cron.WithLogger(cron.VerbosePrintfLogger(logger)))

Background - Cron spec format

There are two cron spec formats in common usage:

The original version of this package included an optional "seconds" field, which made it incompatible with both of these formats. Now, the "standard" format is the default format accepted, and the Quartz format is opt-in.

cron's People

Contributors

bgaifullin avatar bruth avatar cettoana avatar choleraehyq avatar cloudfly avatar daddye avatar daft-panda avatar dannyfeliz avatar dennisfrancis avatar elimisteve avatar icholy avatar ktogo avatar lukescott avatar mtojek avatar nnao45 avatar nono avatar patito avatar ricardolonga avatar robfig avatar soltysh avatar sparrc avatar tdterry avatar theothertomelliott avatar tilinna avatar vankofalcon avatar vayan avatar xiaoxiaosn avatar yvanoers avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cron's Issues

Pass the JobID to the Job

cron.AddFunc()/cron.AddJob() return the ID of the added job.

When cron runs the job, it should pass Run() the ID of the job.

Improper statement in doc about job concurrency

The doc statements, below, makes me feel ambiguous.

Callers may register Funcs to be invoked on a given schedule. Cron will run them in their own goroutines.

I think cron will run each cron job in different goroutines. I.e., cron will handle the concurrency about the jobs.
However, after reading the code in cron.go, i find that cron just call the registered job and handle them sequentially.
I think this statements may be more proper:

Callers may registers Funcs to be invoked on a given schedule.
Single cron instance will run registered Funcs sequentially.
Different cron instances will be run concurrently by cron package itself.

Putting the clock forwards causes cron to catch-up and run multiple times

If you set a job to run @every 5s and start it works perfectly within my test program. However if you put the clock fowards an hour it will run approximately 720 times as the effective time will be playing catch-up with the current time (now). This can happen when laptops goes to sleep and then wake up. This is the issue I'm facing and my maintenance tasks to run multiple times on laptop resume.

By quickly taking a look I think the fix is to change line 161 from:

e.Next = e.Schedule.Next(effective)

To

e.Next = e.Schedule.Next(now)

I don't want to patch my code with this before you agree that this fix is correct. Please let me know.

There is no way to wait for completion of started Jobs after calling Stop()

When calling Stop() there may still be be Jobs whose Goroutines have been created but were never scheduled. It is therefore impossible to wait for all Jobs to complete, because some may not yet be running, making it impossible to track them.

The right place for adding to a sync.WaitGroup would be in Cron:run() right before go c.runWithRecovery(e.Job). You could then add a new Cron:Wait() function for waiting for jobs to finish after calling Stop().

Using Asia/Kolkata(+0530) timezone cause schedule inaccuracy

Example Code:

func main() {
    schedule, _ := cron.Parse("0 0 12 * * ?")
    fmt.Printf("%s\n", time.Now())
    fmt.Printf("%s\n", schedule.Next(time.Now()))
}

Output:
2016-04-01 11:09:16.342426894 +0530 IST
2016-04-02 12:00:00 +0530 IST

After examination of spec.go, I think the problem was caused by usage of
time.Truncate in SpecSchedule's method Next, which should use time.Date
instead to round up hours/minutes/seconds.

Any other ideas?

Non-idiomatic struct field naming convention.

This is a very minor issue.

Go allows struct fields to be named by their type alone:

type myStruct struct {
    int
}

func (m myStruct) getVal() int {
    return m.int
}

The code in this package often uses this convention, but names the fields explicitly:

type myStruct struct {
    int int
}

The idiomatic way would be to omit the name and simply list the type as shown in the first example. Again, it's a very minor concern.

A small bug when using c.AddFunc after c.Start

when using

c:=cron.New()
c.Start()
time.Sleep(5*time.Second)
c.AddFunc("* * * * * *",myfunc)

the myfunc is

func myfunc(){
fmt.Println(time.Unix(time.Now().Unix(), 0).Format("2006-01-02 15:04:05"))

the output is:(current time is 15:16:07)

2015-11-23 15:16:07  <- first in sleep 5
2015-11-23 15:16:07  <- 2nd in sleep 5
2015-11-23 15:16:07  <- 3rd in sleep 5
2015-11-23 15:16:07  <- 4th in sleep 5
2015-11-23 15:16:07  <- 5th in sleep 5
2015-11-23 15:16:07  <- truely the first second
2015-11-23 15:16:08  <- truely the second second
2015-11-23 15:16:09  <- truely the third second

However,modify cron.go from

// Run the scheduler.. this is private just due to the need to synchronize
// access to the 'running' state variable.
func (c *Cron) Run() {
    // Figure out the next activation times for each entry.
    now := time.Now().Local()
    for _, entry := range c.entries {
        entry.Next = entry.Schedule.Next(now)
    }

    for {

        // Determine the next entry to run.
        sort.Sort(byTime(c.entries))

To

// Run the scheduler.. this is private just due to the need to synchronize
// access to the 'running' state variable.
func (c *Cron) Run() {
    // Figure out the next activation times for each entry.
-   now := time.Now().Local()                                 <--------------assigned 'new' before the for loop
-   for _, entry := range c.entries {                      
-       entry.Next = entry.Schedule.Next(now)
-   }                                                                     

    for {
+       now := time.Now().Local()                     
+       for _, entry := range c.entries {              
+           entry.Next = entry.Schedule.Next(now) 
+       }                                                                      

        // Determine the next entry to run.
        sort.Sort(byTime(c.entries))

may solve this problem.

Cron:Entries() may return nil when called concurrently

This is a theoretical issue I have not yet encountered in practice (but think could actually happen) that I came across while thinking about the source code, in particular these bits:

// Entries returns a snapshot of the cron entries.
func (c *Cron) Entries() []*Entry {
	if c.running {
		c.snapshot <- nil // A
		x := <-c.snapshot // B
		return x
	}
	return c.entrySnapshot()
}
		case <-c.snapshot: // C
			c.snapshot <- c.entrySnapshot() // D

Let's say Goroutine 1 is executing the run() loop containing C and D. Now Goroutine 2 executes Entries() until A, where it blocks. Goroutine 1 executes C, but before it gets a chance to execute D the scheduler decides to preempt it in favour of Goroutine 3, which executes Entries() until A, where it puts nil into the snapshot channel, leading Goroutine 2 to receive nil in B!

Since entrySnapshot() is a non-trivial function I feel like there's a good chance the Goroutine might get preempted during its execution.

The issue lies in using the same channel for communication in both directions. You could instead have a channel of channels over which you send a result channel created in Entries().

The question mark logic seems implemented wrongly

The doc said that:

Question mark ( ? )

Question mark may be used instead of '*' for leaving either day-of-month or day-of-> week blank.

However, according to the code in cron, question mark and asterisk is equal.

    if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
        start = r.min
        end = r.max
        extra_star = starBit
        }

And ignoring the day of week will make it like adding asterisk as the value of day of week

Version 2

Hello,
Is v2 still supported ?
Are recent fixes on master planned to be ported to v2 ?

weekly cron seems to never run

Hey! I was wondering if there's an inherent issue with long-term jobs, I was running 0 10 * * 6 to run each Saturday with no luck. I'll try it again this week but I wanted to check to make sure it should work fine

thanks!

Question - sequencing

Is it possible to sequence a job to start after another job has finished ?
You never know how long the first job will take.
This might be an anti - pattern I know, because sequences and timed jobs are really opposites, but worth asking.

Thanks in advance for this library

v2 Release

Any way you could create a v2 release, so people using glide, etc can use semantic versioning in our projects?

Thanks!

missing 0th hour when called with TimeZone in cron.v2

This a bug with v2 branch

I tried to see if this is related to #55 , but its mentioned that the code is fixed. v2 diverged from master.

Is there a plan to merge v2 with master?

import "gopkg.in/robfig/cron.v2"
func main() {
	c := cron.New()
	c.Start()
	weekday := "0,1,2,3,4,5,6"
	hour := "0,1,2,3,4,5,6,7,8,9,10" // ,11,12,13,16,17,18,19,20,21,22,23 // hour does not work as expected
	tzName := "Asia/Kolkata"
	cron1 := fmt.Sprintf("TZ=%s 0 0 %s * * %s", tzName, hour, weekday)
	id, _ := c.AddFunc(cron1, func() { fmt.Println("Func to call") })
	entry := c.Entry(id)

	t := entry.Next
	fmt.Println(t)
	for i := 0; i < 24; i++ {
		t = entry.Schedule.Next(t)
		fmt.Println(t)
	}
}

Output is missing 00:00:00 from 9th Dec.
Current Date: Thu Dec 8 19:02:53 IST 2016

2016-12-09 01:00:00 +0530 IST
2016-12-09 02:00:00 +0530 IST
2016-12-09 03:00:00 +0530 IST
2016-12-09 04:00:00 +0530 IST
2016-12-09 05:00:00 +0530 IST
2016-12-09 06:00:00 +0530 IST
2016-12-09 07:00:00 +0530 IST
2016-12-09 08:00:00 +0530 IST
2016-12-09 09:00:00 +0530 IST
2016-12-09 10:00:00 +0530 IST
2016-12-10 00:00:00 +0530 IST
2016-12-10 01:00:00 +0530 IST
2016-12-10 02:00:00 +0530 IST
2016-12-10 03:00:00 +0530 IST
2016-12-10 04:00:00 +0530 IST
2016-12-10 05:00:00 +0530 IST
2016-12-10 06:00:00 +0530 IST
2016-12-10 07:00:00 +0530 IST
2016-12-10 08:00:00 +0530 IST
2016-12-10 09:00:00 +0530 IST
2016-12-10 10:00:00 +0530 IST
2016-12-11 00:00:00 +0530 IST
2016-12-11 01:00:00 +0530 IST
2016-12-11 02:00:00 +0530 IST
2016-12-11 03:00:00 +0530 IST

works fine without timezone set. master does not have code for TZ . Fails when set.

import 	"github.com/robfig/cron"
func main() {
	c := cron.New()
	c.Start()
	weekday := "0,1,2,3,4,5,6"
	hour := "0,1,2,3,4,5,6,7,8,9,10" // ,11,12,13,16,17,18,19,20,21,22,23 // hour does not work as expected
	// tzName := "Asia/Kolkata"
	cron1 := fmt.Sprintf("0 0 %s * * %s", hour, weekday)
	c.AddFunc(cron1, func() { fmt.Println("Func to call") })

	fmt.Println(len(c.Entries()))
	entry := c.Entries()[0]

	t := entry.Next
	fmt.Println(t)
	for i := 0; i < 24; i++ {
		t = entry.Schedule.Next(t)
		fmt.Println(t)
	}

}

Output:

2016-12-09 00:00:00 +0530 IST
2016-12-09 01:00:00 +0530 IST
2016-12-09 02:00:00 +0530 IST
2016-12-09 03:00:00 +0530 IST
2016-12-09 04:00:00 +0530 IST
2016-12-09 05:00:00 +0530 IST
2016-12-09 06:00:00 +0530 IST
2016-12-09 07:00:00 +0530 IST
2016-12-09 08:00:00 +0530 IST
2016-12-09 09:00:00 +0530 IST
2016-12-09 10:00:00 +0530 IST
2016-12-10 00:00:00 +0530 IST
2016-12-10 01:00:00 +0530 IST
2016-12-10 02:00:00 +0530 IST
2016-12-10 03:00:00 +0530 IST
2016-12-10 04:00:00 +0530 IST
2016-12-10 05:00:00 +0530 IST
2016-12-10 06:00:00 +0530 IST
2016-12-10 07:00:00 +0530 IST
2016-12-10 08:00:00 +0530 IST
2016-12-10 09:00:00 +0530 IST
2016-12-10 10:00:00 +0530 IST
2016-12-11 00:00:00 +0530 IST
2016-12-11 01:00:00 +0530 IST
2016-12-11 02:00:00 +0530 IST

The function multiple starts immediately after add into cron

I'm trying to add function into CRON to running every minute:
c.AddFunc("*/1 * * * *", func() { log.Println("Run every minute") })

If I do it immediately after cron object was created, then code works as expected - the function will be executed every minute, starting at the beginning of the next minute.
But if I will add the function in a minute after the current (or later - after 2, 3 and more minutes), it begins to execute immediately after you add into the cron.
In addition, it executed as many times as the minutes passed between the creation of the cron and function was added.
For example, if you try to run the following code:

package main

import (
        "log"
        "time"
        "gopkg.in/robfig/cron.v2"
)

func main() {
        c := cron.New()
        c.Start()
        log.Println("Start CRON.")
        time.Sleep(2 * time.Minute)
        log.Println("Add function into CRON.")
        c.AddFunc("*/1 * * * *", func() { log.Println("Run every minute") })
        time.Sleep(5 * time.Second)
        c.Stop()
}

So, the function will call exactly 2 times immediately after adding into cron, because it took 2 minutes between the start of the cron (12:31:36) and adding the function into it (12:33:36).

2017/05/29 12:31:36 Start CRON.
2017/05/29 12:33:36 Add function into CRON.
2017/05/29 12:33:36 Run every minute
2017/05/29 12:33:36 Run every minute

If between execute c.Start() and c.AddFunc took 3 minutes, then we would see in the logs three calls Run every minute.
In my opinion, the first time the function should be executed at the beginning of the next minute - 12:34:00 and repeat every minute.

Go process need to stand by? Or else scheduled job will not be running?

Just read through the docs, so this is not an exactly Unix/Linux crontab style cron?

After I start the job runner, the go process still need to stand by there forever, or else all the scheduled job will not be running? Not like handling over the control to os, then even the go program exit, os will still invoke the jobs as scheduled periodically?

Like:

func main() {
  c := cron.New()
  c.AddFunc("* * * * * *", func() { fmt.Println("?????????????????????????") })
  c.Start()
  time.Sleep(time.Second * 50)
}

Then go program exit, nothing is there....

Am I correct?

Monthly job executed every day

I had a strange behavior:

I have a monthly job for sending out emails running at 8:30 first day of every month.

    c := cron.New()
        ...
    c.AddFunc("15 8 1 * *", func() { EmailMonthlySummaries(db) })
    c.Start()

But the job executed every day for the three consecutive days. Any ideas what is going on?

Stop a not running cron block program

Hello,
I think that when Stop() is called on a not running cron, it block the program.
Is there a when to know if cron is started ? running is private.

Cron Format not the same as Cron Spec

Out of all the libraries and sites I've seen everybody who uses the six * supports year instead of seconds.
For Example:
http://www.nncron.ru/help/EN/working/cron-format.htm

I personally think it would be better to follow what everyone else is doing to avoid confusion. It also lets it work with libraries that convert cron strings into human-readable format.

I don't think running a function every second is very good use case nor is running functions at specific second intervals. Might as well just use "sleep" or tickers. Years probably won't be that useful either but having a more compatible format would be nice.

Add start time

For example:Today is 2015-12-24 , I want 2015-12-28 after to execute once a day. How can I do it now?

License

What license is this code released under?

feature: from the end of the month

I know the CRON expressions are modeled after CRON, however, I have a basic need to schedule events at the end of the month. Some times the last day and some times the last 4 or 5 days. As of systemd 233 they added a '~' to the expression syntax to support exactly that. Any chance of getting that included?

trigger twice or thrice

In scheduling some job often repeated execution
Test expression:
0/2 * * * * ?
0 */2 * * * *

Panic if stop is called before start

This was obviously an error on my part, but discovered that calling Stop without calling Start will block since nothing is receiving c.stop <- struct{}{}. This is definitely a programmer error, I suggest panicking if the cron is not running.

// Stop the cron scheduler.
func (c *Cron) Stop() {
    if !c.running {
        panic("cron: not running")
    }
    c.stop <- struct{}{}
    c.running = false
}

@midnight doesn't seem to be working

I have 2 jobs one that runs @every 15m and another at @midnight. Midnight one didn't even start, I don't see anything wrong with my code and since first one is working I am bit confused. Should I use regular cron expression ?

Usage error in doc.go

// This:
c.AddFunc("0 5 * * * *",  func() { fmt.Println("Every 5 minutes") })

// Should be:
c.AddFunc("0 */5 * * * *",  func() { fmt.Println("Every 5 minutes") })
// OR
c.AddFunc("0 5 * * * *",  func() { fmt.Println("5th minute of every hour") })

Possible deadlock

It looks that there is a a deadlock somewhere.
I've used tried of using cron with a service manager, and the error is:

Mar 11 18:51:54 serve foo[2257]: panic: runtime error: invalid memory address or nil pointer dereference
Mar 11 18:51:54 serve foo[2257]: [signal 0xb code=0x1 addr=0x30 pc=0x4acd83]
Mar 11 18:51:54 serve foo[2257]: goroutine 1 [running]:
Mar 11 18:51:54 serve foo[2257]: panic(0x6bc260, 0xc82000a0b0)
Mar 11 18:51:54 serve foo[2257]:         /usr/local/lib/go/src/runtime/panic.go:464 +0x3ff
Mar 11 18:51:54 serve foo[2257]: github.com/robfig/cron.(*Cron).AddJob(0x0, 0x70b480, 0xa, 0x7f227577e528, 0x775390, 0x0, 0x0)
Mar 11 18:51:54 serve foo[2257]:         /usr/local/lib/go-3party/src/github.com/robfig/cron/cron.go:96 +0x1c3
Mar 11 18:51:54 serve foo[2257]: github.com/robfig/cron.(*Cron).AddFunc(0x0, 0x70b480, 0xa, 0x775390, 0x0, 0x0)
Mar 11 18:51:54 serve foo[2257]:         /usr/local/lib/go-3party/src/github.com/robfig/cron/cron.go:87 +0x8d
Mar 11 18:51:54 serve foo[2257]: main.Program.Start(0x7f227577e4c0, 0xc820062140, 0x0, 0xc82005a600, 0xc820056180, 0x7f227577e4c0, 0xc820062140, 0x0, 0x0)
Mar 11 18:51:54 serve foo[2257]:         /data/taka/code/go/src/indoo/cmd/foo/foo.go:68 +0xb5
Mar 11 18:51:54 serve foo[2257]: main.(*Program).Start(0xc82005c6c0, 0x7f227577e4c0, 0xc820062140, 0x0, 0x0)
Mar 11 18:51:54 serve foo[2257]:         <autogenerated>:1 +0xe0
Mar 11 18:51:54 serve foo[2257]: github.com/kardianos/service.(*systemd).Run(0xc820062140, 0x0, 0x0)
Mar 11 18:51:54 serve foo[2257]:         /usr/local/lib/go-3party/src/github.com/kardianos/service/service_systemd_linux.go:126 +0xc4
Mar 11 18:51:54 serve foo[2257]: main.main()
Mar 11 18:51:54 serve foo[2257]:         /data/taka/code/go/src/indoo/cmd/foo/foo.go:50 +0x55c

And the code is:

package main

import (
    "flag"
    "os"
    "time"

    log "github.com/Sirupsen/logrus"
    "github.com/kardianos/service"
    "github.com/robfig/cron"
)

var (
    FlagInstall   = flag.Bool("install", false, "install setups up the service in the OS")
    FlagUninstall = flag.Bool("uninstall", false, "uninstall removes the service from the OS")
)

func main() {
    flag.Parse()

    serviceConfig := &service.Config{
        Name:        "foo",
        DisplayName: "Foo daemon",
        Description: "tester of daemon",
        //UserName:    "ubuntu",
    }

    serv, err := service.New(Program{}, serviceConfig)
    if err != nil {
        log.Fatal(err)
    }
    serviceManager := service.ChosenSystem()

    if *FlagInstall {
        if !serviceManager.Detect() {
            log.Fatal("the service manager of this system is not available")
        }
        if err = serv.Install(); err != nil {
            log.Fatal(err)
        }
        os.Exit(0)
    } else if *FlagUninstall {
        if err = serv.Uninstall(); err != nil {
            log.Fatal(err)
        }
        os.Exit(0)
    }

    if err = serv.Run(); err != nil {
        log.Fatal(err)
    }
}

type Program struct {
    service service.Service
    cron_   *cron.Cron
}

func (p Program) Start(s service.Service) error {
    p.service = s
    p.cron_.AddFunc("@every 20s", func() {
        log.Print("++")
    })

    p.cron_.Start()
    go p.run()
    log.Info("Start")
    return nil
}

func (p Program) run() {
    //p.cron_.Start()

    log.Info("run")
}

func (p Program) Stop(s service.Service) error {
    p.cron_.Stop()
    log.Info("Stop")

    time.Sleep(10*time.Millisecond)
    return nil
}

Multiple jobs run in unexpected time interval

# go run hello.go 
2017/06/07 19:54:19 Start
2017/06/07 19:54:29 @every 1m30s
2017/06/07 19:54:39 @every 1m30s
2017/06/07 19:54:49 @every 1m30s
2017/06/07 19:54:59 @every 1m30s
2017/06/07 19:55:09 @every 1m30s
2017/06/07 19:55:19 @every 1m30s
2017/06/07 19:55:19 @every 1m30s
2017/06/07 19:55:19 @every 1m
2017/06/07 19:55:29 @every 1m30s
2017/06/07 19:55:39 @every 1m30s
2017/06/07 19:55:49 @every 1m30s
2017/06/07 19:55:49 @every 1m30s
2017/06/07 19:55:59 @every 1m30s
2017/06/07 19:56:09 @every 1m30s
2017/06/07 19:56:19 @every 1m30s
2017/06/07 19:56:19 @every 1m30s
2017/06/07 19:56:19 @every 1m
2017/06/07 19:56:29 @every 1m30s
2017/06/07 19:56:39 @every 1m30s
2017/06/07 19:56:49 @every 1m30s
2017/06/07 19:56:59 @every 1m30s
# cat hello.go
package main

import "log"
import "github.com/robfig/cron"
import "time"

func main() {
        c := cron.New()
        c.AddFunc("@every 1m", func() {
                log.Println("@every 1m")
        })
        for _, v := range []string{"@every 10s", "@every 1m", "@every 24h", "@every 1m30s"} {
                c.AddFunc(v, func() {
                        log.Println(v)
                })
        }
        c.Start()
        log.Println("Start")
        time.Sleep(600 * time.Second)
}

As the above result and code, when multiple jobs are added, the first 10s-interval job executes the last 1m30s-interval task.

Cron not doing anything

Hi @robfig,

I set a cron for some tasks but they weren't being executed. In order to understand I did this:

func main() {
c := cron.New()
c.AddFunc("@every 10s", func() { log.Println("Every sec") })
c.Start()
}

Go run this file and it doesn't print the message. What am I missing ?

1-31/10 vs */10 dom

I would think 1-31/10 vs */10 would be the same, but the code handles this case differently:

master/spec.go

    if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
        return domMatch && dowMatch
    }
    return domMatch || dowMatch

Is there any doc somewhere that specifies why this is?

I'm asking because I added week year to my fork, and this is how I'm currently handling it:

wy-field/spec.go

    if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
        return domMatch && dowMatch && woyMatch
    }
    return domMatch || (dowMatch && woyMatch)

I'm wondering if domMatch && dowMatch && woyMatch is correct in the starBit case.

Parse options & other features

We use the cron package at the company I work for, and we've made some modifications that were needed for our backend. I have four features separated into separate branches, and I'm wondering if you would be interested in merging these.

  1. Parse options https://github.com/webconnex/cron/tree/parser-options
    This adds Parser and NewParser with the ability to:

    • Select which fields are included. So you can exclude seconds, or any other field you don't want to use.
    • Set other options that might be useful, like making certain assumptions about the schedule.

    The Parse function now looks like this:

    var defaultParser = NewParser(
        Second | Minute | Hour | Dom | Month | DowOptinal | Descriptor,
    )
    
    // Parse returns a new crontab schedule representing the given spec.
    // It returns a descriptive error if the spec is not valid.
    //
    // It accepts
    //   - Full crontab specs, e.g. "* * * * * ?"
    //   - Descriptors, e.g. "@midnight", "@every 1h30m"
    func Parse(spec string) (_ Schedule, err error) {
        return defaultParser.Parse(spec)
    }

    This could be a resolution for #58. All you would need to do is remove Second and it would be spec compliant. But for those who need it, they can create a new parser with the option.

    I'm not sure about Dow being optional and being "spec complaint" - but there are two options for this - Dow and DowOptional which are interchangeable - one makes it required, the other allows you to omit it.

  2. Week year (built upon parser-options) https://github.com/webconnex/cron/tree/wy-field

    Adds another option after Dow that specifies the week of the year. This allows you to do bi-weekly schedules. It's used in conjunction with Dow.

  3. Year field (built upon wy-field + parser-options) https://github.com/webconnex/cron/tree/year-field

    Adds year field after week year. Range 2000 - 2099. Utilizes multi-bits. Can change to larger range, it just uses a larger slice (current size is 2).

  4. Approx Dom (built upon parser-options) https://github.com/webconnex/cron/tree/approx-date

    Adds a ~ symbol to be used at the beginning of a field (currently only supported for Dom) to specify approximate. If, for example, you say 31 and there are only 30 days in the target month, Next will choose the 30th. Or the 28th/29th in February.

    This is similar to L or L-1 in quartz, but differs from L-1 in that we don't want "the second to last day" we want a specific day in the month if it exists, but fall back to an earlier day if it doesn't. So L-1 would always land on the 30th, 28th (leap year Feb), or 27th (non-leap year Feb). ~30 would be the 30th, 29th (leap year Feb), and 28th (non-leap year Feb).

    An ApproxDay parser option was added to allow you to say "always assume approx date" without having to specify ~. In our case we always want this option in our system, while others may want to specify it in the schedule specifically.

The naming of the parser options could probably use an adjustment. And there may be some room for some better tests. I have added some for week year and approx dom. And the documentation would have to be updated. If interested, please let me know which areas need some improvement.

I also have plans to add L and # options from quartz - not sure how to approach those yet. Those options are probably further down pipe for me as the above features were to fill an immediate need for us.

I'm also planning on taking another stab at DST and not skipping / repeating. I had a PR for that a while back - and there was a comment about it being supported now - but the docs doesn't indicate that. I think I can also handle that in a less confusing way this time around :).

"@every" is starting from program start

Hi,

We noticed a weird behaviour, or at least something worth documenting:

@every 12h is going to run every 12h, from the program start, so it's not equivalent to 1 run at midnight and 1 at noon (which we expected).

Thanks

Flesh out README

It'd be helpful if the README at least had a subset of the examples in the godocs, as well as a pointer that the user should be using the gopkg.in paths.

Job run twice when timer triggers to early

I have had an instance where some jobs ran twice which looks to be cause by the timer triggering a few seconds to early.

The line (cron.go:201 on current master)
e.Next = e.Schedule.Next(now)
will cause a job to run again if the timer pops early.

In my case the jobs was scheduled a few days ahead - i don't have an explantation why the started a few seconds early.

I notice this was changed from:
e.Next = e.Schedule.Next(effectiveTime)
to prevent catch up effect in the machine sleeps.

However this new behaviour is a real problem for me.

I would suggest a check if when the timer is triggered: is now before the effective time - if so continue and let the timer be scheduled again. We could also mitigate by never setting timer for more than 1 hour so now is regularly reset?

However this could be an issue if it triggers before and keeps waking up to early?

Can I also ask about the v1 and v2 branches (i assume v1 is very old and should be ignored) v2 has extra features - but is missing some fixes from master....

I am going to submit a pull request

Seconds field should be optional to conform to unix standard

The below is an excerpt from the crontab man page. I think that it would be useful to accept the same input as the crontab. You can always add a 0 for the seconds field if it isn't provided.

INPUT FILES
       In the POSIX locale, the user or application shall ensure that a crontab entry is a text file consisting of lines of six fields each. The fields shall be separated by <blank>s. The first five fields
       shall be integer patterns that specify the following:

        1. Minute [0,59]

        2. Hour [0,23]

        3. Day of the month [1,31]

        4. Month of the year [1,12]

        5. Day of the week ([0,6] with 0=Sunday)

       Each of these patterns can be either an asterisk (meaning all valid values), an element, or a list of elements separated by commas. An element shall be either a number or two numbers separated by  a
       hyphen (meaning an inclusive range). The specification of days can be made by two fields (day of the month and day of the week).  If month, day of month, and day of week are all asterisks, every day
       shall be matched. If either the month or day of month is specified as an element or list, but the day of week is an asterisk, the month and day of month fields shall specify the days that match.  If
       both month and day of month are specified as an asterisk, but day of week is an element or list, then only the specified days of the week match. Finally, if either the month or day of month is spec‐
       ified as an element or list, and the day of week is also specified as an element or list, then any day matching either the month and day of month, or the day of week, shall be matched.

       The sixth field of a line in a crontab entry is a string that shall be executed by sh at the specified times. A percent sign character in this field shall be translated to a <newline>. Any character
       preceded  by  a backslash (including the '%' ) shall cause that character to be treated literally. Only the first line (up to a '%' or end-of-line) of the command field shall be executed by the com‐
       mand interpreter. The other lines shall be made available to the command as standard input.

       Blank lines and those whose first non- <blank> is '#' shall be ignored.

       The text files /usr/lib/cron/cron.allow and /usr/lib/cron/cron.deny shall contain zero or more user names, one per line, of users who are, respectively, authorized or denied access  to  the  service
       underlying the crontab utility.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.