Выполнение календарных вычислений

NSDate обеспечивает абсолютную шкалу и эпоха для дат и времени, которые могут тогда быть представлены в определенном календаре для calendrical вычислений или пользовательского дисплея. Для выполнения календарных вычислений обычно необходимо получать элементы компонента даты, такие как год, месяц и день. Необходимо использовать предоставленные методы для контакта с calendrical вычислениями, потому что они принимают во внимание угловые случаи как запуск летнего времени или окончание и високосные годы.

Добавление компонентов к дате

Вы используете dateByAddingComponents:toDate:options: метод для добавления компонентов даты (таких как часы или месяцы) к существующей дате. Можно обеспечить столько компонентов, сколько Вы желаете. Перечисление 9 показывает, как вычислить дату полтора часа в будущем.

Перечисление 9  полтора часа с этого времени

NSDate *today = [[NSDate alloc] init];
NSCalendar *gregorian = [[NSCalendar alloc]
          initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
[offsetComponents setHour:1];
[offsetComponents setMinute:30];
// Calculate when, according to Tom Lehrer, World War III will end
NSDate *endOfWorldWar3 = [gregorian dateByAddingComponents:offsetComponents
          toDate:today options:0];

Компоненты для добавления могут быть отрицательными. Перечисление 10 показывает, как можно получить воскресенье на текущей неделе (использующий Григорианский календарь).

Перечисление 10  , Получающее воскресенье на текущей неделе

NSDate *today = [[NSDate alloc] init];
NSCalendar *gregorian = [[NSCalendar alloc]
          initWithCalendarIdentifier:NSGregorianCalendar];
 
// Get the weekday component of the current date
NSDateComponents *weekdayComponents = [gregorian components:NSWeekdayCalendarUnit
          fromDate:today];
 
/*
Create a date components to represent the number of days to subtract from the current date.
The weekday value for Sunday in the Gregorian calendar is 1, so subtract 1 from the number of days to subtract from the date in question.  (If today is Sunday, subtract 0 days.)
*/
NSDateComponents *componentsToSubtract = [[NSDateComponents alloc] init];
[componentsToSubtract setDay: 0 - ([weekdayComponents weekday] - 1)];
 
NSDate *beginningOfWeek = [gregorian dateByAddingComponents:componentsToSubtract
          toDate:today 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];

Воскресенье не является началом недели во всех локалях. Перечисление 11 иллюстрирует, как можно вычислить первый момент недели (как определено локалью календаря):

Перечисление 11  , Получающее начало недели

NSDate *today = [[NSDate alloc] init];
NSDate *beginningOfWeek = nil;
BOOL ok = [gregorian rangeOfUnit:NSWeekCalendarUnit startDate:&beginningOfWeek
                     interval:NULL forDate: today];

Определение временных различий

Существует несколько способов вычислить количество времени между датами. В зависимости от контекста, в котором сделано вычисление, пользователь, вероятно, ожидает различное поведение. Какой бы ни вычисление, которое Вы используете, пользователю должно быть ясно, как выполняется вычисление. Так как Какао реализует время согласно стандарту NTP, эти методы игнорируют секунды прыжка в вычислении. Вы используете components:fromDate:toDate:options: определить временное различие между двумя датами в модулях кроме секунд (который можно вычислить с NSDate метод timeIntervalSinceDate:). Перечисление 12 показывает, как получить число месяцев и дней между двумя датами с помощью Григорианского календаря.

Перечисление 12  , Получающее различие между двумя датами

NSDate *startDate = ...;
NSDate *endDate = ...;
 
NSCalendar *gregorian = [[NSCalendar alloc]
                 initWithCalendarIdentifier:NSGregorianCalendar];
 
NSUInteger unitFlags = NSMonthCalendarUnit | NSDayCalendarUnit;
 
NSDateComponents *components = [gregorian components:unitFlags
                                          fromDate:startDate
                                          toDate:endDate options:0];
NSInteger months = [components month];
NSInteger days = [components day];

Этот метод обрабатывает переполнение, как можно ожидать. Если fromDate: и toDate: параметры являются годом и на расстоянии в 3 дня, и Вы спрашиваете в течение только дней между, он возвращается NSDateComponents объект со значением 368 (или 369 в високосный год) для дневного компонента. Однако этот метод усекает результаты вычисления к самому маленькому предоставленному модулю. Например, если fromDate: параметр соответствует Яну 14, 2010 в 23:30 и toDate: параметр соответствует Яну 15, 2010 в 8:00, между этими двумя датами существует только 8,5 часов. Если Вы просите число дней, Вы добираетесь 0, потому что 8,5 часов составляют меньше чем 1 день. Могут быть ситуации, где это должно быть 1 днем. Необходимо решить, какое поведение пользователи ожидают в особом случае. Если у Вас действительно должно быть вычисление, возвращающее число дней, вычисленных числом полуночи между этими двумя датами, можно использовать категорию для NSCalendar подобный тому в Перечислении 13.

  Дни перечисления 13 между двумя датами, как число полуночи между

@implementation NSCalendar (MySpecialCalculations)
-(NSInteger)daysWithinEraFromDate:(NSDate *) startDate toDate:(NSDate *) endDate
{
     NSInteger startDay=[self ordinalityOfUnit:NSDayCalendarUnit
          inUnit: NSEraCalendarUnit forDate:startDate];
     NSInteger endDay=[self ordinalityOfUnit:NSDayCalendarUnit
          inUnit: NSEraCalendarUnit forDate:endDate];
     return endDay-startDay;
}
@end

Этот подход работает на другие единицы времени по календарю путем указания различного NSCalendarUnit значение для ordinalityOfUnit: параметр. Например, можно вычислить число лет на основе числа раз, между которым присутствует 1:00, 12:00 Яна.

Не используйте этот метод для сравнения вторых различий, потому что это переполняется NSInteger на 32-разрядных платформах. Этот метод только допустим, если Вы остаетесь в течение той же эры (в Григорианском календаре, это означает, что обеими датами должен быть AD, или оба должны быть BC). Если действительно необходимо сравнить даты через границу эры, можно использовать что-то подобное категории в Перечислении 14.

  Дни перечисления 14 между двумя датами в различные эры

@implementation NSCalendar (MyOtherMethod)
-(NSInteger) daysFromDate:(NSDate *) startDate toDate:(NSDate *) endDate
{
     NSCalendarUnit units=NSEraCalendarUnit | NSYearCalendarUnit |
          NSMonthCalendarUnit | NSDayCalendarUnit;
     NSDateComponents *comp1=[self components:units fromDate:startDate];
     NSDateComponents *comp2=[self components:units fromDate endDate];
     [comp1 setHour:12];
     [comp2 setHour:12];
     NSDate *date1=[self dateFromComponents: comp1];
     NSDate *date2=[self dateFromComponents: comp2];
     return [[self components:NSDayCalendarUnit fromDate:date1 toDate:date2 options:0] day];
}
@end

Этот метод создает компоненты из данных дат, и затем нормализует время и сравнивает эти две даты. Это вычисление является более дорогим, чем сравнение дат в течение эры. Если Вы не должны пересекать граничное использование эры метод, показанный в Перечислении 13 вместо этого.

Проверка, когда падает дата

Если необходимо определить, находится ли дата в пределах текущей недели (или любой модуль в этом отношении), можно использовать NSCalendar метод rangeOfUnit:startDate:interval:forDate:. Перечисление 15 показывает метод, определяющий, находится ли данная дата в пределах на этой неделе. Неделя в этом случае определяется как период между воскресеньем в полночь к следующей субботе как раз перед полуночью (в Григорианском календаре).

Перечисление 15  , Определяющее, является ли дата на этой неделе

-(BOOL)isDateThisWeek:(NSDate *)date {
     NSDate *start;
     NSTimeInterval extends;
     NSCalendar *cal=[NSCalendar autoupdatingCurrentCalendar];
     NSDate *today=[NSDate date];
     BOOL success= [cal rangeOfUnit:NSWeekCalendarUnit startDate:&start
          interval: &extends forDate:today];
     if(!success)return NO;
     NSTimeInterval dateInSecs = [date timeIntervalSinceReferenceDate];
     NSTimeInterval dayStartInSecs= [start timeIntervalSinceReferenceDate];
     if(dateInSecs > dayStartInSecs && dateInSecs < (dayStartInSecs+extends)){
          return YES;
     }
     else {
          return NO;
     }
}

Этот код использование NSTimeInterval значения для даты для тестирования и запуск недели и использования те, чтобы определить, является ли дата на этой неделе.

Основанные на неделе календари

Основанный на неделе календарь определяется неделями года. Вместо года, месяца и дня даты, основанный на неделе календарь определяется недельным годом, недельным числом, и рабочий день.

Когда первая неделя календаря накладывается на прошлой неделе календаря предыдущего года, Однако это может быть сложно. В этом случае существует два важных свойства календаря:

  1. Каков первый день недели?

  2. Сколько дней делает неделю около начала года, должны иметь в течение обычного календарного года для него, чтобы считаться первой неделей в основанный на неделе календарный год?

Первый день основанного на неделе календаря года находится в первый день недели. Если на той неделе удовлетворяет определенный ответ для второй точки выше, первая неделя предпочтена, чтобы быть неделей, содержащей Яна 1.

Например, предположите, что первый день недели определяется как в понедельник в основанной на неделе календарной интерпретации Григорианского календаря. Считайте 2009/2010 переход показанным в Таблице 1 и Таблице 2:

Таблица 1 декабря 2009  календарь

В воскресенье

В понедельник

Во вторник

В среду

В четверг

В пятницу

В субботу

20

21

22

23

24

25

26

27

28

29

30

31

Таблица 2 января 2010  календарь

В воскресенье

В понедельник

Во вторник

В среду

В четверг

В пятницу

В субботу

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Так как первый день недели является понедельником, 2010, который основанный на неделе календарный год может начать или 28 декабря или 4 января. Т.е. 30 декабря 2009 (обычный) могло быть 30 декабря 2010 (основано на неделе).

Для выбора между этими двумя возможностями существует второй критерий. В 2010 неделя 28 декабря - Ян 3 имеет 3 дня. В 2010 неделя Ян 10 с 4 Янами имеет 7 дней.

Если минимальное число дней на первой неделе определяется как 1 или 2 или 3, неделя от 28 декабря удовлетворяет первые недельные критерии и была бы неделей 1 из основанного на неделе календарного года 2010. Иначе, неделя Яна 4 является первой неделей.

Как другой пример, предположите, что Вы хотели определить основанный на неделе календарь, таким образом, что первая неделя календарного года начинается с первого возникновения определенного рабочего дня.

В Таблице 2 понедельник 4 января первый понедельник обычного года, таким образом, основанный на неделе календарь начинается в тот день. Что Вы запрашиваете, тогда то, что первая неделя Вашего основанного на неделе календаря полностью в течение нового обычного года или что минимальное число дней на первой неделе равняется 7.

NSYearForWeekOfYearCalendarUnit число года основанной на неделе календарной интерпретации календаря, с которым Вы работаете, где два свойства основанного на неделе календаря, обсужденного в вышеупомянутом, соответствуют этим двум свойствам NSCalendar: firstWeekday и minimumDaysInFirstWeek.