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.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.
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:
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-Narithmetic. 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-5Weekdays at 18:00
- 0 0 1 * *Once a month, on the 1st at midnight
Cheatsheet
Full cheatsheet| Field | Req. | Range | Wildcards |
|---|---|---|---|
| minute | yes | 0-59 | , - * / |
| hour | yes | 0-23 | , - * / |
| day of month | yes | 1-31 | , - * / |
| month | yes | 1-12 or JAN-DEC | , - * / |
| day of week | yes | 0-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 * * *
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 * * *
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.
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