Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IntervalFormat #188

Closed
jungshik opened this issue Oct 18, 2017 · 14 comments · Fixed by #532
Closed

Add IntervalFormat #188

jungshik opened this issue Oct 18, 2017 · 14 comments · Fixed by #532
Assignees
Labels
c: datetime Component: dates, times, timezones s: in progress Status: the issue has an active proposal

Comments

@jungshik
Copy link

jungshik commented Oct 18, 2017

https://github.com/tc39/ecma402/blob/master/README.md does not have 'Interval format'. So, I guess it's not on the table, yet.

This is a place holder for IntervalFormat. I'll add more details later.

For now, briefly what this API would do is to format an interval with the minimum redundancy given two points in time and a "pattern" (out of a fixed list of patterns).

For instance, given 2016-04-05 and 2016-04-07 and "yMMMd", the formatted result can be "April 5 - 7, 2016". For "MMMd", it'd be 'Apr 5 - 7'.

For 2017-05-12 and 2017-07-04, the result can be "May 12 - July 4" for "MMMd" and "May 12 - Jul 4, 2017" for "yMMMMd"

BTW, CLDR has necessary data and ICU has an implementation.

@jungshik
Copy link
Author

ICU API doc

@zbraniecki
Copy link
Member

IntervalFormat is one way to think about it. Two others are:

  • we could try to support ranges across our formatters - that would handle a date range, plural rules range and unit range and number range.
  • we could try to fold it into DateTimeFormat as sth like dtf.formatRange(date1, date2)

@caridy caridy added the s: help wanted Status: help wanted; needs proposal champion label Oct 24, 2017
@jungshik
Copy link
Author

@fabalbon is interested in this feature.

@fabalbon
Copy link
Member

fabalbon commented Feb 6, 2018

Yes! I'm interested in adding this feature.

I agree with Zibi's idea of folding this into Intl.DateTimeFormat by adding a formatRange(date1, date2) method. It looks like a reasonable extension as the set of options used when formatting dates also apply when formatting date intervals.

Also, I think that this same approach can be used to add support for ranges on other formatters.

My biggest concern with this would be the implementation. For instance, if using ICU and CLDR, when a Intl.DateTimeFormat object is created, implementations would need to instance an ICU DateIntervalFormat which will load all date interval data, in addition to all the date/time format data that is already being loaded. @jungshik, do you think this could be a problem?

A second question that needs to be discussed is if we should use formatRange or formatInterval. Several libraries such as ICU, JodaTime, Swift use "Interval" when referring to date/time formatting as it's more date/time specific, but if we decide to add ranges support across all formatters, then formatRange(), which is more generic, might be a better choice.

@fabalbon
Copy link
Member

fabalbon commented Feb 11, 2018

Example usage:

let date1 = new Date(Date.UTC(2007, 0, 10, 10, 0, 0));
let date2 = new Date(Date.UTC(2007, 0, 10, 11, 0, 0));
let date3 = new Date(Date.UTC(2007, 0, 20, 10, 0, 0));
// > 'Wed, 10 Jan 2007 10:00:00 GMT'
// > 'Wed, 10 Jan 2007 11:00:00 GMT'
// > 'Sat, 20 Jan 2007 10:00:00 GMT'

let fmt1 = new Intl.DateTimeFormat("en", {
    year: '2-digit',
    month: 'numeric',
    day: 'numeric',
    hour: 'numeric',
    minute: 'numeric'
});
console.log(fmt1.format(date1));
console.log(fmt1.formatRange(date1, date2));
console.log(fmt1.formatRange(date1, date3));
// > '1/10/07, 10:00 AM'
// > '1/10/07, 10:00 – 11:00 AM'
// > '1/10/07, 10:00 AM – 1/20/07, 10:00 AM'

let fmt2 = new Intl.DateTimeFormat("en", {
    year: 'numeric',
    month: 'short',
    day: 'numeric'
});
console.log(fmt2.format(date1));
console.log(fmt2.formatRange(date1, date2));
console.log(fmt2.formatRange(date1, date3));
// > 'Jan 10, 2007'
// > 'Jan 10, 2007'
// > 'Jan 10 – 20, 2007'

In addition to formatRange(), it might be reasonable to also include a formatRangeToParts() method in this proposal. formatRangeToParts() could return an Array with all the tokens similar to what formatToParts() does. Regarding this, two questions come to mind:

  1. Should the delimiter used to separate both parts of the interval be labeled as "literal" or it would be better to use a different type? I can imagine applications wanting to style this token differently, but I don't know if this is applicable in all locales. In CLDR/ICU date interval formatting is achieved using a pattern (such as "MMM d – MMM d, y", or "{0} – {1}" in the fallback case), so nothing prevents prepending or appending a symbol to represent the interval in some locales.
  2. Should the tokens somehow indicate to which of the two parts of the interval they belong to? If someone needs this, it can be detected by looking for the first repeated token type.

@zbraniecki, any thoughts?

@jungshik
Copy link
Author

@fabalbon

f using ICU and CLDR, when a Intl.DateTimeFormat object is created, implementations would need to instance an ICU DateIntervalFormat which will load all date interval data, in addition to all the date/time format data that is already being loaded.

If that's a concern, we can lazy-load/create DateIntervalFormat when 'formatRange' method is called, I think assuming that formatRange is more rarely used than format.

As for formatRange vs formatInterval, I don't have a strong opinion on that. Well, I'm slightly more inclined to go with formatRange, but either way is fine.

@fabalbon
Copy link
Member

