Skip to content

scheduler

The scheduler package provides periodic task execution using cron expressions.

Core Components:

  • Scheduler: Executes a runner according to a cron schedule. Implements Runner interface so it can be used as an application service.
  • New(cronExpr, runner): Creates a new scheduler with a cron expression.

Supported cron formats:

  • Standard 5-field cron: "minute hour day month weekday" (e.g., "0 9 * * MON-FRI")
  • Custom descriptors: @yearly, @monthly, @weekly, @daily, @hourly
  • Interval syntax: @every 30s, @every 5m, @every 2h (use this for simple intervals)

Full package docs at pkg.go.dev

  1. Create a task function

    func scheduledTask(ctx context.Context) error {
    log.InfoContext(ctx, "scheduled task executed")
    return nil
    }

    The function receives a context with a unique trace ID for each execution.

  2. Create a scheduler

    s, err := scheduler.New("@every 1s", application.RunnerFunc(scheduledTask))
    if err != nil {
    log.Fatal(err)
    }

    First argument is a cron expression (use @every syntax for intervals). Second is any application.Runner implementation. Use application.RunnerFunc to wrap a function.

  3. Run the scheduler

    err := s.Run(ctx)

    The scheduler blocks and executes the task at each interval until the context is cancelled.

    Expected output:

    time=2025-01-01T12:00:01.000+00:00 level=INFO msg="scheduler task started" traceID=abc-123
    time=2025-01-01T12:00:01.000+00:00 level=INFO msg="scheduled task executed" traceID=abc-123
    time=2025-01-01T12:00:01.000+00:00 level=INFO msg="scheduler task finished" traceID=abc-123
    time=2025-01-01T12:00:02.000+00:00 level=INFO msg="scheduler task started" traceID=def-456
    time=2025-01-01T12:00:02.000+00:00 level=INFO msg="scheduled task executed" traceID=def-456
    time=2025-01-01T12:00:02.000+00:00 level=INFO msg="scheduler task finished" traceID=def-456
  4. Handle errors

    func scheduledTask(ctx context.Context) error {
    if err := doWork(); err != nil {
    return err // Logged automatically, scheduler continues
    }
    return nil
    }

    Errors returned from the task are logged but do not stop the scheduler. The next execution proceeds as normal.

Since Scheduler implements the Runner interface, it can be registered as a service in an Application:

app := application.New()
s, err := scheduler.New("@every 1m", application.RunnerFunc(scheduledTask))
if err != nil {
log.Fatal(err)
}
app.RegisterService("scheduler", s)
app.Run(ctx)

The scheduler starts when the application runs and stops when the application shuts down.

The scheduler uses cron expressions for all scheduling needs, from simple intervals to complex patterns.

Standard Cron Syntax (minute hour day month weekday):

// Every 5 minutes
scheduler.New("*/5 * * * *", runner)
// Every hour at minute 0
scheduler.New("0 * * * *", runner)
// Every 2 hours at minute 0
scheduler.New("0 */2 * * *", runner)
// Every day at 9:30 AM
scheduler.New("30 9 * * *", runner)
// Weekdays at 9 AM
scheduler.New("0 9 * * MON-FRI", runner)
// First day of every month at midnight
scheduler.New("0 0 1 * *", runner)

Custom Descriptors:

// Every hour (at minute 0)
scheduler.New("@hourly", runner)
// Every day at midnight
scheduler.New("@daily", runner)
// Every Sunday at midnight
scheduler.New("@weekly", runner)
// First day of month at midnight
scheduler.New("@monthly", runner)
// January 1st at midnight
scheduler.New("@yearly", runner)

Interval Syntax:

// Every 30 seconds
scheduler.New("@every 30s", runner)
// Every 5 minutes
scheduler.New("@every 5m", runner)
// Every 2 hours
scheduler.New("@every 2h", runner)
// Every 12 hours
scheduler.New("@every 12h", runner)
Use CaseSyntaxExample
Simple fixed interval@every@every 5m
Specific time of dayStandard cron30 9 * * * (9:30 AM daily)
Weekday-specificStandard cron0 9 * * MON-FRI
Complex patternsStandard cron*/15 9-17 * * MON-FRI
Common schedulesDescriptors@daily, @hourly

Examples

