Boolean CFCalendarGetTimeRangeOfUnit()

in CoreFoundation/Locale.subproj/CFCalendar.c [1497:1757]


Boolean CFCalendarGetTimeRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit unit, CFAbsoluteTime at, CFAbsoluteTime *startp, CFTimeInterval *tip) {
    __CFCalendarValidateAndCapTimeRange(at);
    // Note: We do not toll-free bridge for Swift
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), Boolean, (NSCalendar *)calendar, _rangeOfUnit:(NSCalendarUnit)unit startTime:startp interval:tip forAT:at);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());

    const CFTimeInterval inf_ti = 4398046511104.0;
    CFStringRef ident = CFCalendarGetIdentifier(calendar);
    switch (unit) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
    case kCFCalendarUnitCalendar: return false;
    case kCFCalendarUnitTimeZone: return false;
#pragma GCC diagnostic pop
    case kCFCalendarUnitEra:;
        if (kCFCalendarIdentifierGregorian == ident || kCFCalendarIdentifierISO8601 == ident) {
            if (at < -63113904000.0) {
                if (startp) *startp = -63113904000.0 - inf_ti;
            } else {
                if (startp) *startp = -63113904000.0;
            }
            if (tip) *tip = inf_ti;
            return true;
        } else if (kCFCalendarIdentifierRepublicOfChina == ident) {
            if (at < -2808691200.0) {
                if (startp) *startp = -2808691200.0 - inf_ti;
            } else {
                if (startp) *startp = -2808691200.0;
            }
            if (tip) *tip = inf_ti;
            return true;
        } else if (kCFCalendarIdentifierCoptic == ident) {
            if (at < -54162518400.0) {
                if (startp) *startp = -54162518400.0 - inf_ti;
            } else {
                if (startp) *startp = -54162518400.0;
            }
            if (tip) *tip = inf_ti;
            return true;
        } else if (kCFCalendarIdentifierBuddhist == ident) {
            if (at < -80249875200.0) {
                return false;
            }
            if (startp) *startp = -80249875200.0;
            if (tip) *tip = inf_ti;
            return true;
        } else if (kCFCalendarIdentifierIslamic == ident) {
            if (at < -43499980800.0) {
                return false;
            }
            if (startp) *startp = -43499980800.0;
            if (tip) *tip = inf_ti;
            return true;
        } else if (kCFCalendarIdentifierIslamicCivil == ident) {
            if (at < -43499894400.0) {
                return false;
            }
            if (startp) *startp = -43499894400.0;
            if (tip) *tip = inf_ti;
            return true;
        } else if (kCFCalendarIdentifierIslamicTabular == ident) {
            if (at < -43499980800.0) {
                return false;
            }
            if (startp) *startp = -43499980800.0;
            if (tip) *tip = inf_ti;
            return true;
        } else if (kCFCalendarIdentifierIslamicUmmAlQura == ident) {
            if (at < -43499980800.0) {
                return false;
            }
            if (startp) *startp = -43499980800.0;
            if (tip) *tip = inf_ti;
            return true;
        } else if (kCFCalendarIdentifierHebrew == ident) {
            if (at < -181778083200.0) {
                return false;
            }
            if (startp) *startp = -181778083200.0;
            if (tip) *tip = inf_ti;
            return true;
        } else if (kCFCalendarIdentifierPersian == ident) {
            if (at < -43510176000.0) {
                return false;
            }
            if (startp) *startp = -43510176000.0;
            if (tip) *tip = inf_ti;
            return true;
        } else if (kCFCalendarIdentifierIndian == ident) {
            if (at < -60645542400.0) {
                return false;
            }
            if (startp) *startp = -60645542400.0;
            if (tip) *tip = inf_ti;
            return true;
        } else if (kCFCalendarIdentifierEthiopicAmeteAlem == ident) {
            if (at < -236439216000.0) {
                return false;
            }
            if (startp) *startp = -236439216000.0;
            if (tip) *tip = inf_ti;
            return true;
        } else if (kCFCalendarIdentifierEthiopicAmeteMihret == ident) {
            if (at < -236439216000.0) {
                return false;
            }
            if (at < -62872416000.0) {
                if (startp) *startp = -236439216000.0;
                if (tip) *tip = -62872416000.0 - -236439216000.0;
                return true;
            }
            if (startp) *startp = -62872416000.0;
            if (tip) *tip = inf_ti;
            return true;
        } else if (kCFCalendarIdentifierJapanese == ident) {
            if (at < -42790982400.0) {
                return false;
            }
            break;
        } else if (kCFCalendarIdentifierChinese == ident) {
            if (at < -146325744000.0) {
                return false;
            }
            break;
        }
        break;
    case kCFCalendarUnitYear: break;
    case kCFCalendarUnitYearForWeekOfYear: break;
    case kCFCalendarUnitQuarter: break;
    case kCFCalendarUnitMonth: break;
    case kCFCalendarUnitDay: break;
    case kCFCalendarUnitHour:
        {
            CFTimeZoneRef timezone = CFCalendarCopyTimeZone(calendar);
            CFTimeInterval ti = CFTimeZoneGetSecondsFromGMT(timezone, at);
            CFRelease(timezone);
#if 1
            CFTimeInterval fixedAT = at + ti; // compute local time
            fixedAT = floor(fixedAT / 3600.0) * 3600.0;
            fixedAT -= ti; // compute GMT
            if (startp) *startp = fixedAT;
#else
            NSTimeInterval secondsToSubtract = fmod(fmod(at + ti, 3600.0) + 3600.0, 3600.0); // amount of seconds into the current local time's hour
            if (startp) *startp = at - secondsToSubtract;
#endif
            if (tip) *tip = 3600.0;
            return true;
        }
    case kCFCalendarUnitMinute:
        if (startp) *startp = floor(at / 60.0) * 60.0;
        if (tip) *tip = 60.0;
        return true;
    case kCFCalendarUnitSecond:
        if (startp) *startp = floor(at);
        if (tip) *tip = 1.0;
        return true;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
    case kCFCalendarUnitNanosecond:
        if (startp) *startp = floor(at * 1.0e+9) * 1.0e-9;
        if (tip) *tip = 1.0e-9;
        return true;
