Проблема календаря NSC с эпохой до нашей эры

Привет,

Недавно столкнулся с большой проблемой (как мне кажется) с классом NSCalendar.

В моей задаче мне нужно работать с большими временными периодами, начиная с 4000 г. до н.э. до 2000 г. н.э. (григорианский календарь). Кое-где меня заставили увеличить какой-то NSDate на 100-летний интервал. При увеличении лет на временной шкале AD (0 -> ...) все работало нормально, но когда я попробовал то же самое с BC, я был немного сбит с толку.

Проблема в том, что когда вы пытаетесь добавить 100 лет к 3000 г. до н.э. [отредактировано], вы получите 3100 г. до н.э. [отредактировано], несмотря ни на что ... Лично мне это показалось странным и нелогичным. Правильный результат должен быть 2900 г. до н.э.

Вот пример кода, чтобы вы могли увидеть это «неправильное» поведение:

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

// initing
NSDateComponents *comps = [[[NSDateComponents alloc] init] autorelease];
[comps setYear:-1000];
NSDate *date = [gregorian dateFromComponents:comps];

// math
NSDateComponents *deltaComps = [[[NSDateComponents alloc] init] autorelease];
[deltaComps setYear:100];

date = [gregorian dateByAddingComponents:deltaComps toDate:date options:0];

// output
NSString *dateFormat = @"yyyy GG";

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:dateFormat];
NSLog(@"%@", [formatter stringFromDate:date]);

Что вы можете сказать об этом поведении? Это так должно работать или это ошибка? Я в замешательстве: S.

Кстати: метод [NSCalendar components: fromDate: toDate: options:] не позволяет нам вычислить разницу между годами в эпоху до нашей эры ... дополнительное «ПОЧЕМУ?» в этом ящике Пандоры.

P.S .: Я копался в официальной документации и других ресурсах, но ничего не нашел относительно этой проблемы (или, может быть, это должно работать так, а я идиот?).


person GregoryM    schedule 15.09.2010    source источник
comment
3000AD + 100AD = 3100AD. Это основная арифметика. Судя по вашему описанию, математика верна. Я подозреваю, что в своем коде вы хотите добавить -1000 к -100, чтобы получить 1100BC, но поскольку вы не показали нам результат своего кода, мы не знаем, как он себя ведет.   -  person Jonathan Grynspan    schedule 15.09.2010
comment
«… Когда вы пытаетесь добавить 100 лет к 3000AD, вы получаете 3100AD, несмотря ни на что ... Лично мне это показалось странным и нелогичным. Правильный результат должен быть 2900 г. до н.э. »Вы хотели сказать« до нашей эры »в этом абзаце? В противном случае это не имеет смысла: 3000 + 100 г. н.э. = 3100 г. н.э .; это правильный результат.   -  person Peter Hosey    schedule 15.09.2010
comment
Джонатан: извините, я ошибся, это не AD, это BC.   -  person GregoryM    schedule 16.09.2010
comment
Питер: да, ты прав, просто поторопился немного.   -  person GregoryM    schedule 16.09.2010


Ответы (3)


Я нашел простой обходной путь для этой ошибки. Вот:

@interface NSCalendar (EraFixes)

- (NSDate *)dateByAddingComponentsRegardingEra:(NSDateComponents *)comps toDate:(NSDate *)date options:(NSUInteger)opts;

@end

@implementation NSCalendar (EraFixes)

- (NSDate *)dateByAddingComponentsRegardingEra:(NSDateComponents *)comps toDate:(NSDate *)date options:(NSUInteger)opts
{
    NSDateComponents *toDateComps = [self components:NSEraCalendarUnit fromDate:date];
    NSDateComponents *compsCopy = [[comps copy] autorelease];

    if ([toDateComps era] == 0) //B.C. era
    {
        if ([comps year] != NSUndefinedDateComponent) [compsCopy setYear:-[comps year]];
    }

    return [self dateByAddingComponents:compsCopy toDate:date options:opts];
}

