NSTimer not working accurately. - objective-c

I am trying to make a countdown timer, however I am struggling with my code. I have a UIDatePicker to select the date to countdown to, but ever every time I try to do a countdown the seconds start at 54 seconds instead of adjusting to the actual time of the device. The code is very simple and straightforward, but I am struggling to figure it out.
- (IBAction)startCountdown:(id)sender {
if (ti == 0||ti <= 0) {
[self stopCountdown:self];
}
//Set up a timer that calls the updateTime method every second to update the label
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateTime)
userInfo:nil
repeats:YES];
}
-(void)updateTime{
//Get the time left until the specified date and convert time into seconds.
NSInteger ti = ((NSInteger)[self.datePicker.date timeIntervalSinceNow]); //this is the key part of the code.
NSInteger seconds = ti % 60;
NSInteger minutes = (ti / 60) % 60;
NSInteger hours = (ti / 3600) % 24;
//NSInteger days = (ti / 86400);
//Update the lable with the remaining time
//self.countdownLabel.text = [NSString stringWithFormat:#"%02i days %02i hrs %02i min %02i sec", days, hours, minutes, seconds];
self.countdownLabel.text = [NSString stringWithFormat:#"%02i hrs %02i min %02i sec", hours, minutes, seconds];
}
Please help me out it would be much appreciated!
Thank you in advance friends!!

There is more easy way to extract time units that can prevent errors in calculations. Take a look at [NSCalendar components:fromDate:toDate:options:]
It may become:
NSDate *now = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSCalendarUnit components = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
NSDateComponents *diff = [calendar components:components fromDate:self.datePicker.date toDate:now options:0];
NSInteger seconds = [diff second];
NSInteger minutes = [diff minute];
NSInteger hours = [diff hour];

Related

Calculate the next event from now given an NSDate in the past and a repeat interval

How can I calculate the first date later than now from a date in the past by repeating a certain interval? For example:
// Date in the past
NSDate *pastDate = [NSDate my_dateWithString:#"11/09/2001"];
// Time interval
NSTimeInterval repeatInterval = 14 * 24 * 60 * 60; // 2 weeks
// Current date
NSDate *now = [NSDate date]; // 23/07/2015
// Start calculating next date ->
NSDate *nextDate = /* interval added to past date */
// Is result later than now?
// No? Calculate again
// Abracadabra
NSLog(#"nexDate = %#",nextDate); // 28/07/2015
I don't want to use iterations. I'm concerned about calculating a case like a start date one year in the past and a repeat interval of a day.
Here is my solution, but it has iterations.
NSDateComponents *twoWeeksDateComponents = [NSDateComponents new];
twoWeeksDateComponents.weekOfMonth = 2;
NSDate *date = self.picker.date;
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *now = [NSDate date];
NSDate *nextDate = date;
NSComparisonResult result = [nextDate compare:now];
while (result == NSOrderedAscending) {
nextDate = [calendar dateByAddingComponents:kTwoWeeksDateComponents
toDate:nextDate
options:NSCalendarMatchNextTime];
result = [nextDate compare:now];
}
NSLog(#"nexDate = %#",nextDate); // 28/07/2015
You can indeed do this without iteration, using a little arithmetic instead. You're looking for the nearest multiple of some factor f strictly greater than another value n. It's a bit more complicated being a calendrical calculation, but still just arithmetic (and NSCalendar of course does all the heavy lifting for you -- e.g., leap years).
NSDate * pastDate = ...;
NSDate * now = [NSDate date];
Get your repeat interval however you like from the user and construct an NSDateComponents representing it:
NSCalendarUnit repeatUnit = NSCalendarUnitWeekOfYear;
NSUInteger repeatInterval = 2;
NSDateComponents * repeatComps = [NSDateComponents new];
[repeatComps setValue:repeatInterval forComponent:repeatUnit];
Given the repeat unit, find the amount of that unit that occurs between the two dates, as another date components object:
NSCalendar * cal = [NSCalendar currentCalendar];
NSDateComponents * deltaComps = [cal components:repeatUnit
fromDate:pastDate
toDate:now
options:0];
NSInteger deltaVal = [deltaComps valueForComponent:repeatUnit];
Now comes the arithmetical bit. Round the delta down to the nearest multiple of the repeat interval. Then adding that rounded value (using the appropriate unit) will produce a date equal to or earlier than now.
Now, as jawboxer has pointed out, for a repeatUnit of NSCalendarUnitMonth, you get unexpected results if you calculate the date using that value and afterwards add one more repeat interval (as the original version of this answer did). Instead, add one to the number of repeats immediately; then the calendar correctly handles month increments.
NSInteger repeatsJustPastNow = 1 + deltaVal - (deltaVal % repeatInterval);
NSDateComponents * toNowComps = [NSDateComponents new];
[toNowComps setValue:repeatsJustPastNow
forComponent:repeatUnit];
NSDate * nextDate;
nextDate = [cal dateByAddingComponents:toNowComps
toDate:pastDate
options:0];
Jawboxer's Swift version of this corrected code is on Github.
EDIT: As Josh pointed out this doesn't account for leap years. So this is a good example of an implementation that might seem to work over short term testing but has holes in it. It also will be off by some hours in the log as dateFromString: will compensate for timezone while the log will log it as GMT.
NSDateFormatter* format = [[NSDateFormatter alloc] init];
format.dateFormat = #"yyyy-MM-dd 'at' HH:mm";
NSDate *pastDate = [format dateFromString: #"2001-09-11 at 00:00"];
NSTimeInterval repeatInterval = 14 * 24 * 60 * 60; // 2 weeks
NSDate *nowDate = [NSDate date];
NSTimeInterval timeFromPastDate = [nowDate timeIntervalSinceDate: pastDate];
NSTimeInterval modulus = timeFromPastDate / repeatInterval;
modulus -= floor(modulus);
NSDate *nextDate = [NSDate dateWithTimeInterval: repeatInterval * (1 - modulus) sinceDate: nowDate];
NSLog(#"nexDate = %#",nextDate); // 28th/July/2015, if current date is 23rd/July/2015
I made another solution early.
NSDate *date = ...;
static NSTimeInterval k2WeeksInterval = 14 * 24 * 60 * 60.0;
NSTimeInterval timeInterval = -date.timeIntervalSinceNow;
double repeats = ceil(timeInterval / k2WeeksInterval);
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [NSDateComponents new];
components.weekOfYear = repeats * 2;
NSDate *nextDate = [calendar dateByAddingComponents:components
toDate:date
options:NSCalendarMatchNextTime];

Converting time to HH:MM

I currently have a float value like 12.5, 4, 17.5. I want these to correspond to the times 12:30PM, 4:00AM, and 5:30PM.
I've currently achieved something close to this with the hack
if (time > 12.5) {
time = abs(roundedValue - 12);
[lab setText:[NSString stringWithFormat:#"%i:00PM",(int)time]];
} else {
[lab setText:[NSString stringWithFormat:#"%i:00AM",(int)time]];
}
But I know this is bad practice. What's a better way to convert these numbers to times?
This is just basic math, you have a value, say 12.5, which consists of a number of hours, 12, and a fraction of an hour, 0.5. There are 60 mins in an hour so the number of minutes is just the fraction times 60.
If you want to use the 12 hour clock there is a small quirk, hours > 12 need to be reduced by 12 but noon (12) is pm and midnight (0 or 24) is am. So the test for am/pm is not the same test as whether to subtract 12.
Here is one way to do it (with minimal checking):
NSString *hoursToString(double floatHours)
{
int hours = trunc(floatHours); // number of hours
int mins = round( (floatHours - hours) * 60 ); // mins is the fractional part times 60
// rounding might result in 60 mins...
if (mins == 60)
{
mins = 0;
hours++;
}
// we haven't done a range check on floatHours, also the above can add 1, so reduce to 0 -> 23
hours %= 24;
// if you are using 24 hour clock you can finish here and format to the two values
BOOL pm = hours >= 12; // 0 - 11 = am, 12 - 23 = pm
if (hours > 12) hours -= 12; // 13 - 23 -> 1 -> 11
return [NSString stringWithFormat:#"%d:%02d %s", hours, mins, (pm ? "pm" : "am")];
}
You call this simply as:
hoursToString(13.1) // returns 1:06 pm
No need to use NSDate et al.
HTH
Solved by doing the following:
NSDate *now = [NSDate date];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:now];
[components setHour:0];
NSDate *today10am = [calendar dateFromComponents:components];
NSDate *newDate = [NSDate dateWithTimeInterval:roundedValue*60*60 sinceDate:today10am];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"h:mm aa"];
[lab setText:[dateFormat stringFromDate:newDate]];
Sorry Hot Licks, guess this was within my comprehension.
You can use the following -
NSNumber *time = [NSNumber numberWithDouble:([yourTime doubleValue] - 3600)];
NSTimeInterval interval = [time doubleValue];
NSDate *yourDate = [NSDate date];
yourDate = [NSDate dateWithTimeIntervalSince1970:interval];
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setDateFormat:#"HH:mm:ss"];
NSLog(#"result: %#", [dateFormatter stringFromDate:yourDate]);

NSDate countdown from NOW to NSDate

How can I show a countdown in HH:mm:ss format from NOW to a desired NSDate that will happen in the future?
Start at the documentation.
NSDate *future = // whatever
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(updateCounter:) userInfo:nil repeats:YES];
- (void)updateCounter:(NSTimer *)tmr
{
NSTimeInterval iv = [future timeIntervalSinceNow];
int h = iv / 3600;
int m = (iv - h * 3600) / 60;
int s = iv - h * 3600 - m * 60;
aUILabel.text = [NSString stringWithFormat:#"%02d:%02d:%02d", h, m, s];
if (h + m + s <= 0) {
[tmr invalidate];
}
}
You have to use a timer for ticking the date.
Store a future date, and keep on substracting future date - [nsdate today]...
Calculate the time in seconds and calculate it into Hours, minutes, seconds...
//Make two properties NSDate *nowDate, *futureDate
futureDate=...;
nowDate=[NSDate date];
long elapsedSeconds=[nowDate timeIntervalSinceDate:futureDate];
NSLog(#"Elaped seconds:%ld seconds",elapsedSeconds);
NSInteger seconds = elapsedSeconds % 60;
NSInteger minutes = (elapsedSeconds / 60) % 60;
NSInteger hours = elapsedSeconds / (60 * 60);
NSString *result= [NSString stringWithFormat:#"%02ld:%02ld:%02ld", hours, minutes, seconds];
This will come handy for you...kindly check the project...
Give this code a shot:
NSTimer* timer= [NSTimer scheduledTimerWithInterval: [self.futureDate timeIntervalSinceNow] target: self selector: #selector(countdown:) userInfo: nil, repeats: YES];
The countdown: method:
- (void) countdown: (NSTimer*) timer
{
if( [self.futureDate timeIntervalSinceNow] <= 0)
{
[timer invalidate];
return;
}
NSDateComponents* comp= [ [NSCalendar currentCalendar] components: NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit startingDate: [NSDate date] toDate: self.futureDate options: 0];
NSLog(#"%lu:%lu:%lu", comp.hour,comp.minute.comp.second);
}

UIDatePicker setting incorrect time when configured to use a 30 minute interval

I have a UIDatePicker that only takes times in 30min intervals. On viewDidLoad I want to get the current time to the nearest half hour. How would I go about doing this?
Use NSDateComponents to get and manipulate the hour and minute of a date. Here's how I did it:
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit) //Need to pass all this so we can get the day right later
fromDate:[NSDate date]];
[components setCalendar:calendar]; //even though you got the components from a calendar, you have to manually set the calendar anyways, I don't know why but it doesn't work otherwise
NSInteger hour = components.hour;
NSInteger minute = components.minute;
//my rounding logic is maybe off a minute or so
if (minute > 45)
{
minute = 0;
hour += 1;
}
else if (minute > 15)
{
minute = 30;
}
else
{
minute = 0;
}
//Now we set the componentns to our rounded values
components.hour = hour;
components.minute = minute;
// Now we get the date back from our modified date components.
NSDate *toNearestHalfHour = [components date];
self.datePicker.date = toNearestHalfHour;
Hope this helps!

Why does this clock that I created seem to never release any of it memory?

I created a clock for my app that I'm working on that starts off normal (counting by one second each time), but then freezes every two seconds, then every four, then eight, etc. until it reaches twenty and the app crashes, with no error in xcode showing, not even a Recieved Memory Warning.
I went to Instruments and found that when using my clock, my app's memory was increasing at an alarming rate (although, any rate is alarming to me). The problem is, I can't figure out what it is exactly that is causing the memory to go so high. The app is running on ARC, so I know that I didn't forget to release something. The only thing I can think of is that each time the number is loaded it's stored into memory and never released. How that is, I have no idea, and how to fix it I have even less of an idea. Please let me know what I can do. Here is my code:
-(void)updateTime {
calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
updateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTime) userInfo:nil repeats:YES];
currentTime = [NSDate date];
dateComponents = [calendar components:unitFlags fromDate:currentTime];
year = [dateComponents year];
month = [dateComponents month];
day = [dateComponents day];
hour = [dateComponents hour];
minute = [dateComponents minute];
second = [dateComponents second];
if (hour >= 12) {
lblAMPM.text = #"PM";
} else {
lblAMPM.text = #"AM";
}
//if (isTwelveHour == YES) {
//make a 12 hrs clock instead of 24
if (hour >= 12) {
hour = 12 - (24-hour);
}
//}
//if midnight, show hour as 12 instead of 0
if (hour == 0) {
hour = 12;
}
if (second < 10) {
seconds = [NSString stringWithFormat:#"0%i", second];
} else {
seconds = [NSString stringWithFormat:#"%i", second];
}
if (minute < 10) {
lblTime.text = [NSString stringWithFormat:#"%i:0%i:%#", hour, minute, seconds];
} else {
lblTime.text = [NSString stringWithFormat:#"%i:%i:%#", hour, minute, seconds];
}
[lblDate setText:[NSString stringWithFormat:#"%d/%d/%d", month, day, year]];
}
- (void) updateDateFirst:(int)firstComponent setSecond:(int)secondComponent{
calendar = [NSCalendar currentCalendar];
unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
updateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTime) userInfo:nil repeats:YES];
currentTime = [NSDate date];
dateComponents = [calendar components:unitFlags fromDate:currentTime];
year = [dateComponents year];
month = [dateComponents month];
day = [dateComponents day];
[lblDate setText:[NSString stringWithFormat:#"%d/%d/%d", firstComponent, secondComponent, year]];
}
- (void)viewDidUnload {
lblTime = nil;
lblAMPM = nil;
lblDate = nil;
[super viewDidUnload];
}
I am calling updateTimer in viewDidAppear, just as an FYI.
Every time updateTime is called it creates a new timer and adds it to the current run loop when you call:
updateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTime) userInfo:nil repeats:YES];
The run loop retains these timers. So although you keep replacing the NSTimer pointed to by your ivar updateTimer they are never dealloc'ed. ARC manages each NSTimer assigned to updateTimer so they have matching retain/release calls on them. However, the retain by the run loop when they are first added is never paired with a matching release since the timers are repeating and never invalidated. So the sequence goes like this:
You call - (void) updateDateFirst:(int)firstComponent setSecond:(int)secondComponent to start things off and it creates an NSTimer.
This timer fires 1 second later and calls updateTime, which creates a new timer. The original timer is repeating so it will fire again in 1 second. Now you have 2 timers.
Both timers fire roughly 1 second later, call updateTime, and create 2 new timers. Now you have 4 timers....
The standard way you would do this is just to create 1 repeating timer at the beginning and not in each call to updateTimer. Then when you are done, you call:
[updateTimer invalidate];
upateTimer = nil;
This stops and frees the timer.
You create new NSTimer objects every time that updateTime is called. So the number of allocated and running timers increases by a factor of 2 every second!
After one second, updateTime is called and creates a new timer. But the first timer keeps running because of the repeats:YES option.
After the next second, both timers fire and call updateTime. Now you have 4 timers.
And so on ...
I don't know what your updateDateFirst: is for, but it creates another timer.
So you should create the timer only once, for example in viewDidAppear. And don't forget to stop the timer (using invalidate), for example in viewWillDisappear.