blob: e28f3a01ccbf6f98b632375ed07657b310995ad2 [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2019 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
//
#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
#if canImport(SwiftFoundation) && !DEPLOYMENT_RUNTIME_OBJC
@testable import SwiftFoundation
#else
@testable import Foundation
#endif
#endif
enum ContainsInOrderResult: Equatable {
case success
case missed(String)
case doesNotEndWithLastElement
}
extension String {
func containsInOrder(requiresLastToBeAtEnd: Bool = false, _ substrings: [String]) -> ContainsInOrderResult {
var foundRange: Range<String.Index> = startIndex ..< startIndex
for substring in substrings {
if let newRange = range(of: substring, options: [], range: foundRange.upperBound..<endIndex, locale: nil) {
foundRange = newRange
} else {
return .missed(substring)
}
}
if requiresLastToBeAtEnd {
return foundRange.upperBound == endIndex ? .success : .doesNotEndWithLastElement
} else {
return .success
}
}
func assertContainsInOrder(requiresLastToBeAtEnd: Bool = false, _ substrings: String...) {
let result = containsInOrder(requiresLastToBeAtEnd: requiresLastToBeAtEnd, substrings)
XCTAssert(result == .success, "String '\(self)' (must end with: \(requiresLastToBeAtEnd)) does not contain in sequence: \(substrings) — reason: \(result)")
}
}
class TestDateIntervalFormatter: XCTestCase {
private var formatter: DateIntervalFormatter!
override func setUp() {
super.setUp()
formatter = DateIntervalFormatter()
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateStyle = .long
formatter.timeStyle = .full
}
override func tearDown() {
formatter = nil
super.tearDown()
}
func testStringFromDateToDateAcrossThreeBillionSeconds() {
let older = Date(timeIntervalSinceReferenceDate: 0)
let newer = Date(timeIntervalSinceReferenceDate: 3e9)
let result = formatter.string(from: older, to: newer)
result.assertContainsInOrder("January 1", "2001", "12:00:00 AM", "Greenwich Mean Time",
"January 25", "2096", "5:20:00 AM", "Greenwich Mean Time")
}
func testStringFromDateToDateAcrossThreeMillionSeconds() {
let older = Date(timeIntervalSinceReferenceDate: 0)
let newer = Date(timeIntervalSinceReferenceDate: 3e6)
let result = formatter.string(from: older, to: newer)
result.assertContainsInOrder("January 1", "2001", "12:00:00 AM", "Greenwich Mean Time",
"February 4", "2001", "5:20:00 PM", "Greenwich Mean Time")
}
func testStringFromDateToDateAcrossThreeBillionSecondsReversed() {
let older = Date(timeIntervalSinceReferenceDate: 0)
let newer = Date(timeIntervalSinceReferenceDate: 3e9)
let result = formatter.string(from: newer, to: older)
result.assertContainsInOrder("January 25", "2096", "5:20:00 AM", "Greenwich Mean Time",
"January 1", "2001", "12:00:00 AM", "Greenwich Mean Time")
}
func testStringFromDateToDateAcrossThreeMillionSecondsReversed() {
let older = Date(timeIntervalSinceReferenceDate: 0)
let newer = Date(timeIntervalSinceReferenceDate: 3e6)
let result = formatter.string(from: newer, to: older)
result.assertContainsInOrder("February 4", "2001", "5:20:00 PM", "Greenwich Mean Time",
"January 1", "2001", "12:00:00 AM", "Greenwich Mean Time")
}
func testStringFromDateToSameDate() throws {
let date = Date(timeIntervalSinceReferenceDate: 3e6)
// For a range from a date to itself, we represent the date only once, with no interdate separator.
let result = formatter.string(from: date, to: date)
result.assertContainsInOrder(requiresLastToBeAtEnd: true, "February 4", "2001", "5:20:00 PM", "Greenwich Mean Time")
let firstFebruary = try XCTUnwrap(result.range(of: "February"))
XCTAssertNil(result[firstFebruary.upperBound...].range(of: "February")) // February appears only once.
}
func testStringFromDateIntervalAcrossThreeMillionSeconds() throws {
let interval = DateInterval(start: Date(timeIntervalSinceReferenceDate: 0), duration: 3e6)
let result = try XCTUnwrap(formatter.string(from: interval))
result.assertContainsInOrder("January 1", "2001", "12:00:00 AM", "Greenwich Mean Time",
"February 4", "2001", "5:20:00 PM", "Greenwich Mean Time")
}
func testStringFromDateToDateAcrossOneWeek() {
formatter.dateTemplate = "MMMd"
do {
let older = Date(timeIntervalSinceReferenceDate: 0)
let newer = Date(timeIntervalSinceReferenceDate: 3600 * 24 * 7)
let result = formatter.string(from: older, to: newer)
result.assertContainsInOrder(requiresLastToBeAtEnd: true, "Jan", "1", "8")
}
do {
let older = Date(timeIntervalSinceReferenceDate: 3600 * 24 * 28)
let newer = Date(timeIntervalSinceReferenceDate: 3600 * 24 * 34)
let result = formatter.string(from: older, to: newer)
result.assertContainsInOrder(requiresLastToBeAtEnd: true, "Jan", "29", "Feb", "4")
}
}
#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS))
func testStringFromDateToDateAcrossOneWeekWithMonthMinimization() {
formatter.dateTemplate = "MMMd"
formatter.boundaryStyle = .minimizeAdjacentMonths
do {
let older = Date(timeIntervalSinceReferenceDate: 0)
let newer = Date(timeIntervalSinceReferenceDate: 3600 * 24 * 7)
let result = formatter.string(from: older, to: newer)
result.assertContainsInOrder(requiresLastToBeAtEnd: true, "Jan", "1", "8")
}
do {
let older = Date(timeIntervalSinceReferenceDate: 3600 * 24 * 28)
let newer = Date(timeIntervalSinceReferenceDate: 3600 * 24 * 34)
let result = formatter.string(from: older, to: newer)
result.assertContainsInOrder(requiresLastToBeAtEnd: true, "Jan", "29", "4")
XCTAssertNil(result.range(of: "Feb"))
}
}
#endif
func testStringFromDateToDateAcrossSixtyDays() {
formatter.dateTemplate = "MMMd"
let older = Date(timeIntervalSinceReferenceDate: 0)
let newer = Date(timeIntervalSinceReferenceDate: 3600 * 24 * 60)
let result = formatter.string(from: older, to: newer)
result.assertContainsInOrder(requiresLastToBeAtEnd: true, "Jan", "1", "Mar", "2")
}
#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS))
func testStringFromDateToDateAcrossSixtyDaysWithMonthMinimization() {
formatter.dateTemplate = "MMMd"
formatter.boundaryStyle = .minimizeAdjacentMonths
let older = Date(timeIntervalSinceReferenceDate: 0)
let newer = Date(timeIntervalSinceReferenceDate: 3600 * 24 * 60)
// Minimization shouldn't do anything since this spans more than a month
let result = formatter.string(from: older, to: newer)
result.assertContainsInOrder(requiresLastToBeAtEnd: true, "Jan", "1", "Mar", "2")
}
#endif
func testStringFromDateToDateAcrossFiveHours() throws {
do {
let older = Date(timeIntervalSinceReferenceDate: 0)
let newer = Date(timeIntervalSinceReferenceDate: 3600 * 5)
let result = formatter.string(from: older, to: newer)
result.assertContainsInOrder(requiresLastToBeAtEnd: true, "January", "1", "2001", "12:00:00 AM", "5:00:00 AM", "GMT")
let firstJanuary = try XCTUnwrap(result.range(of: "January"))
XCTAssertNil(result[firstJanuary.upperBound...].range(of: "January")) // January appears only once.
}
do {
let older = Date(timeIntervalSinceReferenceDate: 3600 * 22)
let newer = Date(timeIntervalSinceReferenceDate: 3600 * 27)
let result = formatter.string(from: older, to: newer)
result.assertContainsInOrder(requiresLastToBeAtEnd: true,
"January", "1", "2001", "10:00:00 PM", "Greenwich Mean Time",
"January", "2", "2001", "3:00:00 AM", "Greenwich Mean Time")
}
}
func testStringFromDateToDateAcrossEighteenHours() throws {
let older = Date(timeIntervalSinceReferenceDate: 0)
let newer = Date(timeIntervalSinceReferenceDate: 3600 * 18)
let result = formatter.string(from: older, to: newer)
result.assertContainsInOrder(requiresLastToBeAtEnd: true, "January", "1", "2001", "12:00:00 AM", "6:00:00 PM", "GMT")
let firstJanuary = try XCTUnwrap(result.range(of: "January"))
XCTAssertNil(result[firstJanuary.upperBound...].range(of: "January")) // January appears only once.
}
let fixtures = [Fixtures.dateIntervalFormatterDefault,
Fixtures.dateIntervalFormatterValuesSetWithoutTemplate,
Fixtures.dateIntervalFormatterValuesSetWithTemplate ]
func assertEqualAndNonnil(_ lhs: DateIntervalFormatter?, _ rhs: DateIntervalFormatter?, _ message: @autoclosure () -> String = "") throws {
XCTAssertNotNil(lhs)
XCTAssertNotNil(rhs)
let a = try XCTUnwrap(lhs)
let b = try XCTUnwrap(rhs)
XCTAssertEqual(a.dateStyle, b.dateStyle, message())
XCTAssertEqual(a.timeStyle, b.timeStyle, message())
XCTAssertEqual(a.dateTemplate, b.dateTemplate, message())
XCTAssertEqual(a.locale, b.locale, message())
if a.calendar != b.calendar {
// We're fine if the calendars are equal except for having timezones with the same name.
XCTAssertEqual(a.calendar.timeZone.identifier, b.calendar.timeZone.identifier, message())
let aWithBsTimezone = a.copy() as! DateIntervalFormatter
aWithBsTimezone.calendar.timeZone = b.calendar.timeZone
XCTAssertEqual(aWithBsTimezone.calendar, b.calendar, message())
} else {
// It's good!
XCTAssertEqual(a.calendar, b.calendar, message())
}
if a.timeZone != b.timeZone {
XCTAssertEqual(a.timeZone.identifier, b.timeZone.identifier, message())
} else {
// It's good!
XCTAssertEqual(a.timeZone, b.timeZone, message())
}
}
func testCodingRoundtrip() throws {
for fixture in fixtures {
let original = try fixture.make()
let coder = NSKeyedArchiver(forWritingWith: NSMutableData())
coder.encode(original, forKey: NSKeyedArchiveRootObjectKey)
coder.finishEncoding()
let data = coder.encodedData
let decoder = NSKeyedUnarchiver(forReadingWith: data)
let object = decoder.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as? DateIntervalFormatter
XCTAssertNil(decoder.error)
try assertEqualAndNonnil(object, original, "Comparing in-memory fixture '\(fixture.identifier)'")
}
}
func testDecodingFixtures() throws {
for fixture in fixtures {
try fixture.loadEach { (fixtureValue, variant) in
let original = try fixture.make()
try assertEqualAndNonnil(original, fixtureValue, "Comparing loaded fixture \(fixture.identifier) with variant \(variant)")
}
}
}
static var allTests: [(String, (TestDateIntervalFormatter) -> () throws -> Void)] {
var tests: [(String, (TestDateIntervalFormatter) -> () throws -> Void)] = [
("testStringFromDateToDateAcrossThreeBillionSeconds", testStringFromDateToDateAcrossThreeBillionSeconds),
("testStringFromDateToDateAcrossThreeMillionSeconds", testStringFromDateToDateAcrossThreeMillionSeconds),
("testStringFromDateToDateAcrossThreeBillionSecondsReversed", testStringFromDateToDateAcrossThreeBillionSecondsReversed),
("testStringFromDateToDateAcrossThreeMillionSecondsReversed", testStringFromDateToDateAcrossThreeMillionSecondsReversed),
("testStringFromDateToSameDate", testStringFromDateToSameDate),
("testStringFromDateIntervalAcrossThreeMillionSeconds", testStringFromDateIntervalAcrossThreeMillionSeconds),
("testStringFromDateToDateAcrossOneWeek", testStringFromDateToDateAcrossOneWeek),
("testStringFromDateToDateAcrossSixtyDays", testStringFromDateToDateAcrossSixtyDays),
("testStringFromDateToDateAcrossFiveHours", testStringFromDateToDateAcrossFiveHours),
("testStringFromDateToDateAcrossEighteenHours", testStringFromDateToDateAcrossEighteenHours),
("testCodingRoundtrip", testCodingRoundtrip),
("testDecodingFixtures", testDecodingFixtures),
]
#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS))
tests.append(contentsOf: [
("testStringFromDateToDateAcrossOneWeekWithMonthMinimization", testStringFromDateToDateAcrossOneWeekWithMonthMinimization),
("testStringFromDateToDateAcrossSixtyDaysWithMonthMinimization", testStringFromDateToDateAcrossSixtyDaysWithMonthMinimization),
])
#endif
return tests
}
}