@end

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

РЕДАКТИРОВАТЬ: удалено ошибочно добавленное автозапускание, спасибо, Джон.

person GregoryM    schedule 16.09.2010
comment
Я рад, что вы это обнаружили, я потратил день на отслеживание проблем смещения даты BC, и в конце концов моя проблема была вызвана именно этой ошибкой / аномалией. Кстати, результат использования components: fromDate: не должен запускаться автоматически. - person John Stephen; 07.05.2012

Это ошибка или особенность. В документе Apple никогда не говорится, что они имеют в виду под добавлением компонентов к календарной дате. Для них совершенно бесплатно определять «добавление компонента» к дате до н.э. просто как добавление к компоненту года.

Да, я согласен с вами, что это нелогично и я думаю, что это ошибка.

Вам нужно преобразовать свой NSDate в любой

  • второй из эпохи UNIX (1.1.1970) с использованием -timeIntervalSince1970
  • второй из эпохи OS X (1.1.2001) с использованием -timeIntervalSinceReferenceDate

Затем вы можете выполнить расчет и преобразовать его обратно в NSDate. Я думаю, что постоянно работать с григорианским календарем - плохая идея ... Лучше было бы преобразовать его в григорианский календарь непосредственно перед тем, как вы показываете это в графическом интерфейсе.

person Yuji    schedule 15.09.2010
comment
Ваша идея на самом деле верна, но есть проблема. Когда вы пытаетесь добавить 100 лет к определенному NSDate, вы используете [NSCalendar dateByAddingComponents: ...], почему? Потому что вы не знаете, сколько секунд прошло именно в этих 100 лет, верно? И, как я указывал ранее, метод [NSCalendar components: fromDate: toDate: options:] вообще не работает в эпоху до нашей эры, так что нет никакого шанса получить точное количество секунд за 100 лет, не так ли? - person GregoryM; 16.09.2010
comment
Если вам действительно нужна эта конкретная операция, я думаю, вам нужно написать их самостоятельно. Честно говоря, я не понимаю, что вы имеете в виду, говоря «прибавить 100 лет». Сколько 100 лет плюс 400 лет н.э. 29 февраля? В любом случае, чтобы иметь дело с високосными годами, вам нужно создать какое-то специальное правило. - person Yuji; 16.09.2010
comment
Я думаю, что добавление 100 лет к какой-то дате - не такая уж «специфическая» операция :) и что я должен написать свои собственные методы для этого. Мне просто было интересно, насколько непроверены классы Apple. Модульные тесты Afaik были изобретены слишком давно ... - person GregoryM; 16.09.2010

Представьте, что у вас есть Дата с 1-м моментом нашей эры - AD 0001-01-01 00:00:00. Что было раньше? BC 0001-01-01 00:00:01. Если бы разработчики Какао использовали базовую арифметику для этой задачи, вы бы получили AD 0000-12-31 23:59:59. Это разумно для григорианского календаря? Думаю, нет. Поэтому мне кажется, что наиболее удобным способом реализации календаря было использование флага Era и изменение «направления времени» при работе с эрой BC, чтобы в каждом случае получать даты, удобочитаемые для человека.

Кстати: [NSCalendar dateByAddingComponents:toDate:options:] действительно ведет себя странно и не может считать временной интервал между датами BC, я тоже проверил. Итак, для дат BC вы можете использовать обходной путь, например переводя даты в AD и затем находя diff.

person parametr    schedule 15.09.2010
comment
Собственно, второй до него - BC 0001-12-31 23:59:59. Только цифры года идут в обратном направлении. - person Boaz Stuller; 15.09.2010
comment
Основная арифметика отлично работает в NSCalendar (кроме случая, который я описал). Если вычесть 100 лет из 50 года нашей эры, получится 51 год до нашей эры. Но когда вы имеете дело с датами до нашей эры, это кажется «перевернутым». - person GregoryM; 16.09.2010