Pergunta

Greetings,

Recently I faced a big problem (as it seems to me) with NSCalendar class.

In my task I need to work with a large time periods starting from 4000BC to 2000AD (Gregorian calendar). In some place I was forced to increment some NSDate by 100 year interval. When incrementing the years in AD timeline (0->...) everything worked fine, but when I tried the same thing with BC i was a little confused.

The problem is, when you try to add 100 years to 3000BC [edited] year, you get 3100BC [edited] no matter what... Personally i found it strange and illogical. The right result should be 2900BC.

Here is the code sample for you to see this "not right" behavior:

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]);

What can you say about this behavior? Is this how it should work or is this a bug? I'm confused :S.

BTW.: the method [NSCalendar components:fromDate:toDate:options:] doesn't allow us to calculate the difference between years in BC era... additional 'WHY?' in this Pandora's box.

P.S.: I was digging through official documentation and other resources but found nothing regarding this problem (or maybe it's intended to work so and I'm an idiot?).

Foi útil?

Solução

I found a simple workaround for this bug. Here it is:

@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

If you wonder why I invert only years, the answer is simple, every other component except years is incrementing and decrementing in the right way (I haven't tested them all, but months and days seem to work fine).

EDIT: removed mistakenly added autorelease, thanks John.

Outras dicas

It's a bug and or a feature. The Apple doc never says what they mean by adding components to the calendrical date. It's perfectly free for them to define "adding a component" to the BCE date as just the addition to the year component.

Yes I agree with you that it's counterintuitive and I think it's a bug.

You need to convert your NSDate to either

  • the second from the UNIX epoch (1.1.1970) using -timeIntervalSince1970
  • the second from the OS X epoch (1.1.2001) using -timeIntervalSinceReferenceDate

You can then perform the calculation, and convert it back to an NSDate. I think it's a bad idea to work in the Gregorian calendar all the time... It would be better to convert to the Gregorian calendar just before you show it on the GUI.

Imagine that you have Date with 1st moment of our era - AD 0001-01-01 00:00:00. What was the moment before? BC 0001-01-01 00:00:01. If Cocoa developers used basic arithmetic's for this task, you would get AD 0000-12-31 23:59:59. Is that reasonable for Gregorian calendar? I guess not. So, it seems to me that the most convenient way to implement calendar was to use Era flag, and change "time direction" when dealing with BC era to get human-readable dates in every case.

BTW.: [NSCalendar dateByAddingComponents:toDate:options:] really behaves strange and is unable to count time interval between BC dates, I checked too. So, for BC dates you may use workaround, e.g. by translating dates to AD and then finding diff.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top