Skip to main content

robfig/cron - last day of the month in Go

At 04:30, every day.

Next runs · UTC

Calendar

September 2026 · UTC

0 runs

About this tool

robfig/cron is the dominant cron library in the Go ecosystem, but its standard parser does not support the L modifier. To run a job on the last day of every month, you have two options: a runtime check inside the job, or the portable multi-cron approach with three cron entries.
See the full robfig/cron guide for the parser's syntax options and gotchas, or the general last-day-of-month guide for the cross-library overview.

Runtime check inside the job

The cleanest pattern: schedule daily, check if today is the last day, exit early otherwise. The schedule expression stays standard cron; the “last day” logic lives in Go where it's easier to test.

go
package main


import (
    "time"


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


func isLastDayOfMonth(t time.Time) bool {
    return t.AddDate(0, 0, 1).Day() == 1
}


func main() {
    c := cron.New()
    c.AddFunc("0 0 * * *", func() {
        if !isLastDayOfMonth(time.Now()) {
            return
        }
        // ... your last-day-of-month work here
    })
    c.Start()
    select {}
}

Multi-cron approach (no runtime check)

If you prefer to keep the “last day” logic in the cron expression itself, register three crons covering the actual last days of each month length:

go
c := cron.New()
c.AddFunc("0 0 30 4,6,9,11 *", monthEndJob)        // 30-day months
c.AddFunc("0 0 31 1,3,5,7,8,10,12 *", monthEndJob) // 31-day months
c.AddFunc("0 0 28 2 *", monthEndJob)               // February (28)
c.AddFunc("0 0 29 2 *", func() {                   // February (29) - leap years only
    if time.Now().Month() == time.February && isLastDayOfMonth(time.Now()) {
        monthEndJob()
    }
})
c.Start()

The Feb 29 entry needs a runtime check because the literal value 29 matches every Feb 29, not just leap years. The function double-checks “is today truly the last day of February?”.

Go cron libraries that support L

  • github.com/adhocore/gronx - supports L, W, # modifiers and step ranges. Quartz-compatible.
  • github.com/gorhill/cronexpr - supports L, W, L-N arithmetic. Last updated less frequently but stable.
  • github.com/go-co-op/gocron - uses its own interval API rather than cron strings; supports “last day of month” via scheduler.Cron(...) with a custom format.

If you're already on robfig/cron and just need last-day-of-month support, the runtime-check pattern is probably less disruptive than swapping libraries.

Pitfalls when faking L in robfig/cron

  • Timezone - robfig/cron defaults to local time; be explicit with cron.WithLocation(time.UTC) if your job runs in containers.
  • Daylight saving - the “last day at midnight” firing can be skipped or doubled when DST transitions happen at month boundaries.
  • Idempotency - if you use a runtime check, still make the job idempotent. A retry, a misfire or a container restart could trigger the job twice in the same day.

Examples

  • 0 18 * * *
    Every day at 18:00
  • 0 */5 * * *
    Every 5 hours
  • 0 18 * * 1-5
    Weekdays at 18:00
  • 0 0 1 * *
    Once a month, on the 1st at midnight

Cheatsheet

Full cheatsheet
FieldReq.RangeWildcards
minuteyes0-59, - * /
houryes0-23, - * /
day of monthyes1-31, - * /
monthyes1-12 or JAN-DEC, - * /
day of weekyes0-6 or names; some dialects use 0-7 or 1-7 — see Dialects, - * /

Frequently asked questions

Syntax

Does robfig/cron support the L modifier?

Out of the box, robfig/cron v3 uses a strict standard parser that does NOT support L. You can opt into a more permissive parser via cron options, but as of v3.x there is no built-in L token. The portable approach is to schedule daily and self-skip in the job, or use a wrapper that computes the last day at runtime.

What options does robfig/cron's parser take?

robfig/cron's cron.NewParser accepts a bitmask of fields: Second, Minute, Hour, Dom, Month, Dow, Descriptor. To enable seconds: cron.SecondOptional | cron.Minute | …. The L modifier is not in this list - there's no flag to enable it.

Does ? make robfig/cron understand the last day?

No. robfig/cron accepts ? only as a day-field wildcard; it does not turn L into a supported last-day operator.

Timing

What schedule should wrap a robfig last-day check?

Use 0 0 * * * to invoke the check daily at midnight. The Go function should continue only when adding one calendar day produces day 1. Expression: 0 0 * * *

Open in editor

Days and months

How do I get last-day-of-month behaviour with robfig/cron?

Schedule the job daily (0 0 * * *) and have the job's first action be a check: if time.Now().AddDate(0, 0, 1).Day() == 1, the current day is the last of the month - proceed. Otherwise, return early. This keeps the cron expression standard and the date logic in Go where it belongs. Expression: 0 0 * * *

Open in editor
Can I use the multi-cron last-day-of-month pattern with robfig?

Only with a February guard. A 28,29 day list fires twice in leap-year Februaries, so the February callback must still test whether tomorrow is day 1. A daily 0 0 * * * schedule with the Go last-day predicate is simpler.

Does the Go runtime-check pattern handle leap years?

Yes. t.AddDate(0, 0, 1).Day() == 1 is true on February 28 in ordinary years and February 29 in leap years, with no hard-coded month lengths.

Timezones and DST

Which timezone should the robfig last-day check use?

Use the same *time.Location for both the cron scheduler and the date check. Mixing a UTC schedule with time.Now() in local time can evaluate different calendar dates near midnight.

Can DST affect a robfig month-end job?

Yes. A wall-clock schedule follows its configured location and may encounter a skipped or repeated local time. Midnight is usually safer than transition-hour schedules, but the job should remain idempotent.

Platforms

What about other Go cron libraries?

Most Go cron libraries (gocron, cronexpr, adhocore/gronx) follow either standard cron syntax or Quartz-style. gronx from adhocore explicitly supports L. cronexpr supports L and W. If L support matters, prefer one of those over robfig/cron v3, or switch to a wrapper-based approach.

Can I copy a Quartz last-day expression into robfig/cron?

No. Quartz uses a leading seconds field and supports L, while robfig/cron defaults to five fields and has no built-in L. Keep the date check in Go.

Quartz cron guide

Debugging

How do I stop duplicate robfig month-end runs?

Use an idempotency key for the target month and wrap the job with cron.SkipIfStillRunning. A lock protects overlap; the month key also protects retries and restarts.

How should I test robfig last-day logic?

Pass a time.Time into a small predicate and test 28-, 29-, 30-, and 31-day month boundaries. Avoid reading time.Now() directly inside the predicate.

How do I know whether the daily robfig schedule registered?

Check the error returned by AddFunc. The standard daily expression should register, while an unsupported L expression is rejected by the default parser.

Crontap

Can Crontap run the robfig last-day workflow?

Yes. You can schedule a daily HTTP call on Crontap and keep the last-day check in the receiving Go handler, using the same timezone and idempotency rules.

Write the cron here. Run it on Crontap.

Point Crontap at any URL. Pick any cron. Done.

WordPress, Shopify, Railway, Cloud Run, Vercel, HubSpot, Ghost, your own box. If it answers HTTP, Crontap can drive it on a clock you can read, in the timezone that actually matters, and page you when something breaks.

1.9k+ teams · Free forever tier · No credit card