#pragma GCC diagnostic pop
    case kCFCalendarUnitWeek_Deprecated: break;
    case kCFCalendarUnitWeekOfMonth: break;
    case kCFCalendarUnitWeekOfYear: break;
    case kCFCalendarUnitWeekdayOrdinal:
    case kCFCalendarUnitWeekday:
        unit = kCFCalendarUnitDay; break;
    }

    if (!calendar->_cal) {
        __CFCalendarSetupCal(calendar);
        if (!calendar->_cal) {
            return false;
        }
    }

    // Set UCalendar to first instant of unit prior to 'at'
    __CFCalendarSetToFirstInstant(calendar, unit, at);

    UErrorCode status = U_ZERO_ERROR;
    UDate end = 0.0, start = __cficu_ucal_getMillis(calendar->_cal, &status);
    CFAbsoluteTime start_at = start / 1000.0 - kCFAbsoluteTimeIntervalSince1970;

    if (tip) {
#pragma GCC diagnostic push // See 10693376
#pragma GCC diagnostic ignored "-Wswitch-enum"
        switch (unit) {
        case kCFCalendarUnitEra: {
            __cficu_ucal_add(calendar->_cal, UCAL_ERA, 1, &status);
            UDate newdate = __cficu_ucal_getMillis(calendar->_cal, &status);
            if (newdate == start) {
                // ICU refused to do the addition, probably because we are
                // at the limit of UCAL_ERA.
                if (startp) *startp = start_at;
                if (tip) *tip = inf_ti;
                return true;
            }
            break;
        }
        case kCFCalendarUnitYear:
            __cficu_ucal_add(calendar->_cal, UCAL_YEAR, 1, &status);
            break;
        case kCFCalendarUnitYearForWeekOfYear:
            __cficu_ucal_add(calendar->_cal, UCAL_YEAR_WOY, 1, &status);
            break;
        case kCFCalendarUnitQuarter: {
            // #warning adding 3 months and tacking any 13th month in the last quarter is not right for Hebrew
            __cficu_ucal_add(calendar->_cal, UCAL_MONTH, 3, &status);
            int32_t m = __cficu_ucal_get(calendar->_cal, UCAL_MONTH, &status);
            if (12 == m) { // for calendars with 13 months
                __cficu_ucal_add(calendar->_cal, UCAL_MONTH, 1, &status);
                // workaround ICU bug with Coptic, Ethiopic calendars
                int32_t d = __cficu_ucal_get(calendar->_cal, UCAL_DAY_OF_MONTH, &status);
                int32_t d1 = __cficu_ucal_getLimit(calendar->_cal, UCAL_DAY_OF_MONTH, UCAL_ACTUAL_MINIMUM, &status);
                if (d != d1) {
                    __cficu_ucal_set(calendar->_cal, UCAL_DAY_OF_MONTH, d1);
                }
            }
            break;
        }
        case kCFCalendarUnitMonth:
            __cficu_ucal_add(calendar->_cal, UCAL_MONTH, 1, &status);
            break;
        case kCFCalendarUnitWeek_Deprecated:
            __cficu_ucal_add(calendar->_cal, UCAL_WEEK_OF_YEAR, 1, &status);
            break;
        case kCFCalendarUnitWeekOfYear:
            __cficu_ucal_add(calendar->_cal, UCAL_WEEK_OF_YEAR, 1, &status);
            break;
        case kCFCalendarUnitWeekOfMonth:
            __cficu_ucal_add(calendar->_cal, UCAL_WEEK_OF_MONTH, 1, &status);
            break;
        case kCFCalendarUnitDay:
            __cficu_ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, 1, &status);
            break;
        }
#pragma GCC diagnostic pop // See 10693376

        // move back to 0h0m0s, in case the start of the unit wasn't at 0h0m0s
        __cficu_ucal_set(calendar->_cal, UCAL_HOUR_OF_DAY, __cficu_ucal_getLimit(calendar->_cal, UCAL_HOUR_OF_DAY, UCAL_ACTUAL_MINIMUM, &status));
        __cficu_ucal_set(calendar->_cal, UCAL_MINUTE, __cficu_ucal_getLimit(calendar->_cal, UCAL_MINUTE, UCAL_ACTUAL_MINIMUM, &status));
        __cficu_ucal_set(calendar->_cal, UCAL_SECOND, __cficu_ucal_getLimit(calendar->_cal, UCAL_SECOND, UCAL_ACTUAL_MINIMUM, &status));
        __cficu_ucal_set(calendar->_cal, UCAL_MILLISECOND, 0);

        status = U_ZERO_ERROR;
        end = __cficu_ucal_getMillis(calendar->_cal, &status);
    }
    
    CFAbsoluteTime t, end_at;
    CFTimeInterval length;
    end_at = end / 1000.0 - kCFAbsoluteTimeIntervalSince1970;
    if (__CFCalendarGetTimeRangeOfTimeZoneTransition(calendar, end_at, &t, &length)) {
        end = (end_at - length + kCFAbsoluteTimeIntervalSince1970) * 1000.0;
    }
    
    if (startp) *startp = start_at;
    if (tip) *tip = (end - start) / 1000.0;
    return true;
}