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

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\) 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.

  /// 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

So you are suggesting to use the iso8601 calendar but what timezone? Iirc there are some date components that are not representable in a conversion to iso8601 because of quirks with daylight savings and time zones. The other issue is that the value would not round trip: say I stored a DateComponents with the Gregorian calendar and the timezone for americas/Los Angeles that would mean that when it would be serialized it would loose that information and be deserialized as iso8601 calendar with GMT-8.

Nope, what I’m proposing is just for a `DateComponents` that has only some components value but not a complete date-time value, for example a calendar date only components (“2017-09-03”) or a time of the day only components ("23:59:59”). On the calendar, since ISO 8601 supports Gregorian calendar only, I suggest we should throw and encoding error out to tell the user. For the last concern, “timezone” I still don’t have a better solution but if user really want to preserve the timezone information then they can use the `custom` strategy

···

On 3 Sep BE 2560, at 23:19, Philippe Hausler <phausler@apple.com> wrote:

On Sep 3, 2017, at 12:54 AM, Pitiphong Phongpattranont via swift-evolution <swift-evolution@swift.org> 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\) 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.

/// 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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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\) 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.

  /// 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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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@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\) 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.

/// 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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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@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\) 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.

/// 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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hi Itai,

Thanks for a good and solid explanation. First I want to express my use case that was the reason I try to pitch this idea. We have a schedule service and the data model that represent the occurrence holds an information on what date of this occurrence was created in terms of a date string “2017-09-06” which is conform to ISO 8601. In this case we don’t intend to store a `Date` value (which is a specific point in the time line) but only store an information of what date it was occurred. So I decided to parse the date string into a `DateComponents` value.

What motivates me to pitch this idea in the end was that I tried to rely on the auto synthesized code by the compiler. Since the `DateComponents` already conforms to the Encodable/Decodable protocol which already allow the compiler to synthesize the code for encode(to:) and init(from:), however since I need to decode from the data I told you at first, those generated code will fail to decode the `DateComponents` and cause me some bugs. That’s what encourage me to do this pitch.

— Pitiphong P.

···

On 7 Sep BE 2560, at 01:03, Itai Ferber <iferber@apple.com> wrote:

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:

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
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 Dates. 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@apple.com <mailto:iferber@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\) 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.

/// 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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Hi Itai,

As I told you in my last email that I’m thinking about the ISO 8601 case. After thinking about that, having a discussion in the Swift Evolution and reading your emails, I think it may not worth to add this into Swift Standard Library. I think the use case is not that much so it’s not worth the cost of maintenance alone not to mention or think about how to implement it properly (if we choose to do and support the `iso8601` strategy.

I think I will close this pitch and would like to thank you for reviewing and discussing on this.

— Pitiphong P.

···

On 7 Sep BE 2560, at 01:03, Itai Ferber <iferber@apple.com> wrote:

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:

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
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 Dates. 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@apple.com <mailto:iferber@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\) 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.

/// 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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Hi Pitiphong,

Thanks for taking the time and energy to pitch this, too! If we can find a good solution for matching this up with ISO 8601, and we have high demand for this feature, I think it will be worth reconsidering again in the future.
Thanks for the input!

— Itai

···

On 8 Sep 2017, at 13:00, Pitiphong Phongpattranont wrote:

Hi Itai,

As I told you in my last email that I’m thinking about the ISO 8601 case. After thinking about that, having a discussion in the Swift Evolution and reading your emails, I think it may not worth to add this into Swift Standard Library. I think the use case is not that much so it’s not worth the cost of maintenance alone not to mention or think about how to implement it properly (if we choose to do and support the `iso8601` strategy.

I think I will close this pitch and would like to thank you for reviewing and discussing on this.

— Pitiphong P.

On 7 Sep BE 2560, at 01:03, Itai Ferber <iferber@apple.com> wrote:

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:

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
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 Dates. 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@apple.com >>> <mailto:iferber@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\) 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.

/// 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@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution