or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration-options.mdcore-scheduler.mdindex.mdjob-management.mdjob-wrappers.mdlogging.mdschedule-parsing.mdschedule-types.md
tile.json

schedule-types.mddocs/

Schedule Types

Built-in Schedule implementations for different scheduling patterns.

Schedule Interface

All schedules implement the Schedule interface.

type Schedule interface {
    // Next returns the next activation time, later than the given time.
    // Next is invoked initially, and then each time the job is run.
    Next(time.Time) time.Time
}

SpecSchedule

Traditional crontab-based schedule with bit field representation.

// SpecSchedule specifies a duty cycle based on a traditional crontab
// specification. It is computed initially and stored as bit sets.
type SpecSchedule struct {
    Second   uint64           // Seconds bit field (0-59)
    Minute   uint64           // Minutes bit field (0-59)
    Hour     uint64           // Hours bit field (0-23)
    Dom      uint64           // Day of month bit field (1-31)
    Month    uint64           // Month bit field (1-12)
    Dow      uint64           // Day of week bit field (0-6)
    Location *time.Location   // Override location for this schedule
}

// Next returns the next time this schedule is activated, greater than the
// given time. If no time can be found within five years, returns the zero time.
func (s *SpecSchedule) Next(t time.Time) time.Time

Usage:

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

// Typically created via parsing
sched, _ := cron.ParseStandard("30 * * * *")

// Can also construct manually (advanced)
specSched := &cron.SpecSchedule{
    Second:   1 << 0,               // 0 seconds
    Minute:   1 << 30,              // 30 minutes
    Hour:     ^uint64(0),           // All hours (*)
    Dom:      ^uint64(0),           // All days of month (*)
    Month:    ^uint64(0),           // All months (*)
    Dow:      ^uint64(0),           // All days of week (*)
    Location: time.UTC,
}

// Get next activation time
next := specSched.Next(time.Now())
fmt.Println("Next run:", next)

Timezone Handling

The Location field determines how the schedule interprets times:

// Parse with timezone
sched, _ := cron.ParseStandard("CRON_TZ=America/New_York 0 6 * * ?")
// The SpecSchedule will have Location set to America/New_York

// Or set manually
tokyo, _ := time.LoadLocation("Asia/Tokyo")
specSched.Location = tokyo

Next() Behavior

  • Searches for the next matching time after the given time
  • Handles month/day wrap-arounds correctly
  • Accounts for daylight saving time transitions
  • Returns zero time if no valid time found within 5 years
  • Rounds to the second (nanoseconds are truncated)

ConstantDelaySchedule

Simple recurring schedule with a fixed delay between activations.

// ConstantDelaySchedule represents a simple recurring duty cycle.
// Does not support jobs more frequent than once a second.
type ConstantDelaySchedule struct {
    Delay time.Duration
}

// Next returns the next activation time.
// Rounds to the second.
func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time

Creating Constant Delays

// Every returns a schedule that activates once every duration.
// Delays of less than a second are rounded up to 1 second.
// Subsecond fields are truncated.
func Every(duration time.Duration) ConstantDelaySchedule

Usage:

import (
    "time"
    "github.com/robfig/cron/v3"
)

// Every 5 minutes
sched := cron.Every(5 * time.Minute)

// Every 1 hour
sched := cron.Every(time.Hour)

// Every 30 seconds
sched := cron.Every(30 * time.Second)

// Less than 1 second rounds up to 1 second
sched := cron.Every(500 * time.Millisecond) // Becomes 1 second

// Use with scheduler
c := cron.New()
c.Schedule(cron.Every(10*time.Minute), myJob)

Next() Behavior

  • Adds the delay to the given time
  • Rounds to the second (removes nanoseconds)
  • Simple arithmetic, no calendar math
  • Does not account for daylight saving time

Example:

sched := cron.Every(5 * time.Minute)

now := time.Now()
next1 := sched.Next(now)
next2 := sched.Next(next1)
next3 := sched.Next(next2)

fmt.Println("Now:   ", now)
fmt.Println("Next 1:", next1) // now + 5 minutes
fmt.Println("Next 2:", next2) // next1 + 5 minutes
fmt.Println("Next 3:", next3) // next2 + 5 minutes

Interval vs Runtime

The interval does NOT account for job runtime:

c := cron.New()

// Job takes 3 minutes to run, scheduled every 5 minutes
c.AddFunc("@every 5m", func() {
    fmt.Println("Starting...")
    time.Sleep(3 * time.Minute)
    fmt.Println("Done")
})

// Timeline:
// 0:00 - Job starts
// 0:03 - Job finishes
// 0:05 - Job starts again (only 2 minutes of idle time)
// 0:08 - Job finishes
// 0:10 - Job starts again

