0

I have the following (terible) method that constantly checks the current time, and when a certain time is reached (in this case midnight) an NSLog statement is run once to signify something useful being done:

- (void) checkTime {

while (true){

    NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];

    NSDate *now = [[NSDate alloc] init];

    NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init];
    [outputFormatter setDateFormat:@"HH:mm"];

    NSString *nowDateString = [outputFormatter stringFromDate:now];

    if([nowDateString isEqualToString:@"00:00"]){
        NSLog(@"Store previous days data..");
        BOOL stillMidnight = YES;
        while(stillMidnight == YES){
            NSDate *latestNow = [[NSDate alloc] init];

            NSDateFormatter *latestOutputFormatter = [[NSDateFormatter alloc] init];
            [latestOutputFormatter setDateFormat:@"HH:mm"];

            NSString *latestString = [latestOutputFormatter stringFromDate:latestNow];
            //Check if it is still midnight
            if([latestString isEqualToString:@"00:01"]){
                //leave while
                stillMidnight = NO;
            }
        }
        NSLog(@"No longer midnight");
    }

    [loopPool drain];

}

}

The above method gets called as follows from the applicationDidFinishLaunchingWithOption method:

[self performSelectorInBackground:@selector(checkTime) withObject:nil];

This code runs the NSLog(@"Store previous days data..") once at midnight, which is what I need, but is there a more elegant solution to this problem?

Thanks,

Jack

Jack Nutkins
  • 1,555
  • 5
  • 36
  • 71
  • also, couldn't you have asked this in your previous question on the topic: http://stackoverflow.com/questions/9028463/objective-c-perform-a-function-at-given-time-date/9028535#9028535? – Peter Sarnowski Jan 28 '12 at 14:27
  • Hmmm.... let us get this straight. The user launches the application which runs your `while` loop to which perpetually checks the time? Would you rather do this every second or so? – Eric Brotto Jan 28 '12 at 14:30

3 Answers3

5

You'd be better off:

  1. getting the current date;
  2. working out when midnight was today;
  3. scheduling a one-shot timer to occur one day after that; and
  4. repeating

It's tempting to just schedule a repeating timer that first fires with the date calculated at (3) and every 24 hours hence, but that would fail to allow for daylight savings. So, e.g. (coded directly in here, untested)

- (void)scheduleNextTimedAction
{
    // get the date now and the calendar the user is using
    // (which will include their time zone, helpfully)
    NSDate *dateNow = [NSDate date];
    NSCalendar *relevantCalendar = [NSCalendar currentCalendar];

    // decompose the current date to components; we'll
    // just ask for month, day and year here for brevity;
    // check out the other calendar units to decide whether
    // that's something you consider acceptable
    NSDateComponents *componentsForNow =
         [relevantCalendar components:
                  NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit
                  fromDate:dateNow];

    // we could explicitly set the time to midnight now,
    // but since that's 00:00 it'll already be the value
    // in the date components per the standard Cocoa object
    // creation components, so...

    // get the midnight that last occurred
    NSDate *lastMidnight = [relevantCalendar dateFromComponents:componentsForNow];

    // can we just add 24 hours to that? No, because of DST. So...

    // create components that specify '1 day', however long that may be
    NSDateComponents *oneDay = [[NSDateComponents alloc] init];
    oneDay.day = 1;

    // hence ask the calendar what the next midnight will be
    NSDate *nextMidnight = [relevantCalendar
                 dateByAddingComponents:oneDay
                 toDate:lastMidnight
                 options:0];
    [oneDay release];

    // now create a timer to fire at the next midnight, to call
    // our periodic function. NB: there's no convenience factory
    // method that takes an NSDate, so we'll have to alloc/init
    NSTimer *timer = [[NSTimer alloc]
                 initWithFireDate:nextMidnight
                 interval:0.0 // we're not going to repeat, so...
                 target:self
                 selector:@selector(doTimedAction:)
                 userInfo:nil
                 repeats:NO];

    // schedule the timer on the current run loop
    [[NSRunLoop currentRunLoop]
                 addTimer:timer
                 forMode: NSDefaultRunLoopMode];

    // timer is retained by the run loop, so we can forget about it
    [timer release];
}

- (void)doTimedAction:(NSTimer *)timer
{
    NSLog(@"do action");
    [self scheduleNextTimedAction];
}
Jack Nutkins
  • 1,555
  • 5
  • 36
  • 71
Tommy
  • 99,986
  • 12
  • 185
  • 204
  • I've no idea whether this code works, but I have to give a comment point for just mentioning the nightmare that is daylight-saving time changes, (Oracle: Database halted - time has gone backwards). – Martin James Jan 28 '12 at 15:23
3

If you want to execute code at an arbitrary point in time, you best set up Local Notifications.

Peter Sarnowski
  • 11,900
  • 5
  • 36
  • 33
0

You can use either an UILocalNotification if the timer should also alert the user when your app is not running Push notification guide or NSTimer, which can be initialized with firing date or interval as well as selector to be called. Note that NSTimer will not fire if your app is in the background but in this case rather will fire the moment your app gets active again.

Dennis Bliefernicht
  • 5,147
  • 1
  • 28
  • 24