Number of days between two NSDate dates with time zone - timezone

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

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

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

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

IOS NSDateFormatter Problems

We are building an app which requires the storage of a date for an entry on the device, the app will be international so we hit two dilema's / challenges.
User Preferences
If the user chooses the 12 hour rahter than 24 hour format we are returned from [NSDate date] a date like this 2012-07-17 11:26:03 AM which for sorting in a SQLite database is less than optimal as we cannot store it as a date.
User Locale
Typically this is ok however here in blighty we have a wonderfult thing called british summertime. which adds one hour every October 25th - 30th in a cycle and removes one hour every March 25 - 31th in a cycle so if no adjustment is made for 8 months of the year the time is one hour behind.
What I need to achieve is a consistent date formatted like this: 2012-07-17 11:26:03 no matter where the device is located and also taking into account where GMT+1 comes into place.
Any help would be awesome.
EDIT*
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"yyyy-MM-dd'T'HH:mm";
NSTimeZone *gmt = [NSTimeZone timeZoneWithAbbreviation:#"GMT+01:00"];
[dateFormatter setTimeZone:gmt];
NSString *timeStamp = [dateFormatter stringFromDate:[NSDate date]];
NSDate *localDate = [dateFormatter dateFromString:timeStamp];
NSLocale* currentLocale = [NSLocale currentLocale];
NSString* countryCode = [currentLocale objectForKey:NSLocaleCountryCode];
NSLog(#"Country Code: %#", countryCode);
NSLog(#"Current Loacle: %#", currentLocale);
if(([countryCode isEqualToString: #"GB"])){
NSDate *mydate = [NSDate date];
NSDate *fiddlyFoo = [mydate dateByAddingTimeInterval:3600];
NSLog(#"Print GMT +1 %#",fiddlyFoo);
} else {
NSLog(#"Print GMT Local %#",localDate);
}
I'm doing something like this now. Note that NSDate "knows" about the current timezone and daylight savings time etc. So you just need to get the GMT version of the time in a sortable representation. I'd suggest RFC 3339 but you can use variations on it per your needs:
This code:
NSCalendar *gregorian = [[NSCalendar alloc]initWithCalendarIdentifier:NSGregorianCalendar];
// Create a local date for London, for testing purposes
NSDateComponents *comps = [NSDateComponents new];
[comps setTimeZone:[NSTimeZone timeZoneWithName:#"Europe/London"]];
[comps setDay:1];
[comps setMonth:7];
[comps setYear:2012];
[comps setHour:14];
NSDate *date = [gregorian dateFromComponents:comps];
// Just want to show this date is 2PM in London July 1st
NSDateFormatter *curFormat = [NSDateFormatter new];
[curFormat setDateStyle:NSDateFormatterFullStyle];
[curFormat setTimeStyle:NSDateFormatterFullStyle];
NSLog(#"Reference date is good no: %#", [curFormat stringFromDate:date]);
// So now we get the date as a rfc3339 string, referenced to GMT
NSString *timeString;
{
NSDateFormatter *rfc3339 = [NSDateFormatter new];
[rfc3339 setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss'Z'"];
rfc3339.timeZone = [NSTimeZone timeZoneWithName:#"UTC"];
timeString = [rfc3339 stringFromDate:date];
}
// referenced to UTC (sortable with any other time), can save in SQL DB etc
NSLog(#"Date as rfc3339 string: %#", timeString);
// Now lets convert it back into a BST time
NSDate *newDate;
{
NSDateFormatter *rfc3339 = [NSDateFormatter new];
[rfc3339 setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss'Z'"];
rfc3339.timeZone = [NSTimeZone timeZoneWithName:#"UTC"];
newDate = [rfc3339 dateFromString:timeString];
// we want to show this as a string 2012-07-17 11:26:03
NSDateFormatter *newFormatter = [NSDateFormatter new];
[newFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"]; // local time
[newFormatter setTimeZone:[NSTimeZone timeZoneWithName:#"Europe/London"]];
NSLog(#"Local string using 24 hour clock: %#", [newFormatter stringFromDate:newDate]);
}
Generates this output, which I believe is what you want:
TimeTester[58558:f803] Reference date is good no: Sunday, July 1, 2012 9:00:00 AM Eastern Daylight Time
TimeTester[58558:f803] Date as rfc3339 string: 2012-07-01T13:00:00Z
TimeTester[58558:f803] Local string using 24 hour clock: 2012-07-01 14:00:00

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