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

Ability to specify a custom format for Intl.DateTimeFormat #554

Closed
sazzer opened this issue Mar 10, 2021 · 18 comments
Closed

Ability to specify a custom format for Intl.DateTimeFormat #554

sazzer opened this issue Mar 10, 2021 · 18 comments

Comments

@sazzer
Copy link

sazzer commented Mar 10, 2021

(Apologies if this is the wrong way to approach this or if it's been covered before, though I couldn't see it if so)

As best I can tell, Intl.DateTimeFormat doesn't have any ability to specify the exact format string to use. Instead it is always based upon the locale with the ability to customise individual portions of the produced string. This works great, but it lacks a bit of flexibility that is needed in certain cases.

For example, doing formatting in a particular locale specifying the "medium" style for date and time is easy:

> new Intl.DateTimeFormat('en-CA', {dateStyle: 'medium', timeStyle: 'medium'}).format(new Date());
'Mar. 10, 2021, 4:13:32 p.m.'

However, if you want to make subtle changes to that then you're in trouble. For example, it's impossible to do that in this locale such that there is no comma after the day-of-month.

It seems reasonable to have something like:

new Intl.DateTimeFormat('en-CA', {formatString: '%M %d %y, %h:%m:%s %a'}).format(new Date());
'Mar. 10 2021, 4:13:32 p.m.' // Note the lack of comma after the "10"

This then formats the individual components based on the locale rules - %M becomes the month name as used in en-CA - but lets you have better control over the exact structure of the output string.

@ray007
Copy link

ray007 commented Mar 10, 2021

I'd suggest ICU format syntax and even have a little project which does exactly that on top of Intl.DateTimeFormat: https://github.com/ray007/simple_dt.js.
There are other more mature libs out there which do the same (and maybe better) but need more code/data last I checked.

@sazzer
Copy link
Author

sazzer commented Mar 11, 2021

Oh - I've no doubt that there are plenty of ways to do this already. Either lightweight such as your solution, or more heavyweight such as Moment or similar. It just feels like something that the core library could do, and would fill in a potential gap that people are currently needing to pull in other libraries to achieve.

ICU format syntax makes a lot of sense. I used the above purely because it's what Java does, and that's what I use for my day job. That would then make my example actually be:

new Intl.DateTimeFormat('en-CA', {formatString: 'MMM dd yyyy, h:mm:ss a'}).format(new Date());
'Mar. 10 2021, 4:13:32 p.m.' // Note the lack of comma after the "10"

@zbraniecki
Copy link
Member

zbraniecki commented Mar 11, 2021

What you're asking for is a non-internationalized date format. Such an API, if it existed, should live not on Intl, but on Date - it is a date formatter.

@sazzer
Copy link
Author

sazzer commented Mar 11, 2021

That's a fair point. However, what about the bits that are translated? For example:

new Intl.DateTimeFormat('en-CA', {formatString: 'MMM dd yyyy, h:mm:ss a'}).format(new Date());
'Mar. 10 2021, 4:13:32 p.m.'

new Intl.DateTimeFormat('fr-CA', {formatString: 'MMM dd yyyy, h:mm:ss a'}).format(new Date());
'Mars 10 2021, 4:13:32 après-midi' // The month name and time period are now in French instead of English.

I agree though that to an extent this is less about internationalisation since you can very easily use it to generate strings in the wrong format for the target locale.

@zbraniecki
Copy link
Member

what about the bits that are translated?

