diff --git a/Sources/Schema/DurationValue.swift b/Sources/Schema/DurationValue.swift
index 4f4cf67..80b5254 100644
--- a/Sources/Schema/DurationValue.swift
+++ b/Sources/Schema/DurationValue.swift
@@ -1,4 +1,7 @@
extension PureXML.Schema {
+ /// The outcome of comparing two durations, which form a partial order.
+ enum DurationOrder { case lessThan, equal, greaterThan, incomparable }
+
/// An `xs:duration` as its two independent signed components, months and
/// seconds, with the XSD 1.0 order relation. Because a month is 28 to 31
/// days, the order is partial: P and Q compare by adding each to the four
@@ -10,14 +13,13 @@ extension PureXML.Schema {
let months: Int
let seconds: Double
- enum Order { case lessThan, equal, greaterThan, incomparable }
-
- /// The four reference dateTimes (year, month, day) at T00:00:00Z.
- private static let references: [(year: Int, month: Int, day: Int)] = [
- (1696, 9, 1), (1697, 2, 1), (1903, 3, 1), (1903, 7, 1),
+ /// The four reference points (year, month); each is the first of its
+ /// month at T00:00:00Z, so the day is always 1 and need not be carried.
+ private static let references: [(year: Int, month: Int)] = [
+ (1696, 9), (1697, 2), (1903, 3), (1903, 7),
]
- func compare(to other: DurationValue) -> Order {
+ func compare(to other: DurationValue) -> DurationOrder {
var sawLess = false
var sawGreater = false
var sawEqual = false
@@ -40,11 +42,11 @@ extension PureXML.Schema {
return .incomparable
}
- /// The instant, in seconds, of `reference` + (months, seconds): add the
- /// months with calendar rollover (clamping the day to the resulting
- /// month's length, per the spec), then add the seconds component.
+ /// The instant, in seconds, of `reference` (day 1) + (months, seconds):
+ /// add the months with calendar rollover (clamping the day to the
+ /// resulting month's length, per the spec), then add the seconds.
private static func instant(
- _ reference: (year: Int, month: Int, day: Int),
+ _ reference: (year: Int, month: Int),
addingMonths addMonths: Int,
seconds: Double,
) -> Double {
@@ -52,7 +54,7 @@ extension PureXML.Schema {
let yearsCarried = Int((Double(total) / 12.0).rounded(.down))
let newYear = reference.year + yearsCarried
let newMonth = total - yearsCarried * 12 + 1
- let newDay = Swift.min(reference.day, DateTimeValue.daysInMonth(year: newYear, month: newMonth))
+ let newDay = Swift.min(1, DateTimeValue.daysInMonth(year: newYear, month: newMonth))
let days = DateTimeValue.daysFromCivil(year: newYear, month: newMonth, day: newDay)
return Double(days) * 86400 + seconds
}
diff --git a/Sources/Schema/SimpleType.swift b/Sources/Schema/SimpleType.swift
index 1906434..ec7a513 100644
--- a/Sources/Schema/SimpleType.swift
+++ b/Sources/Schema/SimpleType.swift
@@ -266,7 +266,7 @@ public extension PureXML.Schema {
/// the ordered range), so an incomparable result is a violation.
private func durationRangeError(_ value: String) -> String? {
guard let actual = DurationValue(value) else { return nil }
- func order(_ bound: String?) -> DurationValue.Order? {
+ func order(_ bound: String?) -> DurationOrder? {
bound.flatMap(DurationValue.init).map(actual.compare(to:))
}
if let order = order(facets.minInclusive), order == .lessThan || order == .incomparable {
diff --git a/Tests/SchemaDurationFacetTests.swift b/Tests/SchemaDurationFacetTests.swift
index d32c939..aa3f2f9 100644
--- a/Tests/SchemaDurationFacetTests.swift
+++ b/Tests/SchemaDurationFacetTests.swift
@@ -21,25 +21,25 @@ struct SchemaDurationFacetTests {
@Test("minInclusive enforces the lower bound")
func test_minInclusive() throws {
- let s = try schema(#""#)
- #expect(try s.validate("P1Y").isEmpty) // equal
- #expect(try s.validate("P2Y").isEmpty) // greater
- #expect(try !s.validate("P6M").isEmpty) // less
+ let doc = try schema(#""#)
+ #expect(try doc.validate("P1Y").isEmpty) // equal
+ #expect(try doc.validate("P2Y").isEmpty) // greater
+ #expect(try !doc.validate("P6M").isEmpty) // less
}
@Test("minExclusive rejects the bound itself")
func test_minExclusive() throws {
- let s = try schema(#""#)
- #expect(try !s.validate("P1Y").isEmpty) // equal: rejected
- #expect(try s.validate("P2Y").isEmpty) // greater
+ let doc = try schema(#""#)
+ #expect(try !doc.validate("P1Y").isEmpty) // equal: rejected
+ #expect(try doc.validate("P2Y").isEmpty) // greater
}
@Test("maxInclusive enforces the upper bound and rejects incomparable values")
func test_maxInclusivePartialOrder() throws {
- let s = try schema(#""#)
- #expect(try s.validate("P20D").isEmpty) // less
- #expect(try !s.validate("P40D").isEmpty) // greater
+ let doc = try schema(#""#)
+ #expect(try doc.validate("P20D").isEmpty) // less
+ #expect(try !doc.validate("P40D").isEmpty) // greater
// P1M is incomparable to P30D (28..31 days) so it is not <= P30D.
- #expect(try !s.validate("P1M").isEmpty)
+ #expect(try !doc.validate("P1M").isEmpty)
}
}
diff --git a/Tests/SchemaListFacetTests.swift b/Tests/SchemaListFacetTests.swift
index 77c2543..0a4caf9 100644
--- a/Tests/SchemaListFacetTests.swift
+++ b/Tests/SchemaListFacetTests.swift
@@ -21,18 +21,18 @@ struct SchemaListFacetTests {
@Test("length on NMTOKENS counts items, not characters")
func test_nmtokensLength() throws {
- let s = try schema("xs:NMTOKENS", #""#)
- #expect(try s.validate("alpha beta gamma").isEmpty) // 3 items: valid
- #expect(try !s.validate("alpha beta").isEmpty) // 2 items: invalid
- #expect(try !s.validate("alpha beta gamma delta").isEmpty) // 4 items: invalid
+ let doc = try schema("xs:NMTOKENS", #""#)
+ #expect(try doc.validate("alpha beta gamma").isEmpty) // 3 items: valid
+ #expect(try !doc.validate("alpha beta").isEmpty) // 2 items: invalid
+ #expect(try !doc.validate("alpha beta gamma delta").isEmpty) // 4 items: invalid
}
@Test("minLength and maxLength on IDREFS count items")
func test_idrefsMinMax() throws {
- let s = try schema("xs:IDREFS", #""#)
- #expect(try s.validate("a b").isEmpty) // 2 items: valid
- #expect(try s.validate("a b c").isEmpty) // 3 items: valid
- #expect(try !s.validate("a").isEmpty) // 1 item: invalid
- #expect(try !s.validate("a b c d").isEmpty) // 4 items: invalid
+ let doc = try schema("xs:IDREFS", #""#)
+ #expect(try doc.validate("a b").isEmpty) // 2 items: valid
+ #expect(try doc.validate("a b c").isEmpty) // 3 items: valid
+ #expect(try !doc.validate("a").isEmpty) // 1 item: invalid
+ #expect(try !doc.validate("a b c d").isEmpty) // 4 items: invalid
}
}
diff --git a/Tests/SchemaUnionFacetTests.swift b/Tests/SchemaUnionFacetTests.swift
index f60e480..8e07115 100644
--- a/Tests/SchemaUnionFacetTests.swift
+++ b/Tests/SchemaUnionFacetTests.swift
@@ -23,31 +23,31 @@ struct SchemaUnionFacetTests {
@Test("Enumeration on a union restricts to the listed values")
func test_enumeration() throws {
- let s = try schema(#""#)
- #expect(try s.validate("5").isEmpty) // listed
- #expect(try s.validate("foo").isEmpty) // listed
- #expect(try !s.validate("7").isEmpty) // valid int, not listed
- #expect(try !s.validate("bar").isEmpty) // valid string, not listed
+ let doc = try schema(#""#)
+ #expect(try doc.validate("5").isEmpty) // listed
+ #expect(try doc.validate("foo").isEmpty) // listed
+ #expect(try !doc.validate("7").isEmpty) // valid int, not listed
+ #expect(try !doc.validate("bar").isEmpty) // valid string, not listed
}
@Test("Union enumeration compares in value space, not lexically")
func test_enumerationValueSpace() throws {
- let s = try schema(#""#)
+ let doc = try schema(#""#)
// 05 is a valid xs:int equal to the enumerated 5, so it is in the set.
- #expect(try s.validate("05").isEmpty)
+ #expect(try doc.validate("05").isEmpty)
}
@Test("Pattern on a union constrains the lexical value")
func test_pattern() throws {
- let s = try schema(#""#)
- #expect(try s.validate("abc").isEmpty) // valid string, matches
- #expect(try !s.validate("5").isEmpty) // valid int, fails pattern
+ let doc = try schema(#""#)
+ #expect(try doc.validate("abc").isEmpty) // valid string, matches
+ #expect(try !doc.validate("5").isEmpty) // valid int, fails pattern
}
@Test("A union with no facets still admits any member value")
func test_noFacets() throws {
- let s = try schema("")
- #expect(try s.validate("42").isEmpty)
- #expect(try s.validate("anything").isEmpty)
+ let doc = try schema("")
+ #expect(try doc.validate("42").isEmpty)
+ #expect(try doc.validate("anything").isEmpty)
}
}