How did you solve the problem of cross daylight saving timer?

Hello erlang community,
I am a game developer, and our game servers are deployed around the world. However, our system has a timer failure issue after switching to daylight saving time, so how can we solve this problem?
example

erlang:send_after(now() + 15*60*1000,self(),start),
now()  ->
    {M, S, _} = os:timestamp(),
    M * 1000000 + S.

Hi @emmm

I also have the same question. However here is Periodic sending a message based on clock wall time some @rickard explanation that could help you too.

Hi
Have you resolved the problem you encountered?
I may not have described it clearly, my English is not good. :sob:
For example, I started a timer today until tomorrow at 5am, but midway through daylight saving time, the timer may have timed out at 4am or 6am, which is not allowed.

Well, FWIW, erlang:send_after does exactly what it says it does: send the given message after the given number of milliseconds has ticked by. Turning the hands on your wall clock forward or backward doesn’t change that :woman_shrugging:

what you would need there is to change from a flat interval into one where you go “time until the desired wallclock time”. You would likely need a timezone library like qdate, and compute “now plus one day” (which you can do with the relative times functionality) to compute the interval between now and the relative later time, and then set a timer.

There’s even a whole date arithmetic type of function.

The moment you touch timezones, you need to start going for libraries that care about timezone database and only convert to the timers as a last step once you’ve computed everything else.

5 Likes

The important part to understand with timezones is that there’s two different sets of thinking about time.

There’s absolute time (time constantly progressing/utc time/the time programmers usually think in) and there’s wall time (the time you see when you look at the clock in a certain location). now() is rooted in the absolute time and plainly adding 15 minutes to it will get you to 15 minutes later in absolute time, but you cannot know which wall time you’ll have after those 15 minutes without additional information.

To queue something “due 5 o’clock” in a certain timezone/location you need to work with wall time and map that back to absolute time. A timezone library can tell you what absolute time value to expect for a given wall time and timezone. From there you can then calculate back to which interval you need.

You also need to do this mapping from scratch each time when dealing with datetimes in the future, as timezone definitions do change, therefore there is no one fixed mapping between wall time and absolute time and nothing prevents a change between the time you did the calculation and the time you did the calculation for. That’s usually fine, but something to keep in mind, especially for far in the future datetimes.

1 Like

thanks

thank u , i got it !!

Just want to point out some details…

erlang:send_after/3’s argument 1 is a relative time in milliseconds.

now() + Whatever looks like calculating an absolute time in the future.

os:timestamp() returns the OS system time which is the operating systems view of POSIX time. It is a time that starts at the Epoch; 1970-01-01.

The calculation M * 1000000 + S gives the the current OS system time as a number of seconds since the Epoch.

The constant 15*60*1000 looks like 15 minutes in milliseconds.

So, you calculate the number of seconds since the epoch, add 15 minutes in milliseconds, and starts a timer to fire in that number of milliseconds from now. This does not make any sense.

Should you have intended to use the option {abs,true} to erlang:send_after/4, then the time should be an absolute time in Erlang Monotonic Time milliseconds. The Erlang Monotonic Time is offset from Erlang system time, which is an adjusted view of the OS system time. It is possible to find out the current offset between Erlang Monotonic Time and Erlang system time using erlang:time_offset/0,1, but the offset from OS system time is harder to know since the OS operator, NTP daemon and whatnot may change the OS system time any time…

So the matter of time is complicated. Hence there is good reason to use a support library unless you need a more or less relative timeout.

There is also a lot about this in the Erlang documentation: Erlang -- Time and Time Correction in Erlang

1 Like

Basing events on local wall clock time is also problematic if you happen to put the execution time within the respective adjustment period. Here in Germany that is 2:00am/3:00am local time, but it is different in other countries.

So if you put your execution time at, say, 2:30am, this time will occur twice on my wall clock on the day when changing to winter time (here, 29th of october 2023 next), and won’t occur at all when changing to summer time (here, 31st of march 2024 next).

