Why does NSDateComponents give me a different date? - ios

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

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

Localize the NSDate and Convert timezone of NSDate

In my iPhone / iPad app I am showing a UIDatePicker for time. It will display time in this format, 11:00 AM. When User clicks on the time row we expand the time row to display this datePicker row.
I have a time stamp in string "starts": "11:00", // time of day in ISO-8601 format
I need to show this on the picker wheel as selected time when it gets opened up. For this, first of all I get the date at 12 AM using https://stackoverflow.com/a/9040554/4082792. Then I convert the given time (11:00) to number of seconds and add it to the midnight time to get the current time. This time is in local timezone (as I specify the timezone while using NSDateFormatter). When I try to setDate to UIDatePicker with this date, It gives me incorrect time, even though the time is correct in the NSDate variable. For 11:00 AM, it gives me 6:40 while the local time is 4:30.
So, I have two questions :
1) Why is the time wrong on wheel.
2) How can I convert the NSDate from one timezone to another, I need to show it in the local time format.
Snippet :
NSString *strDate = #"11:00";
NSDate *date = [NSDate date];
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
NSUInteger preservedComponents = (NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit);
date = [calendar dateFromComponents:[calendar components:preservedComponents fromDate:date]];
///Start time
NSString *startTime = #"11:00";
NSArray *startTimeSeparatedByColon = [startTime componentsSeparatedByString:#":"]; /// From 22:10 to [22, 10];
NSInteger hourPartOfStart = startTimeSeparatedByColon[0] ? [startTimeSeparatedByColon[0] integerValue] : 0;
NSInteger minutePartOfStart = startTimeSeparatedByColon[1] ? [startTimeSeparatedByColon[1] integerValue] : 0;
NSTimeInterval totalTime = (hourPartOfStart*60*60+minutePartOfStart*60);
NSDate *finalDate = [NSDate dateWithTimeInterval:totalTime sinceDate:date];
NSDate *dt = [NSDate dateWithTimeInterval:[NSTimeZone localTimeZone].secondsFromGMT sinceDate:finalDate];
self.datePicker.date = dt;
By default, iOS converts date into the device's time zone.
But if you want to convert date into another time zone, here is the code for that:
NSTimeZone *currentDateTimeZone = [NSTimeZone timeZoneWithAbbreviation:#"EST"];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeZone:currentDateTimeZone];
You can get the date in "EST" time zone from this dateFormatter object.
Convert "EDT" TimeZone
NSString *format = #"EEEE dd-MMMM-yyyy HH:mm:ss z";
NSDateFormatter *date_EDTDateFormate = [[NSDateFormatter alloc] init];
date_EDTDateFormate setDateFormat:format];
date_EDTDateFormate setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"EDT"]];
NSString *stringEDT = [dateFormatter_EDT stringFromDate:date_System];

NSDate returning different value on conversion from string - Not GMT related

I hope this isn't GMT related or I will feel rather stupid.
2 quick related questions. Why is this converting to a different date? Is losing a day. I looked at time zones as stated in other answers but it is always the GMT timezone 0000 which is what I expected. I presume the error is in my setDateFormat but I can't see how to fix it.
NSString *stringFromDate = self.dateLabel.currentTitle;
NSLog(#"StringFromDateWeight! %#", stringFromDate);
NSDateFormatter *df = [[NSDateFormatter alloc] init];
//Convert Back to NSDate
[df setDateFormat:#"ddMMyyyy"];
NSDate *inputedDate = [df dateFromString: stringFromDate];
NSLog(#"StringFromDateWeight2! %#", inputedDate);
NSLog(#"StringFromDateWeight! %#", stringFromDate); is
17072013
NSLog(#"StringFromDateWeight2! %#", inputedDate); is
2013-07-16 23:00:00 +0000
I am also using the code below to compare 2 dates and am I right in that it returns in seconds? How would I change it to return in days?
int intervall = (int) [theDate timeIntervalSinceDate: now];
If you don't explicitly set a timezone NSDateFormatter will use your local timezone. You don't set one, so your formatter will create a NSDate that is at "midnight July 17" in your timezone. The description method of NSDate will return a date that is formatted in UTC timezone. Since you get "July 16 23:00:00" I guess your timezone is UTC+1.
You have two options. Calculate in UTC by setting the timezone explicitly.
df.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
Or, usually more useful, don't look at the description of NSDate and use [inputedDate descriptionWithLocale:[NSLocale currentLocale]] for debugging, which will print the date formatted for your timezone.
If you want to display the date to the user use another NSDateFormatter (preferably with dateStyle and timeStyle and not dateFormat, because hardcoded dateFormats are evil)
It's just the display that is different, the underlying NSDate object is still the same.
Regarding your second question:
In many timezones there are 2 days each year that don't have 24 hours, so you can't calculate anything with the seconds you get from timeIntervalSinceDate:.
You have to use NSDateComponents and NSCalendar. Fortunately there is already a method that does exactly what you want. components:fromDate:toDate:options:
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [calendar components:NSDayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit|NSSecondCalendarUnit fromDate:inputedDate toDate:[NSDate date] options:0];
NSLog(#"Date was %d days (and %d hours, %d minutes and %d seconds) ago", components.day, components.hour, components.minute, components.second);
If you only need the number of days you can remove all components except NSDayCalendarUnit

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

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

Resources