Timezoneoffset error when daylightsaving in effect in iOS - 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];

Related

About NSDateComponents:Why I can't get the correct day component?

I want get day component from localDate, [NSDate localDate] just transform the GMT into local time.
NSDate *localDate=[NSDate localDate];
// localDate:2017-03-13 18:35:35 +0000
NSLog(#"localDate:%#",localDate);
NSInteger dayComponentLocal=[[NSCalendar currentCalendar] component:(NSCalendarUnitDay) fromDate:localDate];
NSLog(#"dayComponentLocal:%ld",(long)dayComponentLocal);
But why the output is 14?
There is the implement of [NSDate localDate]:
NSDate *date=[NSDate date];
NSTimeZone *zone=[NSTimeZone systemTimeZone];
NSInteger interval=[zone secondsFromGMTForDate:date];
NSDate *localDate=[date dateByAddingTimeInterval:interval];
return localDate;
And there is another error when I want to get the first weekday of current month, there is the code:
NSDate *date=[NSDate localDate];
NSInteger dayComponents=[[NSCalendar currentCalendar] component:(NSCalendarUnitDay) fromDate:date];
NSInteger secondsInADay=24*60*60;
NSDate *firstDay=[date dateByAddingTimeInterval:-((dayComponents-1)*secondsInADay)];
NSInteger weekdayComponent=[[NSCalendar currentCalendar] component:(NSCalendarUnitWeekday) fromDate:firstDay];
NSLog(#"firstDay:%#",firstDay);
NSLog(#"weekday Of firstDay:%ld",weekdayComponent);
due to the wrong dayComponents, I subtract one more day from 'localDate' and get the wrong firstDay '2017-02-28 20:09:32 +0000' rather than the correct answer '2017-03-01 20:09:32 +0000'. 2-28 is Tuesday, but I get 4, which means Wednesday,
Why?
You are adding the number of seconds from GMT in localDate. As your time zone is positive you need to subtract the seconds (-interval) to get UTC.
By the way never ever use 86400 for date math. Use the date calculation methods of NSCalendar
For example you get the first weekday of the current month with
NSCalendar *calendar = [NSCalendar currentCalendar];
// calendar.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
NSDate *startDate;
NSTimeInterval interval;
[calendar rangeOfUnit:NSCalendarUnitMonth startDate:&startDate interval:&interval forDate:[NSDate date]];
NSInteger weekday = [calendar component:NSCalendarUnitWeekday fromDate:startDate];
NSLog(#"%ld", weekday);
To get the date related to UTC uncomment the time zone line.
Remove the time components from the date before taking the day component.
Try this code
NSDate *localDate=[NSDate localDate];
// localDate:2017-03-13 18:35:35 +0000
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"dd-MM-yyyy"];
NSString *strDate = [dateFormatter stringFromDate: localDate];
localDate = [dateFormatter dateFromString: strDate];
NSInteger dayComponentLocal=[[NSCalendar currentCalendar] component:(NSCalendarUnitDay) fromDate:localDate];
NSLog(#"dayComponentLocal:%ld",(long)dayComponentLocal);
You will get output 13. This occur maybe because of difference in time zone

Objective C Calculate Amount of Time Between Now and 3:00

I am making an app to use at school and I want to make a countdown timer to countdown the amount of time between now and the end of school, which for me is 3:00. For example, at 11:15, it will read 3:45.
So far, I have figured out how to make the countdown timer and I have the following code: countdownTimer.text = [Formatter stringFromDate: [NSDate date]];
This code doesn't actually work yet, but I think it will work if I figure out how to subtract the current time from a set time and then use that value where date is, however I am open to other suggestions on how to approach the problem.
You can use NSCalendar method dateBySettingHour:minute:second: to get the NSDate object associated with 3pm today:
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *now = [NSDate date];
NSDate *schoolOut = [calendar dateBySettingHour:15 minute:0 second:0 ofDate:now options:0];
There are lots of different ways to get the NSDate object associated with 3pm today, but the above is just one example. You could also use components:fromDate of NSCalendar to extract the NSDateComponents of now, then adjust the hour, minute and second and then create a new NSDate object using dateFromComponents (also a NSCalendar method).
Anyway, once you have a NSDate object that represents your target date/time, you can then use NSDateComponentsFormatter to display the time interval between two dates in a nice format.
NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init];
formatter.allowedUnits = NSCalendarUnitHour | NSCalendarUnitMinute;
formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleFull;
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
NSString *string = [formatter stringFromDate:now toDate:schoolOut];
You can adjust the unitsStyle and zeroFormattingBehavior to adjust the format of the string.

Why does NSDateComponents give me a different date?

Here's the code:
NSDate *dateLocalNow = [self getLocalDate:[NSDate date]];
NSLog(#"%#",dateLocalNow);
NSDateComponents *dateToCheckAgainst = [[NSCalendar currentCalendar] components:NSMinuteCalendarUnit | NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit|NSMinuteCalendarUnit fromDate: dateLocalNow];
NSLog(#"%#", dateToCheckAgainst);
getLocalDate gives me the date in the local timezone. If the NSLog for dateLocalNow outputs:
2014-10-29 01:01:55 +0000
Here's getLocalDate source:
-(NSDate *)getLocalDate:(NSDate *)date {
NSDate* sourceDate = date;
NSTimeZone* sourceTimeZone = [NSTimeZone timeZoneWithAbbreviation:#"GMT"];
NSTimeZone* destinationTimeZone = [NSTimeZone systemTimeZone];
NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:sourceDate];
NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:sourceDate];
NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset;
NSDate* destinationDate = [[NSDate alloc] initWithTimeInterval:interval sinceDate:sourceDate];
return destinationDate;
}
Why does NSLog for dateToCheck against give me the following?:
Calendar Year: 2014
Month: 10
Leap month: no
Day: 28
Minute: 1
An NSDate is independent of time zone. Internally, it stores the number of seconds that have elapsed since a reference point in time. That reference point has many human-readable labels. Here are a few human-readable labels for Cocoa's standard reference date:
1/1/01, 12:00:00 AM GMT
12/31/00, 6:00:00 PM CST
1/1/01, 4:00:00 AM GMT+4
These labels all represent the same instant in time, but that instant can be labeled in many different ways. There is only one way to represent that instant as an NSDate.
You've made a new NSDate that is the original NSDate, adjusted by some time zone offsets, but the iOS SDK doesn't know or care. It considers your new NSDate to be an instant in time based on an offset from its standard reference date. That new instant is different from your original instant (unless your system time zone happens to be GMT). You shouldn't expect it to produce the same result when converted to a human-readable string or an NSDateComponents, unless you set the time zone on your NSDateFormatter or NSCalendar just right—which you didn't do in your posted code.
So what do you do about this? You don't try to create an NSDate that is offset from another NSDate based on time zone offsets. Instead, you specify the time zone when you are converting an NSDate to a human-readable string, by setting the timeZone property of your NSDateFormatter. Or you set the timeZone property of your NSCalendar when you ask it for the components of the date. If you're constructing a date from components, you can also set the timeZone of the NSDateComponents before using the calendar to convert the components to an NSDate.
Thus:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSDate *now = [NSDate date];
NSDateFormatter *f = [[NSDateFormatter alloc] init];
f.dateStyle = NSDateFormatterShortStyle;
f.timeStyle = NSDateFormatterLongStyle;
f.timeZone = [NSTimeZone systemTimeZone];
NSLog(#"from formatter = %#", [f stringFromDate:now]);
NSCalendar *calendar = [NSCalendar currentCalendar];
calendar.timeZone = [NSTimeZone systemTimeZone];
NSDateComponents *components = [calendar components:NSMinuteCalendarUnit | NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:now];
NSLog(#"components = %#", components);
}
return 0;
}
Output:
2014-10-29 00:24:19.762 commandline[28543:303] from formatter = 10/29/14, 12:24:19 AM CDT
2014-10-29 00:24:19.763 commandline[28543:303] components = <NSDateComponents: 0x1001027a0>
Calendar Year: 2014
Month: 10
Leap month: no
Day: 29
Minute: 24
Can you step back and tell us what you're trying to do? This routine feels like you're trying to take [NSDate date] and "convert" it to local time. But that's not how dates work in Cocoa. NSDate doesn't have a concept of timezone. Only date formatters (and calendars) do.
So, when you get [NSDate date], it retrieves the current time. Yes, if you NSLog it, it may show it to you in GMT, but if you use a NSDateFormatter with the default timezone or grab components from a NSCalendar, it will always be in the local timezone (unless you override it as something else), with no adjustment needed.
+0000 means it is the GMT reperesentation of the local date.Use NSDateFormatter to log the display date and it will give the components displayed

Number of days between two NSDate dates with time zone

I calculate number of days between two dates:
NSDateComponents *datesDiff = [calendar components: NSDayCalendarUnit
fromDate: someDate
toDate: currentDate
options: 0];
But this method has one disadvantage - it doesn't take in account time zone.
So, for example, if I'm in +2GMT and local time is 1:00AM, current date is yesterday.
How to compare dates in specified time zone (without 'hacking')?
PS: Preventing answers with calculation of time difference, I need difference of actual days:
yesterday 23:00 vs. today 1:00 - 1 day
yesterday 1:00 vs. today 23:00 - 1 day
today 1:00 vs. today 23:00 - 0 days
(all this in current time zone)
I don't know if it meets your criteria of not being hacky, but a fairly simple way seems to be defining a GMT adjusted date something like this:
NSDate *newDate = [oldDate dateByAddingTimeInterval:(-[[NSTimeZone localTimeZone] secondsFromGMT])
See this question for more details.
Why don't you configure the dateformatter to default all dates to GMT time and then compare.
NSDate *now = [NSDate date];
NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:#"GMT"]; // Sets to GMT time.
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setCalendar:gregorianCalendar];
[formatter setTimeZone:timeZone];
[formatter setDateFormat:#"yyyyMMddHH"];
NSString *dateString = [formatter stringFromDate:now];
// do whatever with the dates
[gregorianCalendar release];
[formatter release];

NSDate Math? Now and Fire timer in the future

I have been racking my brains on this one looking for a quick solution.
I need to fire off a NSTime at a specific time based on the current time in a specific time zone.
For example, I get the current time in PST:
NSDate *now = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"HH:mm:ss";
NSCalendar * cal = [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar];
[cal setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"PST"]];
NSDateComponents * comps = [cal components:NSHourCalendarUnit fromDate:now];
I see that the time is 2 (14) getting [comps hour] and I need to fire a timer at 2:30PM (14:30) on that day. I need help converting it back and doing the math to say how long it is before the timer fires. I will be setting up several different timers based on the current time. While I am looking at the time in PST I know that the timers and setting of the time is done in GMT. That's another twist...
Thanks in advance...
I solved the problem myself.. But thanks for the tips...
if ([date earlierDate:newDate] == date) // it is before 7AM... fire at 7AM
and so on....

Resources