5:00am, as said in the original post, is AFAIK not in an adjustment period in any country that observes DST, but still, you may want to keep that in mind.

Yes, the time issue is very complex. Our project has not yet addressed this issue. The current method of handling is to stop the system before the daylight saving time conversion and then restart it after the conversion. It’s a bit silly.

Can you specify more exactly what you want to do?

Do you want a timer to fire tomorrow at 05:00 local time even if there is a DST change tomorrow at 02:00–03:00 local time?

In that case:

{Today,_} = calendar:local_time(),
Days      = calendar:date_to_gregorian_days(Today),
Tomorrow  = calendar:gregorian_days_to_date(Days+1),
[UTC]     = calendar:local_time_to_universal_time_dst({Tomorrow,{5,0,0}}),
GregSec   = calendar:datetime_to_gregorian_seconds(UTC),
Epoch     = calendar:datetime_to_gregorian_seconds({{1970,1,1},{0,0,0}}),
Offset    = erlang:time_offset(millisecond),
AbsTime   = (GregSec - Epoch)*1000 - Offset, % millisecond
erlang:send_after(AbsTime, self(), start, [{abs,true}]),

Might do something like what you want.

The code ignores leap seconds so it might be some 30:ish seconds off (currently) depending on how close your OS system time is to the Coordinated Universal Time.

At the line [UTC] = ... there might instead be a an empty list or a list of two UTC times returned, depending on if the time 05:00 happens zero, one, or two times. But if you know that your time 05:00 does not happen during daylight saving time adjustment this is no problem. Otherwise you will have to select one or invent another time (add an hour?).

The code assumes that Offset will be the same or close enough when the timer fires, that is, that the OS system clock isn’t adjusted (known as a Time Warp in the Erlang VM) before the timer fires.
It is also possible to monitor time warps (erlang:monitor(time_offset, clock_service)), notice the new time offset, calculate a new absolute time, cancel the old timer and start a new. If you are so inclined…

Note that if the task is to fire a timer at a specified time in Coordinated Universal Time instead of local time the calculation becomes a bit simpler.

1 Like

Maybe out of this question
I’m writing game server too, but I’m using erlang:system_time/1
you can play with this code in shell, the result can be negative or not_negative(win10)

f(),{M,S,MS}=os:timestamp(), M*1000000000+S*1000+(MS div 1000)-erlang:system_time(millisecond).

And, In fact, I have my question too
I check source code in otp project

  • os:timestamp/0 call GetSystemTime(erts/emulator/sys/win32/sys_time.c:706)

  • calendar:local_time/0 call time((time_t *)0)(erts/emulator/beam/erl_time_sup.c:1440)

  • erlang:system_time/1 call time_sup.r.o.get_time()(erts/emulator/beam/erl_time_sup.c:2401),If I correct understanding time_sup.r.o.get_time() call time((time_t *)0)(erts/emulator/beam/erl_time_sup.c:1410)

GetSystemTime seem not equal to time((time_t *)0)
So, If I can mix use os, calendar and erlang?

os:timestamp/0 returns the OS System Time which is the OS’ view of POSIX Time.

calendar:local_time/0 just calls erlang:localtime/0 which returns the localized OS System Time, that is; in the local time zone and Daylight Saving Time according to the OS.

erlang:system_time/0,1 returns the Erlang System Time, which is “as close as we get” to the OS System Time. When the OS System Time gets adjusted, see Erlang’s Time Warp Modes, for less than a minute the Erlang System Time differs from the OS System Time until the VM warps the time by adjusting it to the OS System Time.

The Erlang System Time can, via the Time Offset be used to calculate the Erlang Monotonic Time, which is used for timers in the VM.

So, the functions e.g in calendar handling OS System Time / Posix Time and local time can be used to calculate a point in OS System Time.

We must set timers in Erlang Monotonic Time. If we want to get close to OS System Time we can aim at Erlang System Time instead. Erlang Monotonic Time and Erlang System Time has a defined offset that can be monitored, which makes it possible to hit a specific point in Erlang System Time.

1 Like