1
0
mirror of https://github.com/apple/swift-foundation.git synced 2025-05-16 01:42:23 +08:00

Compare commits

...

10 Commits

Author SHA1 Message Date
Alex Hoppen
2292b40ccd
Merge b9a6d4d0b12d26195c68ccf304108199a21099b9 into fa43c96f5f91baaa0a86a1c44f9158ee36861c93 2025-03-19 08:43:59 +13:00
Christopher Thielen
fa43c96f5f
Move temporarily relocated FileManager extensions back to their original locations () 2025-03-18 11:29:53 -07:00
michael-yuji
2f65d91455
Fix _copyDirectoryMetadata compilation error on FreeBSD ()
* Fix FreeBSD build

* address comments
2025-03-17 14:00:41 -07:00
Tony Parker
117041b61f
ISO8601ComponentStyle proposal ()
* Add ISO8601ComponentStyle proposal

* Update Proposals/NNNN-ISO8601ComponentsStyle.md

* Update Proposals/NNNN-ISO8601ComponentsStyle.md

* Rename NNNN-ISO8601ComponentsStyle.md to 0021-ISO8601ComponentsStyle.md

* Update Proposals/0021-ISO8601ComponentsStyle.md

* Update Proposals/0021-ISO8601ComponentsStyle.md

---------

Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com>
2025-03-14 11:06:05 -07:00
Daniel Eggert
b95199858b
[Proposal] URI Templating ()
* Proposal: URI Templating

* throwing → fallible

* Clarifications, typos, grammar.

* Add links to implementation and pitch.

* Change status to “awaiting review”.

* Use failing initializer on URL instead of makeURL()

* Update metadata

* Rename
2025-03-13 17:42:40 -07:00
Tony Parker
a95c2c8d43
Clean up some NO_FILESYSTEM paths () 2025-03-13 12:50:39 -07:00
Tony Parker
2ea76c322f
Add an HTTP format style for both Date and DateComponents ()
* Add an HTTP format style for both `Date` and `DateComponents`.

* Correct the comments to properly use DateComponents type

* Use default values instead of crashing when formatting date components with missing or invalid fields

* Use 59 seconds instead of 0 for leap second of 60 - this results in being off by only 1 second in this case instead of 59.

* Address some review feedback
2025-03-13 11:39:57 -07:00
Jeremy Schonfeld
4c021b6e57
Make trivial AttributedString dynamicMemberLookup subscripts inlinable () 2025-03-12 12:43:14 -07:00
Tina L
6cde050fb3
Introduce compatibility behavior of Calendar.date(byAdding:value:to:wrappingComponents) ()
Some app is passing in `value: -9223372036854775808` to the function below, and force unwrapping the result. It then crashes when the force unwrap fails.

```swift
/// - returns: A new date, or nil if a date could not be calculated with the given input.
public func date(byAdding component: Component, value: Int, to date: Date, wrappingComponents: Bool = false) -> Date?
```

This is caused by . Prior to the change, we were using `_CalendarICU`'s implementation, where we truncate the input value to `Int32`, which becomes 0 in this case. That results in us returning the input `date` unchanged.

Now with , we truthfully return `nil` because the calculation cannot be done.

We could restore the old behavior, but that implementation is incorrect. It's also clearly a wrong assumption on the client side. Add a compatibility check and restore the old behavior if needed.

Resolves 145862455
2025-03-12 09:12:30 -07:00
Tina L
3c9559d68a
Revert ()
Revert https://github.com/swiftlang/swift-foundation/pull/1160 because it caused some app to fail to launch.
2025-03-11 11:59:44 -07:00
23 changed files with 1533 additions and 969 deletions