This information belongs to Intl.DisplayNames API which may be extended to return translations for symbols such as WeekDay, MonthName` etc.

@sazzer
Copy link
Author

sazzer commented Mar 11, 2021

Ah - fair enough. So the suggestion is a new method on or around Date that does this, and itself depends on Intl.DisplayName? That would work just as well :)

@zbraniecki
Copy link
Member

Ah - fair enough. So the suggestion is a new method on or around Date that does this, and itself depends on Intl.DisplayName? That would work just as well :)

I don't know if Date would want to depend on Intl - so far we avoided that connection, although that may change with Temporal.

My initial intuitive idea would be to not bind them. We would extend Intl.DisplayNames to get localizable strings we have about date/time like WeekDay, MonthName, and we'd add a Date API for formatting date to a pattern.

Then, a user would write a shallow method that does two steps:

  1. Take a pattern and localize the localizable symbols using Intl.DisplayNames, turning "%M %d %y, %h:%m:%s %a" into "July %d %y, %h:%m:%s a.m."
  2. Take the output of that and pass it to Date.prototype.formatPatten which would produce the final "July 24 2021, 12:32:09 a.m."

@sazzer
Copy link
Author

sazzer commented Mar 11, 2021

If that's the correct answer then that's fair enough, though that seems like it's more painful for the end-users to use instead of having something in the standard library that already did that for you.

In particular, the bits of the output string that you want to translate will vary depending on the pattern being used. Going through the ICU list it looks like you have: Era, Quarter, Month, Weekday, Period, and Zone. And most of those have variations within them - "Mon" vs "Monday", for example. I think I counted 30 different rows in the ICU fields list that would need to be handled. That's a fairly complicated "shallow method" for users to need to write each time. (And I know that the user wouldn't need to handle all 30 of those each time, but potentially they would need a different subset of them each time)

(This all assumes that I'm correct in understanding "a user would write a shallow method" to mean that the burden is on the developer using this API to do that work, instead of it being on the API itself)

@rxaviers
Copy link
Member

rxaviers commented Mar 11, 2021

@sazzer may I ask what's the use case please? I see you need a strict control over the formatted output, but you also don't want to re-implement an i18n library.

Like @zbraniecki mentioned, what's being asked is for a non-internationalized date format. This indeed defeats the purpose of an internationalization library, which is to allow your product to support any locale with no code change. By specifying patterns like this, your code will probably need to maintain if-else conditions (or a map) to select the correct pattern based on the locale, which isn't what you want from i18n point of view.

Maybe related... On #210, it's been discussed how to use browser's algorithm with user data, but discussion isn't concluded.

@sazzer
Copy link
Author

sazzer commented Mar 11, 2021

Our exact use case is mostly handled already by Intl.DateTimeFormat, except that want the output to be subtly different from what is generated. For example, we want the generated value for en-CA to not have a comma after the day.

We can - and are - solving this ourselves easily enough by using an alternative i18n library exactly as you say. I just raised this because it felt like a shame to need to pull in another library for this when the core Intl.DateTimeFormat can almost do exactly what we want.

If the answer is that this is something that should be done with an external library then that's fair enough :) Obviously it's unrealistic for the core library to do everything. But as I say, it feels like it is so close to offering this already and just stops short.

@rxaviers
Copy link
Member

I think what's being discussed in #210 would be a solution for you.

Last but not least, if you believe the comma (in this en-CA case) is a linguistic "bug", it's possible to submit a ticket to CLDR http://cldr.unicode.org that upon fix will be reflected in ICU and all browsers.

@zbraniecki
Copy link
Member

Our exact use case is mostly handled already by Intl.DateTimeFormat

That's a recurring pattern of our work on ECMA-402 - the more features we add, the more people's needs are "almost" fulfilled which drives them to request an extension of the ECMA-402 for their particular need over adding a library for it.
The problem is that those needs become very tailored and diverse and it's a long tail.

I'd say that your particular use case should be handled by DisplayNames+Date API.

@aphillips
Copy link

What's being described here is the equivalent of e.g. Java's (or ICU's) SimpleDateFormat API. My comment here will be slightly ironic, since I just finished building internal-to-Amazon training titled "Why you should never use SimpleDateFormat", but I'm not sure that this is should not be an Intl API. While I strongly discourage the use of "picture string" APIs (which require localization of the picture string), there are cases--mostly to do with serialization forms--that can make use of fixed formats. Generally skeletons are by far the better solution. But developers need to meet designers requirements sometimes that most of us would find icky.

Creating an "apparently" internationalized formatter in Date runs the risk of competing APIs that we then have to educate developers not to use.

@sffc
Copy link
Contributor

sffc commented Mar 11, 2021

An API that renders a date according to a specific format is being discussed as a potential future extension of Temporal: js-temporal/proposal-temporal-v2#5. Please chime in over there with feedback that could help shape the development of that feature.

As @rxaviers mentioned, approaches to let apps patch locale data are being discussed in #210.

As @zbraniecki mentioned, we have Intl.DisplayNames to get the localized string components, although note that we decided not to include month and weekday names in Intl.DisplayNames because they are calendar-dependent; you can get them directly from Intl.DateTimeFormat if you enable only a single field.

I don't think there's anything else actionable on this issue, so I will close it.

@LMCom
Copy link

LMCom commented Jan 14, 2022

> new Intl.DateTimeFormat('en-CA', {dateStyle: 'medium', timeStyle: 'medium'}).format(new Date());
'Mar. 10, 2021, 4:13:32 p.m.'

However, if you want to make subtle changes to that then you're in trouble. For example, it's impossible to do that in this locale such that there is no comma after the day-of-month.

I am here because of almost the same problem. It is questionable, if the comma plus the space between date and time (i. e. the separator between date and time) has to be unchangeable, although there could be a common default for each locale. I could imagine an option like dateTimeSeparator for this.

new Intl.DateTimeFormat('en-CA', {dateStyle: 'medium', timeStyle: 'medium', dateTimeSeparator: ' '}).format(new Date());
'Mar. 10, 2021 4:13:32 p.m.'

With such an option, the locale could provide a default separator, but it would be adjustable.

I wrote a small utility function to solve this.

function toLocalDateTime(date, locale, timezone, dateTimeSeparator = ' ', dateStyle = 'medium', timeStyle = 'medium')
{
    if(!date) {
        return ''
    }

    let dateObj;

    if(typeof date === 'string') {
        dateObj = new Date(date)
    }
    else if(date instanceof Date) {
        dateObj = date
    }
    else throw 'Argument "date" must be a string or a Date object.'

    return (new Intl.DateTimeFormat(locale, { timeZone: timezone, dateStyle: dateStyle })).format(dateObj) +
        dateTimeSeparator +
        (new Intl.DateTimeFormat(locale, { timeZone: timezone, timeStyle: timeStyle })).format(dateObj)
}

@camohub
Copy link

camohub commented Jan 27, 2022

Please add this function to Date object.

@secf4ult
Copy link

like @LMCom said, dateTimeSeparator and timeSeperator would be more customizable

@moimikey
Copy link

lil more JavaScript:

const [month, day, year] = new Intl.DateTimeFormat("en-US").format(Date.now()).split('/');
console.log(`${year}/${month}/${day}`); // 2023/7/27

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants