[swift-evolution] [Pitch] DateComponents{Encoding/Decoding}Strategy in JSON{Encoder/Decoder}

Itai Ferber iferber at apple.com
Wed Sep 6 13:03:34 CDT 2017


Hi Pitiphong,

Don’t worry — your original email was clear, and we are on the same 
page about `Date{En,De}codingStrategy` and 
`DateComponents{En,De}codingStrategy` being separate things.
To clarify my points, though, there are two main things I want to say:

1. I think there is a mismatch here between your goal of representing 
the components of a date (and what `DateComponents` can specifically 
hold) and the goal of ISO 8601
2. I think that there is an inherent problem in parsing `DateComponents` 
due to ambiguity

I think both of these issues can be solved by reading and writing a 
`Date` (formatted however you need it to be) instead of 
`DateComponents`.

To elaborate:

* `DateComponents` is meant to be a container for an _arbitrary_ subset 
of information about a `Date`. A `Date` represents a specific instant in 
time, but `DateComponents` are effectively meaningless without 
additional context. In the examples that you give, it’s possible to 
represent the concepts at hand with `DateComponents`, but in order to 
make those components actionable and meaningful, you still need to 
convert them to `Date`s. Note also that:

     * It’s entirely possible to create a `DateComponents` which 
represents a date which does not exist, or a time which does not exist
     * Any of these concepts can also be represented by a `Date` instead 
of just components; e.g., an all-day event can be represented by a 
`Date` that represents the beginning of the day (`00:00:00`) and a flag 
that indicates that the time of the event can be ignored, or by a start 
`Date` that represents the start of the day and and end `Date` that 
represents the end of the day

* Unlike `DateComponents`, ISO 8601 strings have some structure to them. 
They cannot represent just a time zone, for instance, or some singular 
components of a date/time (e.g. a month without a year, a day without a 
month and year, a minute without an hour, a second without a minute and 
hour, etc.). I think this is a relatively large conceptual mismatch that 
is worth considering deeply. There are a lot of `DateComponents` 
instances which simply cannot be represented by an ISO 8601 string
* There is also the issue of decoding arbitrary ISO 8601 strings into 
`DateComponents`. `DateComponents`, having no structure at all, have no 
specified format they can expect to decode from, and ISO 8601 does not 
always provide that structure. Consider the following example:

     * ISO 8601 allows for date representations by year, month, and day 
(`YYYY-MM-DD`), among other forms. But it also allows days to be left 
unspecified (`YYYY-MM`), and even months (`YYYY`)
     * Similarly, it allows for a time representations by hour, minute, 
and second (`hh:mm:ss`), but also just hour and minute (`hh:mm`), and 
just hour (`hh`). Importantly, it allows time separators to be omitted 
(`hhmmss`, `hhmm`, `hh`)
     * Consider then, attempting to parse the string `"2017"` without 
any context — what `DateComponents` should be read out? Intuitively, 
`2017` looks like a year (`YYYY`), but it is equally valid to parse as 
the time `20:17` (`hhmm`). Without knowing the expected format, parsing 
is ambiguous

   We cannot promise to parse `DateComponents` in all cases because 
there are many combinations of strings that are just completely 
ambiguous.

* So, to get at the core of this — if there is a specific format that 
you would like to encode to and from, why not do so with a `Date` and a 
`DateFormatter` (or if you need ISO 8601 specifically, 
`ISO8601DateFormatter`)? With a formatter, the format is unambiguous 
because you explicitly provide it, and there is nothing the date can’t 
represent that `DateComponents` can. You can always parse the date and 
pull out only those components that you care about. You also mention 
interoperability with an external JSON source — how is that source 
producing a string/parsing one back? [What I’m getting at here is: 
what is the value of adding a new, potentially risky strategy over 
existing methods that might work just as well, or better?]
* And lastly, if `.iso8601` is not necessarily a good fit for this 
strategy, what separates `.custom` from just overriding `encode(to:)` 
and `init(from:)` and writing the components out in the format that you 
need?

I think answers to these questions can help us push this forward. :)

— Itai

On 5 Sep 2017, at 10:41, Pitiphong Phongpattranont wrote:

