Проблема с часовым поясом из-за перехода на летнее время (таинственное отсутствие двух часов)

#objective-c #cocoa-touch #nsdate

#цель-c #прикосновение какао #nsdate #objective-c #cocoa-touch

Вопрос:

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

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

 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:

Поскольку никто не смог ответить, я продолжил свое исследование и обнаружил простую, но фатальную деталь в документации NSData : description метод, который вызывается при попытке напечатать NSDate как NSString по умолчанию, использует «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]]);