@ -0,0 +1,256 @@
# URI Templating
* Proposal: [SF-00020](00020-uri-templating.md)
* Authors: [Daniel Eggert](https://github.com/danieleggert)
* Review Manager: [Tina L](https://github.com/itingliu)
* Status: **Review: March 14, 2025...March 21, 2025**
* Implementation: [swiftlang/swift-foundation#1198](https://github.com/swiftlang/swift-foundation/pull/1198)
* Review: ([pitch](https://forums.swift.org/t/pitch-uri-templating/78030))
## Introduction
This proposal adds support for [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) _URI templates_ to the Swift URL type.
Although there are multiple levels of expansion, the core concept is that you can define a _template_ such as
```
http://example.com/~{username}/
http://example.com/dictionary/{term:1}/{term}
http://example.com/search{?q,lang}
```
and then _expand_ these using named values (i.e. a dictionary) into a `URL`.
The templating has a rich set of options for substituting various parts of URLs. [RFC 6570 section 1.2](https://datatracker.ietf.org/doc/html/rfc6570#section-1.2) lists all 4 levels of increasing complexity.
## Motivation
[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) provides a simple, yet powerful way to allow for variable expansion in URLs.
This provides a mechanism for a server to convey to clients how to construct URLs for specific resources. In the [RFC 8620 JMAP protocol](https://datatracker.ietf.org/doc/html/rfc8620) for example, the server sends it client a template such as
```
https://jmap.example.com/download/{accountId}/{blobId}/{name}?accept={type}
```
and the client can then use variable expansion to construct a URL for resources. The API contract between the server and the client defines which variables this specific template has, and which ones are optional.
Since URI templates provide a powerful way to define URL patterns with placeholders, they are adopted in various standards.
## Proposed solution
```swift
guard
let template = URL.Template("http://www.example.com/foo{?query,number}"),
let url = URL(
template: template,
variables: [
"query": "bar baz",
"number": "234",
]
)
else { return }
```
The RFC 6570 template gets parsed as part of the `URL.Template(_:)` initializer. It will return `nil` if the passed in string is not a valid template.
The `Template` can then be expanded with _variables_ to create a URL:
```swift
extension URL {
public init?(
template: URL.Template,
variables: [URL.Template.VariableName: URL.Template.Value]
)
}
```
## Detailed design
### Templates and Expansion
[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) defines 8 different kinds of expansions:
* Simple String Expansion: `{var}`
* Reserved Expansion: `{+var}`
* Fragment Expansion: `{#var}`
* Label Expansion with Dot-Prefix: `{.var}`
* Path Segment Expansion: `{/var}`
* Path-Style Parameter Expansion: `{;var}`
* Form-Style Query Expansion: `{?var}`
* Form-Style Query Continuation: `{&var}`
Additionally, RFC 6570 allows for _prefix values_ and _composite values_.
Prefix values allow for e.g. `{var:3}` which would result in (up to) the first 3 characters of `var`.
Composite values allow `/mapper{?address*}` to e.g. expand into `/mapper?city=Newport%20Beach&state=CA`.
This implementation covers all levels and expression types defined in the RFC.
### API Details
There are 3 new types:
* `URL.Template`
* `URL.Template.VariableName`
* `URL.Template.Value`
All new API is guarded by `@available(FoundationPreview 6.2, *)`.
#### `Template`
`URL.Template` represents a parsed template that can be used to create a `URL` from it by _expanding_ variables according to RFC 6570.
Its sole API is its initializer:
```swift
extension URL {
/// A template for constructing a URL from variable expansions.
///
/// This is an template that can be expanded into
/// a ``URL`` by calling ``URL(template:variables:)``.
///
/// Templating has a rich set of options for substituting various parts of URLs. See
/// [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) for
/// details.
public struct Template: Sendable, Hashable {}
}
extension URL.Template {
/// Creates a new template from its text form.
///
/// The template string needs to be a valid RFC 6570 template.
///
/// If parsing the template fails, this will return `nil`.
public init?(_ template: String)
}
```
It will return `nil` if the provided string can not be parsed as a valid template.
#### Variables
Variables are represented as a `[URL.Template.VariableName: URL.Template.Value]`.
#### `VariableName`
The `URL.Template.VariableName` type is a type-safe wrapper around `String`
```swift
extension URL.Template {
/// The name of a variable used for expanding a template.
public struct VariableName: Sendable, Hashable {
public init(_ key: String)
}
}
```
The following extensions and conformances make it easy to convert between `VariableName` and `String`:
```swift
extension String {
public init(_ key: URL.Template.VariableName)
}
extension URL.Template.VariableName: CustomStringConvertible {
public var description: String
}
extension URL.Template.VariableName: ExpressibleByStringLiteral {
public init(stringLiteral value: String)
}
```
#### `Value`
The `URL.Template.Value` type can represent the 3 different kinds of values that RFC 6570 supports:
```swift
extension URL.Template {
/// The value of a variable used for expanding a template.
///
/// A ``Value`` can be one of 3 kinds:
/// 1. "text": a single `String`
/// 2. "list": an array of `String`
/// 3. "associative list": an ordered array of key-value pairs of `String` (similar to `Dictionary`, but ordered).
public struct Value: Sendable, Hashable {}
}
extension URL.Template.Value {
/// A text value to be used with a ``URL.Template``.
public static func text(_ text: String) -> URL.Template.Value
/// A list value (an array of `String`s) to be used with a ``URL.Template``.
public static func list(_ list: some Sequence<String>) -> URL.Template.Value
/// An associative list value (ordered key-value pairs) to be used with a ``URL.Template``.
public static func associativeList(_ list: some Sequence<(key: String, value: String)>) -> URL.Template.Value
}
```
To make it easier to use hard-coded values, the following `ExpressibleBy…` conformances are provided:
```swift
extension URL.Template.Value: ExpressibleByStringLiteral {
public init(stringLiteral value: String)
}
extension URL.Template.Value: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: String...)
}
extension URL.Template.Value: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, String)...)
}
```
#### Expansion to `URL`
Finally, `URL.Template` has this factory method:
```swift
extension URL {
/// Creates a new `URL` by expanding the RFC 6570 template and variables.
///
/// This will fail if variable expansion does not produce a valid,
/// well-formed URL.
///
/// All text will be converted to NFC (Unicode Normalization Form C) and UTF-8
/// before being percent-encoded if needed.
///
/// - Parameters:
/// - template: The RFC 6570 template to be expanded.
/// - variables: Variables to expand in the template.
public init?(
template: URL.Template,
variables: [URL.Template.VariableName: URL.Template.Value]
)
}
```
This will only fail (return `nil`) if `URL.init?(string:)` fails.
It may seem counterintuitive when and how this could fail, but a string such as `http://example.com:bad%port/` would cause `URL.init?(string:)` to fail, and URI Templates do not provide a way to prevent this. It is also worth noting that it is valid to not provide values for all variables in the template. Expansion will still succeed, generating a string. If this string is a valid URL, depends on the exact details of the template. Determining which variables exist in a template, which are required for expansion, and whether the resulting URL is valid is part of the API contract between the server providing the template and the client generating the URL.
Additionally, the new types `URL.Template`, `URL.Template.VariableName`, and `URL.Template.Value` all conform to `CustomStringConvertible`.
### Unicode
The _expansion_ that happens as part of calling `URL(template:variables:)` will
* convert text to NFC (Unicode Normalization Form C)
* convert text to UTF-8 before being percent-encoded (if needed).
as per [RFC 6570 section 1.6](https://datatracker.ietf.org/doc/html/rfc6570#section-1.6).
## Source compatibility
These changes are additive only and are not expected to have an impact on source compatibility.
## Implications on adoption
This feature can be freely adopted and un-adopted in source code with no deployment constraints and without affecting source compatibility.
## Future directions
Since this proposal covers all of RFC 6570, the current expectation is for it to not be extended further.
## Alternatives considered
Instead of `URL.init?(template:variables)`, the API could have used a method on `URL.Template`, e.g. `URL.Template.makeURL(variables:)`. The `URL.init?` approach would be less discoverable. There was some feedback to the initial pitch, though, that preferred the `URL.init?` method which aligns with the existing `URL.init?(string:)` initializer. This initializer approach aligns more with existing `URL.init?(string:)` and `String.init(format:)`.
Additionally, the API _could_ expose a (non-failing!) `URL.Template.expand(variables:)` (or other naming) that returns a `String`. But since the purpose is very clearly to create URLs, it feels like that would just add noise.
Using a DSL (domain-specific language) for `URL.Template` could improve type safety. However, because servers typically send templates as strings for client-side processing and request generation, the added complexity of a DSL outweighs its benefits. The proposed implementation is string-based (_stringly typed_) because that is what the RFC 6570 mandates.
There was a lot of interest during the pitch to have an API that lends itself to _routing_, providing a way to go back-and-forth between a route and its variables. But thats a very different use case than the RFC 6570 templates provide, and it would be better suited to have a web server routing specific API, either in Foundation or in a web server specific package. [pointfreeco/swift-url-routing](https://github.com/pointfreeco/swift-url-routing) is one such example.
Instead of using the `text`, `list` and `associativeList` names (which are _terms of art_ in RFC 6570), the names `string`, `array`, and `orderedDictioary` would align better with normal Swift naming conventions. The proposal favors the _terms of art_, but there was some interest in using changing this.

@ -0,0 +1,212 @@
# ISO8601 Components Formatting and Parsing
* Proposal: SF-0021
* Author(s): Tony Parker <anthony.parker@apple.com>
* Status: **Review: March 17, 2025...March 24, 2025**
* Intended Release: _Swift 6.2_
* Review: ([pitch](https://forums.swift.org/t/pitch-iso8601-components-format-style/77990))
*_Related issues_*
* https://github.com/swiftlang/swift-foundation/issues/323
* https://github.com/swiftlang/swift-foundation/issues/967
* https://github.com/swiftlang/swift-foundation/issues/1159
## Revision history
* **v1** Initial version
## Introduction
Based upon feedback from adoption of `ISO8601FormatStyle`, we propose two changes and one addition to the API:
- Change the behavior of the `includingFractionalSeconds` flag with respect to parsing. `ISO8601FormatStyle` will now always allow fractional seconds regardless of the setting.
- Change the behavior of the time zone flag with respect to parsing. `ISO8601FormatStyle` will now always allow hours-only time zone offsets.
- Add a _components_ style, which formats `DateComponents` into ISO8601 and parses ISO8601-formatted `String`s into `DateComponents`.
## Motivation
The existing `Date.ISO8601FormatStyle` type has one property for controlling fractional seconds, and it is also settable in the initializer. The existing behavior is that parsing _requires_ presence of fractional seconds if set, and _requires_ absence of fractional seconds if not set.
If the caller does not know if the string contains the fractional seconds or not, they are forced to parse the string twice:
```swift
let str = "2022-01-28T15:35:46Z"
var result = try? Date.ISO8601FormatStyle(includingFractionalSeconds: false).parse(str)
if result == nil {
result = try? Date.ISO8601FormatStyle(includingFractionalSeconds: true).parse(str)
}
```
In most cases, the caller simply does not care if the fractional seconds are present or not. Therefore, we propose changing the behavior of the parser to **always** allow fractional seconds, regardless of the setting of the `includeFractionalSeconds` flag. The flag is still used for formatting.
With respect to time zone offsets, the parser has always allowed the optional presence of seconds, as well the optional presence of `:`. We propose extending this behavior to allow optional minutes as well. The following are considered well-formed by the parser:
```
2022-01-28T15:35:46 +08
2022-01-28T15:35:46 +0800
2022-01-28T15:35:46 +080000
2022-01-28T15:35:46 +08
2022-01-28T15:35:46 +08:00
2022-01-28T15:35:46 +08:00:00
```
In order to provide an alternative for cases where strict parsing is required, a new parser is provided that returns the _components_ of the parsed date instead of the resolved `Date` itself. This new parser also provides a mechanism to retrieve the time zone from an ISO8601-formatted string. Following parsing of the components, the caller can resolve them into a `Date` using the regular `Calendar` and `DateComponents` API.
## Proposed solution and example
In addition to the behavior change above, we propose introducing a new `DateComponents.ISO8601FormatStyle`. The API surface is nearly identical to `Date.ISO8601FormatStyle`, with the exception of the output type. It reuses the same inner types, and they share a common implementation. The full API surface is in the detailed design, below.
Formatting ISO8601 components is just as straightforward as formatting a `Date`.
```swift
let components = DateComponents(year: 1999, month: 12, day: 31, hour: 23, minute: 59, second: 59)
let formatted = components.formatted(.iso8601Components)
print(formatted) // 1999-12-31T23:59:59Z
```
Parsing ISO8601 components follows the same pattern as other parse strategies:
```swift
let components = try DateComponents.ISO8601FormatStyle().parse("2022-01-28T15:35:46Z")
// components are: DateComponents(timeZone: .gmt, year: 2022, month: 1, day: 28, hour: 15, minute: 35, second: 46))
```
If further conversion to a `Date` is required, the existing `Calendar` API can be used:
```swift
let date = components.date // optional result, date may be invalid
```
## Detailed design
The full API surface of the new style is:
```swift
@available(FoundationPreview 6.2, *)
extension DateComponents {
/// Options for generating and parsing string representations of dates following the ISO 8601 standard.
public struct ISO8601FormatStyle : Sendable, Codable, Hashable {
public var timeSeparator: Date.ISO8601FormatStyle.TimeSeparator { get }
/// If set, fractional seconds will be present in formatted output. Fractional seconds may be present in parsing regardless of the setting of this property.
public var includingFractionalSeconds: Bool { get }
public var timeZoneSeparator: Date.ISO8601FormatStyle.TimeZoneSeparator { get }
public var dateSeparator: Date.ISO8601FormatStyle.DateSeparator { get }
public var dateTimeSeparator: Date.ISO8601FormatStyle.DateTimeSeparator { get }
public init(from decoder: any Decoder) throws
public func encode(to encoder: any Encoder) throws
public func hash(into hasher: inout Hasher)
public static func ==(lhs: ISO8601FormatStyle, rhs: ISO8601FormatStyle) -> Bool
public var timeZone: TimeZone = TimeZone(secondsFromGMT: 0)!
// The default is the format of RFC 3339 with no fractional seconds: "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
public init(dateSeparator: Date.ISO8601FormatStyle.DateSeparator = .dash, dateTimeSeparator: Date.ISO8601FormatStyle.DateTimeSeparator = .standard, timeSeparator: Date.ISO8601FormatStyle.TimeSeparator = .colon, timeZoneSeparator: Date.ISO8601FormatStyle.TimeZoneSeparator = .omitted, includingFractionalSeconds: Bool = false, timeZone: TimeZone = TimeZone(secondsFromGMT: 0)!)
}
}
@available(FoundationPreview 6.2, *)
extension DateComponents.ISO8601FormatStyle {
public func year() -> Self
public func weekOfYear() -> Self
public func month() -> Self
public func day() -> Self
public func time(includingFractionalSeconds: Bool) -> Self
public func timeZone(separator: Date.ISO8601FormatStyle.TimeZoneSeparator) -> Self
public func dateSeparator(_ separator: Date.ISO8601FormatStyle.DateSeparator) -> Self
public func dateTimeSeparator(_ separator: Date.ISO8601FormatStyle.DateTimeSeparator) -> Self
public func timeSeparator(_ separator: Date.ISO8601FormatStyle.TimeSeparator) -> Self
public func timeZoneSeparator(_ separator: Date.ISO8601FormatStyle.TimeZoneSeparator) -> Self
}
@available(FoundationPreview 6.2, *)
extension DateComponents.ISO8601FormatStyle : FormatStyle {
public func format(_ value: DateComponents) -> String
}
@available(FoundationPreview 6.2, *)
public extension FormatStyle where Self == DateComponents.ISO8601FormatStyle {
static var iso8601Components: Self
}
@available(FoundationPreview 6.2, *)
public extension ParseableFormatStyle where Self == DateComponents.ISO8601FormatStyle {
static var iso8601Components: Self
}
@available(FoundationPreview 6.2, *)
public extension ParseStrategy where Self == DateComponents.ISO8601FormatStyle {
@_disfavoredOverload
static var iso8601Components: Self
}
@available(FoundationPreview 6.2, *)
extension DateComponents.ISO8601FormatStyle : ParseStrategy {
public func parse(_ value: String) throws -> DateComponents
}
@available(FoundationPreview 6.2, *)
extension DateComponents.ISO8601FormatStyle: ParseableFormatStyle {
public var parseStrategy: Self
}
@available(FoundationPreview 6.2, *)
extension DateComponents.ISO8601FormatStyle : CustomConsumingRegexComponent {
public typealias RegexOutput = DateComponents
public func consuming(_ input: String, startingAt index: String.Index, in bounds: Range<String.Index>) throws -> (upperBound: String.Index, output: DateComponents)?
}
@available(FoundationPreview 6.2, *)
extension RegexComponent where Self == DateComponents.ISO8601FormatStyle {
@_disfavoredOverload
public static var iso8601Components: DateComponents.ISO8601FormatStyle
public static func iso8601ComponentsWithTimeZone(includingFractionalSeconds: Bool = false, dateSeparator: Date.ISO8601FormatStyle.DateSeparator = .dash, dateTimeSeparator: Date.ISO8601FormatStyle.DateTimeSeparator = .standard, timeSeparator: Date.ISO8601FormatStyle.TimeSeparator = .colon, timeZoneSeparator: Date.ISO8601FormatStyle.TimeZoneSeparator = .omitted) -> Self
public static func iso8601Components(timeZone: TimeZone, includingFractionalSeconds: Bool = false, dateSeparator: Date.ISO8601FormatStyle.DateSeparator = .dash, dateTimeSeparator: Date.ISO8601FormatStyle.DateTimeSeparator = .standard, timeSeparator: Date.ISO8601FormatStyle.TimeSeparator = .colon) -> Self
public static func iso8601Components(timeZone: TimeZone, dateSeparator: Date.ISO8601FormatStyle.DateSeparator = .dash) -> Self
}
```
Unlike the `Date` format style, formatting with a `DateComponents` style can have a mismatch between the specified output fields and the contents of the `DateComponents` struct. In the case where the input `DateComponents` is missing required values, then the formatter will fill in default values to ensure correct output.
```swift
let components = DateComponents(year: 1999, month: 12, day: 31)
let formatted = components.formatted(.iso8601Components) // 1999-12-31T00:00:00Z
```
## Impact on existing code
The change to always allow fractional seconds will affect existing code. As described above, we believe the improvement in the API surface is worth the risk of introducing unexpected behavior for the rare case that a parser truly needs to specify the exact presence or absence of frational seconds.
If code depending on this new behavior must be backdeployed before Swift 6.2, then Swift's `if #available` checks may be used to parse twice on older releases of the OS or Swift.
## Alternatives considered
### "Allowing" Option
We considered adding a new flag to `Date.ISO8601FormatStyle` to control the optional parsing of fractional seconds. However, the truth table quickly became confusing:
#### Formatting
| `includingFractionalSeconds` | `allowingFractionalSeconds` | Fractional Seconds |
| -- | -- | -- |
| `true` | `true` | Included |
| `true` | `false` | Included |
| `false` | `true` | Excluded |
| `false` | `true` | Excluded |
#### Parsing
| `includingFractionalSeconds` | `allowingFractionalSeconds` | Fractional Seconds |
| -- | -- | -- |
| `true` | `true` | Required Present |
| `true` | `false` | Required Present |
| `false` | `true` | ? |
| `false` | `true` | Allow Present or Missing |
In addition, all the initializers needed to be duplicated to add the new option.
In practice, the additional complexity did not seem worth the tradeoff with the potential for a compatibility issue with the existing style. This does require callers to be aware that the behavior has changed in the release in which this feature ships. Therefore, we will be clear in the documentation about the change.

@ -33,6 +33,7 @@ extension AttributeContainer {
}
@preconcurrency
@inlinable // Trivial implementation, allows callers to optimize away the keypath allocation
public subscript<K: AttributedStringKey>(dynamicMember keyPath: KeyPath<AttributeDynamicLookup, K>) -> K.Value? where K.Value : Sendable {
get { self[K.self] }
set { self[K.self] = newValue }

@ -81,6 +81,7 @@ extension AttributedString.Runs.Run {
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension AttributedString.Runs.Run {
@preconcurrency
@inlinable // Trivial implementation, allows callers to optimize away the keypath allocation
public subscript<K: AttributedStringKey>(
dynamicMember keyPath: KeyPath<AttributeDynamicLookup, K>
) -> K.Value?

@ -243,6 +243,7 @@ extension AttributedString: AttributedStringProtocol {
}
@preconcurrency
@inlinable // Trivial implementation, allows callers to optimize away the keypath allocation
public subscript<K: AttributedStringKey>(
dynamicMember keyPath: KeyPath<AttributeDynamicLookup, K>
) -> K.Value? where K.Value: Sendable {

@ -177,6 +177,7 @@ extension AttributedSubstring {
}
@preconcurrency
@inlinable // Trivial implementation, allows callers to optimize away the keypath allocation
public subscript<K: AttributedStringKey>(
dynamicMember keyPath: KeyPath<AttributeDynamicLookup, K>
) -> K.Value? where K.Value : Sendable {

@ -194,6 +194,7 @@ extension DiscontiguousAttributedSubstring {
}
}
@inlinable // Trivial implementation, allows callers to optimize away the keypath allocation
public subscript<K: AttributedStringKey>(
dynamicMember keyPath: KeyPath<AttributeDynamicLookup, K>
) -> K.Value? where K.Value : Sendable {

@ -601,7 +601,14 @@ public struct Calendar : Hashable, Equatable, Sendable {
return nil
}
return self.date(byAdding: dc, to: date.capped, wrappingComponents: wrappingComponents)
let result = self.date(byAdding: dc, to: date.capped, wrappingComponents: wrappingComponents)
#if FOUNDATION_FRAMEWORK
// Compatibility path - we found some apps depending on the result being non-nil
if Calendar.compatibility2 {
return result ?? date
}
#endif
return result
}
/// Returns a sequence of `Date`s, calculated by adding a scaled amount of `Calendar.Component`s to a starting `Date`.
@ -674,6 +681,19 @@ public struct Calendar : Hashable, Equatable, Sendable {
return dc
}
/// Same as `dateComponents:from:` but uses the more efficient bitset form of ComponentSet.
/// Prefixed with `_` to avoid ambiguity at call site with the `Set<Component>` method.
internal func _dateComponents(_ components: ComponentSet, from date: Date, in timeZone: TimeZone) -> DateComponents {
var dc = _calendar.dateComponents(components, from: date.capped, in: timeZone)
// Fill out the Calendar field of dateComponents, if requested.
if components.contains(.calendar) {
dc.calendar = self
}
return dc
}
/// Returns all the date components of a date, as if in a given time zone (instead of the `Calendar` time zone).
///
/// The time zone overrides the time zone of the `Calendar` for the purposes of this calculation.

@ -55,3 +55,29 @@ extension Date {
}
#endif // FOUNDATION_FRAMEWORK
}
@available(FoundationPreview 6.2, *)
extension DateComponents {
/// Converts `self` to its textual representation.
/// - Parameter format: The format for formatting `self`.
/// - Returns: A representation of `self` using the given `format`. The type of the representation is specified by `FormatStyle.FormatOutput`.
public func formatted<F: FormatStyle>(_ format: F) -> F.FormatOutput where F.FormatInput == DateComponents {
format.format(self)
}
// Parsing
/// Creates a new `DateComponents` by parsing the given representation.
/// - Parameter value: A representation of a date. The type of the representation is specified by `ParseStrategy.ParseInput`.
/// - Parameters:
/// - value: A representation of a date. The type of the representation is specified by `ParseStrategy.ParseInput`.
/// - strategy: The parse strategy to parse `value` whose `ParseOutput` is `DateComponents`.
public init<T: ParseStrategy>(_ value: T.ParseInput, strategy: T) throws where T.ParseOutput == Self {
self = try strategy.parse(value)
}
/// Creates a new `DateComponents` by parsing the given string representation.
@_disfavoredOverload
public init<T: ParseStrategy, Value: StringProtocol>(_ value: Value, strategy: T) throws where T.ParseOutput == Self, T.ParseInput == String {
self = try strategy.parse(String(value))
}
}

@ -10,20 +10,10 @@
//
//===----------------------------------------------------------------------===//
#if canImport(Darwin)
import Darwin
#elseif canImport(Bionic)
import Bionic
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#elseif os(Windows)
import CRT
import WinSDK
#elseif os(WASI)
import WASILibc
#endif
private enum Base64Error: Error {
case invalidElementCount
case cannotDecode
}
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
extension Data {
@ -36,10 +26,21 @@ extension Data {
/// - parameter base64String: The string to parse.
/// - parameter options: Encoding options. Default value is `[]`.
public init?(base64Encoded base64String: __shared String, options: Base64DecodingOptions = []) {
guard let result = try? Base64.decode(string: base64String, options: options) else {
#if FOUNDATION_FRAMEWORK
if let d = NSData(base64Encoded: base64String, options: NSData.Base64DecodingOptions(rawValue: options.rawValue)) {
self.init(referencing: d)
} else {
return nil
}
self = result
#else
var encoded = base64String
let decoded = encoded.withUTF8 {
// String won't pass an empty buffer with a `nil` `baseAddress`.
Data(decodingBase64: BufferView(unsafeBufferPointer: $0)!, options: options)
}
guard let decoded else { return nil }
self = decoded
#endif
}
/// Initialize a `Data` from a Base-64, UTF-8 encoded `Data`.
@ -49,10 +50,39 @@ extension Data {
/// - parameter base64Data: Base-64, UTF-8 encoded input data.
/// - parameter options: Decoding options. Default value is `[]`.
public init?(base64Encoded base64Data: __shared Data, options: Base64DecodingOptions = []) {
guard let result = try? Base64.decode(data: base64Data, options: options) else {
#if FOUNDATION_FRAMEWORK
if let d = NSData(base64Encoded: base64Data, options: NSData.Base64DecodingOptions(rawValue: options.rawValue)) {
self.init(referencing: d)
} else {
return nil
}
self = result
#else
let decoded = base64Data.withBufferView {
Data(decodingBase64: $0, options: options)
}
guard let decoded else { return nil }
self = decoded
#endif
}
init?(decodingBase64 bytes: borrowing BufferView<UInt8>, options: Base64DecodingOptions = []) {
guard bytes.count.isMultiple(of: 4) || options.contains(.ignoreUnknownCharacters)
else { return nil }
// Every 4 valid ASCII bytes maps to 3 output bytes: (bytes.count * 3)/4
let capacity = (bytes.count * 3) >> 2
// A non-trapping version of the calculation goes like this:
// let (q, r) = bytes.count.quotientAndRemainder(dividingBy: 4)
// let capacity = (q * 3) + (r==0 ? 0 : r-1)
let decoded = try? Data(
capacity: capacity,
initializingWith: { //FIXME: should work with borrowed `bytes`
[bytes = copy bytes] in
try Data.base64DecodeBytes(bytes, &$0, options: options)
}
)
guard let decoded else { return nil }
self = decoded
}
// MARK: - Create base64
@ -72,6 +102,110 @@ extension Data {
public func base64EncodedData(options: Base64EncodingOptions = []) -> Data {
Base64.encodeToData(bytes: self, options: options)
}
// MARK: - Internal Helpers
/**
This method decodes Base64-encoded data.
If the input contains any bytes that are not valid Base64 characters,
this will throw a `Base64Error`.
- parameter bytes: The Base64 bytes
- parameter output: An OutputBuffer to be filled with decoded bytes
- parameter options: Options for handling invalid input
- throws: When decoding fails
*/
static func base64DecodeBytes(
_ bytes: borrowing BufferView<UInt8>, _ output: inout OutputBuffer<UInt8>, options: Base64DecodingOptions = []
) throws {
guard bytes.count.isMultiple(of: 4) || options.contains(.ignoreUnknownCharacters)
else { throw Base64Error.invalidElementCount }
// This table maps byte values 0-127, input bytes >127 are always invalid.
// Map the ASCII characters "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -> 0...63
// Map '=' (ASCII 61) to 0x40.
// All other values map to 0x7f. This allows '=' and invalid bytes to be checked together by testing bit 6 (0x40).
let base64Decode: StaticString = """
\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\
\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\
\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{3e}\u{7f}\u{7f}\u{7f}\u{3f}\
\u{34}\u{35}\u{36}\u{37}\u{38}\u{39}\u{3a}\u{3b}\u{3c}\u{3d}\u{7f}\u{7f}\u{7f}\u{40}\u{7f}\u{7f}\
\u{7f}\u{00}\u{01}\u{02}\u{03}\u{04}\u{05}\u{06}\u{07}\u{08}\u{09}\u{0a}\u{0b}\u{0c}\u{0d}\u{0e}\
\u{0f}\u{10}\u{11}\u{12}\u{13}\u{14}\u{15}\u{16}\u{17}\u{18}\u{19}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\
\u{7f}\u{1a}\u{1b}\u{1c}\u{1d}\u{1e}\u{1f}\u{20}\u{21}\u{22}\u{23}\u{24}\u{25}\u{26}\u{27}\u{28}\
\u{29}\u{2a}\u{2b}\u{2c}\u{2d}\u{2e}\u{2f}\u{30}\u{31}\u{32}\u{33}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}
"""
assert(base64Decode.isASCII)
assert(base64Decode.utf8CodeUnitCount == 128)
assert(base64Decode.hasPointerRepresentation)
let ignoreUnknown = options.contains(.ignoreUnknownCharacters)
var currentByte: UInt8 = 0
var validCharacterCount = 0
var paddingCount = 0
var index = 0
for base64Char in bytes {
var value: UInt8 = 0
var invalid = false
if base64Char >= base64Decode.utf8CodeUnitCount {
invalid = true
} else {
value = base64Decode.utf8Start[Int(base64Char)]
if value & 0x40 == 0x40 { // Input byte is either '=' or an invalid value.
if value == 0x7f {
invalid = true
} else if value == 0x40 { // '=' padding at end of input.
paddingCount += 1
continue
}
}
}
if invalid {
if ignoreUnknown {
continue
} else {
throw Base64Error.cannotDecode
}
}
validCharacterCount += 1
// Padding found in the middle of the sequence is invalid.
if paddingCount > 0 {
throw Base64Error.cannotDecode
}
switch index {
case 0:
currentByte = (value << 2)
case 1:
currentByte |= (value >> 4)
output.appendElement(currentByte)
currentByte = (value << 4)
case 2:
currentByte |= (value >> 2)
output.appendElement(currentByte)
currentByte = (value << 6)
case 3:
currentByte |= value
output.appendElement(currentByte)
index = -1
default:
fatalError("Invalid state")
}
index += 1
}
guard (validCharacterCount + paddingCount) % 4 == 0 else {
// Invalid character count of valid input characters.
throw Base64Error.cannotDecode
}
}
}
// This base64 implementation is heavily inspired by:
@ -212,7 +346,7 @@ extension Base64 {
return self._encodeWithLineBreaks(input: input, buffer: buffer, length: &length, options: options)
}
let omitPaddingCharacter = false
let omitPaddingCharacter = false // options.contains(.omitPaddingCharacter)
Self.withUnsafeEncodingTablesAsBufferPointers(options: options) { (e0, e1) throws(Never) -> Void in
let to = input.count / 3 * 3
@ -413,613 +547,3 @@ extension Base64 {
}
}
}
// MARK: - Decoding -
extension Base64 {
enum DecodingError: Error, Equatable {
case invalidLength
case invalidCharacter(UInt8)
case unexpectedPaddingCharacter
case unexpectedEnd
}
static func decode(string encoded: String, options: Data.Base64DecodingOptions = []) throws(DecodingError) -> Data {
let result = encoded.utf8.withContiguousStorageIfAvailable { bufferPointer in
// `withContiguousStorageIfAvailable` sadly does not support typed throws, so we need
// to use Result to get the error out without allocation for the error.
Result(catching: { () throws(DecodingError) -> Data in
try Self._decodeToData(from: bufferPointer, options: options)
})
}
if let result {
return try result.get()
}
var encoded = encoded
encoded.makeContiguousUTF8()
return try Self.decode(string: encoded, options: options)
}
static func decode(data encoded: Data, options: Data.Base64DecodingOptions = []) throws(DecodingError) -> Data? {
let result = encoded.withContiguousStorageIfAvailable { bufferPointer in
// `withContiguousStorageIfAvailable` sadly does not support typed throws, so we need
// to use Result to get the error out without allocation for the error.
Result(catching: { () throws(DecodingError) -> Data in
try Self._decodeToData(from: bufferPointer, options: options)
})
}
if let result {
return try result.get()
}
return try Self.decode(bytes: Array(encoded), options: options)
}
static func decode<Buffer: Collection>(bytes: Buffer, options: Data.Base64DecodingOptions = []) throws(DecodingError) -> Data where Buffer.Element == UInt8 {
guard bytes.count > 0 else {
return Data()
}
let result = bytes.withContiguousStorageIfAvailable { bufferPointer in
// `withContiguousStorageIfAvailable` sadly does not support typed throws, so we need
// to use Result to get the error out without allocation for the error.
Result(catching: { () throws(DecodingError) -> Data in
try Self._decodeToData(from: bufferPointer, options: options)
})
}
if let result {
return try result.get()
}
return try self.decode(bytes: Array(bytes), options: options)
}
static func _decodeToData(from inBuffer: UnsafeBufferPointer<UInt8>, options: Data.Base64DecodingOptions) throws(DecodingError) -> Data {
guard inBuffer.count > 0 else {
return Data()
}
let outputLength = ((inBuffer.count + 3) / 4) * 3
let pointer = malloc(outputLength)
let other = pointer?.bindMemory(to: UInt8.self, capacity: outputLength)
let target = UnsafeMutableBufferPointer(start: other, count: outputLength)
var length = outputLength
if options.contains(.ignoreUnknownCharacters) {
try Self._decodeIgnoringErrors(from: inBuffer, into: target, length: &length, options: options)
} else {
// for whatever reason I can see this being 10% faster for larger payloads. Maybe better
// branch prediction?
try self._decode(from: inBuffer, into: target, length: &length, options: options)
}
return Data(bytesNoCopy: pointer!, count: length, deallocator: .free)
}
static func _decode(
from inBuffer: UnsafeBufferPointer<UInt8>,
into outBuffer: UnsafeMutableBufferPointer<UInt8>,
length: inout Int,
options: Data.Base64DecodingOptions
) throws(DecodingError) {
let remaining = inBuffer.count % 4
guard remaining == 0 else { throw DecodingError.invalidLength }
let outputLength = ((inBuffer.count + 3) / 4) * 3
let fullchunks = remaining == 0 ? inBuffer.count / 4 - 1 : inBuffer.count / 4
guard outBuffer.count >= outputLength else {
preconditionFailure("Expected the out buffer to be at least as long as outputLength")
}
try Self.withUnsafeDecodingTablesAsBufferPointers(options: options) { (d0, d1, d2, d3) throws(DecodingError) in
var outIndex = 0
if fullchunks > 0 {
for chunk in 0 ..< fullchunks {
let inIndex = chunk * 4
let a0 = inBuffer[inIndex]
let a1 = inBuffer[inIndex + 1]
let a2 = inBuffer[inIndex + 2]
let a3 = inBuffer[inIndex + 3]
var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2)] | d3[Int(a3)]
if x >= Self.badCharacter {
// TODO: Inspect characters here better
throw DecodingError.invalidCharacter(inBuffer[inIndex])
}
withUnsafePointer(to: &x) { ptr in
ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in
outBuffer[outIndex] = newPtr[0]
outBuffer[outIndex + 1] = newPtr[1]
outBuffer[outIndex + 2] = newPtr[2]
outIndex += 3
}
}
}
}
// inIndex is the first index in the last chunk
let inIndex = fullchunks * 4
let a0 = inBuffer[inIndex]
let a1 = inBuffer[inIndex + 1]
var a2: UInt8?
var a3: UInt8?
if inIndex + 2 < inBuffer.count, inBuffer[inIndex + 2] != Self.encodePaddingCharacter {
a2 = inBuffer[inIndex + 2]
}
if inIndex + 3 < inBuffer.count, inBuffer[inIndex + 3] != Self.encodePaddingCharacter {
a3 = inBuffer[inIndex + 3]
}
var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2 ?? 65)] | d3[Int(a3 ?? 65)]
if x >= Self.badCharacter {
// TODO: Inspect characters here better
throw DecodingError.invalidCharacter(inBuffer[inIndex])
}
withUnsafePointer(to: &x) { ptr in
ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in
outBuffer[outIndex] = newPtr[0]
outIndex += 1
if a2 != nil {
outBuffer[outIndex] = newPtr[1]
outIndex += 1
}
if a3 != nil {
outBuffer[outIndex] = newPtr[2]
outIndex += 1
}
}
}
length = outIndex
}
}
static func _decodeIgnoringErrors(
from inBuffer: UnsafeBufferPointer<UInt8>,
into outBuffer: UnsafeMutableBufferPointer<UInt8>,
length: inout Int,
options: Data.Base64DecodingOptions
) throws(DecodingError) {
assert(options.contains(.ignoreUnknownCharacters))
let outputLength = ((inBuffer.count + 3) / 4) * 3
guard outBuffer.count >= outputLength else {
preconditionFailure("Expected the out buffer to be at least as long as outputLength")
}
try Self.withUnsafeDecodingTablesAsBufferPointers(options: options) { (d0, d1, d2, d3) throws(DecodingError) in
var outIndex = 0
var inIndex = 0
fastLoop: while inIndex + 3 < inBuffer.count {
let a0 = inBuffer[inIndex]
let a1 = inBuffer[inIndex &+ 1]
let a2 = inBuffer[inIndex &+ 2]
let a3 = inBuffer[inIndex &+ 3]
var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2)] | d3[Int(a3)]
if x >= Self.badCharacter {
if a3 == Self.encodePaddingCharacter {
break // the loop
}
// error fast path. we assume that illeagal errors are at the boundary.
// lets skip them and then return to fast mode!
if !self.isValidBase64Byte(a0, options: options) {
if !self.isValidBase64Byte(a1, options: options) {
if !self.isValidBase64Byte(a2, options: options) {
if !self.isValidBase64Byte(a3, options: options) {
inIndex &+= 4
continue
} else {
inIndex &+= 3
continue
}
} else {
inIndex &+= 2
continue
}
} else {
inIndex &+= 1
continue
}
}
// error slow path... the first character is valid base64
let b0 = a0
var b1: UInt8? = nil
var b2: UInt8? = nil
var b3: UInt8? = nil
let startIndex = inIndex
inIndex &+= 1
scanForValidCharacters: while inIndex < inBuffer.count {
guard self.isValidBase64Byte(inBuffer[inIndex], options: options) else {
if inBuffer[inIndex] == Self.encodePaddingCharacter {
inIndex = startIndex
break fastLoop
}
inIndex &+= 1
continue scanForValidCharacters
}
defer { inIndex &+= 1 }
if b1 == nil {
b1 = inBuffer[inIndex]
} else if b2 == nil {
b2 = inBuffer[inIndex]
} else if b3 == nil {
b3 = inBuffer[inIndex]
break scanForValidCharacters
}
}
guard let b1, let b2, let b3 else {
throw DecodingError.invalidLength
}
x = d0[Int(b0)] | d1[Int(b1)] | d2[Int(b2)] | d3[Int(b3)]
} else {
inIndex &+= 4
}
withUnsafePointer(to: &x) { ptr in
ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in
outBuffer[outIndex] = newPtr[0]
outBuffer[outIndex &+ 1] = newPtr[1]
outBuffer[outIndex &+ 2] = newPtr[2]
outIndex &+= 3
}
}
}
if inIndex == inBuffer.count {
// all done!
length = outIndex
return
}
guard inIndex + 3 < inBuffer.count else {
if options.contains(.ignoreUnknownCharacters) {
// ensure that all remaining characters are unknown
while inIndex < inBuffer.count {
let value = inBuffer[inIndex]
if self.isValidBase64Byte(value, options: options) || value == Self.encodePaddingCharacter {
throw DecodingError.invalidCharacter(inBuffer[inIndex])
}
inIndex &+= 1
}
length = outIndex
return
}
throw DecodingError.invalidLength
}
let a0 = inBuffer[inIndex]
let a1 = inBuffer[inIndex + 1]
var a2: UInt8 = 65
var a3: UInt8 = 65
var padding2 = false
var padding3 = false
if inBuffer[inIndex + 2] == Self.encodePaddingCharacter {
padding2 = true
} else {
a2 = inBuffer[inIndex + 2]
}
if inBuffer[inIndex + 3] == Self.encodePaddingCharacter {
padding3 = true
} else {
if padding2 && self.isValidBase64Byte(inBuffer[inIndex + 3], options: options) {
throw DecodingError.unexpectedPaddingCharacter
}
a3 = inBuffer[inIndex + 3]
}
var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2)] | d3[Int(a3)]
if x >= Self.badCharacter {
var b0: UInt8? = nil
var b1: UInt8? = nil
var b2: UInt8? = nil
var b3: UInt8? = nil
scanForValidCharacters: while inIndex < inBuffer.count {
defer { inIndex &+= 1 }
let value = inBuffer[inIndex]
if self.isValidBase64Byte(value, options: options) {
if b0 == nil {
b0 = value
} else if b1 == nil {
b1 = value
} else if b2 == nil {
b2 = value
} else if b3 == nil {
if padding2 { throw DecodingError.unexpectedPaddingCharacter }
b3 = value
break scanForValidCharacters
}
} else if value == Self.encodePaddingCharacter {
guard b0 != nil, b1 != nil else {
throw DecodingError.invalidLength
}
if b2 == nil {
padding2 = true
b2 = 65
} else if b3 == nil {
padding3 = true
b3 = 65
break scanForValidCharacters
}
}
}
guard let b0, let b1, let b2, let b3 else {
if b0 == nil {
length = outIndex
return
}
throw DecodingError.invalidLength
}
x = d0[Int(b0)] | d1[Int(b1)] | d2[Int(b2)] | d3[Int(b3)]
assert(x < Self.badCharacter)
}
withUnsafePointer(to: &x) { ptr in
ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in
outBuffer[outIndex] = newPtr[0]
outIndex += 1
if !padding2 {
outBuffer[outIndex] = newPtr[1]
outIndex += 1
}
if !padding3 {
outBuffer[outIndex] = newPtr[2]
outIndex += 1
}
}
}
length = outIndex
}
}
static func withUnsafeDecodingTablesAsBufferPointers<R, E: Swift.Error>(options: Data.Base64DecodingOptions, _ body: (UnsafeBufferPointer<UInt32>, UnsafeBufferPointer<UInt32>, UnsafeBufferPointer<UInt32>, UnsafeBufferPointer<UInt32>) throws(E) -> R) throws(E) -> R {
let decoding0 = Self.decoding0
let decoding1 = Self.decoding1
let decoding2 = Self.decoding2
let decoding3 = Self.decoding3
assert(decoding0.count == 256)
assert(decoding1.count == 256)
assert(decoding2.count == 256)
assert(decoding3.count == 256)
// Workaround that `withUnsafeBufferPointer` started to support typed throws in Swift 6.1
let result = decoding0.withUnsafeBufferPointer { d0 -> Result<R, E> in
decoding1.withUnsafeBufferPointer { d1 -> Result<R, E> in
decoding2.withUnsafeBufferPointer { d2 -> Result<R, E> in
decoding3.withUnsafeBufferPointer { d3 -> Result<R, E> in
Result { () throws(E) -> R in
try body(d0, d1, d2, d3)
}
}
}
}
}
return try result.get()
}
static func isValidBase64Byte(_ byte: UInt8, options: Data.Base64DecodingOptions) -> Bool {
switch byte {
case UInt8(ascii: "A")...UInt8(ascii: "Z"),
UInt8(ascii: "a")...UInt8(ascii: "z"),
UInt8(ascii: "0")...UInt8(ascii: "9"):
true
case UInt8(ascii: "-"), UInt8(ascii: "_"):
false // options.contains(.base64UrlAlphabet)
case UInt8(ascii: "/"), UInt8(ascii: "+"):
true // !options.contains(.base64UrlAlphabet)
default:
false
}
}
static let badCharacter: UInt32 = 0x01FF_FFFF
static let decoding0: [UInt32] = [
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x0000_00F8, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_00FC,
0x0000_00D0, 0x0000_00D4, 0x0000_00D8, 0x0000_00DC, 0x0000_00E0, 0x0000_00E4,
0x0000_00E8, 0x0000_00EC, 0x0000_00F0, 0x0000_00F4, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000,
0x0000_0004, 0x0000_0008, 0x0000_000C, 0x0000_0010, 0x0000_0014, 0x0000_0018,
0x0000_001C, 0x0000_0020, 0x0000_0024, 0x0000_0028, 0x0000_002C, 0x0000_0030,
0x0000_0034, 0x0000_0038, 0x0000_003C, 0x0000_0040, 0x0000_0044, 0x0000_0048,
0x0000_004C, 0x0000_0050, 0x0000_0054, 0x0000_0058, 0x0000_005C, 0x0000_0060,
0x0000_0064, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x0000_0068, 0x0000_006C, 0x0000_0070, 0x0000_0074, 0x0000_0078,
0x0000_007C, 0x0000_0080, 0x0000_0084, 0x0000_0088, 0x0000_008C, 0x0000_0090,
0x0000_0094, 0x0000_0098, 0x0000_009C, 0x0000_00A0, 0x0000_00A4, 0x0000_00A8,
0x0000_00AC, 0x0000_00B0, 0x0000_00B4, 0x0000_00B8, 0x0000_00BC, 0x0000_00C0,
0x0000_00C4, 0x0000_00C8, 0x0000_00CC, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
]
static let decoding1: [UInt32] = [
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x0000_E003, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_F003,
0x0000_4003, 0x0000_5003, 0x0000_6003, 0x0000_7003, 0x0000_8003, 0x0000_9003,
0x0000_A003, 0x0000_B003, 0x0000_C003, 0x0000_D003, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000,
0x0000_1000, 0x0000_2000, 0x0000_3000, 0x0000_4000, 0x0000_5000, 0x0000_6000,
0x0000_7000, 0x0000_8000, 0x0000_9000, 0x0000_A000, 0x0000_B000, 0x0000_C000,
0x0000_D000, 0x0000_E000, 0x0000_F000, 0x0000_0001, 0x0000_1001, 0x0000_2001,
0x0000_3001, 0x0000_4001, 0x0000_5001, 0x0000_6001, 0x0000_7001, 0x0000_8001,
0x0000_9001, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x0000_A001, 0x0000_B001, 0x0000_C001, 0x0000_D001, 0x0000_E001,
0x0000_F001, 0x0000_0002, 0x0000_1002, 0x0000_2002, 0x0000_3002, 0x0000_4002,
0x0000_5002, 0x0000_6002, 0x0000_7002, 0x0000_8002, 0x0000_9002, 0x0000_A002,
0x0000_B002, 0x0000_C002, 0x0000_D002, 0x0000_E002, 0x0000_F002, 0x0000_0003,
0x0000_1003, 0x0000_2003, 0x0000_3003, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
]
static let decoding2: [UInt32] = [
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x0080_0F00, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x00C0_0F00,
0x0000_0D00, 0x0040_0D00, 0x0080_0D00, 0x00C0_0D00, 0x0000_0E00, 0x0040_0E00,
0x0080_0E00, 0x00C0_0E00, 0x0000_0F00, 0x0040_0F00, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000,
0x0040_0000, 0x0080_0000, 0x00C0_0000, 0x0000_0100, 0x0040_0100, 0x0080_0100,
0x00C0_0100, 0x0000_0200, 0x0040_0200, 0x0080_0200, 0x00C0_0200, 0x0000_0300,
0x0040_0300, 0x0080_0300, 0x00C0_0300, 0x0000_0400, 0x0040_0400, 0x0080_0400,
0x00C0_0400, 0x0000_0500, 0x0040_0500, 0x0080_0500, 0x00C0_0500, 0x0000_0600,
0x0040_0600, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x0080_0600, 0x00C0_0600, 0x0000_0700, 0x0040_0700, 0x0080_0700,
0x00C0_0700, 0x0000_0800, 0x0040_0800, 0x0080_0800, 0x00C0_0800, 0x0000_0900,
0x0040_0900, 0x0080_0900, 0x00C0_0900, 0x0000_0A00, 0x0040_0A00, 0x0080_0A00,
0x00C0_0A00, 0x0000_0B00, 0x0040_0B00, 0x0080_0B00, 0x00C0_0B00, 0x0000_0C00,
0x0040_0C00, 0x0080_0C00, 0x00C0_0C00, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
]
static let decoding3: [UInt32] = [
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x003E_0000, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x003F_0000,
0x0034_0000, 0x0035_0000, 0x0036_0000, 0x0037_0000, 0x0038_0000, 0x0039_0000,
0x003A_0000, 0x003B_0000, 0x003C_0000, 0x003D_0000, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000,
0x0001_0000, 0x0002_0000, 0x0003_0000, 0x0004_0000, 0x0005_0000, 0x0006_0000,
0x0007_0000, 0x0008_0000, 0x0009_0000, 0x000A_0000, 0x000B_0000, 0x000C_0000,
0x000D_0000, 0x000E_0000, 0x000F_0000, 0x0010_0000, 0x0011_0000, 0x0012_0000,
0x0013_0000, 0x0014_0000, 0x0015_0000, 0x0016_0000, 0x0017_0000, 0x0018_0000,
0x0019_0000, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x001A_0000, 0x001B_0000, 0x001C_0000, 0x001D_0000, 0x001E_0000,
0x001F_0000, 0x0020_0000, 0x0021_0000, 0x0022_0000, 0x0023_0000, 0x0024_0000,
0x0025_0000, 0x0026_0000, 0x0027_0000, 0x0028_0000, 0x0029_0000, 0x002A_0000,
0x002B_0000, 0x002C_0000, 0x002D_0000, 0x002E_0000, 0x002F_0000, 0x0030_0000,
0x0031_0000, 0x0032_0000, 0x0033_0000, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF,
]
}

@ -31,6 +31,10 @@ import WinSDK
@preconcurrency import WASILibc
#endif
#if FOUNDATION_FRAMEWORK && NO_FILESYSTEM
@_spi(ENABLE_EXCLAVE_STORAGE) import C
#endif
func _fgetxattr(_ fd: Int32, _ name: UnsafePointer<CChar>!, _ value: UnsafeMutableRawPointer!, _ size: Int, _ position: UInt32, _ options: Int32) -> Int {
#if canImport(Darwin)
return fgetxattr(fd, name, value, size, position, options)
@ -329,13 +333,13 @@ internal func readBytesFromFile(path inPath: PathOrURL, reportProgress: Bool, ma
}
let fileSize = min(Int(clamping: filestat.st_size), maxLength ?? Int.max)
let fileType = mode_t(filestat.st_mode) & S_IFMT
let fileType = mode_t(filestat.st_mode) & mode_t(S_IFMT)
#if !NO_FILESYSTEM
let shouldMap = shouldMapFileDescriptor(fd, path: inPath, options: options)
#else
let shouldMap = false
#endif
if fileType != S_IFREG {
// EACCES is still an odd choice, but at least we have a better error for directories.
let code = (fileType == S_IFDIR) ? EISDIR : EACCES

@ -12,8 +12,10 @@
#if FOUNDATION_FRAMEWORK
internal import _ForSwiftFoundation
#if !NO_FILESYSTEM
internal import DarwinPrivate // for VREG
#endif
#endif
internal import _FoundationCShims

@ -2135,10 +2135,6 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect
/// - parameter options: Options for the read operation. Default value is `[]`.
/// - throws: An error in the Cocoa domain, if `url` cannot be read.
public init(contentsOf url: __shared URL, options: ReadingOptions = []) throws {
#if NO_FILESYSTEM
let d = try NSData(contentsOf: url, options: NSData.ReadingOptions(rawValue: options.rawValue))
self.init(referencing: d)
#else
if url.isFileURL {
self = try readDataFromFile(path: .url(url), reportProgress: true, options: options)
} else {
@ -2150,16 +2146,10 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect
try self.init(_contentsOfRemote: url, options: options)
#endif
}
#endif
}
internal init(contentsOfFile path: String, options: ReadingOptions = []) throws {
#if NO_FILESYSTEM
let d = try NSData(contentsOfFile: path, options: NSData.ReadingOptions(rawValue: options.rawValue))
self.init(referencing: d)
#else
self = try readDataFromFile(path: .path(path), reportProgress: true, options: options)
#endif
}
// -----------------------------------

@ -201,7 +201,40 @@ extension stat {
}
}
#if FOUNDATION_FRAMEWORK
extension FileProtectionType {
var intValue: Int32? {
switch self {
case .complete: PROTECTION_CLASS_A
case .init(rawValue: "NSFileProtectionWriteOnly"), .completeUnlessOpen: PROTECTION_CLASS_B
case .init(rawValue: "NSFileProtectionCompleteUntilUserAuthentication"), .completeUntilFirstUserAuthentication: PROTECTION_CLASS_C
case .none: PROTECTION_CLASS_D
#if !os(macOS)
case .completeWhenUserInactive: PROTECTION_CLASS_CX
#endif
default: nil
}
}
init?(intValue value: Int32) {
switch value {
case PROTECTION_CLASS_A: self = .complete
case PROTECTION_CLASS_B: self = .completeUnlessOpen
case PROTECTION_CLASS_C: self = .completeUntilFirstUserAuthentication
case PROTECTION_CLASS_D: self = .none
#if !os(macOS)
case PROTECTION_CLASS_CX: self = .completeWhenUserInactive
#endif
default: return nil
}
}
}
#endif
#endif
extension FileAttributeKey {
fileprivate static var _extendedAttributes: Self { Self("NSFileExtendedAttributes") }
}
extension _FileManagerImpl {
func createFile(

@ -69,6 +69,12 @@ extension stat {
}
#endif
#if FOUNDATION_FRAMEWORK && os(macOS)
extension URLResourceKey {
static var _finderInfoKey: Self { URLResourceKey("_NSURLFinderInfoKey") }
}
#endif
extension _FileManagerImpl {
#if os(macOS) && FOUNDATION_FRAMEWORK
private struct _HFSFinderInfo {

@ -960,6 +960,49 @@ enum _FileOperations {
private static func _copyDirectoryMetadata(srcFD: CInt, srcPath: @autoclosure () -> String, dstFD: CInt, dstPath: @autoclosure () -> String, delegate: some LinkOrCopyDelegate) throws {
#if !os(WASI) && !os(Android)
// Copy extended attributes
#if os(FreeBSD)
// FreeBSD uses the `extattr_*` calls for setting extended attributes. Unlike like, the namespace for the extattrs are not determined by prefix of the attribute
for namespace in [EXTATTR_NAMESPACE_SYSTEM, EXTATTR_NAMESPACE_USER] {
// if we don't have permission to list attributes in system namespace, this returns -1 and skips it
var size = extattr_list_fd(srcFD, namespace, nil, 0)
if size > 0 {
// we are allocating size + 1 bytes here such that we have room for the last null terminator
try withUnsafeTemporaryAllocation(of: CChar.self, capacity: size + 1) { keyList in
// The list of entry returns by `extattr_list_*` contains the length(1 byte) of the attribute name, follow by the Non-NULL terminated attribute name. (See exattr(2))
size = extattr_list_fd(srcFD, namespace, keyList.baseAddress!, size)
guard size > 0 else { continue }
var keyLength = Int(keyList.baseAddress!.pointee)
var current = keyList.baseAddress!.advanced(by: 1)
let end = keyList.baseAddress!.advanced(by: size)
keyList.baseAddress!.advanced(by: size).pointee = 0
while current < end {
let nextEntry = current.advanced(by: keyLength)
// get the length of next key, if this is the last entry, this points to the explicitly zerod byte at `end`.
keyLength = Int(nextEntry.pointee)
// zero the length field of the next name, so current name can pass in as a null-terminated string
nextEntry.pointee = 0
// this also set `current` to `end` after iterating all entries
defer { current = nextEntry.advanced(by: 1) }
var valueSize = extattr_get_fd(srcFD, namespace, current, nil, 0)
if valueSize >= 0 {
try withUnsafeTemporaryAllocation(of: UInt8.self, capacity: valueSize) { valueBuffer in
valueSize = extattr_get_fd(srcFD, namespace, current, valueBuffer.baseAddress!, valueSize)
if valueSize >= 0 {
if extattr_set_fd(srcFD, namespace, current, valueBuffer.baseAddress!, valueSize) != 0 {
try delegate.throwIfNecessary(errno, srcPath(), dstPath())
}
}
}
}
}
}
}
}
#else
var size = flistxattr(srcFD, nil, 0)
if size > 0 {
try withUnsafeTemporaryAllocation(of: CChar.self, capacity: size) { keyList in
@ -985,6 +1028,7 @@ enum _FileOperations {
}
}
#endif
#endif
var statInfo = stat()
if fstat(srcFD, &statInfo) == 0 {
#if !os(WASI) // WASI doesn't have fchown for now

@ -0,0 +1,452 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
@available(FoundationPreview 6.2, *)
public extension FormatStyle where Self == Date.HTTPFormatStyle {
static var http: Self {
return Date.HTTPFormatStyle()
}
}
@available(FoundationPreview 6.2, *)
public extension ParseableFormatStyle where Self == Date.HTTPFormatStyle {
static var http: Self { .init() }
}
@available(FoundationPreview 6.2, *)
public extension ParseStrategy where Self == Date.HTTPFormatStyle {
@_disfavoredOverload
static var http: Self { .init() }
}
@available(FoundationPreview 6.2, *)
extension Date.HTTPFormatStyle : ParseStrategy {
public var parseStrategy: Date.HTTPFormatStyle { self }
}
@available(FoundationPreview 6.2, *)
extension Date.HTTPFormatStyle : FormatStyle {
}
@available(FoundationPreview 6.2, *)
extension Date {
/// Options for generating and parsing string representations of dates following the HTTP date format from [RFC 9110 § 5.6.7](https://www.rfc-editor.org/rfc/rfc9110.html#http.date).
public struct HTTPFormatStyle : Sendable, Hashable, Codable, ParseableFormatStyle {
let componentsStyle = DateComponents.HTTPFormatStyle()
public init() {}
public init(from decoder: any Decoder) throws {}
public func format(_ date: Date) -> String {
// <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
let components = Calendar(identifier: .gregorian)._dateComponents([.weekday, .day, .month, .year, .hour, .minute, .second], from: date, in: .gmt)
return componentsStyle.format(components)
}
public func parse(_ value: String) throws -> Date {
guard let (_, date) = parse(value, in: value.startIndex..<value.endIndex) else {
throw parseError(value, exampleFormattedString: self.format(Date.now))
}
return date
}
fileprivate func parse(_ value: String, in range: Range<String.Index>) -> (String.Index, Date)? {
var v = value[range]
guard !v.isEmpty else {
return nil
}
let result = v.withUTF8 { buffer -> (Int, Date)? in
let view = BufferView(unsafeBufferPointer: buffer)!
guard let comps = try? componentsStyle.components(from: value, in: view) else {
return nil
}
// HTTP dates are always GMT
guard let date = Calendar(identifier: .gregorian).date(from: comps.components) else {
return nil
}
return (comps.consumed, date)
}
guard let result else {
return nil
}
let endIndex = value.utf8.index(v.startIndex, offsetBy: result.0)
return (endIndex, result.1)
}
}
}
// MARK: - Regex
@available(FoundationPreview 6.2, *)
extension Date.HTTPFormatStyle : CustomConsumingRegexComponent {
public typealias RegexOutput = Date
public func consuming(_ input: String, startingAt index: String.Index, in bounds: Range<String.Index>) throws -> (upperBound: String.Index, output: Date)? {
guard index < bounds.upperBound else {
return nil
}
// It's important to return nil from parse in case of a failure, not throw. That allows things like the firstMatch regex to work.
return self.parse(input, in: index..<bounds.upperBound)
}
}
@available(FoundationPreview 6.2, *)
extension RegexComponent where Self == Date.HTTPFormatStyle {
/// Creates a regex component to match an HTTP date and time, such as "2015-11-14'T'15:05:03'Z'", and capture the string as a `Date` using the time zone as specified in the string.
public static var http: Date.HTTPFormatStyle {
return Date.HTTPFormatStyle()
}
}
// MARK: - Components
@available(FoundationPreview 6.2, *)
public extension FormatStyle where Self == DateComponents.HTTPFormatStyle {
static var http: Self {
return DateComponents.HTTPFormatStyle()
}
}
@available(FoundationPreview 6.2, *)
public extension ParseableFormatStyle where Self == DateComponents.HTTPFormatStyle {
static var http: Self { .init() }
}
@available(FoundationPreview 6.2, *)
public extension ParseStrategy where Self == DateComponents.HTTPFormatStyle {
@_disfavoredOverload
static var http: Self { .init() }
}
@available(FoundationPreview 6.2, *)
extension DateComponents.HTTPFormatStyle : FormatStyle {
}
@available(FoundationPreview 6.2, *)
extension DateComponents.HTTPFormatStyle : ParseStrategy {
public var parseStrategy: DateComponents.HTTPFormatStyle { self }
}
@available(FoundationPreview 6.2, *)
extension DateComponents {
/// Converts `DateComponents` into RFC 9110-compatible "HTTP date" `String`, and parses in the reverse direction.
/// This parser does not do validation on the individual values of the components. An optional date can be created from the result using `Calendar(identifier: .gregorian).date(from: ...)`.
/// When formatting, missing or invalid fields are filled with default values: `Sun`, `01`, `Jan`, `2000`, `00:00:00`, `GMT`. Note that missing fields may result in an invalid date or time. Other values in the `DateComponents` are ignored.
public struct HTTPFormatStyle : Sendable, Hashable, Codable, ParseableFormatStyle {
public init() {
}
// MARK: - Format
public func format(_ components: DateComponents) -> String {
let capacity = 32 // It is believed no HTTP date can exceed this size (max should be 26)
return withUnsafeTemporaryAllocation(of: CChar.self, capacity: capacity + 1) { _buffer in
var buffer = OutputBuffer(initializing: _buffer.baseAddress!, capacity: _buffer.count)
switch components.weekday {
case 2:
buffer.appendElement(CChar(UInt8(ascii: "M")))
buffer.appendElement(CChar(UInt8(ascii: "o")))
buffer.appendElement(CChar(UInt8(ascii: "n")))
case 3:
buffer.appendElement(CChar(UInt8(ascii: "T")))
buffer.appendElement(CChar(UInt8(ascii: "u")))
buffer.appendElement(CChar(UInt8(ascii: "e")))
case 4:
buffer.appendElement(CChar(UInt8(ascii: "W")))
buffer.appendElement(CChar(UInt8(ascii: "e")))
buffer.appendElement(CChar(UInt8(ascii: "d")))
case 5:
buffer.appendElement(CChar(UInt8(ascii: "T")))
buffer.appendElement(CChar(UInt8(ascii: "h")))
buffer.appendElement(CChar(UInt8(ascii: "u")))
case 6:
buffer.appendElement(CChar(UInt8(ascii: "F")))
buffer.appendElement(CChar(UInt8(ascii: "r")))
buffer.appendElement(CChar(UInt8(ascii: "i")))
case 7:
buffer.appendElement(CChar(UInt8(ascii: "S")))
buffer.appendElement(CChar(UInt8(ascii: "a")))
buffer.appendElement(CChar(UInt8(ascii: "t")))
case 1:
// Sunday, or default / missing
fallthrough
default:
buffer.appendElement(CChar(UInt8(ascii: "S")))
buffer.appendElement(CChar(UInt8(ascii: "u")))
buffer.appendElement(CChar(UInt8(ascii: "n")))
}
buffer.appendElement(CChar(UInt8(ascii: ",")))
buffer.appendElement(CChar(UInt8(ascii: " ")))
let day = components.day ?? 1
buffer.append(day, zeroPad: 2)
buffer.appendElement(CChar(UInt8(ascii: " ")))
switch components.month {
case 2:
buffer.appendElement(CChar(UInt8(ascii: "F")))
buffer.appendElement(CChar(UInt8(ascii: "e")))
buffer.appendElement(CChar(UInt8(ascii: "b")))
case 3:
buffer.appendElement(CChar(UInt8(ascii: "M")))
buffer.appendElement(CChar(UInt8(ascii: "a")))
buffer.appendElement(CChar(UInt8(ascii: "r")))
case 4:
buffer.appendElement(CChar(UInt8(ascii: "A")))
buffer.appendElement(CChar(UInt8(ascii: "p")))
buffer.appendElement(CChar(UInt8(ascii: "r")))
case 5:
buffer.appendElement(CChar(UInt8(ascii: "M")))
buffer.appendElement(CChar(UInt8(ascii: "a")))
buffer.appendElement(CChar(UInt8(ascii: "y")))
case 6:
buffer.appendElement(CChar(UInt8(ascii: "J")))
buffer.appendElement(CChar(UInt8(ascii: "u")))
buffer.appendElement(CChar(UInt8(ascii: "n")))
case 7:
buffer.appendElement(CChar(UInt8(ascii: "J")))
buffer.appendElement(CChar(UInt8(ascii: "u")))
buffer.appendElement(CChar(UInt8(ascii: "l")))
case 8:
buffer.appendElement(CChar(UInt8(ascii: "A")))
buffer.appendElement(CChar(UInt8(ascii: "u")))
buffer.appendElement(CChar(UInt8(ascii: "g")))
case 9:
buffer.appendElement(CChar(UInt8(ascii: "S")))
buffer.appendElement(CChar(UInt8(ascii: "e")))
buffer.appendElement(CChar(UInt8(ascii: "p")))
case 10:
buffer.appendElement(CChar(UInt8(ascii: "O")))
buffer.appendElement(CChar(UInt8(ascii: "c")))
buffer.appendElement(CChar(UInt8(ascii: "t")))
case 11:
buffer.appendElement(CChar(UInt8(ascii: "N")))
buffer.appendElement(CChar(UInt8(ascii: "o")))
buffer.appendElement(CChar(UInt8(ascii: "v")))
case 12:
buffer.appendElement(CChar(UInt8(ascii: "D")))
buffer.appendElement(CChar(UInt8(ascii: "e")))
buffer.appendElement(CChar(UInt8(ascii: "c")))
case 1:
// Jan or default value
fallthrough
default:
buffer.appendElement(CChar(UInt8(ascii: "J")))
buffer.appendElement(CChar(UInt8(ascii: "a")))
buffer.appendElement(CChar(UInt8(ascii: "n")))
}
buffer.appendElement(CChar(UInt8(ascii: " ")))
let year = components.year ?? 2000
buffer.append(year, zeroPad: 4)
buffer.appendElement(CChar(UInt8(ascii: " ")))
let h = components.hour ?? 0
let m = components.minute ?? 0
let s = components.second ?? 0
buffer.append(h, zeroPad: 2)
buffer.appendElement(CChar(UInt8(ascii: ":")))
buffer.append(m, zeroPad: 2)
buffer.appendElement(CChar(UInt8(ascii: ":")))
buffer.append(s, zeroPad: 2)
buffer.appendElement(CChar(UInt8(ascii: " ")))
buffer.appendElement(CChar(UInt8(ascii: "G")))
buffer.appendElement(CChar(UInt8(ascii: "M")))
buffer.appendElement(CChar(UInt8(ascii: "T")))
// Null-terminate
buffer.appendElement(CChar(0))
// Make a string
let initialized = buffer.relinquishBorrowedMemory()
return String(validatingUTF8: initialized.baseAddress!)!
}
}
// MARK: - Parse
fileprivate struct ComponentsParseResult {
var consumed: Int
var components: DateComponents
}
public func parse(_ value: String) throws -> DateComponents {
guard let (_, components) = parse(value, in: value.startIndex..<value.endIndex) else {
throw parseError(value, exampleFormattedString: Date.HTTPFormatStyle().format(Date.now))
}
return components
}
private func parse(_ value: String, in range: Range<String.Index>) -> (String.Index, DateComponents)? {
var v = value[range]
guard !v.isEmpty else {
return nil
}
let result = v.withUTF8 { buffer -> (Int, DateComponents)? in
let view = BufferView(unsafeBufferPointer: buffer)!
guard let comps = try? components(from: value, in: view) else {
return nil
}
return (comps.consumed, comps.components)
}
guard let result else {
return nil
}
let endIndex = value.utf8.index(v.startIndex, offsetBy: result.0)
return (endIndex, result.1)
}
fileprivate func components(from inputString: String, in view: borrowing BufferView<UInt8>) throws -> ComponentsParseResult {
// https://www.rfc-editor.org/rfc/rfc9110.html#http.date
// <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
var it = view.makeIterator()
var dc = DateComponents()
// Despite the spec, we allow the weekday name to be optional.
guard let maybeWeekday1 = it.peek() else {
throw parseError(inputString, exampleFormattedString: Date.HTTPFormatStyle().format(Date.now))
}
if isASCIIDigit(maybeWeekday1) {
// This is the first digit of the day. Weekday is not present.
} else {
// Anything else must be a day-name (Mon, Tue, ... Sun)
guard let weekday1 = it.next(), let weekday2 = it.next(), let weekday3 = it.next() else {
throw parseError(inputString, exampleFormattedString: Date.HTTPFormatStyle().format(Date.now))
}
dc.weekday = switch (weekday1, weekday2, weekday3) {
case (UInt8(ascii: "S"), UInt8(ascii: "u"), UInt8(ascii: "n")):
1
case (UInt8(ascii: "M"), UInt8(ascii: "o"), UInt8(ascii: "n")):
2
case (UInt8(ascii: "T"), UInt8(ascii: "u"), UInt8(ascii: "e")):
3
case (UInt8(ascii: "W"), UInt8(ascii: "e"), UInt8(ascii: "d")):
4
case (UInt8(ascii: "T"), UInt8(ascii: "h"), UInt8(ascii: "u")):
5
case (UInt8(ascii: "F"), UInt8(ascii: "r"), UInt8(ascii: "i")):
6
case (UInt8(ascii: "S"), UInt8(ascii: "a"), UInt8(ascii: "t")):
7
default:
throw parseError(inputString, exampleFormattedString: Date.HTTPFormatStyle().format(Date.now), extendedDescription: "Malformed weekday name")
}
// Move past , and space to weekday
try it.expectCharacter(UInt8(ascii: ","), input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now), extendedDescription: "Missing , after weekday")
try it.expectCharacter(UInt8(ascii: " "), input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now), extendedDescription: "Missing space after weekday")
}
dc.day = try it.digits(minDigits: 2, maxDigits: 2, input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now), extendedDescription: "Missing or malformed day")
try it.expectCharacter(UInt8(ascii: " "), input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now))
// month-name (Jan, Feb, ... Dec)
guard let month1 = it.next(), let month2 = it.next(), let month3 = it.next() else {
throw parseError(inputString, exampleFormattedString: Date.HTTPFormatStyle().format(Date.now), extendedDescription: "Missing month")
}
dc.month = switch (month1, month2, month3) {
case (UInt8(ascii: "J"), UInt8(ascii: "a"), UInt8(ascii: "n")):
1
case (UInt8(ascii: "F"), UInt8(ascii: "e"), UInt8(ascii: "b")):
2
case (UInt8(ascii: "M"), UInt8(ascii: "a"), UInt8(ascii: "r")):
3
case (UInt8(ascii: "A"), UInt8(ascii: "p"), UInt8(ascii: "r")):
4
case (UInt8(ascii: "M"), UInt8(ascii: "a"), UInt8(ascii: "y")):
5
case (UInt8(ascii: "J"), UInt8(ascii: "u"), UInt8(ascii: "n")):
6
case (UInt8(ascii: "J"), UInt8(ascii: "u"), UInt8(ascii: "l")):
7
case (UInt8(ascii: "A"), UInt8(ascii: "u"), UInt8(ascii: "g")):
8
case (UInt8(ascii: "S"), UInt8(ascii: "e"), UInt8(ascii: "p")):
9
case (UInt8(ascii: "O"), UInt8(ascii: "c"), UInt8(ascii: "t")):
10
case (UInt8(ascii: "N"), UInt8(ascii: "o"), UInt8(ascii: "v")):
11
case (UInt8(ascii: "D"), UInt8(ascii: "e"), UInt8(ascii: "c")):
12
default:
throw parseError(inputString, exampleFormattedString: Date.HTTPFormatStyle().format(Date.now), extendedDescription: "Month \(String(describing: dc.month)) is out of bounds")
}
try it.expectCharacter(UInt8(ascii: " "), input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now))
dc.year = try it.digits(minDigits: 4, maxDigits: 4, input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now))
try it.expectCharacter(UInt8(ascii: " "), input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now))
let hour = try it.digits(minDigits: 2, maxDigits: 2, input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now))
if hour < 0 || hour > 23 {
throw parseError(inputString, exampleFormattedString: Date.HTTPFormatStyle().format(Date.now), extendedDescription: "Hour \(hour) is out of bounds")
}
dc.hour = hour
try it.expectCharacter(UInt8(ascii: ":"), input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now))
let minute = try it.digits(minDigits: 2, maxDigits: 2, input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now))
if minute < 0 || minute > 59 {
throw parseError(inputString, exampleFormattedString: Date.HTTPFormatStyle().format(Date.now), extendedDescription: "Minute \(minute) is out of bounds")
}
dc.minute = minute
try it.expectCharacter(UInt8(ascii: ":"), input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now))
let second = try it.digits(minDigits: 2, maxDigits: 2, input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now))
// second '60' is supported in the spec for leap seconds, but Foundation does not support leap seconds. 60 is adjusted to 59.
if second < 0 || second > 60 {
throw parseError(inputString, exampleFormattedString: Date.HTTPFormatStyle().format(Date.now), extendedDescription: "Second \(second) is out of bounds")
}
// Foundation does not support leap seconds. We convert 60 seconds into 59 seconds.
if second == 60 {
dc.second = 59
} else {
dc.second = second
}
try it.expectCharacter(UInt8(ascii: " "), input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now))
// "GMT"
try it.expectCharacter(UInt8(ascii: "G"), input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now), extendedDescription: "Missing GMT time zone")
try it.expectCharacter(UInt8(ascii: "M"), input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now), extendedDescription: "Missing GMT time zone")
try it.expectCharacter(UInt8(ascii: "T"), input: inputString, onFailure: Date.HTTPFormatStyle().format(Date.now), extendedDescription: "Missing GMT time zone")
// Time zone is always GMT, calendar is always Gregorian
dc.timeZone = .gmt
dc.calendar = Calendar(identifier: .gregorian)
// Would be nice to see this functionality on BufferView, but for now we calculate it ourselves.
let utf8CharactersRead = it.curPointer - view.startIndex._rawValue
return ComponentsParseResult(consumed: utf8CharactersRead, components: dc)
}
}
}

