Reputation: 41
I'm having a hard time understanding an exception to how dateByAddingComponents handles Daylight Savings. I've read through the Apple Date and Time Programming Guide and expected dateByAddingComponents to take into account DST changes. However, on the date of the DST change, its not working for me.
Here's the code:
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[gregorian setTimeZone:[NSTimeZone localTimeZone]];
NSDateComponents *midnight = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit) fromDate:self.currentDate];
midnight.hour = 0;
midnight.minute = 0;
midnight.second = 0;
NSDate *startDate = [gregorian dateFromComponents:midnight];
NSDateComponents *offSetComponents = [[NSDateComponents alloc] init];
[offSetComponents setDay:1];
NSDate *endDate = [gregorian dateByAddingComponents:offSetComponents toDate:startDate options:0];
//Calculate start time from config (hours/min from seconds)
int startTimeInMinutes = self.club.clubConfiguration.startTime.integerValue;
int startTimeHours = startTimeInMinutes / 60;
int startTimeMins = startTimeInMinutes % 60;
NSLog(@"---- startTimeHours %i", startTimeHours);
NSLog(@"---- startTImeMins %i", startTimeMins);
NSDateComponents *offSetComponents2 = [[NSDateComponents alloc] init];
[offSetComponents2 setHour:startTimeHours];
[offSetComponents2 setMinute:startTimeMins];
NSDate *firstTeeTime = [gregorian dateByAddingComponents:offSetComponents2 toDate:startDate options:0];
Explanation: I'm getting a startTimeInMinutes from the server that I use to calculate the firstTeeTime. For example, I'm expecting to add 6 hours to the startDate (12am in my use case) and get 6am (localTimeZone).
Using dateByAddingComponents works both before and after the DST change however, on the day of DST change Sunday Nov 3, I'm getting 5am.
Theory: Since there are actually 2 2am's on Sunday Nov 3rd, I may have to account for that? If thats the case, I'd have to write some logic to account for the actual day of DST change and add an offset if appropriate using daylightSavingTimeOffsetForDate.
What am I missing???
EDIT: Ok, I decided to work around the issue by determining if today was the DST change and add/remove an hour offset. Feels kinda like I'm missing something here about NSDate however, it works. Hope this helps someone else out there scratching their heads all morning.
Work Around Code:
////// Work around for DST
NSTimeZone *currentZone = [gregorian timeZone];
NSDate *dstTransitionDate = [currentZone nextDaylightSavingTimeTransitionAfterDate:startDate];
NSTimeInterval dstOffset = [currentZone daylightSavingTimeOffsetForDate:endDate];
NSDateComponents *startDateComponents = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:startDate];
NSDateComponents *dstTransitionDateComponents = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:dstTransitionDate];
int offset = 0;
if ( [startDateComponents year] == [dstTransitionDateComponents year] &&
[startDateComponents month] == [dstTransitionDateComponents month] &&
[startDateComponents day] == [dstTransitionDateComponents day])
{
if (dstOffset > 0){
offset = -1;
} else {
offset = 1;
}
}
//////
Upvotes: 4
Views: 578
Reputation: 22559
I totally sympathize with you, as I have fallen into the same trap. First, I scratched my head at the results and docs, then attempted a fool's errand by attempting to roll out a custom "adding date components" logic.
Then, came across your answer, which worked beautifully, and my tests finally pass:
// assert!
XCTAssertEqual(dateFormatter.stringFromDate(dstSwitch.date), "11/1/15, 12:00 AM")
// Offseting the date should just work
do {
let dstOffset = dstSwitch + 3.hours
XCTAssertEqual(dateFormatter.stringFromDate(dstOffset.date), "11/1/15, 3:00 AM")
}
... But hang on! Words of wisdom from @RobNapier set me on the right path ... We are adding 6 hours, which means a person at the event will only spend six hours of their life. If the start date is midnight, they will adjust their watches at 2 AM back to 1 AM, and therefor spend the lost hour in oblivion according to the calendar.
So... If the event truly starts at midnight, and ends at 6 AM, heed Rob's words and don't use the duration. Use the exact date components. Because that duration is actually 7 hours.
For fun, I came to that realization when my tests bothered me. Why should I only add the offset on the same day? Why does changing days "just works"? ... Liiiight bulb.
And, btw, my test are for a library I am rolling out that should end the short comings of all date calculation and end the confusion .. With really concise syntax for swift. Will be available as Datez, part of Kitz.
Upvotes: 1