// Simple interval - runs every 5 minutes starting immediately
s, _ := scheduler.New("@every 5m", runner)
// Clock-aligned - runs at :00, :05, :10, :15, etc.
s, _ := scheduler.New("*/5 * * * *", runner)
// Daily at specific time
s, _ := scheduler.New("30 9 * * *", runner) // 9:30 AM daily
// Weekday business hours only
s, _ := scheduler.New("0 9 * * MON-FRI", runner) // 9 AM weekdays
scheduler.go
package main
import (
"context"
"time"
"github.com/platforma-dev/platforma/application"
"github.com/platforma-dev/platforma/log"
"github.com/platforma-dev/platforma/scheduler"
)
func scheduledTask(ctx context.Context) error {
log.InfoContext(ctx, "scheduled task executed")
return nil
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
s, err := scheduler.New("@every 1s", application.RunnerFunc(scheduledTask))
if err != nil {
log.ErrorContext(ctx, "failed to create scheduler", "error", err)
return
}
go func() {
time.Sleep(3500 * time.Millisecond)
cancel()
}()
s.Run(ctx)
}
scheduler-cron.go
package main
import (
"context"
"fmt"
"time"
"github.com/platforma-dev/platforma/application"
"github.com/platforma-dev/platforma/log"
"github.com/platforma-dev/platforma/scheduler"
)
func dailyBackup(ctx context.Context) error {
log.InfoContext(ctx, "executing daily backup task")
return nil
}
func weekdayReport(ctx context.Context) error {
log.InfoContext(ctx, "generating weekday report")
return nil
}
func frequentHealthCheck(ctx context.Context) error {
log.InfoContext(ctx, "performing health check")
return nil
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Example 1: Using @every syntax - every 5 seconds
s1, err := scheduler.New("@every 5s", application.RunnerFunc(func(ctx context.Context) error {
log.InfoContext(ctx, "@every syntax: every 5 seconds")
return nil
}))
if err != nil {
log.ErrorContext(ctx, "failed to create scheduler 1", "error", err)
return
}
// Example 2: Using @every syntax - every 3 seconds
s2, err := scheduler.New("@every 3s", application.RunnerFunc(func(ctx context.Context) error {
log.InfoContext(ctx, "@every syntax: every 3 seconds")
return nil
}))
if err != nil {
log.ErrorContext(ctx, "failed to create scheduler 2", "error", err)
return
}
// Example 3: Daily task (would run at midnight, but won't execute in this demo)
s3, err := scheduler.New("@daily", application.RunnerFunc(dailyBackup))
if err != nil {
log.ErrorContext(ctx, "failed to create scheduler 3", "error", err)
return
}
// Example 4: Weekday task (would run at 9 AM on weekdays, won't execute in this demo)
s4, err := scheduler.New("0 9 * * MON-FRI", application.RunnerFunc(weekdayReport))
if err != nil {
log.ErrorContext(ctx, "failed to create scheduler 4", "error", err)
return
}
// Example 5: Hourly task (won't execute in this demo)
s5, err := scheduler.New("@hourly", application.RunnerFunc(frequentHealthCheck))
if err != nil {
log.ErrorContext(ctx, "failed to create scheduler 5", "error", err)
return
}
fmt.Println("Starting cron scheduler demo...")
fmt.Println("Active schedulers:")
fmt.Println(" 1. Every 5 seconds (@every 5s)")
fmt.Println(" 2. Every 3 seconds (@every 3s)")
fmt.Println(" 3. Daily at midnight (@daily) - won't execute in demo")
fmt.Println(" 4. Weekdays at 9 AM (0 9 * * MON-FRI) - won't execute in demo")
fmt.Println(" 5. Hourly (@hourly) - won't execute in demo")
fmt.Println()
fmt.Println("Watch the logs for executions. Demo will run for 15 seconds.")
fmt.Println()
// Start all schedulers in background
go s1.Run(ctx)
go s2.Run(ctx)
go s3.Run(ctx)
go s4.Run(ctx)
go s5.Run(ctx)
// Run for 15 seconds to demonstrate the frequent tasks
time.Sleep(15 * time.Second)
cancel()
// Allow graceful shutdown
time.Sleep(100 * time.Millisecond)
fmt.Println()
fmt.Println("Demo completed!")
}