iOS: Formula for alert notifications - ios

I'm trying to write an application that will send the user an alert in the Notification Center 60 hours before the date arrives. Here is the code:
localNotif.fireDate = [eventDate dateByAddingTimeInterval:-60*60*60];
I was wondering if the -60*60*60 formula will work to alert them 60 hours prior to the date? I'm not sure at all how the formula works, I would like to set it up to alert 10 minutes before the date for testing purposes, then change it back to 60 hours once I confirm that everything is correct. Does any one know the formula to use for both of these?
Any help is much appreciated, thank you!

A crude but easy-to-code way is to add/subtract seconds from an NSDate directly:
NSDate *hourLaterDate = [date dateByAddingTimeInterval: 60*60];
NSDate *hourEarlierDate = [date dateByAddingTimeInterval: -60*60];
You can see how it works by logging the dates:
NSDate *now = [NSDate date];
NSDate *hourLaterDate = [now dateByAddingTimeInterval: 60*60];
NSLog(#"%# => %#", now, hourLaterDate);
In this approach a date is interpreted as a number of seconds since the reference date. So, internally it's just a big number of type double.
A tedious-to-code but pedantically correct way to do these calculations is by interpreting dates as dates expressed in a calendar system. The same thing achieved as calendrical calculations:
NSDateComponents *hour = [[NSDateComponents alloc] init];
[hour setHour: 1];
NSCalendar *calendar= [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar];
NSDate *hourLaterDate = [calendar dateByAddingComponents: hour
toDate: date
options: 0];
[hour release];
[calendar release];
These calculations take into account time zones, daylight saving time, leap years, etc. They can also be more expressive in terms of what you're calculating.
Before using any of these approaches you have to decide what exactly you need: a timestamp or a full-blown calendar date.

Related

Store time zone independent time-only in NSDate object

I am creating an app which includes a timetable feature, allowing the user to specify periods in a particular day (e.g. Period 1 goes from 9:00 to 10:00; Period 2 goes from 10:00 to 11:00 and so on). As the app is backed by Core Data it seems sensible to store the periods as Core Data managed objects with two NSDate attributes: startTime and endTime.
Assuming that the periods cannot span multiple days, the date in the NSDate objects is irrelevant - I am only interested in the time. While one approach is to simply use NSDateComponents to extract the time (and ignore the date) if I am doing a comparison of NSDate objects, or sorting based upon those objects, then if the dates are different that will affect the comparison or sort. I was thinking that one way around this problem is to manually the startTime and endTime attributes to have the same (arbitrarily) chosen date. Is this the best way around the issue?
Additionally, the times (i.e. NSDate objects) need to be time zone independent. If the user travels from say Australia to the USA then I don't want all the times to now show they have a class in the middle of the night! Compensating for time zone changes is not relevant in this app, so I would like the times to stay static regardless of time zone. I would appreciate advice on this aspect too.
Thanks in advance for any assistance!
If you don't need the "day date" I would recommend to not use NSDate at all, otherwise you will run into various issues. As said in the comments NSDate is not timezone aware, this means you will do a lot of unnecessary conversions to make sure that your app works in all timezones.
I would as well use the approach to save seconds since midnight, or have two fields for hours and minutes since midnight. That basically is local time, always.
To get this information from a UIDatePicker you have to convert the NSDate into NSDateComponents
- (IBAction)datePickerChanged:(UIDatePicker *)sender {
NSDate *date = sender.date;
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
calendar.timeZone = sender.timeZone;
NSDateComponents *components = [calendar components:NSHourCalendarUnit|NSMinuteCalendarUnit fromDate:date];
NSInteger hours = components.hour;
NSInteger minutes = components.minute;
NSLog(#"%02ld:%02ld", (long)hours, (long)minutes);
NSInteger secondsSinceMidnight = hours * (60*60) + minutes * 60;
// save to core data
}
When you want to show your time in the datePicker you have to go the opposite way, you have to create a NSDate (that, for timezone reasons happens to be today) from the saved components. e.g.:
// get from core data
NSInteger secondsSinceMidnight = 8 * (60*60) + 30 * 60;
NSInteger minutes = (secondsSinceMidnight / 60) % 60;
NSInteger hours = (secondsSinceMidnight / 3600);
NSLog(#"%02ld:%02ld", (long)hours, (long)minutes);
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
calendar.timeZone = datePicker.timeZone;
// use today as day, to avoid timezone issues
NSDateComponents *components = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:[NSDate date]];
components.hour = hours;
components.minute = minutes;
datePicker.date = [calendar dateFromComponents:components];

Timezone Calculation Issues

I am making a countdown timer and I want it to globally countdown to Christmas. Therefore I have my timestamp value of 1387929600 which is dec 25 00:00:00. I am adjusting that value according to the different time zones and I am doing it like this:
NSDate *firstDate = [NSDate dateWithTimeIntervalSince1970:1387954801];
NSTimeZone *tz = [NSTimeZone localTimeZone];
int tzInt = [tz secondsFromGMTForDate:firstDate];
int timeDifference = tzInt+1387954801;
xmasDate = [NSDate dateWithTimeIntervalSince1970:timeDifference];
I take the firstDate and do secondsFromGMTForDate and then add it to the original timestamp number to get the xmasDate adjusted for time zones.
This general approach seems to work however the secondsFromGMTForDate is not calculating correctly.
For example when I set my phone to the current time zone it still wants to subtract "-25200" from the timestamp time when it should be zero since its the current time zone.
Am I correct in saying that? I might be missing something in the calculation and maybe I have to add to the timestamp number but I'm not sure.
Any help would be greatly appreciated.
By adding tzInt to a timestamp in GMT, you're doubling the effect of time zone difference instead of cancelling it.
For example, if your timezone is 25200 seconds behind GMT (tzInt=-25200), you need to add 25200 to 1387929600, as Christmas will come later in your time zone.
So the correct calculation of timeDifference is the following:
int timeDifference = 1387954801 - tzInt;
Otherwise the code looks correct, but I'd use the following code instead, which, I think, is easier to understand:
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setDay:25];
[components setMonth:12];
[components setYear:2013];
NSDate *xmasDate = [[NSCalendar currentCalendar] dateFromComponents:components];
NSTimeInterval secondsLeft = [xmasDate timeIntervalSinceNow];

How can I treat dates as if there were no DST adjustment?

Edit: My solution is below.
I have a sort of calendar-related app where the user is able to jump backward and forward certain integer numbers of days. In testing the app around midnight, I realized there is a DST-related problem, and I'm looking for the best solution.
The app is not really based on any calendar in iOS. However, I display the Gregorian date and time so the user has a reference to a familiar date and time.
One of the built in jumps in time is 73 days, which is about 1/5 year. Common dates used will be March equinox +/- integer multiples of 73 days. The problem comes, for example, when jumping from March equinox to a date 73 days previous, because the March equinox is likely to be in DST, whereas the other is not. It depends on the timezone setting on the system. The app, however, has nothing to do with timezone, so I'd rather display times as if there were no such thing as DST, yet still using the local timezone. I hope that makes sense.
Any suggestions on how to achieve this? If the question is too vague, maybe I can add more detail, but I don't want to confuse things by adding too much.
EDIT: I've decide to link sample screen prints from the app to illustrate what's going on. This picture:
http://victorspictures.com/trollart/h37a90c77#h56122c20
shows the configuration for this year's equinox. The caption explains how to read the device. Follow to the next two pictures in the gallery to see how to use the device to calculate the date of Easter.
Anyway, swipe gestures allow the user to move +/- one day or +/- 73 days, which is where the problem arises. I'm adding time to the date display, and maybe that is really all I need to do.
Solution: Celeda's suggestion
Added this method:
- (NSDate *)setNoon: (NSDate *)date
{
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:date];
[components setHour:12];
NSDate *todayNoon = [calendar dateFromComponents:components];
return todayNoon;
}
Then, everywhere a jump was made, it was simply encapsulated in the new method:
newDate = [self setNoon:[previousCode]];
It appears, based on your comment to Hot Licks's answer, that you have some number d representing days since the 1975 vernal equinox (which was at 1975-03-21 05:57:00 UTC), and you want figure out the corresponding day on the Gregorian calendar.
We can use the NSDate, NSCalendar, and NSDateComponents classes to perform this computation. To avoid the effects of DST, we need to set the time zones of the calendar and formatter to UTC.
NSInteger d = 73 * 189; // days since the 1975 vernal equinox
NSTimeZone *utcTimeZone = [NSTimeZone timeZoneWithName:#"UTC"];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
calendar.timeZone = utcTimeZone;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = #"yyyy-MM-dd";
formatter.timeZone = utcTimeZone;
NSDateComponents *epochComponents = [[NSDateComponents alloc] init];
epochComponents.year = 1975;
epochComponents.month = 3;
epochComponents.day = 21;
epochComponents.hour = 5;
epochComponents.minute = 57;
epochComponents.timeZone = utcTimeZone;
NSDate *epochDate = [calendar dateFromComponents:epochComponents];
NSLog(#"epochDate=%#", epochDate);
NSLog(#"epochDate from formatter=%#", [formatter stringFromDate:epochDate]);
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
offsetComponents.day = d;
NSDate *computedDate = [calendar dateByAddingComponents:offsetComponents toDate:epochDate options:0];
NSLog(#"computedDate=%#", computedDate);
NSLog(#"computedDate from formatter=%#", [formatter stringFromDate:computedDate]);
Try commenting out the lines that set the time zone to UTC on the calendar and/or the formatter to see how DST changes the results.
For more information, read the Date and Time Programming Guide.

iOS Finding the time passed since a specific date

I have been playing with NSDate and NSTimeInterval to try and get the time passed since a specific date, but I am not having much luck.
I want to specify an exact constant date (Jan 5th 2012 2PM) and calculate the time passed in Years, Hours, Min, Sec and display this on the screen as a counter.
It seems simple enough; however, browsing through code has yet to prove successful for me.
I also want to see total time in a years months days mins secs format. I appreciate any help, thanks!
This an example I have found that I don't know how to customize:
NSCalendar *c = [NSCalendar currentCalendar];
NSDate *d1 = [NSDate date];
NSDate *d2 = [NSDate dateWithTimeIntervalSince1970:1340323201];//2012-06-22
NSDateComponents *components = [c components:NSHourCalendarUnit fromDate:d2 toDate:d1 options:0];
NSInteger diff = components.minute;
NSDayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit
Like tkanzakic said,
NSDateComponents* components = [c components:(NSYearCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit|NSSecondCalendarUnit) fromDate:d1 toDate:d2 options:0] ;
NSLog(#"%ld years, %ld hours, %ld minutes, %ld seconds", components.year, components.hour, components.minute, components.second) ;

Get an Accurate Time Difference Between two NSDate?

Is there any way to find out an accurate difference between two NSDate?
I have found solutions but they aren't accurate enough. I need to take into account daylight saving, the fact that different months have a different number of days, etc.
A simple calculation such as /60/60/24 etc. to work out minutes, hours and days doesn't take them into account.
Lets say I need to work out the difference between the time right now ([NSDate date]) and December 25th 10:22PM (date chosen by user using date picker [datePicker date]) just as an example, how would I do this?
Knowing the exact time difference isn't the key, so long as I have an accurate difference of days, months and years, it will do.
From Apple's Date & Time Programming Guide:
Listing 12 Getting the difference between two dates
NSDate *startDate = ...;
NSDate *endDate = ...;
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSUInteger unitFlags = NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents *components = [gregorian components:unitFlags
fromDate:startDate
toDate:endDate options:0];
NSInteger months = [components month];
NSInteger days = [components day];
NSDate is completely independent of Timezone. Daylight saving doesn't even come into the picture for NSDate. Only when you convert NSDate into a human readable format (MM/DD/YY, HH:MM:SS format or the like), does Time Zone come into picture.
Make sure that you take into account correct timezone, day-light saving setting when you create NSDate(s). Subsequently, the method, [date1 timeIntervalSinceDate:date2] should always give you accurate time difference.
So, the more accurate question you meant to ask was: How can I get a nicely formatted days, months, years from a difference between two dates. First you want to get the nsTimerInterval (time difference in seconds) and then format it:
How do I break down an NSTimeInterval into year, months, days, hours, minutes and seconds on iPhone?
Small update on the code for latest iOS:
NSDate *startDate = ...;
NSDate *endDate = ...;
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSUInteger unitFlags = NSCalendarUnitMonth | NSCalendarUnitDay;
NSDateComponents *components = [gregorian components:unitFlags
fromDate: startDate
toDate:endDate options:0];
NSInteger months = [components month];
NSInteger days = [components day];

Resources