@ -302,52 +302,6 @@ extension Date.ISO8601FormatStyle : FormatStyle {
let result = withUnsafeTemporaryAllocation(of: CChar.self, capacity: capacity + 1) { _buffer in
var buffer = OutputBuffer(initializing: _buffer.baseAddress!, capacity: _buffer.count)
let asciiZero = CChar(48)
func append(_ i: Int, zeroPad: Int, buffer: inout OutputBuffer<CChar>) {
if i < 10 {
if zeroPad - 1 > 0 {
for _ in 0..<zeroPad-1 { buffer.appendElement(asciiZero) }
}
buffer.appendElement(asciiZero + CChar(i))
} else if i < 100 {
if zeroPad - 2 > 0 {
for _ in 0..<zeroPad-2 { buffer.appendElement(asciiZero) }
}
let (tens, ones) = i.quotientAndRemainder(dividingBy: 10)
buffer.appendElement(asciiZero + CChar(tens))
buffer.appendElement(asciiZero + CChar(ones))
} else if i < 1000 {
if zeroPad - 3 > 0 {
for _ in 0..<zeroPad-3 { buffer.appendElement(asciiZero) }
}
let (hundreds, remainder) = i.quotientAndRemainder(dividingBy: 100)
let (tens, ones) = remainder.quotientAndRemainder(dividingBy: 10)
buffer.appendElement(asciiZero + CChar(hundreds))
buffer.appendElement(asciiZero + CChar(tens))
buffer.appendElement(asciiZero + CChar(ones))
} else if i < 10000 {
if zeroPad - 4 > 0 {
for _ in 0..<zeroPad-4 { buffer.appendElement(asciiZero) }
}
let (thousands, remainder) = i.quotientAndRemainder(dividingBy: 1000)
let (hundreds, remainder2) = remainder.quotientAndRemainder(dividingBy: 100)
let (tens, ones) = remainder2.quotientAndRemainder(dividingBy: 10)
buffer.appendElement(asciiZero + CChar(thousands))
buffer.appendElement(asciiZero + CChar(hundreds))
buffer.appendElement(asciiZero + CChar(tens))
buffer.appendElement(asciiZero + CChar(ones))
} else {
// Special case - we don't do zero padding
var desc = i.numericStringRepresentation
desc.withUTF8 {
$0.withMemoryRebound(to: CChar.self) { buf in
buffer.append(fromContentsOf: buf)
}
}
}
}
let asciiColon = CChar(58)
let asciiDash = CChar(45)
let asciiSpace = CChar(32)
@ -361,7 +315,7 @@ extension Date.ISO8601FormatStyle : FormatStyle {
if formatFields.contains(.year) {
if formatFields.contains(.weekOfYear), let y = components.yearForWeekOfYear {
append(y, zeroPad: 4, buffer: &buffer)
buffer.append(y, zeroPad: 4)
} else {
var y = components.year!
if let era = components.era, era == 0 {
@ -371,7 +325,7 @@ extension Date.ISO8601FormatStyle : FormatStyle {
buffer.appendElement(asciiMinus)
y = -y
}
append(y, zeroPad: 4, buffer: &buffer)
buffer.append(y, zeroPad: 4)
}
needSeparator = true
@ -382,7 +336,7 @@ extension Date.ISO8601FormatStyle : FormatStyle {
buffer.appendElement(asciiDash)
}
let m = components.month!
append(m, zeroPad: 2, buffer: &buffer)
buffer.append(m, zeroPad: 2)
needSeparator = true
}
@ -392,7 +346,7 @@ extension Date.ISO8601FormatStyle : FormatStyle {
}
let woy = components.weekOfYear!
buffer.appendElement(asciiWeekOfYearSeparator)
append(woy, zeroPad: 2, buffer: &buffer)
buffer.append(woy, zeroPad: 2)
needSeparator = true
}
@ -407,13 +361,13 @@ extension Date.ISO8601FormatStyle : FormatStyle {
if weekday >= 10 {
weekday = 10
}
append(weekday - 1, zeroPad: 2, buffer: &buffer)
buffer.append(weekday - 1, zeroPad: 2)
} else if formatFields.contains(.month) {
let day = components.day!
append(day, zeroPad: 2, buffer: &buffer)
buffer.append(day, zeroPad: 2)
} else {
let dayOfYear = components.dayOfYear!
append(dayOfYear, zeroPad: 3, buffer: &buffer)
buffer.append(dayOfYear, zeroPad: 3)
}
needSeparator = true
@ -433,22 +387,22 @@ extension Date.ISO8601FormatStyle : FormatStyle {
switch timeSeparator {
case .colon:
append(h, zeroPad: 2, buffer: &buffer)
buffer.append(h, zeroPad: 2)
buffer.appendElement(asciiColon)
append(m, zeroPad: 2, buffer: &buffer)
buffer.append(m, zeroPad: 2)
buffer.appendElement(asciiColon)
append(s, zeroPad: 2, buffer: &buffer)
buffer.append(s, zeroPad: 2)
case .omitted:
append(h, zeroPad: 2, buffer: &buffer)
append(m, zeroPad: 2, buffer: &buffer)
append(s, zeroPad: 2, buffer: &buffer)
buffer.append(h, zeroPad: 2)
buffer.append(m, zeroPad: 2)
buffer.append(s, zeroPad: 2)
}
if includingFractionalSeconds {
let ns = components.nanosecond!
let ms = Int((Double(ns) / 1_000_000.0).rounded(.towardZero))
buffer.appendElement(asciiPeriod)
append(ms, zeroPad: 3, buffer: &buffer)
buffer.append(ms, zeroPad: 3)
}
needSeparator = true
@ -474,16 +428,16 @@ extension Date.ISO8601FormatStyle : FormatStyle {
} else {
buffer.appendElement(asciiPlus)
}
append(hour, zeroPad: 2, buffer: &buffer)
buffer.append(hour, zeroPad: 2)
if timeZoneSeparator == .colon {
buffer.appendElement(asciiColon)
}
append(minute, zeroPad: 2, buffer: &buffer)
buffer.append(minute, zeroPad: 2)
if second != 0 {
if timeZoneSeparator == .colon {
buffer.appendElement(asciiColon)
}
append(second, zeroPad: 2, buffer: &buffer)
buffer.append(second, zeroPad: 2)
}
}
}
@ -510,86 +464,12 @@ extension Date.ISO8601FormatStyle {
private func components(from inputString: String, in view: borrowing BufferView<UInt8>) throws -> ComponentsParseResult {
let fields = formatFields
let asciiDash : UInt8 = 45 // -
let asciiW : UInt8 = 87 // W
let asciiT : UInt8 = 84 // T
let asciiZero : UInt8 = 48 // 0
let asciiNine : UInt8 = 57 // 9
let asciiSpace : UInt8 = 32 // space
let asciiColon : UInt8 = 58 // :
let asciiPeriod : UInt8 = 46 // .
let asciiMinus : UInt8 = 45 // same as -
let asciiPlus : UInt8 = 43 // +
func isDigit(_ x: UInt8) -> Bool {
x >= asciiZero && x <= asciiNine
}
func expectCharacter(_ expected: UInt8, _ i: inout BufferView<UInt8>.Iterator) throws {
guard let parsed = i.next(), parsed == expected else {
throw parseError(inputString, exampleFormattedString: self.format(Date.now))
}
}
func expectOneOrMoreCharacters(_ expected: UInt8, _ i: inout BufferView<UInt8>.Iterator) throws {
guard let parsed = i.next(), parsed == expected else {
throw parseError(inputString, exampleFormattedString: self.format(Date.now))
}
while let parsed = i.peek(), parsed == expected {
i.advance()
}
}
func expectZeroOrMoreCharacters(_ expected: UInt8, _ i: inout BufferView<UInt8>.Iterator) {
while let parsed = i.peek(), parsed == expected {
i.advance()
}
}
func digits(maxDigits: Int? = nil, nanoseconds: Bool = false, _ i: inout BufferView<UInt8>.Iterator) throws -> Int {
// Consume all leading zeros, parse until we no longer see a digit
var result = 0
var count = 0
// Cap at 10 digits max to avoid overflow
let max = min(maxDigits ?? 10, 10)
while let next = i.peek(), isDigit(next) {
let digit = Int(next - asciiZero)
result *= 10
result += digit
i.advance()
count += 1
if count >= max { break }
}
guard count > 0 else {
// No digits actually found
throw parseError(inputString, exampleFormattedString: self.format(Date.now))
}
if nanoseconds {
// Keeps us in the land of integers
if count == 1 { return result * 100_000_000 }
if count == 2 { return result * 10_000_000 }
if count == 3 { return result * 1_000_000 }
if count == 4 { return result * 100_000 }
if count == 5 { return result * 10_000 }
if count == 6 { return result * 1_000 }
if count == 7 { return result * 100 }
if count == 8 { return result * 10 }
if count == 9 { return result }
throw parseError(inputString, exampleFormattedString: self.format(Date.now))
}
return result
}
var it = view.makeIterator()
var needsSeparator = false
var dc = DateComponents()
if fields.contains(.year) {
let max = dateSeparator == .omitted ? 4 : nil
let value = try digits(maxDigits: max, &it)
let value = try it.digits(maxDigits: max, input: inputString, onFailure: self.format(Date.now))
if fields.contains(.weekOfYear) {
dc.yearForWeekOfYear = value
} else {
@ -604,12 +484,12 @@ extension Date.ISO8601FormatStyle {
if fields.contains(.month) {
if needsSeparator && dateSeparator == .dash {
try expectCharacter(asciiDash, &it)
try it.expectCharacter(UInt8(ascii: "-"), input: inputString, onFailure: self.format(Date.now))
}
// parse month digits
let max = dateSeparator == .omitted ? 2 : nil
let value = try digits(maxDigits: max, &it)
let value = try it.digits(maxDigits: max, input: inputString, onFailure: self.format(Date.now))
guard _calendar.maximumRange(of: .month)!.contains(value) else {
throw parseError(inputString, exampleFormattedString: self.format(Date.now))
}
@ -618,14 +498,14 @@ extension Date.ISO8601FormatStyle {
needsSeparator = true
} else if fields.contains(.weekOfYear) {
if needsSeparator && dateSeparator == .dash {
try expectCharacter(asciiDash, &it)
try it.expectCharacter(UInt8(ascii: "-"), input: inputString, onFailure: self.format(Date.now))
}
// parse W
try expectCharacter(asciiW, &it)
try it.expectCharacter(UInt8(ascii: "W"), input: inputString, onFailure: self.format(Date.now))
// parse week of year digits
let max = dateSeparator == .omitted ? 2 : nil
let value = try digits(maxDigits: max, &it)
let value = try it.digits(maxDigits: max, input: inputString, onFailure: self.format(Date.now))
guard _calendar.maximumRange(of: .weekOfYear)!.contains(value) else {
throw parseError(inputString, exampleFormattedString: self.format(Date.now))
}
@ -639,14 +519,14 @@ extension Date.ISO8601FormatStyle {
if fields.contains(.day) {
if needsSeparator && dateSeparator == .dash {
try expectCharacter(asciiDash, &it)
try it.expectCharacter(UInt8(ascii: "-"), input: inputString, onFailure: self.format(Date.now))
}
if fields.contains(.weekOfYear) {
// parse day of week ('ee')
// ISO8601 "1" is Monday. For our date components, 2 is Monday. Add 1 to account for difference.
let max = dateSeparator == .omitted ? 2 : nil
let value = (try digits(maxDigits: max, &it) % 7) + 1
let value = (try it.digits(maxDigits: max, input: inputString, onFailure: self.format(Date.now)) % 7) + 1
guard _calendar.maximumRange(of: .weekday)!.contains(value) else {
throw parseError(inputString, exampleFormattedString: self.format(Date.now))
@ -656,7 +536,7 @@ extension Date.ISO8601FormatStyle {
} else if fields.contains(.month) {
// parse day of month ('dd')
let max = dateSeparator == .omitted ? 2 : nil
let value = try digits(maxDigits: max, &it)
let value = try it.digits(maxDigits: max, input: inputString, onFailure: self.format(Date.now))
guard _calendar.maximumRange(of: .day)!.contains(value) else {
throw parseError(inputString, exampleFormattedString: self.format(Date.now))
}
@ -666,7 +546,7 @@ extension Date.ISO8601FormatStyle {
} else {
// parse 3 digit day of year ('DDD')
let max = dateSeparator == .omitted ? 3 : nil
let value = try digits(maxDigits: max, &it)
let value = try it.digits(maxDigits: max, input: inputString, onFailure: self.format(Date.now))
guard _calendar.maximumRange(of: .dayOfYear)!.contains(value) else {
throw parseError(inputString, exampleFormattedString: self.format(Date.now))
}
@ -682,30 +562,30 @@ extension Date.ISO8601FormatStyle {
switch dateTimeSeparator {
case .standard:
// parse T
try expectCharacter(asciiT, &it)
try it.expectCharacter(UInt8(ascii: "T"), input: inputString, onFailure: self.format(Date.now))
case .space:
// parse any number of spaces
try expectOneOrMoreCharacters(asciiSpace, &it)
try it.expectOneOrMoreCharacters(UInt8(ascii: " "), input: inputString, onFailure: self.format(Date.now))
}
}
switch timeSeparator {
case .colon:
dc.hour = try digits(&it)
try expectCharacter(asciiColon, &it)
dc.minute = try digits(&it)
try expectCharacter(asciiColon, &it)
dc.second = try digits(&it)
dc.hour = try it.digits(input: inputString, onFailure: self.format(Date.now))
try it.expectCharacter(UInt8(ascii: ":"), input: inputString, onFailure: self.format(Date.now))
dc.minute = try it.digits(input: inputString, onFailure: self.format(Date.now))
try it.expectCharacter(UInt8(ascii: ":"), input: inputString, onFailure: self.format(Date.now))
dc.second = try it.digits(input: inputString, onFailure: self.format(Date.now))
case .omitted:
dc.hour = try digits(maxDigits: 2, &it)
dc.minute = try digits(maxDigits: 2, &it)
dc.second = try digits(maxDigits: 2, &it)
dc.hour = try it.digits(maxDigits: 2, input: inputString, onFailure: self.format(Date.now))
dc.minute = try it.digits(maxDigits: 2, input: inputString, onFailure: self.format(Date.now))
dc.second = try it.digits(maxDigits: 2, input: inputString, onFailure: self.format(Date.now))
}
if includingFractionalSeconds {
try expectCharacter(asciiPeriod, &it)
try it.expectCharacter(UInt8(ascii: "."), input: inputString, onFailure: self.format(Date.now))
let fractionalSeconds = try digits(nanoseconds: true, &it)
let fractionalSeconds = try it.digits(nanoseconds: true, input: inputString, onFailure: self.format(Date.now))
dc.nanosecond = fractionalSeconds
}
@ -715,7 +595,7 @@ extension Date.ISO8601FormatStyle {
if fields.contains(.timeZone) {
// For compatibility with ICU implementation, if the dateTimeSeparator is a space, consume any number (including zero) of spaces here.
if dateTimeSeparator == .space {
expectZeroOrMoreCharacters(asciiSpace, &it)
it.expectZeroOrMoreCharacters(UInt8(ascii: " "))
}
guard let plusOrMinusOrZ = it.next() else {
@ -737,8 +617,8 @@ extension Date.ISO8601FormatStyle {
let m = it.next(), (m == UInt8(ascii: "M") || m == UInt8(ascii: "m")),
let t = it.next(), (t == UInt8(ascii: "T") || t == UInt8(ascii: "t")) {
// Allow GMT followed by + or -, or end of string, or other
if let next = it.peek(), (next == asciiPlus || next == asciiMinus) {
if next == asciiPlus { positive = true }
if let next = it.peek(), (next == UInt8(ascii: "+") || next == UInt8(ascii: "-")) {
if next == UInt8(ascii: "+") { positive = true }
else { positive = false }
it.advance()
} else {
@ -750,8 +630,8 @@ extension Date.ISO8601FormatStyle {
let t = it.next(), (t == UInt8(ascii: "T") || t == UInt8(ascii: "t")),
let c = it.next(), (c == UInt8(ascii: "C") || c == UInt8(ascii: "c")) {
// Allow UTC followed by + or -, or end of string, or other
if let next = it.peek(), (next == asciiPlus || next == asciiMinus) {
if next == asciiPlus { positive = true }
if let next = it.peek(), (next == UInt8(ascii: "+") || next == UInt8(ascii: "-")) {
if next == UInt8(ascii: "+") { positive = true }
else { positive = false }
it.advance()
} else {
@ -759,9 +639,9 @@ extension Date.ISO8601FormatStyle {
tzOffset = 0
skipDigits = true
}
} else if plusOrMinusOrZ == asciiPlus {
} else if plusOrMinusOrZ == UInt8(ascii: "+") {
positive = true
} else if plusOrMinusOrZ == asciiMinus {
} else if plusOrMinusOrZ == UInt8(ascii: "-") {
positive = false
} else {
// Expected time zone, found garbage
@ -773,24 +653,24 @@ extension Date.ISO8601FormatStyle {
// parse Time Zone: ISO8601 extended hms?, with Z
// examples: -08:00, -07:52:58, Z
let hours = try digits(maxDigits: 2, &it)
let hours = try it.digits(maxDigits: 2, input: inputString, onFailure: self.format(Date.now))
// Expect a colon, or not
if let maybeColon = it.peek(), maybeColon == asciiColon {
if let maybeColon = it.peek(), maybeColon == UInt8(ascii: ":") {
// Throw it away
it.advance()
}
let minutes = try digits(maxDigits: 2, &it)
let minutes = try it.digits(maxDigits: 2, input: inputString, onFailure: self.format(Date.now))
if let maybeColon = it.peek(), maybeColon == asciiColon {
if let maybeColon = it.peek(), maybeColon == UInt8(ascii: ":") {
// Throw it away
it.advance()
}
if let secondsTens = it.peek(), isDigit(secondsTens) {
if let secondsTens = it.peek(), isASCIIDigit(secondsTens) {
// We have seconds
let seconds = try digits(maxDigits: 2, &it)
let seconds = try it.digits(maxDigits: 2, input: inputString, onFailure: self.format(Date.now))
tzOffset = (hours * 3600) + (minutes * 60) + seconds
} else {
// If the next character is missing, that's allowed - the time can be something like just -0852 and then the string can end

@ -10,13 +10,136 @@
//
//===----------------------------------------------------------------------===//
package func parseError(_ value: String, exampleFormattedString: String?) -> CocoaError {
package func parseError(_ value: String, exampleFormattedString: String?, extendedDescription: String? = nil) -> CocoaError {
let errorStr: String
if let exampleFormattedString = exampleFormattedString {
errorStr = "Cannot parse \(value). String should adhere to the preferred format of the locale, such as \(exampleFormattedString)."
errorStr = "Cannot parse \(value)\(extendedDescription.map({ ": \($0)." }) ?? ".") String should adhere to the preferred format of the locale, such as \(exampleFormattedString)."
} else {
errorStr = "Cannot parse \(value)."
errorStr = "Cannot parse \(value)\(extendedDescription.map({ ": \($0)." }) ?? ".")"
}
return CocoaError(CocoaError.formatting, userInfo: [ NSDebugDescriptionErrorKey: errorStr ])
}
func isASCIIDigit(_ x: UInt8) -> Bool {
x >= UInt8(ascii: "0") && x <= UInt8(ascii: "9")
}
extension BufferViewIterator<UInt8> {
mutating func expectCharacter(_ expected: UInt8, input: String, onFailure: @autoclosure () -> (String), extendedDescription: String? = nil) throws {
guard let parsed = next(), parsed == expected else {
throw parseError(input, exampleFormattedString: onFailure(), extendedDescription: extendedDescription)
}
}
mutating func expectOneOrMoreCharacters(_ expected: UInt8, input: String, onFailure: @autoclosure () -> (String), extendedDescription: String? = nil) throws {
guard let parsed = next(), parsed == expected else {
throw parseError(input, exampleFormattedString: onFailure(), extendedDescription: extendedDescription)
}
while let parsed = peek(), parsed == expected {
advance()
}
}
mutating func expectZeroOrMoreCharacters(_ expected: UInt8) {
while let parsed = peek(), parsed == expected {
advance()
}
}
mutating func digits(minDigits: Int? = nil, maxDigits: Int? = nil, nanoseconds: Bool = false, input: String, onFailure: @autoclosure () -> (String), extendedDescription: String? = nil) throws -> Int {
// Consume all leading zeros, parse until we no longer see a digit
var result = 0
var count = 0
// Cap at 10 digits max to avoid overflow
let max = min(maxDigits ?? 10, 10)
while let next = peek(), isASCIIDigit(next) {
let digit = Int(next - UInt8(ascii: "0"))
result *= 10
result += digit
advance()
count += 1
if count >= max { break }
}
guard count > 0 else {
// No digits actually found
throw parseError(input, exampleFormattedString: onFailure(), extendedDescription: extendedDescription)
}
if let minDigits, count < minDigits {
// Too few digits found
throw parseError(input, exampleFormattedString: onFailure(), extendedDescription: extendedDescription)
}
if nanoseconds {
// Keeps us in the land of integers
if count == 1 { return result * 100_000_000 }
if count == 2 { return result * 10_000_000 }
if count == 3 { return result * 1_000_000 }
if count == 4 { return result * 100_000 }
if count == 5 { return result * 10_000 }
if count == 6 { return result * 1_000 }
if count == 7 { return result * 100 }
if count == 8 { return result * 10 }
if count == 9 { return result }
throw parseError(input, exampleFormattedString: onFailure(), extendedDescription: extendedDescription)
}
return result
}
}
// Formatting helpers
extension OutputBuffer<CChar> {
static let asciiZero = CChar(48)
mutating func append(_ i: Int, zeroPad: Int) {
if i < 10 {
if zeroPad - 1 > 0 {
for _ in 0..<zeroPad-1 { appendElement(Self.asciiZero) }
}
appendElement(Self.asciiZero + CChar(i))
} else if i < 100 {
if zeroPad - 2 > 0 {
for _ in 0..<zeroPad-2 { appendElement(Self.asciiZero) }
}
let (tens, ones) = i.quotientAndRemainder(dividingBy: 10)
appendElement(Self.asciiZero + CChar(tens))
appendElement(Self.asciiZero + CChar(ones))
} else if i < 1000 {
if zeroPad - 3 > 0 {
for _ in 0..<zeroPad-3 { appendElement(Self.asciiZero) }
}
let (hundreds, remainder) = i.quotientAndRemainder(dividingBy: 100)
let (tens, ones) = remainder.quotientAndRemainder(dividingBy: 10)
appendElement(Self.asciiZero + CChar(hundreds))
appendElement(Self.asciiZero + CChar(tens))
appendElement(Self.asciiZero + CChar(ones))
} else if i < 10000 {
if zeroPad - 4 > 0 {
for _ in 0..<zeroPad-4 { appendElement(Self.asciiZero) }
}
let (thousands, remainder) = i.quotientAndRemainder(dividingBy: 1000)
let (hundreds, remainder2) = remainder.quotientAndRemainder(dividingBy: 100)
let (tens, ones) = remainder2.quotientAndRemainder(dividingBy: 10)
appendElement(Self.asciiZero + CChar(thousands))
appendElement(Self.asciiZero + CChar(hundreds))
appendElement(Self.asciiZero + CChar(tens))
appendElement(Self.asciiZero + CChar(ones))
} else {
// Special case - we don't do zero padding
var desc = i.numericStringRepresentation
desc.withUTF8 {
$0.withMemoryRebound(to: CChar.self) { buf in
append(fromContentsOf: buf)
}
}
}
}
}

@ -679,7 +679,7 @@ extension JSONDecoderImpl: Decoder {
var data: Data?
if isSimple {
data = withBuffer(for: region) { buffer, _ in
try? Base64.decode(bytes: buffer)
Data(decodingBase64: buffer)
}
}
if data == nil {

@ -271,65 +271,4 @@ extension BidirectionalCollection where Element == Unicode.Scalar, Index == Stri
}
// START: Workaround for https://github.com/swiftlang/swift/pull/78697
// The extensions below are temporarily relocated to work around a compiler crash.
// Once that crash is resolved, they should be moved back to their original files.
#if !NO_FILESYSTEM
// Relocated from FileManager+Utilities.swift
#if FOUNDATION_FRAMEWORK && os(macOS)
extension URLResourceKey {
static var _finderInfoKey: Self { URLResourceKey("_NSURLFinderInfoKey") }
}
#endif
// Relocated from FileManager+Files.swift
#if FOUNDATION_FRAMEWORK
internal import DarwinPrivate.sys.content_protection
#endif
#if !os(Windows)
#if FOUNDATION_FRAMEWORK
extension FileProtectionType {
var intValue: Int32? {
switch self {
case .complete: PROTECTION_CLASS_A
case .init(rawValue: "NSFileProtectionWriteOnly"), .completeUnlessOpen: PROTECTION_CLASS_B
case .init(rawValue: "NSFileProtectionCompleteUntilUserAuthentication"), .completeUntilFirstUserAuthentication: PROTECTION_CLASS_C
case .none: PROTECTION_CLASS_D
#if !os(macOS)
case .completeWhenUserInactive: PROTECTION_CLASS_CX
#endif
default: nil
}
}
init?(intValue value: Int32) {
switch value {
case PROTECTION_CLASS_A: self = .complete
case PROTECTION_CLASS_B: self = .completeUnlessOpen
case PROTECTION_CLASS_C: self = .completeUntilFirstUserAuthentication
case PROTECTION_CLASS_D: self = .none
#if !os(macOS)
case PROTECTION_CLASS_CX: self = .completeWhenUserInactive
#endif
default: return nil
}
}
}
#endif
#endif
#endif
#endif
#if !NO_FILESYSTEM
// Relocated from FileManager+Files.swift. Originally fileprivate.
extension FileAttributeKey {
internal static var _extendedAttributes: Self { Self("NSFileExtendedAttributes") }
}
#endif
// END: Workaround for https://github.com/swiftlang/swift/pull/78697

@ -1986,36 +1986,6 @@ extension DataTests {
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: " AQIDBA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "A QIDBA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQ IDBA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQI DBA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQID BA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDB A==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA ==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: " AQIDBA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "A QIDBA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQ IDBA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQI DBA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQID BA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDB A==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA ==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: " AQIDBA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "A QIDBA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQ IDBA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQI DBA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQID BA==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDB A==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA ==", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters))
@ -2026,36 +1996,6 @@ extension DataTests {
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQI DBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQID BAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDB AU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBA U=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQI DBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQID BAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDB AU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBA U=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQI DBAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQID BAU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDB AU=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBA U=", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters))
@ -2065,36 +2005,6 @@ extension DataTests {
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQI DBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQID BAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDB AUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQI DBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQID BAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDB AUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQI DBAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQID BAUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDB AUG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters))
XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters))
}
func test_base64Decode_test1MBDataGoing0to255OverAndOver() {

@ -0,0 +1,138 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#if canImport(TestSupport)
import TestSupport
#endif
#if canImport(FoundationEssentials)
@testable import FoundationEssentials
#endif
#if FOUNDATION_FRAMEWORK
@testable import Foundation
#endif
final class HTTPFormatStyleFormattingTests: XCTestCase {
func test_HTTPFormat() throws {
let date = Date.now
let formatted = date.formatted(.http) // e.g. "Fri, 17 Jan 2025 19:03:05 GMT"
let parsed = try? Date(formatted, strategy: .http)
let result = try XCTUnwrap(parsed)
XCTAssertEqual(date.timeIntervalSinceReferenceDate, result.timeIntervalSinceReferenceDate, accuracy: 1.0)
}
func test_HTTPFormat_components() throws {
let date = Date.now
let formatted = date.formatted(.http) // e.g. "Fri, 17 Jan 2025 19:03:05 GMT"
let parsed = try? DateComponents(formatted, strategy: .http)
let result = try XCTUnwrap(parsed)
let resultDate = Calendar(identifier: .gregorian).date(from: result)
let resultDateUnwrapped = try XCTUnwrap(resultDate)
XCTAssertEqual(date.timeIntervalSinceReferenceDate, resultDateUnwrapped.timeIntervalSinceReferenceDate, accuracy: 1.0)
}
func test_HTTPFormat_variousInputs() throws {
let tests = [
"Mon, 20 Jan 2025 01:02:03 GMT",
"Tue, 20 Jan 2025 10:02:03 GMT",
"Wed, 20 Jan 2025 23:02:03 GMT",
"Thu, 20 Jan 2025 01:10:03 GMT",
"Fri, 20 Jan 2025 01:50:59 GMT",
"Sat, 20 Jan 2025 01:50:60 GMT", // 60 is valid, treated as 0
"Sun, 20 Jan 2025 01:03:03 GMT",
"20 Jan 2025 01:02:03 GMT", // Missing weekdays is ok
"20 Jan 2025 10:02:03 GMT",
"20 Jan 2025 23:02:03 GMT",
"20 Jan 2025 01:10:03 GMT",
"20 Jan 2025 01:50:59 GMT",
"20 Jan 2025 01:50:60 GMT",
"20 Jan 2025 01:03:03 GMT",
"Mon, 20 Jan 2025 01:03:03 GMT",
"Mon, 03 Feb 2025 01:03:03 GMT",
"Mon, 03 Mar 2025 01:03:03 GMT",
"Mon, 14 Apr 2025 01:03:03 GMT",
"Mon, 05 May 2025 01:03:03 GMT",
"Mon, 21 Jul 2025 01:03:03 GMT",
"Mon, 04 Aug 2025 01:03:03 GMT",
"Mon, 22 Sep 2025 01:03:03 GMT",
"Mon, 30 Oct 2025 01:03:03 GMT",
"Mon, 24 Nov 2025 01:03:03 GMT",
"Mon, 22 Dec 2025 01:03:03 GMT",
"Tue, 29 Feb 2028 01:03:03 GMT", // leap day
]
for good in tests {
XCTAssertNotNil(try? Date(good, strategy: .http), "Input \(good) was nil")
XCTAssertNotNil(try? DateComponents(good, strategy: .http), "Input \(good) was nil")
}
}
func test_HTTPFormat_badInputs() throws {
let tests = [
"Xri, 17 Jan 2025 19:03:05 GMT",
"Fri, 17 Janu 2025 19:03:05 GMT",
"Fri, 17Jan 2025 19:03:05 GMT",
"Fri, 17 Xrz 2025 19:03:05 GMT",
"Fri, 17 Jan 2025 19:03:05", // missing GMT
"Fri, 1 Jan 2025 19:03:05 GMT",
"Fri, 17 Jan 2025 1:03:05 GMT",
"Fri, 17 Jan 2025 19:3:05 GMT",
"Fri, 17 Jan 2025 19:03:5 GMT",
"Fri, 17 Jan 2025 19:03:05 GmT",
"Fri, 17 Jan 20252 19:03:05 GMT",
"Fri, 17 Jan 252 19:03:05 GMT",
"fri, 17 Jan 2025 19:03:05 GMT", // miscapitalized
"Fri, 17 jan 2025 19:03:05 GMT",
"Fri, 16 Jan 2025 25:03:05 GMT", // nonsense date
"Fri, 30 Feb 2025 25:03:05 GMT", // nonsense date
]
for bad in tests {
XCTAssertNil(try? Date(bad, strategy: .http), "Input \(bad) was not nil")
XCTAssertNil(try? DateComponents(bad, strategy: .http), "Input \(bad) was not nil")
}
}
func test_HTTPComponentsFormat() throws {
let input = "Fri, 17 Jan 2025 19:03:05 GMT"
let parsed = try? DateComponents(input, strategy: .http)
XCTAssertEqual(parsed?.weekday, 6)
XCTAssertEqual(parsed?.day, 17)
XCTAssertEqual(parsed?.month, 1)
XCTAssertEqual(parsed?.year, 2025)
XCTAssertEqual(parsed?.hour, 19)
XCTAssertEqual(parsed?.minute, 3)
XCTAssertEqual(parsed?.second, 5)
XCTAssertEqual(parsed?.timeZone, TimeZone.gmt)
}
func test_validatingResultOfParseVsString() throws {
// This date will parse correctly, but of course the value of 99 does not correspond to the actual day.
let strangeDate = "Mon, 99 Jan 2025 19:03:05 GMT"
let date = try XCTUnwrap(Date(strangeDate, strategy: .http))
let components = try XCTUnwrap(DateComponents(strangeDate, strategy: .http))
let actualDay = Calendar(identifier: .gregorian).component(.day, from: date)
let componentDay = try XCTUnwrap(components.day)
XCTAssertNotEqual(actualDay, componentDay)
}
}