Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSTimer missing fire date when clock is manually set forward

I am setting up a timer to run a specific function in the future like this:

pingTimer = [[NSTimer alloc] initWithFireDate:pingAtDate
                                     interval:0
                                       target:self
                                     selector:@selector(ping:)
                                     userInfo:nil
                                      repeats:NO];

[[NSRunLoop currentRunLoop] addTimer:pingTimer forMode:NSDefaultRunLoopMode];

The date is approximately 1 week in the future, so for testing purposes I (and others) have been setting the system clock forward 8 days to ensure that the specified event happens. The problem is that it doesn't happen.

When scheduling a few minutes in the future I have observed that the timer still does go off, but it appears to go off after a specific number of minutes. Say I schedule the timer for a date 5 minutes in the future, then I set the clock forward 1 hour, the timer does actually fire 5 minutes later, but since I set the clock forward 1 hour the time that it fires at no longer aligns with the time that it was scheduled to fire at.

This is not what I would expect to happen, as I am calling "initWithFireDate".

Although all of this seems wrong to me (and may be an interesting observation to others) the question is how do I ensure that the timer fires as soon as it notices that it's fire date is in the past (i.e. How do I ensure that my timer will fire when somebody moves the clock past the scheduled firing date).

like image 497
John Bowers Avatar asked May 15 '13 20:05

John Bowers


1 Answers

There were some good comments left, but no complete answers. I am going to pull all the pertinent details together here.

An NSTimer is not a clock-time mechanism. When you set a "FireDate" you cannot be sure that the timer will actually fire at that date. You are actually telling the timer to run for specific amount of time before firing. That amount of time is the difference between when you add the timer to the run loop, and the date that you scheduled the timer to fire at.

If your system goes to sleep (or your application is suspended), your timer is no longer ticking down. It will resume ticking down when your system wakes up (or your application becomes active), but this means that your timer will now NOT execute at the original "FireDate". Rather it will execute at the "FireDate" + (amount of time your computer was asleep).

Similarly if a user changes the system time, this does not affect the timer in any way. If the timer was scheduled to fire at a date 8 hours in the future, it will continue ticking down 8 hours worth of time before it fires.

In the case where you want a timer to fire at a specific clock time in the distant future, you will need to make sure your application is notified of the following events:

  1. Wake from sleep
  2. System time change

When any of these events occur you will need to invalidate and adjust any existing timers.

/* If the clock time changed or we woke from sleep whe have to reset these long term timers */
- (void) resetTimers: (NSNotification*) notification
{
    //Invalidate and Reset long term NSTimers
}

You can observe the following notifications to be notified when those events are going to happen.

[[NSNotificationCenter defaultCenter] addObserver:self                          
                                         selector:@selector(resetTimers:)               
                                             name:NSSystemClockDidChangeNotification            
                                           object:nil];

[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
                                                       selector:@selector(resetTimers:)         
                                                           name:NSWorkspaceDidWakeNotification
                                                         object:nil];
like image 52
John Bowers Avatar answered Oct 02 '22 22:10

John Bowers