Timezone Calculation Issues - ios

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

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

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.

UIDatePicker returns wrong date (-1 day to the real date)

I have a UIDatePicker mm/dd/yy. It works fine, but there is one problem: I set minimum and maximum date to it, and when user tries to choose the forbidden day, month or year, the [datePicker date] property begins working wrong. It returns you the current day - 1 or current month - 1 or current year - 1. I added some pictures, so you can see the situation.
This is correct
This is wrong (After choosing the forbidden date)
Does somebody know, how can I fix this ? Thanks !
UPD:
Code
[self.myDatePicker setMinimumDate:[NSDate date]];
[self.myDatePicker setMaximumDate:[[NSDate date] addTimeInterval:2 * 365.25 * 24 * 60 * 60]]; // to get upto 5 years
NSDate * now = [[NSDate alloc] init];
[self.myDatePicker setDate: now animated: YES];
self.myDatePicker.timeZone = [NSTimeZone localTimeZone];
self.myDatePicker.calendar = [NSCalendar currentCalendar];
Just add one line of code for setting your timezone.
self.datePicker.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
0 is for GMT 00 . Add according to your time zone.
My solution was to set the returned date to 12:00 AM as NSDates work in UTC
NSDate * adjustedDate = [[NSCalendar currentCalendar] dateBySettingHour:12 minute:0 second:0 ofDate:sender.date options:0];
Also for date calculations you shoud use NSCalender methods and not addTimeInterval
It is deffinately something with the timezones and/or Daylight Saving Times. But it must be very subtle, as the code looks fine (beside the interval). Now to my question about if you are in russia:
This year the Kremlin did several back and forth swings on keeping daylight saving times forever. Actually I am not sure, what they decided at last. But maybe it isnt reflected correctly in Cocoa. The the video WWDC 2011 Video "Session 117 - Performing Calendar Calculations" , the presenter even mentions that things like that can happen.
Please try to work with dates with manually set times to noon, as this would keep you out of such mess.
The world just saw a similar misbehavior in iOS 6: the DND-Always-Active bug. I bet this was for a wrong date format (YYYY instead of yyyy)
Also try to set the timezone property on the picker at the very first thing and assign a manually instantiated Gregorian calendar to it.
Check if you use the wrong formatting symbols with big letters: "YYYY".
Replace them with "yyyy".
I ran into the same trouble and this is what i derived:
Don't use [date description] to check NSDate if you want correct representation for your system. Use NSDateFormatter, because it shows date based on your system preferences (in simulator it will be simulators preferences).
For example:
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateStyle:NSDateFormatterShortStyle];
[df setTimeStyle:NSDateFormatterNoStyle];
NSLog(#"date for locale is %#", [df stringFromDate:date]);
Try this,
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.string(from: yourPicker.date)
Might be due to TimeZone...
Set your time zone.
For me this problem is resolved by adding this line
datePicker.date.addingTimeInterval(TimeInterval(TimeZone.current.secondsFromGMT()))
This line adds up the seconds in dateTime of UIDatePicker and dateTime will be equal to current dateTime
Boolean futureevent;
- (void)viewDidLoad
{
futureevent = false;
}
int intervall = (int) [currentdate timeIntervalSinceDate: datePicker.date] / 60;
if (intervall < 1200 && intervall > 0)
{
futureevent = true;
NSDate *newDate1 = [datePicker.date dateByAddingTimeInterval:60*60*24*1];
birthdate = [newDate1.description substringToIndex:spaceRange.location];
}
else
{
if (futureevent)
{
NSDate *newDate1 = [datePicker.date dateByAddingTimeInterval:60*60*24*1];
birthdate = [newDate1.description substringToIndex:spaceRange.location];
}
else
{
birthdate = [datePicker.date.description substringToIndex:spaceRange.location];
}
}
It does not return wrong date. What actually happens is, when you select a so called forbidden date, the date picker gets reset to maximum or minimum allowed date with first moment of the day i.e 12:00AM.
So if you are at a place where time zone is for example, 2 hours ahead of GMT, the date returned by date picker will be yesterday's 10:00PM GMT. So here, you might think it is returning yesterday's date but if you convert it to your time zone, you will get today's date only but time component will be 12:00AM.

Timezoneoffset error when daylightsaving in effect in iOS

friends,I am getting a date based on the calculation I have done below
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *expectedDate = [gregorian dateByAddingComponents:components toDate:startDate options:0];
NSTimeInterval timeZoneOffset = -[[NSTimeZone systemTimeZone] secondsFromGMTForDate:expectedDate];
NSDate *localDate = [expectedDate dateByAddingTimeInterval:(timeZoneOffset)];
NSString *date = [dateFormatter stringFromDate:localDate];
But the date goes wrong when the daylightsaving is in effect,and also the timeZoneOffset changes when the daylightsaving is in effect, but I want the same date irrespective of whether the daylight saving is in effect or no..
So friends,how shall I handle this situation,please help.
Regards
Ranjit
You don't need to take care of daylight saving time yourself, the "dateFormatter" does that automatically for you. Usually you only need a NSDate object in UTC (GMT+0) time and "dateFormatter", which also has a time zone, will display that time in its own time zone.
NSCalendar and NSDateFormatter have time zone settings. NSDate is just a point in time relative to GMT+0.
Example:
"expectedDate" is January 1st 4am (GMT+0)
"dateFormatter" has time zone GMT+2 (e.g. Europe/Berlin) set, then it will output "January 1st 6am" because of its own time zone when converting "expectedDate" into a string.
So basically you just need to ensure that "startDate" is correct and that "gregorian" and "dateFormatter" use the correct time zone. By default they use the system time zone, which seems to be the one you want to use. So you need only these lines (and startDate has to be correct):
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *expectedDate = [gregorian dateByAddingComponents:components toDate:startDate options:0];
NSString *date = [dateFormatter stringFromDate:expectedDate];
If that doesn't work, please post more code about how startDate and dateFormatter are generated.
James, try this
//To Fix DaylightSaving, 1 hr added to startDate.
NSTimeInterval secondsInOneHour = 1 * 60 * 60;
NSDate *expectedDST = [startDate dateByAddingTimeInterval:secondsInOneHour];
NSDate *expectedDate = [gregorian dateByAddingComponents:components toDate:expectedDST options:0];
NSTimeInterval timeZoneOffset = -[[NSTimeZone systemTimeZone] secondsFromGMTForDate:expectedDate];
NSDate *localDate = [expectedDate dateByAddingTimeInterval:timeZoneOffset];

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