Skip to content

Conversation

@akkuman
Copy link

@akkuman akkuman commented Oct 31, 2025

If the user defines the time zone of the container and uses cron at the same time, it will produce wrong results: cron.ParseStandard() defaults to using the local time zone to interpret cron expressions, but the time passed in Next is UTC, which will cause cron to change the original time zone to the time zone corresponding to the Next parameter, resulting in misunderstanding.

example:

current time: 2025-10-31T10:36:00+08:00
current timezone: +08:00

package main

import (
	"fmt"
	"time"

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

func waitForCron(cronExpr string, from time.Time) (time.Duration, error) {
	sched, err := cron.ParseStandard(cronExpr)
	if err != nil {
		return time.Duration(0), err
	}
	// sched.Next() returns the next time that the cron expression will match, beginning in 1ns;
	// we allow matching current time, so we do it from 1ns
	next := sched.Next(from.Add(-1 * time.Nanosecond))
	return next.Sub(from), nil
}

func main() {
	now := time.Now().UTC()
	d, err := waitForCron("0 3 * * *", now)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(d)
}

output: 22m55.403517847s

package main

import (
	"fmt"
	"time"

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

func waitForCron(cronExpr string, from time.Time) (time.Duration, error) {
	sched, err := cron.ParseStandard(cronExpr)
	if err != nil {
		return time.Duration(0), err
	}
	// sched.Next() returns the next time that the cron expression will match, beginning in 1ns;
	// we allow matching current time, so we do it from 1ns
	next := sched.Next(from.Add(-1 * time.Nanosecond))
	return next.Sub(from), nil
}

func main() {
	now := time.Now()
	d, err := waitForCron("0 3 * * *", now)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(d)
}

output: 16h22m28.330960802s

Clearly, the second one is more intuitive.

If the user defines the time zone of the container and uses cron at the same time, it will produce wrong results: cron.ParseStandard() defaults to using the local time zone to interpret cron expressions, but the time passed in Next is UTC, which will cause cron to change the original time zone to the time zone corresponding to the Next parameter, resulting in misunderstanding.
@deitch
Copy link
Collaborator

deitch commented Nov 1, 2025

Hi @akkuman ; thank you for the PR.

If I understood you correctly, the issue is that the cron assumes UTC (hence all of the Now().UTC()), but if it runs on a host or in a container where the local time zone is set to something other than UTC, the results can be unexpected.

For example, if you set it in cron to run every night at midnight, and the host/container is in timezone US Eastern Time, then it is not clear if it will run at 19:00 Eastern Time (midnight UTC) or 24:00 Eastern Time (local midnight, 05:00 UTC). Is that it?

According to your examples, the current code will run at 0300 UTC, and your change will run it at 0300 local time.

If so, your proposed solution is to make everything local.

I am not sure that is the approach to take. In order to avoid confusion - I am currently involved in a project whose nightly releases run at midnight on GitHub Action runners, and there was a lot of confusion as to whose "midnight" that is - I think everything should be UTC. Why would we not instead parse the cron as relevant to UTC? We certainly can clarify the docs so that it says, "it all is UTC", or perhaps we can have an option to configure it to pick UTC vs local time? But I think forcing everything to local time increase confusion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants