Проблема часового пояса с летним временем (таинственный пропавший два часа) - PullRequest
2 голосов
/ 24 мая 2011

У меня есть кусок кода, который работал в прошлом (по крайней мере, я думаю, что он работал, потому что в противном случае проблема была бы распознана ранее). Через несколько недель, может быть после DST, он начал давать неверные результаты.

Этот фрагмент должен генерировать объект NSDate, ссылающийся на первый день данной недели дат в 00:00.

NSCalendar *gregorianCalender = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
NSDate *firstDayDate;

unsigned yearAndWeek = NSTimeZoneCalendarUnit | NSYearCalendarUnit | NSWeekCalendarUnit;

// retrieve the components from the current date
NSDateComponents *compsCurrentDate = [gregorianCalender components:yearAndWeek fromDate:date];

[compsCurrentDate setWeekday:2]; // Monday
[compsCurrentDate setHour:0];
[compsCurrentDate setMinute:0];
[compsCurrentDate setSecond:0];

// make a date from the modfied components
firstDayDate = [gregorianCalender dateFromComponents:compsCurrentDate]; 

NSLog(@"MONDAY: %@", firstDayDate);

Сегодня 24 мая, поэтому последняя строка должна напечатать что-то вроде

MONDAY: 2011-05-23 00:00:00 +0000

но это действительно печатает следующее

MONDAY: 2011-05-22 22:00:00 +0000

что явно не так. 22 мая было последним воскресеньем. Теперь, чтобы сделать странные вещи, результат снова правильный, когда я изменяю setHour:0 на

[compsCurrentDate setHour:2];

Я предполагаю, что с часовыми поясами что-то не так. Но потратив полдня на документацию Apple, google и stackoverflow, я не мог понять, в чем проблема. Возможно, я использую неправильные ключевые слова, или я единственный, с этой проблемой.

Каждый кусок информации высоко ценится! Пожалуйста, дайте мне знать, что вы думаете! Спасибо!

1 Ответ

3 голосов
/ 26 мая 2011

Поскольку никто не смог ответить, я продолжил свое исследование и обнаружил простую, но фатальную деталь в документации NSData: метод description, который вызывается при попытке напечатать NSDate как NSString default использует «default» NSLocale для определения формата и часового пояса . Так что все в моем коде было правильным, реальная проблема, которую я пытался исправить, была где-то еще, и я уже сделал это в понедельник .. Я должен RTFM более интенсивно;)

Следующая строка кода выводит правильное время для моего часового пояса:

NSLog(@"MONDAY: %@", [firstDayDate descriptionWithLocale:[NSLocale systemLocale]]);

Тем не менее, во время моего исследования я обнаружил несколько интересных фрагментов, делающих в основном то же самое (хотя результат был «неправильным», поэтому я не просто заменил свой). Принимая все это во внимание, моя попытка работает, но немного неровно. Самое простое и надежное, что я нашел, - это сама документация Apple, которую также можно найти на различных веб-сайтах:

/* VERSION FROM APPLE DOKU */
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

// Get the weekday component of the current date
NSDateComponents *weekdayComponents = [gregorian components:NSWeekdayCalendarUnit fromDate:date];

/*
 Create a date components to represent the number of days to subtract from the current date.
 The weekday value for Monday in the Gregorian calendar is 2, so subtract 2 from the number of days to subtract from the date in question.  (If today's Sunday, subtract 0 days.)
 */
NSDateComponents *componentsToSubtract = [[NSDateComponents alloc] init];
[componentsToSubtract setDay: 0 - ([weekdayComponents weekday] - 2)];

NSDate *beginningOfWeek = [gregorian dateByAddingComponents:componentsToSubtract toDate:date options:0];

/*
 Optional step:
 beginningOfWeek now has the same hour, minute, and second as the original date (today).
 To normalize to midnight, extract the year, month, and day components and create a new date from those components.
 */
NSDateComponents *components =
[gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit)
             fromDate: beginningOfWeek];
beginningOfWeek = [gregorian dateFromComponents:components];

NSLog(@"MONDAY: %@", [beginningOfWeek descriptionWithLocale:[NSLocale systemLocale]]);

Наконец, для завершения: фрагмент, как вычислить последний день текущей недели.

// reuse the "first day" to calculate the last day
NSDate *endOfLastWeek = [whateveryourobject andmethodiscalled]; 

NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

/*
 endOfLastWeek now is monday in the current week so create date components to add one week to the current date
 */
NSDateComponents *componentsToAdd = [[NSDateComponents alloc] init];
[componentsToAdd setWeek:1];

NSDate *endOfWeek = [gregorian dateByAddingComponents:componentsToAdd toDate:endOfLastWeek options:0];

NSLog(@"SUNDAY: %@", [endOfWeek descriptionWithLocale:[NSLocale systemLocale]]);
...