Use job wrappers like DelayIfStillRunning or SkipIfStillRunning to handle overlapping executions.

Custom Schedule Implementation

You can implement custom Schedule types for specialized scheduling logic.

type Schedule interface {
    Next(time.Time) time.Time
}

Example - Business Days Only:

// BusinessDaySchedule runs at a specific time on business days only
type BusinessDaySchedule struct {
    Hour   int
    Minute int
    Loc    *time.Location
}

func (s BusinessDaySchedule) Next(t time.Time) time.Time {
    // Convert to schedule timezone
    if s.Loc != nil {
        t = t.In(s.Loc)
    }

    // Start with next occurrence
    next := time.Date(t.Year(), t.Month(), t.Day(), s.Hour, s.Minute, 0, 0, t.Location())

    // If already passed today, move to tomorrow
    if next.Before(t) || next.Equal(t) {
        next = next.Add(24 * time.Hour)
    }

    // Skip weekends
    for next.Weekday() == time.Saturday || next.Weekday() == time.Sunday {
        next = next.Add(24 * time.Hour)
    }

    return next
}

// Usage
c := cron.New()
bizSchedule := BusinessDaySchedule{
    Hour:   9,
    Minute: 0,
    Loc:    time.UTC,
}
c.Schedule(bizSchedule, myJob)

Example - Specific Dates:

// SpecificDatesSchedule runs at specific predetermined dates/times
type SpecificDatesSchedule struct {
    Dates []time.Time
    index int
}

func (s *SpecificDatesSchedule) Next(t time.Time) time.Time {
    // Find next date after t
    for s.index < len(s.Dates) {
        if s.Dates[s.index].After(t) {
            next := s.Dates[s.index]
            s.index++
            return next
        }
        s.index++
    }
    // No more dates
    return time.Time{}
}

// Usage
dates := []time.Time{
    time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC),
    time.Date(2024, 2, 15, 10, 0, 0, 0, time.UTC),
    time.Date(2024, 3, 15, 10, 0, 0, 0, time.UTC),
}

c := cron.New()
c.Schedule(&SpecificDatesSchedule{Dates: dates}, myJob)

Comparing Schedule Types

SpecSchedule

Best for:

  • Traditional cron patterns (hourly, daily, specific times)
  • Calendar-based scheduling
  • Timezone-aware scheduling

Characteristics:

  • Handles calendar math (month/year boundaries)
  • Respects timezones and DST
  • Complex bit-field representation
  • Created via parsing or manual construction

Examples:

  • "Every hour at 30 minutes"
  • "Daily at 3:00 AM"
  • "First Monday of each month"

ConstantDelaySchedule

Best for:

  • Fixed intervals
  • Simple recurring tasks
  • When runtime doesn't matter

Characteristics:

  • Simple duration-based
  • No calendar awareness
  • Minimum 1-second granularity
  • Does not account for job runtime

Examples:

  • "Every 5 minutes"
  • "Every 30 seconds"
  • "Every hour"

Custom Schedule

Best for:

  • Business logic requirements
  • Complex patterns not expressible in cron
  • External data-driven schedules

Characteristics:

  • Full control over Next() logic
  • Can integrate with external systems
  • Can implement any pattern

Examples:

  • Business days only
  • Specific predetermined dates
  • Dynamic schedules based on external API
  • Holiday-aware scheduling

Complete Example

package main

import (
    "fmt"
    "time"
    "github.com/robfig/cron/v3"
)

func main() {
    c := cron.New()

    // SpecSchedule via parsing
    c.AddFunc("30 * * * *", func() {
        fmt.Println("SpecSchedule: Every hour at 30 minutes")
    })

    // ConstantDelaySchedule via Every()
    c.Schedule(cron.Every(15*time.Minute), cron.FuncJob(func() {
        fmt.Println("ConstantDelaySchedule: Every 15 minutes")
    }))

    // ConstantDelaySchedule via @every descriptor
    c.AddFunc("@every 10m", func() {
        fmt.Println("ConstantDelaySchedule: Every 10 minutes")
    })

    // Custom schedule
    bizDay := &BusinessDaySchedule{
        Hour:   9,
        Minute: 0,
        Loc:    time.UTC,
    }
    c.Schedule(bizDay, cron.FuncJob(func() {
        fmt.Println("Custom: Business days at 9 AM")
    }))

    c.Start()
    defer c.Stop()

    // Show next run times
    for _, entry := range c.Entries() {
        fmt.Printf("Entry %d: Next run at %v\n", entry.ID, entry.Next)
    }

    select {}
}