blob: 1489c4ec73d987f74f605a4f4a83a5066222784d [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
class TestNSCalendar: XCTestCase {
func test_initWithCalendarIdentifier() {
var calMaybe = NSCalendar(calendarIdentifier: NSCalendar.Identifier("Not a calendar"))
XCTAssertNil(calMaybe)
calMaybe = NSCalendar(calendarIdentifier: .chinese)
guard let cal = calMaybe else {
XCTFail(); return
}
XCTAssertEqual(cal.calendarIdentifier, NSCalendar.Identifier.chinese)
}
func test_calendarWithIdentifier() {
var calMaybe = NSCalendar(identifier: NSCalendar.Identifier("Not a calendar"))
XCTAssertNil(calMaybe)
calMaybe = NSCalendar(identifier: .chinese)
guard let cal = calMaybe else {
XCTFail(); return
}
XCTAssertEqual(cal.calendarIdentifier, NSCalendar.Identifier.chinese)
}
func test_calendarOptions() {
let allOptions: [NSCalendar.Options] = [.matchStrictly, .searchBackwards, .matchPreviousTimePreservingSmallerUnits, .matchNextTimePreservingSmallerUnits, .matchNextTime, .matchFirst, .matchLast]
for (i, option) in allOptions.enumerated() {
var otherOptions: NSCalendar.Options.RawValue = 0
for (j, otherOption) in allOptions.enumerated() {
if (i != j) {
otherOptions |= otherOption.rawValue
}
}
XCTAssertEqual(0, (option.rawValue & otherOptions), "Options should be exclusive")
}
}
func test_isEqualWithDifferentWaysToCreateCalendar() throws {
let date = Date(timeIntervalSinceReferenceDate: 497973600) // 2016-10-12 07:00:00 +0000;
let gregorianCalendar = try XCTUnwrap(NSCalendar(identifier: .gregorian))
let gregorianCalendar2 = try XCTUnwrap(gregorianCalendar.components(.calendar, from: date).calendar) as NSCalendar
XCTAssertEqual(gregorianCalendar, gregorianCalendar2)
let timeZone = try XCTUnwrap(TimeZone(identifier: "Antarctica/Vostok"))
gregorianCalendar.timeZone = timeZone
let gregorianCalendar3 = try XCTUnwrap(gregorianCalendar.components(.calendar, from: date).calendar) as NSCalendar
XCTAssertEqual(gregorianCalendar, gregorianCalendar3)
}
func test_isEqual() throws {
let testCal1 = try XCTUnwrap(NSCalendar(identifier: .gregorian))
let testCal2 = try XCTUnwrap(NSCalendar(identifier: .gregorian))
XCTAssertEqual(testCal1, testCal2)
testCal2.timeZone = try XCTUnwrap(TimeZone(identifier: "Antarctica/Vostok"))
testCal2.locale = Locale(identifier: "ru_RU")
testCal2.firstWeekday += 1
testCal2.minimumDaysInFirstWeek += 1
XCTAssertNotEqual(testCal1, testCal2)
let testCal3 = try XCTUnwrap(NSCalendar(calendarIdentifier: .chinese))
XCTAssertNotEqual(testCal1, testCal3)
}
func test_isEqualCurrentCalendar() {
let testCal1 = NSCalendar.current as NSCalendar
let testCal2 = NSCalendar.current as NSCalendar
XCTAssertEqual(testCal1, testCal2)
XCTAssert(testCal1 !== testCal2)
XCTAssertNotEqual(testCal1.firstWeekday, 4)
testCal1.firstWeekday = 4
XCTAssertNotEqual(testCal1, testCal2)
}
func test_isEqualAutoUpdatingCurrentCalendar() {
let testCal1 = NSCalendar.autoupdatingCurrent as NSCalendar
let testCal2 = NSCalendar.autoupdatingCurrent as NSCalendar
XCTAssertEqual(testCal1, testCal2)
XCTAssert(testCal1 !== testCal2)
XCTAssertNotEqual(testCal1.firstWeekday, 4)
testCal1.firstWeekday = 4
XCTAssertNotEqual(testCal1, testCal2)
}
func test_copy() throws {
let cal = try XCTUnwrap(NSCalendar(identifier: .gregorian))
let calCopy = try XCTUnwrap((cal.copy() as? NSCalendar))
XCTAssertEqual(cal, calCopy)
XCTAssert(cal !== calCopy)
}
func test_copyCurrentCalendar() throws {
let cal = NSCalendar.current as NSCalendar
let calCopy = try XCTUnwrap((cal.copy() as? NSCalendar))
XCTAssertEqual(cal, calCopy)
XCTAssert(cal !== calCopy)
}
// MARK: API Method Tests
let timeIntervalsNext50Months: [TimeInterval] = [
349776000, // 2012-02-01 08:00:00 +0000
352281600, // 2012-03-01 08:00:00 +0000
354956400, // 2012-04-01 07:00:00 +0000
357548400, // 2012-05-01 07:00:00 +0000
360226800, // 2012-06-01 07:00:00 +0000
362818800, // 2012-07-01 07:00:00 +0000
365497200, // 2012-08-01 07:00:00 +0000
368175600, // 2012-09-01 07:00:00 +0000
370767600, // 2012-10-01 07:00:00 +0000
373446000, // 2012-11-01 07:00:00 +0000
376041600, // 2012-12-01 08:00:00 +0000
378720000, // 2013-01-01 08:00:00 +0000
381398400, // 2013-02-01 08:00:00 +0000
383817600, // 2013-03-01 08:00:00 +0000
386492400, // 2013-04-01 07:00:00 +0000
389084400, // 2013-05-01 07:00:00 +0000
391762800, // 2013-06-01 07:00:00 +0000
394354800, // 2013-07-01 07:00:00 +0000
397033200, // 2013-08-01 07:00:00 +0000
399711600, // 2013-09-01 07:00:00 +0000
402303600, // 2013-10-01 07:00:00 +0000
404982000, // 2013-11-01 07:00:00 +0000
407577600, // 2013-12-01 08:00:00 +0000
410256000, // 2014-01-01 08:00:00 +0000
412934400, // 2014-02-01 08:00:00 +0000
415353600, // 2014-03-01 08:00:00 +0000
418028400, // 2014-04-01 07:00:00 +0000
420620400, // 2014-05-01 07:00:00 +0000
423298800, // 2014-06-01 07:00:00 +0000
425890800, // 2014-07-01 07:00:00 +0000
428569200, // 2014-08-01 07:00:00 +0000
431247600, // 2014-09-01 07:00:00 +0000
433839600, // 2014-10-01 07:00:00 +0000
436518000, // 2014-11-01 07:00:00 +0000
439113600, // 2014-12-01 08:00:00 +0000
441792000, // 2015-01-01 08:00:00 +0000
444470400, // 2015-02-01 08:00:00 +0000
446889600, // 2015-03-01 08:00:00 +0000
449564400, // 2015-04-01 07:00:00 +0000
452156400, // 2015-05-01 07:00:00 +0000
454834800, // 2015-06-01 07:00:00 +0000
457426800, // 2015-07-01 07:00:00 +0000
460105200, // 2015-08-01 07:00:00 +0000
462783600, // 2015-09-01 07:00:00 +0000
465375600, // 2015-10-01 07:00:00 +0000
468054000, // 2015-11-01 07:00:00 +0000
470649600, // 2015-12-01 08:00:00 +0000
473328000, // 2016-01-01 08:00:00 +0000
476006400, // 2016-02-01 08:00:00 +0000
478512000, // 2016-03-01 08:00:00 +0000
];
func test_next50MonthsFromDate() throws {
let calendar = try XCTUnwrap(NSCalendar(identifier: .gregorian))
calendar.timeZone = try XCTUnwrap(TimeZone(identifier: "America/Los_Angeles"))
let startDate = Date(timeIntervalSinceReferenceDate: 347113850) // 2012-01-01 12:30:50 +0000
let expectedDates = timeIntervalsNext50Months.map { Date(timeIntervalSinceReferenceDate: $0) }
var count = 0
var resultDates: [Date] = []
let components = NSDateComponents()
components.day = 1
calendar.enumerateDates(startingAfter: startDate, matching: components as DateComponents, options: .matchNextTime) { (date, exactMatch, stop) in
count += 1
if count > expectedDates.count {
stop.pointee = true
return
}
guard let date = date else {
XCTFail()
stop.pointee = true
return
}
resultDates.append(date)
}
XCTAssertEqual(expectedDates, resultDates)
}
let iterableCalendarUnits: [NSCalendar.Unit] = [
.era,
.year,
.month,
.day,
.hour,
.minute,
.second,
.weekday,
.weekdayOrdinal,
.quarter,
.weekOfMonth,
.weekOfYear,
.yearForWeekOfYear,
.nanosecond,
.calendar,
.timeZone,
]
let allCalendarUnits: NSCalendar.Unit = [
.era,
.year,
.month,
.day,
.hour,
.minute,
.second,
.weekday,
.weekdayOrdinal,
.quarter,
.weekOfMonth,
.weekOfYear,
.yearForWeekOfYear,
.nanosecond,
.calendar,
.timeZone,
]
func yieldUnits(in components: NSDateComponents) -> [NSCalendar.Unit] {
return iterableCalendarUnits.filter({ (unit) -> Bool in
switch unit {
case .calendar:
return components.calendar != nil
case .timeZone:
return components.timeZone != nil
default:
return components.value(forComponent: unit) != NSDateComponentUndefined
}
})
}
func units(in components: NSDateComponents) -> NSCalendar.Unit {
return yieldUnits(in: components).reduce([], { (current, toAdd) in
var new = current
new.insert(toAdd)
return new
})
}
func performTest_dateByAdding(with calendar: NSCalendar, components: NSDateComponents, toAdd secondComponents: NSDateComponents, options: NSCalendar.Options, expected: NSDateComponents, addSingleUnit: Bool) throws {
let date = try XCTUnwrap(calendar.date(from: components as DateComponents))
var returnedDate: Date
if addSingleUnit {
let unit = units(in: secondComponents)
let valueToAdd = secondComponents.value(forComponent: unit)
returnedDate = try XCTUnwrap(calendar.date(byAdding: unit, value: valueToAdd, to: date, options: options))
} else {
returnedDate = try XCTUnwrap(calendar.date(byAdding: secondComponents as DateComponents, to: date, options: options))
}
let expectedUnitFlags = units(in: expected)
// If the NSDateComponents is being added to with a time zone, we want the result to be in the same time zone.
let originalTimeZone = calendar.timeZone
let calculationTimeZone = components.timeZone
if let calculationTimeZone = calculationTimeZone {
calendar.timeZone = calculationTimeZone
}
let returnedComponents = calendar.components(expectedUnitFlags, from: returnedDate) as NSDateComponents
XCTAssertEqual(expected, returnedComponents, "\(calendar.calendarIdentifier) \(components) \(secondComponents) Expected \(expected), but received \(returnedComponents)")
if calculationTimeZone != nil {
calendar.timeZone = originalTimeZone
}
}
func performTest_componentsFromDateToDate(with calendar: NSCalendar, from fromComponents: NSDateComponents, to toComponents: NSDateComponents, expected: NSDateComponents, options: NSCalendar.Options) throws {
let fromDate = try XCTUnwrap(calendar.date(from: fromComponents as DateComponents))
let toDate = try XCTUnwrap(calendar.date(from: toComponents as DateComponents))
let expectedUnitFlags = units(in: expected)
let returned = calendar.components(expectedUnitFlags, from: fromDate, to: toDate, options: options) as NSDateComponents
XCTAssertEqual(expected, returned, "Expected \(expected), but received \(returned) - from date: \(fromDate) to date: \(toDate)")
}
func performTest_componentsFromDCToDC(with calendar: NSCalendar, from fromComponents: NSDateComponents, to toComponents: NSDateComponents, expected: NSDateComponents, options: NSCalendar.Options) throws {
let expectedUnitFlags = units(in: expected)
let returned = calendar.components(expectedUnitFlags, from: fromComponents as DateComponents, to: toComponents as DateComponents, options: options) as NSDateComponents
XCTAssertEqual(expected, returned, "Expected \(expected), but received \(returned) - from components: \(fromComponents) to components: \(toComponents)")
}
func performTest_getComponentsInOtherCalendar(from fromCalendar: NSCalendar, to toCalendar: NSCalendar, from fromComponents: NSDateComponents, expected: NSDateComponents) throws {
let originalTZ = fromCalendar.timeZone
fromCalendar.timeZone = toCalendar.timeZone
let fromDate = try XCTUnwrap(fromCalendar.date(from: fromComponents as DateComponents))
fromCalendar.timeZone = originalTZ
let expectedUnitFlags = units(in: expected)
let returned = toCalendar.components(expectedUnitFlags, from: fromDate) as NSDateComponents
XCTAssertEqual(expected, returned, "Expected \(expected), but received \(returned) - from calendar: \(fromCalendar) to calendar: \(toCalendar) from components: \(fromComponents)")
}
func test_dateByAddingUnit_withWrapOption() throws {
let locale = Locale(identifier: "ar_EG")
let calendar = try XCTUnwrap(NSCalendar(identifier: .gregorian))
let timeZone = try XCTUnwrap(TimeZone(abbreviation: "GMT"))
calendar.locale = locale
calendar.timeZone = timeZone
let date = try self.date(fromFixture: "2013-09-20 23:20:28 +0000")
let newDate = try XCTUnwrap(calendar.date(byAdding: .hour, value: 12, to: date, options: .wrapComponents))
XCTAssertEqual(newDate, try self.date(fromFixture: "2013-09-20 11:20:28 +0000"))
}
func date(fromFixture string: String) throws -> Date {
let formatter = DateFormatter()
let calendar = Calendar(identifier: .gregorian)
formatter.calendar = calendar
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
formatter.locale = Locale(identifier: "en_US")
return try XCTUnwrap(formatter.date(from: string))
}
func enumerateTestDates(using block: (NSCalendar, Date, NSDateComponents) throws -> Void) throws {
func yield(to block: (NSCalendar, Date, NSDateComponents) throws -> Void, _ element: (calendarIdentifier: NSCalendar.Identifier, localeIdentifier: String, timeZoneName: String, dateString: String)) throws {
let calendar = try XCTUnwrap(NSCalendar(calendarIdentifier: element.calendarIdentifier))
let currentCalendar = NSCalendar.current as NSCalendar
let autoCalendar = NSCalendar.autoupdatingCurrent as NSCalendar
for aCalendar in [calendar, currentCalendar, autoCalendar] {
if aCalendar.calendarIdentifier != element.calendarIdentifier {
continue
}
let locale = NSLocale(localeIdentifier: element.localeIdentifier) as Locale
let timeZone = try XCTUnwrap(TimeZone(identifier: element.timeZoneName))
calendar.locale = locale
calendar.timeZone = timeZone
let date = try self.date(fromFixture: element.dateString)
let components = calendar.components(self.allCalendarUnits, from: date) as NSDateComponents
try block(calendar, date, components)
}
}
try yield(to: block, (.gregorian, "en_US", "America/Edmonton", "1906-09-01 00:33:52 -0700"))
try yield(to: block, (.gregorian, "en_US", "America/Los_Angeles", "2014-09-23 20:11:39 -0800"))
try yield(to: block, (.gregorian, "en_US", "America/Los_Angeles", "2016-02-29 06:30:33 -0900"))
try yield(to: block, (.hebrew, "he_IL", "Israel", "2018-12-24 05:40:43 +0200"))
try yield(to: block, (.hebrew, "he_IL", "Israel", "2019-03-22 09:23:26 +0200"))
try yield(to: block, (.buddhist, "es_MX", "America/Cancun", "2022-10-26 23:05:34 -0500"))
try yield(to: block, (.buddhist, "es_MX", "America/Cancun", "2014-10-22 07:13:00 -0500"))
try yield(to: block, (.japanese, "ja_JP", "Asia/Tokyo", "2013-08-23 06:09:01 +0900"))
try yield(to: block, (.japanese, "ja_JP", "Asia/Tokyo", "2014-11-23 05:34:30 +0900"))
try yield(to: block, (.persian, "ps_AF", "Asia/Kabul", "2013-07-18 20:55:06 +0430"))
try yield(to: block, (.persian, "ps_AF", "Asia/Kabul", "2015-09-21 23:21:45 +0430"))
try yield(to: block, (.coptic, "ar_EG", "Africa/Cairo", "2013-12-22 19:49:15 +0200"))
try yield(to: block, (.coptic, "ar_EG", "Africa/Cairo", "2015-03-14 17:36:20 +0200"))
try yield(to: block, (.ethiopicAmeteMihret, "am_ET", "Africa/Addis_Ababa", "2014-01-31 10:10:33 +0300"))
try yield(to: block, (.ethiopicAmeteMihret, "am_ET", "Africa/Addis_Ababa", "2013-07-19 13:05:16 +0300"))
try yield(to: block, (.ethiopicAmeteAlem, "am_ET", "Africa/Addis_Ababa", "2015-02-28 16:34:34 +0300"))
try yield(to: block, (.ethiopicAmeteAlem, "am_ET", "Africa/Addis_Ababa", "2017-07-19 15:25:59 +0300"))
try yield(to: block, (.islamic, "ar_SA", "Asia/Riyadh", "2015-06-04 19:28:36 +0300"))
try yield(to: block, (.islamic, "ar_SA", "Asia/Riyadh", "2015-05-21 21:47:39 +0300"))
try yield(to: block, (.islamicCivil, "ar_SA", "Asia/Riyadh", "2002-05-05 22:44:18 +0300"))
try yield(to: block, (.islamicCivil, "ar_SA", "Asia/Riyadh", "2015-11-20 02:21:09 +0300"))
try yield(to: block, (.chinese, "zh_CN", "Asia/Hong_Kong", "2019-04-12 01:12:10 +0800"))
try yield(to: block, (.chinese, "zh_CN", "Asia/Hong_Kong", "2015-04-02 07:13:22 +0800"))
try yield(to: block, (.chinese, "zh_CN", "Asia/Hong_Kong", "2014-10-16 06:12:10 +0800"))
}
func test_getEra_year_month_day_fromDate() throws {
try enumerateTestDates() { (calendar, date, components) in
var era = 0, year = 0, month = 0, day = 0
calendar.getEra(&era, year: &year, month: &month, day: &day, from: date)
XCTAssertEqual(era, components.era)
XCTAssertEqual(year, components.year)
XCTAssertEqual(month, components.month)
XCTAssertEqual(day, components.day)
}
}
func test_getEra_yearForWeekOfYear_weekOfYear_weekday_fromDate() throws {
try enumerateTestDates() { (calendar, date, components) in
var era = 0, yearForWeekOfYear = 0, weekOfYear = 0, weekday = 0
calendar.getEra(&era, yearForWeekOfYear: &yearForWeekOfYear, weekOfYear: &weekOfYear, weekday: &weekday, from: date)
XCTAssertEqual(era, components.era)
XCTAssertEqual(yearForWeekOfYear, components.yearForWeekOfYear)
XCTAssertEqual(weekOfYear, components.weekOfYear)
XCTAssertEqual(weekday, components.weekday)
}
}
func test_getHour_minute_second_nanoseconds_fromDate() throws {
try enumerateTestDates() { (calendar, date, components) in
var hour = 0, minute = 0, second = 0, nanosecond = 0
calendar.getHour(&hour, minute: &minute, second: &second, nanosecond: &nanosecond, from: date)
XCTAssertEqual(hour, components.hour)
XCTAssertEqual(minute, components.minute)
XCTAssertEqual(second, components.second)
XCTAssertEqual(nanosecond, components.nanosecond)
}
}
func test_component_fromDate() throws {
try enumerateTestDates() { (calendar, date, components) in
for unit in iterableCalendarUnits {
if unit == .calendar || unit == .timeZone {
continue
}
let value = calendar.component(unit, from: date)
let expectedValue = components.value(forComponent: unit)
XCTAssertEqual(expectedValue, value)
}
}
}
func test_dateWithYear_month_day_hour_minute_second_nanosecond() throws {
try enumerateTestDates() { (calendar, date, components) in
let returnedDate = try XCTUnwrap(calendar.date(era: components.era, year: components.year, month: components.month, day: components.day, hour: components.hour, minute: components.minute, second: components.second, nanosecond: components.nanosecond))
let interval = date.timeIntervalSince(returnedDate)
XCTAssertEqual(fabs(interval), 0, accuracy: 0.0000001)
}
}
func test_dateWithYearForWeekOfYear_weekOfYear_weekday_hour_minute_second_nanosecond() throws {
try enumerateTestDates() { (calendar, date, components) in
// Era is defined to be in the current era, so the below can only work for dates that are in the current era.
if components.era != calendar.components(.era, from: Date()).era {
return
}
if (calendar.calendarIdentifier == .chinese) {
// chinese calendar does not work for yearForWeekOfYear
return;
}
let returnedDate = try XCTUnwrap(calendar.date(era: components.era, yearForWeekOfYear: components.yearForWeekOfYear, weekOfYear: components.weekOfYear, weekday: components.weekday, hour: components.hour, minute: components.minute, second: components.second, nanosecond: components.nanosecond))
let interval = date.timeIntervalSince(returnedDate)
XCTAssertEqual(fabs(interval), 0, accuracy: 0.0000001)
}
}
func enumerateTestDatesWithStartOfDay(using block: (NSCalendar, Date, Date) throws -> Void) throws {
func yield(to block: (NSCalendar, Date, Date) throws -> Void, _ element: (calendarIdentifier: NSCalendar.Identifier, localeIdentifier: String, timeZoneName: String, dateString: String, startOfDayDateString: String)) throws {
let calendar = try XCTUnwrap(NSCalendar(calendarIdentifier: element.calendarIdentifier))
let currentCalendar = NSCalendar.current as NSCalendar
let autoCalendar = NSCalendar.autoupdatingCurrent as NSCalendar
for aCalendar in [calendar, currentCalendar, autoCalendar] {
if aCalendar.calendarIdentifier != element.calendarIdentifier {
continue
}
let locale = NSLocale(localeIdentifier: element.localeIdentifier) as Locale
let timeZone = try XCTUnwrap(TimeZone(identifier: element.timeZoneName))
calendar.locale = locale
calendar.timeZone = timeZone
let date = try self.date(fromFixture: element.dateString)
let startOfDay = try self.date(fromFixture: element.startOfDayDateString)
try block(calendar, date, startOfDay)
}
}
try yield(to: block, (.gregorian, "en_US", "America/Los_Angeles", "2013-03-26 10:04:16 -0700", "2013-03-26 00:00:00 -0700"))
try yield(to: block, (.gregorian, "pt_BR", "Brazil/East", "2013-10-20 13:10:20 -0200", "2013-10-20 01:00:00 -0200")) // DST jump forward at midnight
try yield(to: block, (.gregorian, "pt_BR", "Brazil/East", "2014-02-15 23:59:59 -0300", "2014-02-15 00:00:00 -0200")) // DST jump backward
}
func test_startOfDayForDate() throws {
try enumerateTestDatesWithStartOfDay() { (calendar, date, startOfDay) in
let resultDate = calendar.startOfDay(for: date)
XCTAssertEqual(startOfDay, resultDate)
}
}
func test_componentsInTimeZone_fromDate() throws {
try enumerateTestDates() { (calendar, date, components) in
let calendarWithoutTimeZone = try XCTUnwrap(NSCalendar(identifier: calendar.calendarIdentifier))
calendarWithoutTimeZone.locale = calendar.locale
let timeZone = calendar.timeZone
let returned = calendarWithoutTimeZone.components(in: timeZone, from: date) as NSDateComponents
XCTAssertEqual(components, returned)
}
}
func enumerateTestDateComparisons(using block: (NSCalendar, Date, Date, NSCalendar.Unit, ComparisonResult) throws -> Void) throws {
func yield(to block: (NSCalendar, Date, Date, NSCalendar.Unit, ComparisonResult) throws -> Void, _ element: (calendarIdentifier: NSCalendar.Identifier, localeIdentifier: String, timeZoneName: String, firstDateString: String, secondDateString: String, granularity: NSCalendar.Unit, expectedResult: ComparisonResult)) throws {
let calendar = try XCTUnwrap(NSCalendar(calendarIdentifier: element.calendarIdentifier))
let currentCalendar = NSCalendar.current as NSCalendar
let autoCalendar = NSCalendar.autoupdatingCurrent as NSCalendar
for aCalendar in [calendar, currentCalendar, autoCalendar] {
if aCalendar.calendarIdentifier != element.calendarIdentifier {
continue
}
let locale = NSLocale(localeIdentifier: element.localeIdentifier) as Locale
let timeZone = try XCTUnwrap(TimeZone(identifier: element.timeZoneName))
calendar.locale = locale
calendar.timeZone = timeZone
let firstDate = try self.date(fromFixture: element.firstDateString)
let secondDate = try self.date(fromFixture: element.secondDateString)
try block(calendar, firstDate, secondDate, element.granularity, element.expectedResult)
}
}
try yield(to: block, (.gregorian, "en_US", "America/Los_Angeles", "2014-09-23 20:11:39 -0800", "2014-09-23 20:11:39 -0800", .year, .orderedSame))
try yield(to: block, (.gregorian, "en_US", "America/Los_Angeles", "2014-09-23 20:11:39 -0800", "2014-09-23 20:11:39 -0800", .month, .orderedSame))
try yield(to: block, (.gregorian, "en_US", "America/Los_Angeles", "2014-09-23 20:11:39 -0800", "2014-09-23 20:11:39 -0800", .day, .orderedSame))
try yield(to: block, (.gregorian, "en_US", "America/Los_Angeles", "2014-09-23 20:11:39 -0800", "2014-09-23 20:11:39 -0800", .hour, .orderedSame))
try yield(to: block, (.gregorian, "en_US", "America/Los_Angeles", "2014-09-23 20:11:39 -0800", "2014-09-23 20:11:39 -0800", .minute, .orderedSame))
try yield(to: block, (.gregorian, "en_US", "America/Los_Angeles", "2014-09-23 20:11:39 -0800", "2014-09-23 20:11:39 -0800", .second, .orderedSame))
// DST fall back
try yield(to: block, (.gregorian, "en_US", "America/Los_Angeles", "2014-11-02 01:30:00 -0700", "2014-11-02 01:30:00 -0800", .day, .orderedSame))
try yield(to: block, (.gregorian, "en_US", "America/Los_Angeles", "2014-11-02 01:30:00 -0700", "2014-11-02 01:30:00 -0800", .hour, .orderedAscending))
try yield(to: block, (.gregorian, "en_US", "America/Los_Angeles", "2014-11-02 01:30:00 -0700", "2014-11-02 01:30:00 -0800", .minute, .orderedAscending))
try yield(to: block, (.gregorian, "en_US", "America/Los_Angeles", "2014-11-02 01:30:00 -0700", "2014-11-02 01:30:00 -0800", .second, .orderedAscending))
// Chinese leap month. First date is not a leap month, 2nd is. Same day.
try yield(to: block, (.chinese, "zh_CN", "Asia/Hong_Kong", "2014-10-07 21:12:10 +0000", "2014-11-06 21:12:10 +0000", .era, .orderedSame))
try yield(to: block, (.chinese, "zh_CN", "Asia/Hong_Kong", "2014-10-07 21:12:10 +0000", "2014-11-06 21:12:10 +0000", .year, .orderedSame))
try yield(to: block, (.chinese, "zh_CN", "Asia/Hong_Kong", "2014-10-07 21:12:10 +0000", "2014-11-06 21:12:10 +0000", .month, .orderedAscending))
try yield(to: block, (.chinese, "zh_CN", "Asia/Hong_Kong", "2014-10-07 21:12:10 +0000", "2014-11-06 21:12:10 +0000", .day, .orderedAscending))
try yield(to: block, (.chinese, "zh_CN", "Asia/Hong_Kong", "2014-10-07 21:12:10 +0000", "2014-11-06 21:12:10 +0000", .hour, .orderedAscending))
try yield(to: block, (.chinese, "zh_CN", "Asia/Hong_Kong", "2014-10-07 21:12:10 +0000", "2014-11-06 21:12:10 +0000", .minute, .orderedAscending))
try yield(to: block, (.chinese, "zh_CN", "Asia/Hong_Kong", "2014-10-07 21:12:10 +0000", "2014-11-06 21:12:10 +0000", .second, .orderedAscending))
// Different eras. era: 235, year: 23, month: 1, day: 22 vs era: 234, year: 23, month: 1, day: 22
try yield(to: block, (.japanese, "ja_JP", "Asia/Tokyo", "2011-01-23 06:12:10 +0900", "1948-01-23 06:12:10 +0900", .era, .orderedDescending))
try yield(to: block, (.japanese, "ja_JP", "Asia/Tokyo", "2011-01-23 06:12:10 +0900", "1948-01-23 06:12:10 +0900", .year, .orderedDescending))
try yield(to: block, (.japanese, "ja_JP", "Asia/Tokyo", "2011-01-23 06:12:10 +0900", "1948-01-23 06:12:10 +0900", .month, .orderedDescending))
try yield(to: block, (.japanese, "ja_JP", "Asia/Tokyo", "2011-01-23 06:12:10 +0900", "1948-01-23 06:12:10 +0900", .day, .orderedDescending))
try yield(to: block, (.japanese, "ja_JP", "Asia/Tokyo", "2011-01-23 06:12:10 +0900", "1948-01-23 06:12:10 +0900", .hour, .orderedDescending))
try yield(to: block, (.japanese, "ja_JP", "Asia/Tokyo", "2011-01-23 06:12:10 +0900", "1948-01-23 06:12:10 +0900", .minute, .orderedDescending))
try yield(to: block, (.japanese, "ja_JP", "Asia/Tokyo", "2011-01-23 06:12:10 +0900", "1948-01-23 06:12:10 +0900", .second, .orderedDescending))
// Same week and week of year, different non-week year.
try yield(to: block, (.buddhist, "zh-Hans_HK", "Asia/Hong_Kong", "2012-12-30 02:34:56 +0000", "2013-01-04 02:34:56 +0000", .yearForWeekOfYear, .orderedSame))
try yield(to: block, (.buddhist, "zh-Hans_HK", "Asia/Hong_Kong", "2012-12-30 02:34:56 +0000", "2013-01-04 02:34:56 +0000", .weekOfYear, .orderedSame))
try yield(to: block, (.buddhist, "zh-Hans_HK", "Asia/Hong_Kong", "2012-12-30 02:34:56 +0000", "2013-01-04 02:34:56 +0000", .weekOfMonth, .orderedAscending))
try yield(to: block, (.buddhist, "zh-Hans_HK", "Asia/Hong_Kong", "2012-12-30 02:34:56 +0000", "2013-01-04 02:34:56 +0000", .weekday, .orderedAscending))
try yield(to: block, (.buddhist, "zh-Hans_HK", "Asia/Hong_Kong", "2012-12-30 02:34:56 +0000", "2013-01-04 02:34:56 +0000", .weekdayOrdinal, .orderedAscending))
// Same non-week year, different week of year
try yield(to: block, (.buddhist, "zh-Hans_HK", "Asia/Hong_Kong", "2012-12-30 02:34:56 +0000", "2012-12-29 02:34:56 +0000", .yearForWeekOfYear, .orderedDescending))
try yield(to: block, (.buddhist, "zh-Hans_HK", "Asia/Hong_Kong", "2012-12-30 02:34:56 +0000", "2012-12-29 02:34:56 +0000", .weekOfYear, .orderedDescending))
try yield(to: block, (.buddhist, "zh-Hans_HK", "Asia/Hong_Kong", "2012-12-30 02:34:56 +0000", "2012-12-29 02:34:56 +0000", .weekOfMonth, .orderedDescending))
try yield(to: block, (.buddhist, "zh-Hans_HK", "Asia/Hong_Kong", "2012-12-30 02:34:56 +0000", "2012-12-29 02:34:56 +0000", .weekday, .orderedDescending))
// Same week, different weekday ordinal
try yield(to: block, (.buddhist, "zh-Hans_HK", "Asia/Hong_Kong", "2012-12-14 02:34:56 +0000", "2012-12-15 02:34:56 +0000", .weekOfYear, .orderedSame))
try yield(to: block, (.buddhist, "zh-Hans_HK", "Asia/Hong_Kong", "2012-12-14 02:34:56 +0000", "2012-12-15 02:34:56 +0000", .weekOfMonth, .orderedSame))
try yield(to: block, (.buddhist, "zh-Hans_HK", "Asia/Hong_Kong", "2012-12-14 02:34:56 +0000", "2012-12-15 02:34:56 +0000", .weekdayOrdinal, .orderedAscending))
}
func test_compareDate_toDate_toUnitGranularity() throws {
try enumerateTestDateComparisons { (calendar, firstDate, secondDate, granularity, result) in
let returned = calendar.compare(firstDate, to: secondDate, toUnitGranularity: granularity)
XCTAssertEqual(returned, result, "Comparison result should match; expected \(result), got \(returned), when comparing \(firstDate) (\(firstDate.timeIntervalSinceReferenceDate) to \(secondDate) (\(secondDate.timeIntervalSinceReferenceDate) with granularity \(granularity)")
}
}
func test_isDate_equalToDate_toUnitGranularity() throws {
try enumerateTestDateComparisons { (calendar, firstDate, secondDate, granularity, result) in
let expected = result == .orderedSame
let returned = calendar.isDate(firstDate, equalTo: secondDate, toUnitGranularity: granularity)
XCTAssertEqual(returned, expected)
}
}
let availableCalendarIdentifiers: [NSCalendar.Identifier] = [
.gregorian,
.buddhist,
.chinese,
.coptic,
.ethiopicAmeteMihret,
.ethiopicAmeteAlem,
.hebrew,
.ISO8601,
.indian,
.islamic,
.islamicCivil,
.japanese,
.persian,
.republicOfChina,
.islamicTabular,
.islamicUmmAlQura,
]
func test_isDateInToday() throws {
var datesTested: [Date] = []
for identifier in availableCalendarIdentifiers {
let calendar = try XCTUnwrap(NSCalendar(identifier: identifier))
var foundDate = false
var dateInToday = Date()
for _ in 0..<10 {
datesTested.append(dateInToday)
if calendar.isDateInToday(dateInToday) {
foundDate = true
break
}
dateInToday += 1
}
XCTAssertTrue(foundDate, "Unable to match any of these dates: \(datesTested)")
// Makes sure it doesn't work for other dates:
XCTAssertFalse(calendar.isDateInToday(Date.distantPast), "Shouldn't match the distant past")
XCTAssertFalse(calendar.isDateInToday(Date.distantFuture), "Shouldn't match the distant future")
}
}
func test_isDateInYesterday() throws {
var datesTested: [Date] = []
for identifier in availableCalendarIdentifiers {
let calendar = try XCTUnwrap(NSCalendar(identifier: identifier))
var foundDate = false
var dateInToday = Date()
for _ in 0..<10 {
let delta = NSDateComponents()
delta.day = -1
let dateInYesterday = try XCTUnwrap(calendar.date(byAdding: delta as DateComponents, to: dateInToday))
datesTested.append(dateInYesterday)
if calendar.isDateInYesterday(dateInYesterday) {
foundDate = true
break
}
dateInToday += 1
}
XCTAssertTrue(foundDate, "Unable to match any of these dates: \(datesTested)")
// Makes sure it doesn't work for other dates:
XCTAssertFalse(calendar.isDateInYesterday(Date.distantPast), "Shouldn't match the distant past")
XCTAssertFalse(calendar.isDateInYesterday(Date.distantFuture), "Shouldn't match the distant future")
}
}
func test_isDateInTomorrow() throws {
var datesTested: [Date] = []
for identifier in availableCalendarIdentifiers {
let calendar = try XCTUnwrap(NSCalendar(identifier: identifier))
var foundDate = false
var dateInToday = Date()
for _ in 0..<10 {
let delta = NSDateComponents()
delta.day = 1
let dateInTomorrow = try XCTUnwrap(calendar.date(byAdding: delta as DateComponents, to: dateInToday))
datesTested.append(dateInTomorrow)
if calendar.isDateInTomorrow(dateInTomorrow) {
foundDate = true
break
}
dateInToday += 1
}
XCTAssertTrue(foundDate, "Unable to match any of these dates: \(datesTested)")
// Makes sure it doesn't work for other dates:
XCTAssertFalse(calendar.isDateInTomorrow(Date.distantPast), "Shouldn't match the distant past")
XCTAssertFalse(calendar.isDateInTomorrow(Date.distantFuture), "Shouldn't match the distant future")
}
}
func enumerateTestWeekends(using block: (NSCalendar, DateInterval) throws -> Void) throws {
func yield(to block: (NSCalendar, DateInterval) throws -> Void, _ element: (calendarIdentifier: NSCalendar.Identifier, localeIdentifier: String, timeZoneName: String, firstDateString: String, secondDateString: String)) throws {
let calendar = try XCTUnwrap(NSCalendar(calendarIdentifier: element.calendarIdentifier))
let locale = NSLocale(localeIdentifier: element.localeIdentifier) as Locale
let timeZone = try XCTUnwrap(TimeZone(identifier: element.timeZoneName))
calendar.locale = locale
calendar.timeZone = timeZone
let firstDate = try self.date(fromFixture: element.firstDateString)
let secondDate = try self.date(fromFixture: element.secondDateString)
try block(calendar, DateInterval(start: firstDate, end: secondDate))
}
try yield(to: block, (.gregorian, "en_US", "America/Edmonton", "1906-09-01 00:33:52 -0700", "1906-09-03 00:00:00 -0700")) // suprise weekend 1
try yield(to: block, (.gregorian, "en_US", "Asia/Damascus", "2006-04-01 01:00:00 +0300", "2006-04-03 00:00:00 +0300")) // suprise weekend 2
try yield(to: block, (.gregorian, "en_US", "Asia/Tehran", "2014-03-22 01:00:00 +0430", "2014-03-24 00:00:00 +0430")) // suprise weekend 3
try yield(to: block, (.gregorian, "en_US", "Africa/Algiers", "1971-04-24 00:00:00 +0000", "1971-04-26 00:00:00 +0100")) // suprise weekday 1
try yield(to: block, (.gregorian, "en_US", "America/Toronto", "1919-03-29 00:00:00 -0500", "1919-03-31 00:30:00 -0400")) // suprise weekday 2
try yield(to: block, (.gregorian, "en_US", "Europe/Madrid", "1978-04-01 00:00:00 +0100", "1978-04-03 00:00:00 +0200")) // suprise weekday 3
try yield(to: block, (.hebrew, "he_IL", "Israel", "2018-03-23 00:00:00 +0200", "2018-03-25 00:00:00 +0300")) // weekend with DST jump
try yield(to: block, (.japanese, "ja_JP", "Asia/Tokyo", "2015-08-22 00:00:00 +0900", "2015-08-24 00:00:00 +0900")) // japanese
try yield(to: block, (.persian, "ps_AF", "Asia/Kabul", "2015-03-19 00:00:00 +0430", "2015-03-21 00:00:00 +0430")) // persian
try yield(to: block, (.coptic, "ar_EG", "Africa/Cairo", "2015-12-18 00:00:00 +0200", "2015-12-20 00:00:00 +0200")) // coptic
try yield(to: block, (.ethiopicAmeteMihret, "am_ET", "Africa/Addis_Ababa", "2015-07-25 00:00:00 +0300", "2015-07-27 00:00:00 +0300")) // ethiopic
try yield(to: block, (.ethiopicAmeteAlem, "am_ET", "Africa/Addis_Ababa", "2015-07-25 00:00:00 +0300", "2015-07-27 00:00:00 +0300")) // ethiopic-amete-alem
try yield(to: block, (.islamic, "ar_SA", "Asia/Riyadh", "2015-05-29 00:00:00 +0300", "2015-05-31 00:00:00 +0300")) // islamic
try yield(to: block, (.islamicCivil, "ar_SA", "Asia/Riyadh", "2015-05-29 00:00:00 +0300", "2015-05-31 00:00:00 +0300")) // islamic-civil
try yield(to: block, (.chinese, "zh_CN", "Asia/Hong_Kong", "2015-01-03 00:00:00 +0800", "2015-01-05 00:00:00 +0800")) // chinese
}
func test_isDateInWeekend() throws {
try enumerateTestWeekends() { (calendar, interval) in
XCTAssertTrue(calendar.isDateInWeekend(interval.start), "Start date should be in weekend")
XCTAssertFalse(calendar.isDateInWeekend(interval.end), "End date should not be in weekend")
XCTAssertFalse(calendar.isDateInWeekend(interval.start - 1), "Just before start date should not be in weekend")
XCTAssertTrue(calendar.isDateInWeekend(interval.end - 1), "Just before end date should be in weekend")
}
}
func test_rangeOfWeekendStartDate_interval_containingDate() throws {
#if !DARWIN_COMPATIBILITY_TESTS // NSCalender range(ofWeekendContaining:) is experimental
try enumerateTestWeekends() { (calendar, interval) in
let startDateResult = calendar.range(ofWeekendContaining: interval.start)
XCTAssertEqual(startDateResult, interval)
let endDateResult = calendar.range(ofWeekendContaining: interval.end)
XCTAssertNil(endDateResult)
let oneSecondBeforeStartResult = calendar.range(ofWeekendContaining: interval.start - 1)
XCTAssertNil(oneSecondBeforeStartResult)
let oneSecondBeforeEndResult = calendar.range(ofWeekendContaining: interval.end - 1)
XCTAssertEqual(oneSecondBeforeEndResult, interval)
}
#endif
}
func test_enumerateDatesStartingAfterDate_chineseEra_matchYearOne() throws {
let calendar = try XCTUnwrap(NSCalendar(calendarIdentifier: .chinese))
let locale = Locale(identifier: "zh_CN")
let timeZone = try XCTUnwrap(TimeZone(identifier: "Asia/Chongqing"))
calendar.locale = locale
calendar.timeZone = timeZone
let startDate = try date(fromFixture: "2013-06-19 15:59:59 +0000")
let matchingComponents = NSDateComponents()
matchingComponents.year = 1
let expectedDate = try date(fromFixture: "1984-02-01 16:00:00 +0000")
var atLeastOnce = false
calendar.enumerateDates(startingAfter: startDate, matching: matchingComponents as DateComponents, options: [.matchStrictly, .searchBackwards]) { (date, exactMatch, stop) in
atLeastOnce = true
if let date = date {
XCTAssertEqual(expectedDate, date)
XCTAssertTrue(exactMatch)
stop.pointee = true
}
}
XCTAssertTrue(atLeastOnce)
}
struct TimeIntervalQuintuple {
var value: (TimeInterval, TimeInterval, TimeInterval, TimeInterval, TimeInterval) = (0, 0, 0, 0, 0)
subscript(_ index: Int) -> TimeInterval {
switch index {
case 0:
return value.0
case 1:
return value.1
case 2:
return value.2
case 3:
return value.3
case 4:
return value.4
default:
fatalError()
}
}
let count = 5
init(_ a: TimeInterval, _ b: TimeInterval, _ c: TimeInterval, _ d: TimeInterval, _ e: TimeInterval) {
value = (a, b, c, d, e)
}
}
func test_enumerateDatesStartingAfterDate_ensureStrictlyIncreasingResults_minuteSecondClearing() throws {
// When we match a specific hour in date enumeration, we clear out lower units (minute and second). This can cause us to accidentally match the components backwards, when we otherwise wouldn't have (e.g. 2018-02-14 00:00:01 -> 2018-02-14 00:00:00).
// This leads us to propose a potential match twice in certain circumstances (when the highest set date component is hour and we're looking for hour 0).
//
// We want to ensure this never happens, and that all matches are strictly increasing or decreasing in time.
let calendar = try XCTUnwrap(NSCalendar(identifier: .gregorian))
calendar.timeZone = try XCTUnwrap(TimeZone(identifier: "America/Los_Angeles"))
let reference = try date(fromFixture: "2018-02-13 12:00:00 -0800")
let expectations: [(minute: Int, second: Int, results: TimeIntervalQuintuple)] = [
(NSDateComponentUndefined, NSDateComponentUndefined, TimeIntervalQuintuple(540288000, 540374400, 540460800, 540547200, 540633600)),
(0 , NSDateComponentUndefined, TimeIntervalQuintuple(540288000, 540374400, 540460800, 540547200, 540633600)),
(NSDateComponentUndefined, 0 , TimeIntervalQuintuple(540288000, 540374400, 540460800, 540547200, 540633600)),
(0 , 0 , TimeIntervalQuintuple(540288000, 540374400, 540460800, 540547200, 540633600)),
]
for expectation in expectations {
let matchComponents = NSDateComponents()
matchComponents.hour = 0
matchComponents.minute = expectation.minute
matchComponents.second = expectation.second
var j = 0
calendar.enumerateDates(startingAfter: reference, matching: matchComponents as DateComponents, options: .matchNextTime) { (match, exactMatch, stop) in
let expected = Date(timeIntervalSinceReferenceDate: TimeInterval(expectation.results[j]))
XCTAssertTrue(exactMatch)
XCTAssertEqual(match, expected)
j += 1
guard j < expectation.results.count else {
stop.pointee = true
return
}
}
}
}
static var allTests: [(String, (TestNSCalendar) -> () throws -> Void)] {
return [
("test_initWithCalendarIdentifier", test_initWithCalendarIdentifier),
("test_calendarWithIdentifier", test_calendarWithIdentifier),
("test_calendarOptions", test_calendarOptions),
("test_isEqualWithDifferentWaysToCreateCalendar", test_isEqualWithDifferentWaysToCreateCalendar),
("test_isEqual", test_isEqual),
("test_isEqualCurrentCalendar", test_isEqualCurrentCalendar),
("test_isEqualAutoUpdatingCurrentCalendar", test_isEqualAutoUpdatingCurrentCalendar),
("test_copy", test_copy),
("test_copyCurrentCalendar", test_copyCurrentCalendar),
("test_next50MonthsFromDate", test_next50MonthsFromDate),
("test_dateByAddingUnit_withWrapOption", test_dateByAddingUnit_withWrapOption),
("test_getEra_year_month_day_fromDate", test_getEra_year_month_day_fromDate),
("test_getEra_yearForWeekOfYear_weekOfYear_weekday_fromDate", test_getEra_yearForWeekOfYear_weekOfYear_weekday_fromDate),
("test_getHour_minute_second_nanoseconds_fromDate", test_getHour_minute_second_nanoseconds_fromDate),
("test_component_fromDate", test_component_fromDate),
("test_dateWithYear_month_day_hour_minute_second_nanosecond", test_dateWithYear_month_day_hour_minute_second_nanosecond),
("test_dateWithYearForWeekOfYear_weekOfYear_weekday_hour_minute_second_nanosecond", test_dateWithYearForWeekOfYear_weekOfYear_weekday_hour_minute_second_nanosecond),
("test_startOfDayForDate", test_startOfDayForDate),
("test_componentsInTimeZone_fromDate", test_componentsInTimeZone_fromDate),
("test_compareDate_toDate_toUnitGranularity", test_compareDate_toDate_toUnitGranularity),
("test_isDate_equalToDate_toUnitGranularity", test_isDate_equalToDate_toUnitGranularity),
("test_isDateInToday", test_isDateInToday),
("test_isDateInYesterday", test_isDateInYesterday),
("test_isDateInTomorrow", test_isDateInTomorrow),
("test_isDateInWeekend", test_isDateInWeekend),
("test_rangeOfWeekendStartDate_interval_containingDate", test_rangeOfWeekendStartDate_interval_containingDate),
("test_enumerateDatesStartingAfterDate_chineseEra_matchYearOne", test_enumerateDatesStartingAfterDate_chineseEra_matchYearOne),
("test_enumerateDatesStartingAfterDate_ensureStrictlyIncreasingResults_minuteSecondClearing", test_enumerateDatesStartingAfterDate_ensureStrictlyIncreasingResults_minuteSecondClearing),
]
}
}