Hi folks. There's been considerable interest in being able to support the very common RFC 9110 HTTP date format style in FoundationEssentials, which would make it available cheaply across the Swift ecosystem. This pitch outlines a proposed shape for this API, and I'd love to hear your feedback.
HTTP Date Format
- Proposal: SF-NNNN
- Authors: Cory Benfield, Tobias
- Review Manager: TBD
- Status: Awaiting implementation
Introduction
The HTTP specification requires that all HTTP servers send a Date
header field that contains the date and time at which a message was originated in a specific format. This proposal adds support to FoundationEssentials
to generate this "HTTP" date format.
Motivation
The HTTP date format is used throughout HTTP to represent instants in time. The format is specified entirely in RFC 9110 § 5.6.7. The format is simple and static, emitting a textual representation in the UTC time zone.
This format is used heavily across the web, causing it to be parsed and serialized enormously frequently. Providing a standard implementation of this transformation for Swift will enable developers on both the client and the server to easily handle this header format. Because the field is used frequently, there is substantial power and efficiency savings to be gained by having a high-performance parser and serializer for this format.
Proposed solution
We propose to add an additional format style to FoundationEssentials
. This formatter would follow the API shape of ISO8601FormatStyle
.
The implementation of this format style will be focused on performance and safety. A principal design goal is to ensure that it is very cheap to parse and serialize these header formats.
Detailed design
The HTTP date format will have add the following new API surface:
extension Date {
public func httpFormat() -> String
}
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 {
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: HTTPFormatStyle, rhs: HTTPFormatStyle) -> Bool
public init() { }
}
}
extension Date.HTTPFormatStyle : FormatStyle {
public typealias FormatInput = Date
public typealias FormatOutput = String
public func format(_ value: Date) -> String
}
public extension FormatStyle where Self == Date.HTTPFormatStyle {
static var http: Self {
return Date.HTTPFormatStyle()
}
}
extension Date.HTTPFormatStyle : ParseStrategy {
public func parse(_ value: String) throws -> Date
}
extension Date.HTTPFormatStyle: ParseableFormatStyle {
public var parseStrategy: Self
}
extension ParseableFormatStyle where Self == Date.HTTPFormatStyle {
public static var http: Self { .init() }
}
extension ParseStrategy where Self == Date.HTTPFormatStyle {
@_disfavoredOverload
public static var http: Self { .init() }
}
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)?
}
extension RegexComponent where Self == Date.HTTPFormatStyle {
/// Creates a regex component to match a RFC 9110 HTTP date and time, such as "Sun, 06 Nov 1994 08:49:37 GMT", and capture the string as a `Date`.
public static var http: Date.HTTPFormatStyle {
return Date.HTTPFormatStyle()
}
}
Source compatibility
There is no impact on source compatibility: this is entirely new API.
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
The HTTP format is exceedingly simple, so it is highly unlikely that any new features or API surface will be added to this format.
Alternatives considered
A custom interface instead of a format style
As the HTTP date format is extremely simple and frequently used in performance-sensitive contexts, we could have chosen to provide a very specific function instead of a general purpose date format. This would have the advantage of "funneling" developers towards the highest performance versions of this interface.
This approach was rejected as being unnecessarily restrictive. While it's important that this format can be accessed in a high performance way, there is no compelling reason to avoid offering a generic format style. There are circumstances in which users may wish to use this date format as an offering in other contexts.