> Hi Itai,
>
> I think my first pitch email was not clear enough and want to sorry 
> for that. I have been working on a calendar app for awhile and 
> understand the concept of calendar or date and time programming in 
> some level. I didn’t pitch the idea of encoding and decoding `Date` 
> value with this `DateComponents{Encoding/Decoding}Strategy`. I still 
> agree that `Date` value should be encoded/decoded with the 
> `Date{Encoding/Decoding}Strategy`. The 
> DateComponents{Encoding/Decoding}Strategy I pitched only apply for 
> `DateComponents` value only.
>
> About the use case, I think there are some application which store an 
> information of a `Date` value that is not include a time value (A date 
> of September 6th, 2017) for example a calendar app which want to store 
> the Start and End date of an `All Day Event` with a value of 
> DateComponents type or an alarm app which want to store just a time of 
> the day that user want to set an recurring alarm (10:30am.)
>
> The problem I found with the current implementation is that I have no 
> control on how the DateComponents implement the conformance methods of 
> the Encodable and Decodable protocol. This means that if I have a 
> service that serialize those properties with a difference notation 
> (ISO 8601 in my case) then I cannot rely on the auto synthesized 
> implementation from the compiler and need to do a manual 
> encoding/decoding by manually implement the Encodable and Decodable
>
> Lastly, on the issue that `ISO8601` standard does not support every 
> components in DateComponents, I still haven’t thought this though 
> and still thinking about it. I want to pitch the idea first and would 
> like to have a discussion/brainstorm on should we do this and how we 
> can do it. My backup plan is doesn’t include the `iso8601` strategy 
> but still have the `custom` strategy for those who need to apply a 
> custom encoding/decoding strategy which will be apply to all values in 
> a payload. Since we encode/decode a JSON from one source at a time and 
> the encoding/decoding strategy of DateComponents of that source should 
> be consistency throughout its types (which may be the types that I own 
> or the types from a 3rd party service), I think this still is a valid 
> use case for providing a custom strategy.
>
> Thank you
> — Pitiphong P.
>
>
>> On 6 Sep BE 2560, at 00:15, Itai Ferber <iferber at apple.com> wrote:
>>
>> Hi Pitiphong,
>>
>> Thanks for pitching this! My main question here is about the use 
>> case. Since encoding/decoding strategies apply to all values in a 
>> payload (whether or not those belong to types that you own), they 
>> inherently come with some risk.
>> What is the use case in mind for needing to encode and decode 
>> DateComponents directly, as opposed to encoding and decoding a Date 
>> instance and pulling the components you need from that?
>>
>> From a correctness standpoint, I also want to point out that 
>> DateComponents is really just a "bag of stuff" that doesn’t 
>> necessarily mean much until converted into a Date through a Calendar 
>> and a TimeZone. There is somewhat of a mismatch between this "bag of 
>> stuff" and what ISO 8601 intends to represent — an actual date and 
>> time. It’s possible to represent things in a DateComponents that 
>> don’t really make sense for (or are not supported by) 
>> ISO-8601-formatted dates. For instance, you can have a DateComponents 
>> which just has a TimeZone, but ISO 8601 does not allow representing a 
>> time zone without a corresponding time. DateComponents also, for 
>> instance, has a quarter component (among others) which I’m almost 
>> certain ISO 8601 has no equivalent for.
>>
>> Given that conceptual mismatch, I think we’d need a very compelling 
>> use case to support this over simply using Date.
>>
>> — Itai
>>
>> On 3 Sep 2017, at 0:55, Pitiphong Phongpattranont via swift-evolution 
>> wrote:
>>
>> Hi folks, I have an idea on improving the JSON{Encoder/Decoder} to 
>> pitch.
>>
>> Since JSON doesn’t have a native representation for 
>> `DateComponents` like it doesn’t have for `Date` too so that 
>> there’re many ways to represent it in JSON, for example ISO 8601, 
>> UNIX timestamp, etc. for Date. There are also a few ways to represent 
>> `DateComponents` too, for example ISO 8601 
>> (https://en.wikipedia.org/wiki/ISO_8601 
>> <https://en.wikipedia.org/wiki/ISO_8601>) also describes how to 
>> represent some of the valid date components (e.g. "2017-09-03”). 
>> Unlike what JSON{Encoder/Decoder} does to represent `Date` value with 
>> several strategy but there is no support like that for 
>> `DateComponents`.
>>
>> The current implementation DateComponents is to encode/decode with 
>> KeyedContainer and cannot provide a custom or ISO 8601 compatible 
>> implementation. So I think JSON{Encoder/Decoder} should have a 
>> strategy for encoding/decoding `DateComponents` just like for Date
>>
>> Here’s an initial `DateComponentsStrategy` strategy that I want 
>> JSON{Encoder/Decoder} I can think of now, any suggestion is welcomed.
>>
>> ```swift
>> /// The strategy to use for encoding `DateComponents` values.
>> public enum DateComponentsStrategy {
>> /// Defer to `Date` for choosing an encoding. This is the default 
>> strategy.
>> case deferredToDateComponents
>>
>> /// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 
>> format).
>> case iso8601
>>
>> /// Encode the `Date` as a custom value encoded by the given closure.
>> ///
>> /// If the closure fails to encode a value into the given encoder, 
>> the encoder will encode an empty automatic container in its place.
>> case custom((DateComponents, Encoder) throws -> Void)
>> }
>> ```
>>
>> What do you guys think about this pitch?
>>
>>
>> Pitiphong Phongpattranont
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>> <https://lists.swift.org/mailman/listinfo/swift-evolution>


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170906/a761a714/attachment.html>


More information about the swift-evolution mailing list