Ok! I prepared an initial stage 0 proposal with a possible API and alternatives:
https://github.com/fabalbon/proposal-intl-DateTimeFormat-formatRange

Regarding .formatRangeToParts(), it looks that the ICU API used to implement this (in V8) is not available in icu::DateIntervalFormat. If several vendors are using ICU to implement this then it might be reasonable to propose it's addition to ICU.

In particular, what is missing is a format() method that accepts an icu::FieldPositionIterator.

@fabalbon
Copy link
Member

fabalbon commented Mar 9, 2018

Going back to one of the previous questions, should the output of formatRangeToParts() somehow indicate the date from where each token comes from?

On the current proposal, the output consists of an array of objects each of which contains a type and a value (similar to the output of formatToParts()). For example:

// '10:00 – 11:00 AM'
[
  { type: 'hour',      value: '10'  },
  { type: 'literal',   value: ':'   },
  { type: 'minute',    value: '00'  },
  { type: 'literal',   value: ' – ' },
  { type: 'hour',      value: '11'  },
  { type: 'literal',   value: ':'   },
  { type: 'minute',    value: '00'  },
  { type: 'literal',   value: ' '   },
  { type: 'dayPeriod', value: 'AM'  }
]

When formatting date intervals, there is no guarantee that the first date will be printed first on the formatted string, or that it will be first on the formatRangeToParts() array. This behavior is locale-dependent.

Therefore, users of this method have no easy way of using a different style depending on the date from which each token comes from (first or second), they only can use a different style based on the type of the token (month, day, etc).

A possible solution to this could be to add an additional field to the return objects to indicate the source date. For example:

// '10:00 – 11:00 AM'
[
  { type: 'hour',      value: '10',  source: 1 },
  { type: 'literal',   value: ':',   source: 1 },
  { type: 'minute',    value: '00',  source: 1 },
  { type: 'literal',   value: ' – ', source: 1 },
  { type: 'hour',      value: '11',  source: 2 },
  { type: 'literal',   value: ':',   source: 2 },
  { type: 'minute',    value: '00',  source: 2 },
  { type: 'literal',   value: ' ',   source: 2 },
  { type: 'dayPeriod', value: 'AM',  source: 2 }
]

Date intervals commonly contain fields that are shared between the two dates (such as dayPeriod, in this example). In these cases, the field could simply be assigned to one of the two dates.

Open questions:

  • Should the return objects indicate the source date?
  • If a source field is added, which should be its name and value?

@rxaviers
Copy link
Member

rxaviers commented Mar 13, 2018

👍 in identifying the sources. Though, I wouldn't tag - as source 1. It's also questionable how to identify AM. (is it 1, or 2, or 1 and 2?)

Does it make sense to consider nested parts? For example:

// '10:00 – 11:00 AM'
[
  {
    type: "rangeStart",
    value: [
      { type: "hour", value: "10" },
      { type: "literal", value: ":" },
      { type: "minute", value: "00" }
    ]
  },
  { type: "literal", value: " – " },
  {
    type: "rangeEnd",
    value: [
      { type: "hour", value: "11" },
      { type: "literal", value: ":" },
      { type: "minute", value: "00" }
    ]
  },
  { type: "literal", value: " " },
  { type: "dayPeriod", value: "AM" }
];

@fabalbon
Copy link
Member

fabalbon commented Mar 15, 2018

I like this approach! I think it's more intuitive, and I agree that is better to clearly indicate if a token is shared or if it doesn't belong to any of the two dates.

The only issue I see with this (although I don't know how relevant it is), is that because we are encoding the token's date on the structure of the returned value, instead of simply putting that information on a tag that can be ignored, we are making this method more convoluted to use when it's user is not interested in the token's date.

I'm not sure, but maybe an alternative might be to keep this array flat, but making the source tag more descriptive? Such as using "startRange", "endRange", "shared" as values?

For example:

// '10:00 – 11:00 AM'
[
  { type: 'hour',      value: '10',  source: "startRange" },
  { type: 'literal',   value: ':',   source: "startRange" },
  { type: 'minute',    value: '00',  source: "startRange" },
  { type: 'literal',   value: ' – ', source: "shared" },
  { type: 'hour',      value: '11',  source: "endRange" },
  { type: 'literal',   value: ':',   source: "endRange" },
  { type: 'minute',    value: '00',  source: "endRange" },
  { type: 'literal',   value: ' ',   source: "shared" },
  { type: 'dayPeriod', value: 'AM',  source: "shared" }
]

A second benefit of keeping the array flat is that it would be compatible with what formatToParts() returns.

@leobalter
Copy link
Member

leobalter commented Mar 7, 2019

The flat style seems better for me and it seems to be the one in the current proposed spec text, right?

https://rawgit.com/fabalbon/proposal-intl-DateTimeFormat-formatRange/master/out/#sec-formatdatetimerangetoparts

@fabalbon
Copy link
Member

fabalbon commented Mar 7, 2019

Yes! We discussed this issue at the 402 working group when we started working on this proposal, and the group's consensus at the time was to use the flat approach.

That is what is proposed on the current spec text draft.

@sffc sffc added s: in progress Status: the issue has an active proposal c: datetime Component: dates, times, timezones and removed s: help wanted Status: help wanted; needs proposal champion labels Mar 19, 2019
@FrankYFTang
Copy link
Contributor

Should we close this issue since we already have formatRange proposal reach stage 3?

@sffc
Copy link
Contributor

sffc commented May 18, 2019

I have the "active proposal" tag. We can close when formatRange reaches Stage 4 and is merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: datetime Component: dates, times, timezones s: in progress Status: the issue has an active proposal
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants