This seems to be a recurring problem for many (me included) and after having pulled my hair way too long I have finally solved the issue.
The assumptions this code relies on is that:
- you are interested in dates, not in time
- you want your date calculations to be oblivious to any Daylight Saving Time
- you want an easy enough string representation of the date such that you can persist it.
In order to achive 1. I use unique NSCalendar instance that is initialized to Gregorian, GMT
This also serves 2.
For 3. the NSDateFormatter is initialized with both the unique calendar and its time zone.
Setting the timezone of the formatter to that of the calendar otherwise used for calculation was the key to all the issues I had!
This should always be true (assume the code below is part of a DateModel
class):
NSDate * today = [DateModel normalize: [NSDate date]] ;
NSString*todayS= [DateModel dateToString: today] ;
BOOL alwaysTrue=[[DateModel normalize: [NSDate date]] isEqual: [DateModel stringToDate: todayS]] ;
Here's the code:
+ (NSDate *) normalize: (NSDate *) date {
NSDateComponents * comps = [[DateModel calendar] components:NSYearCalendarUnit
| NSMonthCalendarUnit
| NSDayCalendarUnit
| NSHourCalendarUnit
| NSMinuteCalendarUnit
| NSSecondCalendarUnit
fromDate:date] ;
[comps setHour:0] ;
[comps setMinute:0] ;
[comps setSecond:0] ;
NSDate * dateOnly = [[DateModel calendar] dateFromComponents:comps] ;
return dateOnly ;
}
+ (NSCalendar *) calendar {
static NSCalendar * calendar = nil ;
if (calendar == nil) {
calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] ;
calendar.timeZone = [NSTimeZone timeZoneWithAbbreviation: @"GMT"] ;
}
return calendar ;
}
+ (NSDateFormatter *) dateFormatter {
static NSDateFormatter * formatter = nil ;
if (formatter == nil) {
formatter = [[NSDateFormatter alloc] init] ;
[formatter setCalendar:[self calendar]] ; // <- This is
[formatter setTimeZone:[self calendar].timeZone] ; // <- critical
[formatter setDateFormat:@"yyyyMMdd"] ;
}
return formatter ;
}
+ (NSDate *) dateFromString: (NSString *) stringDate {
return [DateModel normalize: [[DateModel dateFormatter] dateFromString:stringDate]] ;
}
+ (NSString *) stringFromDate: (NSDate *) date {
return [[DateModel dateFormatter] stringFromDate:[DateModel normalize: date]] ;
}
Hope this helps someone!