Search code examples
xcodemacoscocoansdatenscalendar

NSCalendar - Custom Calendar


On Mac OS X, If you go to system preferences > Date & Time > Open Language and Texts > On the Top click on Formats > and then with the drop down for calendar there is an option for a coptic calendar. How do I display this calendar in a Mac Application?


Solution

  • How do I display this calendar in a Mac Application?

    Well, what do you mean by display? Do you just want to use the coptic calendar in your calculations? If that's the case then all you need is an appropriate NSCalendar instance. Since NSCalendar (and friends) are backed by the ICU, you can use any identifier that ICU supports (under "Key/Type Definitions"). This means you can choose from among:

    • gregorian - (default)
    • islamic - Astronomical Arabic
    • chinese - Traditional Chinese calendar
    • islamic-civil - Civil (algorithmic) Arabic calendar
    • hebrew - Traditional Hebrew Calendar
    • japanese - Imperial Calendar (same as Gregorian except for the year, with one era for each Emperor)
    • buddhist - Thai Buddhist Calendar (same as Gregorian except for the year)
    • persian - Persian Calendar
    • coptic - Coptic Calendar
    • ethiopic - Ethiopic Calendar

    So to get the NSCalendar instance, it'd be a matter of:

    NSCalendar *coptic = [[NSCalendar alloc] initWithCalendarIdentifier:@"coptic"];
    

    However, if you actually want to draw some UI (à la iCal), then you need a lot more information. Fortunately, you can get that from the NSCalendar as well.

    For example, you'll need to know how many months the calendar has:

    NSRange minMonthRange = [coptic minimumRangeOfUnit:NSMonthCalendarUnit]; // {1,13}
    NSRange maxMonthRange = [coptic maximumRangeOfUnit:NSMonthCalendarUnit]; // {1,13}
    

    So it looks like every year has 13 months. OK, how about days in a month?

    NSRange minDayRange = [coptic minimumRangeOfUnit:NSDayCalendarUnit]; // {1,5}
    NSRange maxDayRange = [coptic maximumRangeOfUnit:NSDayCalendarUnit]; // {1,30}
    

    Whoa! It looks like you can have a month that only has 5 days in it! (But no month has more than 30 days) Let's see if we can find out some more:

    NSDateComponents *firstDayOfMonth = [[NSDateComponents alloc] init];
    [firstDayOfMonth setDay:1];
    for (NSInteger y = 1500; y < 1520; ++y) {
        [firstDayOfMonth setYear:y];
        for (NSInteger m = 1; m <= 13; ++m) {
            [firstDayOfMonth setMonth:m];
    
            NSDate *date = [coptic dateFromComponents:firstDayOfMonth];
    
            NSRange rangeOfDaysInMonth = [coptic rangeOfUnit:NSDayCalendarUnit inUnit:NSMonthCalendarUnit forDate:date];
            NSLog(@"%d - %d => %@", y, m, NSStringFromRange(rangeOfDaysInMonth));
        }
    }
    

    This iterates through 20 years, and builds an NSDate that corresponds to the first day of each month in each year. It then looks to see how many days are in that month, and logs the result. (Note that 1500 and 1520 are arbitrary years that I picked)

    This logs:

    1500 - 1 => {1, 30}
    1500 - 2 => {1, 30}
    1500 - 3 => {1, 30}
    1500 - 4 => {1, 30}
    1500 - 5 => {1, 30}
    1500 - 6 => {1, 30}
    1500 - 7 => {1, 30}
    1500 - 8 => {1, 30}
    1500 - 9 => {1, 30}
    1500 - 10 => {1, 30}
    1500 - 11 => {1, 30}
    1500 - 12 => {1, 30}
    1500 - 13 => {1, 5}
    1501 - 1 => {1, 30}
    1501 - 2 => {1, 30}
    1501 - 3 => {1, 30}
    1501 - 4 => {1, 30}
    1501 - 5 => {1, 30}
    1501 - 6 => {1, 30}
    1501 - 7 => {1, 30}
    1501 - 8 => {1, 30}
    1501 - 9 => {1, 30}
    1501 - 10 => {1, 30}
    1501 - 11 => {1, 30}
    1501 - 12 => {1, 30}
    1501 - 13 => {1, 5}
    1502 - 1 => {1, 30}
    1502 - 2 => {1, 30}
    1502 - 3 => {1, 30}
    1502 - 4 => {1, 30}
    1502 - 5 => {1, 30}
    1502 - 6 => {1, 30}
    1502 - 7 => {1, 30}
    1502 - 8 => {1, 30}
    1502 - 9 => {1, 30}
    1502 - 10 => {1, 30}
    1502 - 11 => {1, 30}
    1502 - 12 => {1, 30}
    1502 - 13 => {1, 5}
    1503 - 1 => {1, 30}
    1503 - 2 => {1, 30}
    1503 - 3 => {1, 30}
    1503 - 4 => {1, 30}
    1503 - 5 => {1, 30}
    1503 - 6 => {1, 30}
    1503 - 7 => {1, 30}
    1503 - 8 => {1, 30}
    1503 - 9 => {1, 30}
    1503 - 10 => {1, 30}
    1503 - 11 => {1, 30}
    1503 - 12 => {1, 30}
    1503 - 13 => {1, 6}
    1504 - 1 => {1, 30}
    1504 - 2 => {1, 30}
    1504 - 3 => {1, 30}
    1504 - 4 => {1, 30}
    1504 - 5 => {1, 30}
    1504 - 6 => {1, 30}
    1504 - 7 => {1, 30}
    1504 - 8 => {1, 30}
    1504 - 9 => {1, 30}
    1504 - 10 => {1, 30}
    1504 - 11 => {1, 30}
    1504 - 12 => {1, 30}
    1504 - 13 => {1, 5}
    1505 - 1 => {1, 30}
    1505 - 2 => {1, 30}
    1505 - 3 => {1, 30}
    1505 - 4 => {1, 30}
    1505 - 5 => {1, 30}
    1505 - 6 => {1, 30}
    1505 - 7 => {1, 30}
    1505 - 8 => {1, 30}
    1505 - 9 => {1, 30}
    1505 - 10 => {1, 30}
    1505 - 11 => {1, 30}
    1505 - 12 => {1, 30}
    1505 - 13 => {1, 5}
    1506 - 1 => {1, 30}
    1506 - 2 => {1, 30}
    1506 - 3 => {1, 30}
    1506 - 4 => {1, 30}
    1506 - 5 => {1, 30}
    1506 - 6 => {1, 30}
    1506 - 7 => {1, 30}
    1506 - 8 => {1, 30}
    1506 - 9 => {1, 30}
    1506 - 10 => {1, 30}
    1506 - 11 => {1, 30}
    1506 - 12 => {1, 30}
    1506 - 13 => {1, 5}
    1507 - 1 => {1, 30}
    1507 - 2 => {1, 30}
    1507 - 3 => {1, 30}
    1507 - 4 => {1, 30}
    1507 - 5 => {1, 30}
    1507 - 6 => {1, 30}
    1507 - 7 => {1, 30}
    1507 - 8 => {1, 30}
    1507 - 9 => {1, 30}
    1507 - 10 => {1, 30}
    1507 - 11 => {1, 30}
    1507 - 12 => {1, 30}
    1507 - 13 => {1, 6}
    1508 - 1 => {1, 30}
    1508 - 2 => {1, 30}
    1508 - 3 => {1, 30}
    1508 - 4 => {1, 30}
    1508 - 5 => {1, 30}
    1508 - 6 => {1, 30}
    1508 - 7 => {1, 30}
    1508 - 8 => {1, 30}
    1508 - 9 => {1, 30}
    1508 - 10 => {1, 30}
    1508 - 11 => {1, 30}
    1508 - 12 => {1, 30}
    1508 - 13 => {1, 5}
    1509 - 1 => {1, 30}
    1509 - 2 => {1, 30}
    1509 - 3 => {1, 30}
    1509 - 4 => {1, 30}
    1509 - 5 => {1, 30}
    1509 - 6 => {1, 30}
    1509 - 7 => {1, 30}
    1509 - 8 => {1, 30}
    1509 - 9 => {1, 30}
    1509 - 10 => {1, 30}
    1509 - 11 => {1, 30}
    1509 - 12 => {1, 30}
    1509 - 13 => {1, 5}
    1510 - 1 => {1, 30}
    1510 - 2 => {1, 30}
    1510 - 3 => {1, 30}
    1510 - 4 => {1, 30}
    1510 - 5 => {1, 30}
    1510 - 6 => {1, 30}
    1510 - 7 => {1, 30}
    1510 - 8 => {1, 30}
    1510 - 9 => {1, 30}
    1510 - 10 => {1, 30}
    1510 - 11 => {1, 30}
    1510 - 12 => {1, 30}
    1510 - 13 => {1, 5}
    1511 - 1 => {1, 30}
    1511 - 2 => {1, 30}
    1511 - 3 => {1, 30}
    1511 - 4 => {1, 30}
    1511 - 5 => {1, 30}
    1511 - 6 => {1, 30}
    1511 - 7 => {1, 30}
    1511 - 8 => {1, 30}
    1511 - 9 => {1, 30}
    1511 - 10 => {1, 30}
    1511 - 11 => {1, 30}
    1511 - 12 => {1, 30}
    1511 - 13 => {1, 6}
    1512 - 1 => {1, 30}
    1512 - 2 => {1, 30}
    1512 - 3 => {1, 30}
    1512 - 4 => {1, 30}
    1512 - 5 => {1, 30}
    1512 - 6 => {1, 30}
    1512 - 7 => {1, 30}
    1512 - 8 => {1, 30}
    1512 - 9 => {1, 30}
    1512 - 10 => {1, 30}
    1512 - 11 => {1, 30}
    1512 - 12 => {1, 30}
    1512 - 13 => {1, 5}
    1513 - 1 => {1, 30}
    1513 - 2 => {1, 30}
    1513 - 3 => {1, 30}
    1513 - 4 => {1, 30}
    1513 - 5 => {1, 30}
    1513 - 6 => {1, 30}
    1513 - 7 => {1, 30}
    1513 - 8 => {1, 30}
    1513 - 9 => {1, 30}
    1513 - 10 => {1, 30}
    1513 - 11 => {1, 30}
    1513 - 12 => {1, 30}
    1513 - 13 => {1, 5}
    1514 - 1 => {1, 30}
    1514 - 2 => {1, 30}
    1514 - 3 => {1, 30}
    1514 - 4 => {1, 30}
    1514 - 5 => {1, 30}
    1514 - 6 => {1, 30}
    1514 - 7 => {1, 30}
    1514 - 8 => {1, 30}
    1514 - 9 => {1, 30}
    1514 - 10 => {1, 30}
    1514 - 11 => {1, 30}
    1514 - 12 => {1, 30}
    1514 - 13 => {1, 5}
    1515 - 1 => {1, 30}
    1515 - 2 => {1, 30}
    1515 - 3 => {1, 30}
    1515 - 4 => {1, 30}
    1515 - 5 => {1, 30}
    1515 - 6 => {1, 30}
    1515 - 7 => {1, 30}
    1515 - 8 => {1, 30}
    1515 - 9 => {1, 30}
    1515 - 10 => {1, 30}
    1515 - 11 => {1, 30}
    1515 - 12 => {1, 30}
    1515 - 13 => {1, 6}
    1516 - 1 => {1, 30}
    1516 - 2 => {1, 30}
    1516 - 3 => {1, 30}
    1516 - 4 => {1, 30}
    1516 - 5 => {1, 30}
    1516 - 6 => {1, 30}
    1516 - 7 => {1, 30}
    1516 - 8 => {1, 30}
    1516 - 9 => {1, 30}
    1516 - 10 => {1, 30}
    1516 - 11 => {1, 30}
    1516 - 12 => {1, 30}
    1516 - 13 => {1, 5}
    1517 - 1 => {1, 30}
    1517 - 2 => {1, 30}
    1517 - 3 => {1, 30}
    1517 - 4 => {1, 30}
    1517 - 5 => {1, 30}
    1517 - 6 => {1, 30}
    1517 - 7 => {1, 30}
    1517 - 8 => {1, 30}
    1517 - 9 => {1, 30}
    1517 - 10 => {1, 30}
    1517 - 11 => {1, 30}
    1517 - 12 => {1, 30}
    1517 - 13 => {1, 5}
    1518 - 1 => {1, 30}
    1518 - 2 => {1, 30}
    1518 - 3 => {1, 30}
    1518 - 4 => {1, 30}
    1518 - 5 => {1, 30}
    1518 - 6 => {1, 30}
    1518 - 7 => {1, 30}
    1518 - 8 => {1, 30}
    1518 - 9 => {1, 30}
    1518 - 10 => {1, 30}
    1518 - 11 => {1, 30}
    1518 - 12 => {1, 30}
    1518 - 13 => {1, 5}
    1519 - 1 => {1, 30}
    1519 - 2 => {1, 30}
    1519 - 3 => {1, 30}
    1519 - 4 => {1, 30}
    1519 - 5 => {1, 30}
    1519 - 6 => {1, 30}
    1519 - 7 => {1, 30}
    1519 - 8 => {1, 30}
    1519 - 9 => {1, 30}
    1519 - 10 => {1, 30}
    1519 - 11 => {1, 30}
    1519 - 12 => {1, 30}
    1519 - 13 => {1, 6}
    

    Analyzing this, we see that every month has 30 days, except for the 13th month, which (usually) has 5 days. It appears that once every 4 years, the 13th month has 6 days. That must be how it handles the leap day. This sure is a nice calendar! Everything's nice and regular.

    Let's poke around some more:

    NSRange minHourRange = [coptic minimumRangeOfUnit:NSHourCalendarUnit]; // {0,24}
    NSRange maxHourRange = [coptic maximumRangeOfUnit:NSHourCalendarUnit]; // {0,24}
    

    So every day has 24 hours (daylight savings time jumps are not reflected in this range. The Gregorian calendar also reports a minimum hour range of {0,24} and a maximum hour range of {0,24}, despite some days having 23 hours or 25 hours, depending on the DST transition).

    Things are also normal at the minute and second level (60 each).

    So if you're wanting to display the calendar yourself, you'll need a UI that can handle 13 months in a year, one of which is less than a week long. At the sub-day level, things are what we're used to.


    Of course, you can also go read the Wikipedia article on the Coptic Calendar.


    Edit (based on the comment)

    If all you want to do is to format a date as a string, then you'll (of course) turn to NSDateFormatter:

    NSDateFormatter *f = [[NSDateFormatter alloc] init];
    NSCalendar *coptic = [[NSCalendar alloc] initWithCalendarIdentifier:@"coptic"];
    [f setCalendar:coptic];
    [f setDateStyle:NSDateFormatterLongStyle];
    
    NSDate *rightNow = [NSDate date];
    NSString *formattedDate = [f stringFromDate:rightNow];
    NSLog(@"%@", formattedDate); // logs "Bashans 4, 1728"