Store time zone independent time-only in NSDate object - ios

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];

Related

Get progress of day in Objective-C

I need to get the progress of the current day (For example from 6am and 11pm). I have tried many things and the only code I can get working so far is this code:
//Get the seconds between two days
//Get todays date
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:[NSDate date]];
//Set todays date in a NSCalendar object
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDate *date1 = [gregorian dateFromComponents:components];
//Get tomorrows date
NSDateComponents *components1 = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:[NSDate date]];
components1.day++;
//Set tomorrows date in a NSCalendar object
NSDate *date2 = [gregorian dateFromComponents:components1];
NSTimeInterval secondsBetween = [date2 timeIntervalSinceDate:date1];
NSLog(#"Test: %f", secondsBetween);
This code will get the amount of seconds between two days in total (which doesn't really matter as it's a constant). How do I get the progress between two different times? As the user will be allowed to change the times it counts between I need to be able to calculate this on the fly in a NSTimer.
Edit: I want to know how to use a ProgressView to display the percentage between two times. E.g. The ProgressView would be 50% if it was 9AM and the times were 6AM and 12PM.
The difference in seconds between two dates is
[date2 timeIntervalSinceDate:date1];
That seems to be all you need for your problem.
you only set day/month/year -- set hours/minutes/seconds too.
right now the dates always point to 00:00:00 and not to any TIME
No problem. I use the following method to determine the difference between NSDate values:
- (NSInteger)secondsBetweenDate:(NSDate*)fromDateTime andDate:(NSDate*)toDateTime {
NSDate *fromDate;
NSDate *toDate;
NSCalendar *calendar = [NSCalendar currentCalendar];
//Returns by reference the starting time and duration for the given dates (with second precision).
[calendar rangeOfUnit:NSCalendarUnitSecond startDate:&fromDate
interval:NULL forDate:fromDateTime];
[calendar rangeOfUnit:NSCalendarUnitSecond startDate:&toDate
interval:NULL forDate:toDateTime];
//Returns the difference between the two dates (with second precision)
NSDateComponents *difference = [calendar components:NSCalendarUnitSecond
fromDate:fromDate toDate:toDate options:0];
//returns the number of seconds
return [difference second];
}
To test the above code, I've used an easier way to create two points in time (rather than many lines you've used above). This is simply used to test and you'll obviously use real values in your actual code
NSDate * myDate1 = [NSDate date]; // date as of right now
NSDate * myDate2 = [[NSDate alloc] initWithTimeIntervalSinceNow:1 * 24 * 60 * 60]; // exactly 24 hours into the future
NSLog(#"Second Between: %ld",(long)[self secondsBetweenDate:myDate1 andDate:myDate2]);
//OUTPUT: Seconds: 86400
You can easily tweak the values above to test certain ranges. For reference, this is what each number stands for:
1 (days) * 24 (hours) * 60 (minutes) * 60 (seconds)
OK, with that sorted you just need to figure out how much time has progressed since your starting time. You can do that with two calls to the method created at the start of this post:
//Assuming dates are sourced from some other code
NSDate * startTime // Set elsewhere in the code
NSDate * currentTime // Set elsewhere in the code
NSDate * endTime // Set elsewhere in the code
long fullDuration = [self secondsBetweenDate:startTime andDate:endTime];
long timeElapsed = [self secondsBetweenDate:startTime andDate:currentTime];
float percentComplete = (float) timeElapsed / (float) fullDuration;
Now, since we're going to be using ProgressView I'm keep this percent value as a number between 0 and 1 and the variable as a float.
Finally, assuming you already have the following done:
Added a ProgressView object to your storyboard
Added an outlet to to the object called something like progressViewOutlet
All you need to do is send the update regularly using your timer using the following code:
self.progressViewOutlet.progress = percentComplete;
You can grab my source code below. Let me know if you have any questions
https://dl.dropboxusercontent.com/u/43038596/testDates.zip

UIDatePicker: help to convert pseudo code into objc code

I am trying to implement some pseudo code I have for the date picker. However I am unsure of how to add a minute value to adjust an NSDate object.
Here is the pseudo code:
//minTime is an NSDate object
minTime = currentTime + 30mins - (currentTime % 15)
(currentTime % 15) means that the user can only select in 15mins intervals, and must be 15mins from the current 15min interval. For example, if its 10:50, the user should only be able to select 11:15 from the UIDatePicker. If is 10:20, the user should only be able to select 10:45.
I know how to get the currentTime using [NSDate date] but I do not know how to add mins to it and adjust it.
It is not considered good practice to work with time intervals when working with dates and times. The best solution is to use NSDateComponents to add time periods.
NSDateComponents* dc = [NSDateComponents new];
dc.minutes = 15;
NSDate* newDate = [[NSCalendar currentCalendar] dateByAddingComponents:dc toDate:oldDate options:0];
You can add some minutes to a NSDate using :
NSDate *nowDate = [NSDate date];
NSDate *nowDateAnd2moreMinutes = [nowDate dateByAddingTimeInterval:2*60]; //This add 2 minutes (2 * 60sec)
More information in apple documentation.
Edit I wrote a little function that add minutes :
+(NSDate) addMinutes:(int) minutes toDate:(NSDate) date{
return [date dateByAddingTimeInterval:minutes*60];
}
Bonus : Function that add minutes and seconds to a date
+(NSDate) addMinutes:(int) minutes andSeconds:(int) sec toDate:(NSDate) date{
return [date dateByAddingTimeInterval:(minutes*60)+sec];
}

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: Formula for alert notifications

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.

Resources