Skip to content

Commit

Permalink
Merge pull request #28 from reugn/develop
Browse files Browse the repository at this point in the history
Add support for time.Location in CronTrigger
  • Loading branch information
reugn authored May 24, 2022
2 parents 64275d4 + 8606c06 commit e1a905d
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 43 deletions.
29 changes: 17 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,6 @@ A minimalistic and zero-dependency scheduling library for Go.
Inspired by the [Quartz](https://github.com/quartz-scheduler/quartz) Java scheduler.

### Library building blocks
Job interface. Any type that implements it can be scheduled.
```go
type Job interface {
Execute()
Description() string
Key() int
}
```
Implemented Jobs
- ShellJob
- CurlJob

Scheduler interface
```go
type Scheduler interface {
Expand Down Expand Up @@ -49,7 +37,9 @@ Implemented Schedulers
Trigger interface
```go
type Trigger interface {
// NextFireTime returns the next time at which the Trigger is scheduled to fire.
NextFireTime(prev int64) (int64, error)
// Description returns the description of the Trigger.
Description() string
}
```
Expand All @@ -58,6 +48,21 @@ Implemented Triggers
- SimpleTrigger
- RunOnceTrigger

Job interface. Any type that implements it can be scheduled.
```go
type Job interface {
// Execute is called by a Scheduler when the Trigger associated with this job fires.
Execute()
// Description returns the description of the Job.
Description() string
// Key returns the unique key for the Job.
Key() int
}
```
Implemented Jobs
- ShellJob
- CurlJob

## Cron expression format
| Field Name | Mandatory | Allowed Values | Allowed Special Characters |
| ------------ | --------- | --------------- | -------------------------- |
Expand Down
29 changes: 22 additions & 7 deletions quartz/cron.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,19 @@ type CronTrigger struct {
expression string
fields []*CronField
lastDefined int
location *time.Location
}

// NewCronTrigger returns a new CronTrigger.
// Verify CronTrigger satisfies the Trigger interface.
var _ Trigger = (*CronTrigger)(nil)

// NewCronTrigger returns a new CronTrigger using the UTC location.
func NewCronTrigger(expr string) (*CronTrigger, error) {
return NewCronTriggerWithLoc(expr, time.UTC)
}

// NewCronTriggerWithLoc returns a new CronTrigger with the given time.Location.
func NewCronTriggerWithLoc(expr string, location *time.Location) (*CronTrigger, error) {
fields, err := validateCronExpression(expr)
if err != nil {
return nil, err
Expand All @@ -52,16 +61,22 @@ func NewCronTrigger(expr string) (*CronTrigger, error) {
fields[0].values, _ = fillRange(0, 59)
}

return &CronTrigger{expr, fields, lastDefined}, nil
return &CronTrigger{
expression: expr,
fields: fields,
lastDefined: lastDefined,
location: location,
}, nil
}

// NextFireTime returns the next time at which the CronTrigger is scheduled to fire.
func (ct *CronTrigger) NextFireTime(prev int64) (int64, error) {
parser := NewCronExpressionParser(ct.lastDefined)
return parser.nextTime(prev, ct.fields)
prevTime := time.Unix(prev/int64(time.Second), 0).In(ct.location)
return parser.nextTime(prevTime, ct.fields)
}

// Description returns the CronTrigger description.
// Description returns the description of the trigger.
func (ct *CronTrigger) Description() string {
return fmt.Sprintf("CronTrigger %s", ct.expression)
}
Expand Down Expand Up @@ -141,7 +156,7 @@ const (
yearIndex
)

func (parser *CronExpressionParser) nextTime(prev int64, fields []*CronField) (nextTime int64, err error) {
func (parser *CronExpressionParser) nextTime(prev time.Time, fields []*CronField) (nextTime int64, err error) {
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
Expand All @@ -155,7 +170,7 @@ func (parser *CronExpressionParser) nextTime(prev int64, fields []*CronField) (n
}
}()

tfmt := time.Unix(prev/int64(time.Second), 0).UTC().Format(readDateLayout)
tfmt := prev.Format(readDateLayout)
ttok := strings.Split(strings.Replace(tfmt, " ", " ", 1), " ")
hms := strings.Split(ttok[3], ":")
parser.maxDays = maxDays(intVal(months, ttok[1]), atoi(ttok[4]))
Expand All @@ -169,7 +184,7 @@ func (parser *CronExpressionParser) nextTime(prev int64, fields []*CronField) (n

nstr := fmt.Sprintf("%s %s %s:%s:%s %s", month, strconv.Itoa(dayOfMonth),
hour, minute, second, year)
ntime, err := time.Parse(writeDateLayout, nstr)
ntime, err := time.ParseInLocation(writeDateLayout, nstr, prev.Location())
nextTime = ntime.UnixNano()
return
}
Expand Down
16 changes: 16 additions & 0 deletions quartz/cron_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ func TestCronExpression8(t *testing.T) {
assertEqual(t, result, "Sat Sep 7 12:00:00 2019")
}

func TestCronExpressionWithLoc(t *testing.T) {
prev := int64(1555351200000000000)
result := ""
loc, err := time.LoadLocation("America/New_York")
if err != nil {
t.Fatal(err)
}
cronTrigger, err := quartz.NewCronTriggerWithLoc("* 5 22-23 * * Sun *", loc)
if err != nil {
t.Fatal(err)
} else {
result, _ = iterate(prev, cronTrigger, 100)
}
assertEqual(t, result, "Mon Mar 30 03:05:00 2020")
}

func TestCronDaysOfWeek(t *testing.T) {
daysOfWeek := []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}
expected := []string{
Expand Down
10 changes: 5 additions & 5 deletions quartz/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,24 @@ type Job interface {
// Execute is called by a Scheduler when the Trigger associated with this job fires.
Execute()

// Description returns the Job description.
// Description returns the description of the Job.
Description() string

// Key returns the Job unique key.
// Key returns the unique key for the Job.
Key() int
}

// JobStatus represents a Job status.
type JobStatus int8

const (
// NA is the initial JobStatus.
// NA is the initial Job status.
NA JobStatus = iota

// OK represents a successful JobStatus.
// OK indicates the Job was completed successfully.
OK

// FAILURE represents a failed JobStatus.
// FAILURE indicates the Job failed.
FAILURE
)

Expand Down
3 changes: 3 additions & 0 deletions quartz/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ type StdScheduler struct {
started bool
}

// Verify StdScheduler satisfies the Scheduler interface.
var _ Scheduler = (*StdScheduler)(nil)

// NewStdScheduler returns a new StdScheduler.
func NewStdScheduler() *StdScheduler {
return &StdScheduler{
Expand Down
48 changes: 29 additions & 19 deletions quartz/trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,28 @@ import (
"time"
)

// Trigger is the Triggers interface.
// Triggers are the 'mechanism' by which Jobs are scheduled.
// Trigger represents the mechanism by which Jobs are scheduled.
type Trigger interface {

// NextFireTime returns the next time at which the Trigger is scheduled to fire.
NextFireTime(prev int64) (int64, error)

// Description returns a Trigger description.
// Description returns the description of the Trigger.
Description() string
}

// SimpleTrigger implements the quartz.Trigger interface; uses a time.Duration interval.
// SimpleTrigger implements the quartz.Trigger interface; uses a fixed interval.
type SimpleTrigger struct {
Interval time.Duration
}

// NewSimpleTrigger returns a new SimpleTrigger.
// Verify SimpleTrigger satisfies the Trigger interface.
var _ Trigger = (*SimpleTrigger)(nil)

// NewSimpleTrigger returns a new SimpleTrigger using the given interval.
func NewSimpleTrigger(interval time.Duration) *SimpleTrigger {
return &SimpleTrigger{interval}
return &SimpleTrigger{
Interval: interval,
}
}

// NextFireTime returns the next time at which the SimpleTrigger is scheduled to fire.
Expand All @@ -33,38 +36,45 @@ func (st *SimpleTrigger) NextFireTime(prev int64) (int64, error) {
return next, nil
}

// Description returns a SimpleTrigger description.
// Description returns the description of the trigger.
func (st *SimpleTrigger) Description() string {
return fmt.Sprintf("SimpleTrigger with the interval %d.", st.Interval)
return fmt.Sprintf("SimpleTrigger with interval: %d", st.Interval)
}

// RunOnceTrigger implements the quartz.Trigger interface. Could be triggered only once.
// RunOnceTrigger implements the quartz.Trigger interface.
// This type of Trigger can only be fired once and will expire immediately.
type RunOnceTrigger struct {
Delay time.Duration
expired bool
}

// NewRunOnceTrigger returns a new RunOnceTrigger.
// Verify RunOnceTrigger satisfies the Trigger interface.
var _ Trigger = (*RunOnceTrigger)(nil)

// NewRunOnceTrigger returns a new RunOnceTrigger with the given delay time.
func NewRunOnceTrigger(delay time.Duration) *RunOnceTrigger {
return &RunOnceTrigger{delay, false}
return &RunOnceTrigger{
Delay: delay,
expired: false,
}
}

// NextFireTime returns the next time at which the RunOnceTrigger is scheduled to fire.
// Sets exprired to true afterwards.
func (st *RunOnceTrigger) NextFireTime(prev int64) (int64, error) {
if !st.expired {
next := prev + st.Delay.Nanoseconds()
st.expired = true
func (ot *RunOnceTrigger) NextFireTime(prev int64) (int64, error) {
if !ot.expired {
next := prev + ot.Delay.Nanoseconds()
ot.expired = true
return next, nil
}

return 0, errors.New("RunOnce trigger is expired")
}

// Description returns a RunOnceTrigger description.
func (st *RunOnceTrigger) Description() string {
// Description returns the description of the trigger.
func (ot *RunOnceTrigger) Description() string {
status := "valid"
if st.expired {
if ot.expired {
status = "expired"
}

Expand Down

0 comments on commit e1a905d

Please sign in to comment.