Consider date formatting

# Michael Kriegel (7 years ago)

I would like to suggest to take up date formatting into the standard. Either as optional format parameter on Date.prototype.toDateString() or as a separate method Date.prototype.toFormattedDateString(format).

format should be a string in the form as specified in tc39.github.io/ecma262/#sec-date-time-string-format

I know there are libraries for that, but I think it is fundamental enough to put it into the standard instead.

I hope this was not already discussed before and I just did not find the thread.

# kdex (7 years ago)

So what's wrong with Intl.DateTimeFormat?

# Michael Kriegel (7 years ago)

You cannot set an arbitrary format. From a UI point of view it is great to format it "localized", but if you have strict requirements in your projects, then you have to build it up yourself or use a library. So I do not see, how to use Intl.DateTimeFormat for custom format strings.

# kdex (7 years ago)

You can build custom formats with Intl.DateTimeFormat.prototype.formatToParts.

Combine these results with template literals; isn't that enough?

# Michael Kriegel (7 years ago)

Okay, this is still draft, I did not see it. It is very powerful, yet it makes it more complicated. The format strings, which are widely known and used would be handy to have in addition. However, if "they" decide, not to have them, I can still decide to use formatToParts or a library.

# Bob Myers (7 years ago)

There are third-party libraries which are so widely-used as to be defacto standards. Bob

# Michael Kriegel (7 years ago)

Quoting my initial posting:

I know there are libraries for that, but I think it is fundamental

enough to put it into the standard instead.

Isn't it legitimate to ask for a defacto-standard to become a real standard...

# Matthew Robb (7 years ago)

+1 this sentiment

Raise your hand if you are using Moment.js in projects today? Raise your hand if you ship the library wholesale? Raise your hand if you use webpack tok strip out the locale files which add sig. heft to your bundle?

Moment.js should be standardized...

HOWEVER: The language spec is likely the wrong place. This should probably be a browser spec as the biggest motivation is going to be localization which I find obnoxious to include in an often shipped defacto lib.

  • Matthew Robb
# Isiah Meadows (7 years ago)

I wouldn't quite call Moment and similar "de facto standards", especially not to the degree of something like Underscore/Lodash or Promises/A+ (especially Bluebird), which resulted in numerous obvious additions to the spec - ES5 function and array methods are nearly a clone of those in Underscore, and ES6 promises are a mix of RSVP.js and Bluebird (with implementations initially adapted from the latter). Async functions came after promise users immediately jumped all over co and later, Bluebird's Promise.fromCoroutine, in both Node and the browser.

As for existing work on dates (we all know they're broken), check out this proposal, currently stage 1: tc39/proposal-temporal. (It currently has no date parsing facilities, but ISO 8601 support is being considered.)

# Isiah Meadows (7 years ago)

No, you're probably thinking of ECMA 404, which handles all the ES Intl.* stuff. It's still under the TC39 umbrella, just not part of ES proper.

# Michael Kriegel (7 years ago)

Actually I used it in node.js today - where using/deploying another lib is less "painful" - agreed. Still I don't think it is browser scope only...

# Matthew Robb (7 years ago)

I just mean to say that it makes more sense as a userland lib outside of the context of browsers.

  • Matthew Robb
# Maggie Pint (7 years ago)

Substantial work is being put into a date standard library and formatting currently. There is a stage 1 proposal to rework the date library here: tc39/proposal-temporal. I have a shim for that proposal started here: maggiepint/temporal-shim. I will probably seek stage two for this proposal in November of January. It is quite large, and the shim and spec text take a long time to write, so it can't be moved quickly.

My plan for formatting is to continue to rely on ECMA 402 as we do today. That team has done a wonderful job in the space, and I'm not entirely sure how i could one-up them at this point.

As far as why that proposal isn't just the Moment.js library - quite a few reasons. I maintain the Moment.js library with a couple other people, and the moment team actually created the new temporal proposal based on the knowledge of where the library falls down, and where other libraries have succeeded. Some points of difference from moment: Proposal has all immutable data structures Proposal provides a way to represent a date only, time only, and date time without associated time zone APIs in several corners have 'safety' features that Moment doesn't have to prevent developer error - for instance the user must specify a time zone on a zoned object

Even after this proposal goes though, I assume there will still be libraries, simply because everyone has different preferences. That said, one should no longer HAVE to bring in a date library for anything as they do today.

# kai zhu (7 years ago)

going to rant about why existing date builtins are more than adequate, and that over-engineering is the cause of most date-issues (and justify with working vanilla js code).

moment.js is over-engineering and unnecessary. date manipulation is not that hard using vanilla javascript, if you follow the best-practice of representing it internally throughout your application as a standard utc-iso-string (e.g. "2017-09-21T16:57:06.781Z”). keeping the date represented as a string-by-default saves you the hassle of doing needless custom-serializations when writing it to json or as a dom-element attribute (not to mention making debugging easier by having human-readable values). it is also sort / comparison-operator friendly as well.

in fact, if you enforce keeping the date represented internally as a string, there are only 3 common-cases that you need to worry about dealing with date serialization/deserialization:

  1. date-arithmetic (trivial to do with 3-lines of vanilla javascript code)
  2. writing date to the ui and persistent-storage
  3. reading date from ui and persistent-storage

/*jslint node: true, regexp: true */
'use strict';

function nop() {
/*
 * this function will do nothing
 */
    return;
}

// 1. date-arithmetic
function dateArithmeticAdd(dateIsoString, milliseconds) {
/*
 * this function will return a new dateIsoString with the given (positive or negative) milliseconds added to it
 */
    console.error('dateArithmeticAdd(' + JSON.stringify(dateIsoString) + ',' +
        JSON.stringify(milliseconds) + ')');
    // return serialized dateIsoString
    return new Date(
        // de-serialize dateIsoString and add milliseconds to it
        new Date(dateIsoString).getTime() + milliseconds
    ).toISOString();
}

// 2. writing date to the ui and persistent-storage
function dateWriteToUi(dateIsoString, options) {
/*
 * this function will return the date in a string-format suitable for ui-presentation
 * you can edit this function to suit your application needs
 */
    console.error('dateWriteToUi(' + JSON.stringify(dateIsoString) + ',' +
        JSON.stringify(options) + ')');
    var dateObject;
    // de-serialize dateIsoString
    dateObject = new Date(dateIsoString);
    // present date in local-time instead of utc-time
    if (!options.isUtc) {
        dateObject = new Date(dateObject.getTime() -
            dateObject.getTimezoneOffset() * 60 * 1000);
    }
    dateObject.toISOString().replace((/(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)/), function (
        match0,
        YYYY,
        MM,
        DD,
        hh,
        mm,
        ss
    ) {
        // jslint-hack
        nop(match0);
        dateObject.YYYY = YYYY;
        dateObject.MM = MM;
        dateObject.DD = DD;
        dateObject.hh = hh;
        dateObject.mm = mm;
        dateObject.ss = ss;
    });
    switch (options.type) {
    case 'china':
        return dateObject.YYYY + '-' +
            dateObject.MM + '-' +
            dateObject.DD + ' ' +
            dateObject.hh + ':' +
            dateObject.mm + ':' +
            dateObject.ss;
    case 'germany':
        return dateObject.DD + '.' +
            dateObject.MM + '.' +
            dateObject.YYYY + ' ' +
            dateObject.hh + ':' +
            dateObject.mm + ':' +
            dateObject.ss;
    case 'usa':
        return dateObject.MM + '/' +
            dateObject.DD + '/' +
            dateObject.YYYY + ' ' +
            dateObject.hh + ':' +
            dateObject.mm + ':' +
            dateObject.ss;
    // case 'foo':
    // ...
    // represent in default locale setting
    default:
        return dateObject.toString();
    }
}

// 3. reading date from ui and persistent-storage
function dateReadFromUi(inputText, options) {
/*
 * this function will parse inputText and return an appropriate dateIsoString
 * you can edit this function to suit your application needs
 */
    console.error('dateReadFromUi(' + JSON.stringify(inputText) + ',' +
        JSON.stringify(options) + ')');
    var dateObject;
    // use default Date parser
    dateObject = new Date(inputText);
    switch (options.type) {
    case 'china':
        inputText.replace((/(\d\d\d\d).(\d\d).(\d\d).(\d\d):(\d\d):(\d\d)/), function (
            match0,
            YYYY,
            MM,
            DD,
            hh,
            mm,
            ss
        ) {
            // jslint-hack
            nop(match0);
            dateObject = new Date(YYYY + '-' + MM + '-' + DD + 'T' + hh + ':' + mm + ':' + ss);
        });
        break;
    case 'germany':
        inputText.replace((/(\d\d).(\d\d).(\d\d\d\d).(\d\d):(\d\d):(\d\d)/), function (
            match0,
            DD,
            MM,
            YYYY,
            hh,
            mm,
            ss
        ) {
            // jslint-hack
            nop(match0);
            dateObject = new Date(YYYY + '-' + MM + '-' + DD + 'T' + hh + ':' + mm + ':' + ss);
        });
        break;
    case 'usa':
        inputText.replace((/(\d\d).(\d\d).(\d\d\d\d).(\d\d):(\d\d):(\d\d)/), function (
            match0,
            MM,
            DD,
            YYYY,
            hh,
            mm,
            ss
        ) {
            // jslint-hack
            nop(match0);
            dateObject = new Date(YYYY + '-' + MM + '-' + DD + 'T' + hh + ':' + mm + ':' + ss);
        });
        break;
    // case 'foo':
    // ...
    }
    // inputText was in utc-time
    if (options.isUtc) {
        dateObject = new Date(dateObject.getTime() -
            dateObject.getTimezoneOffset() * 60 * 1000);
    }
    return dateObject.toISOString();
}

// 1. date-arithmetic
console.log('\n1. date-arithmetic');
console.log(dateArithmeticAdd('2000-01-30T00:00:00Z', 24 * 60 * 60 * 1000));
console.log(dateArithmeticAdd('2000-01-30T00:00:00Z', -24 * 60 * 60 * 1000));

// 2. writing date to the ui and persistent-storage
console.log('\n2. writing date to the ui and persistent-storage');
console.log(dateWriteToUi('2000-01-30T00:00:00Z', { type: 'china' }));
console.log(dateWriteToUi('2000-01-30T00:00:00Z', { type: 'china', isUtc: true }));
console.log(dateWriteToUi('2000-01-30T00:00:00Z', { type: 'germany' }));
console.log(dateWriteToUi('2000-01-30T00:00:00Z', { type: 'germany', isUtc: true }));
console.log(dateWriteToUi('2000-01-30T00:00:00Z', { type: 'usa' }));
console.log(dateWriteToUi('2000-01-30T00:00:00Z', { type: 'usa', isUtc: true }));

// 3. reading date from ui and persistent-storage
console.log('\n3. reading date from ui and persistent-storage');
console.log(dateReadFromUi('2000-01-30 00:00:00', { type: 'china' }));
console.log(dateReadFromUi('2000-01-30 00:00:00', { type: 'china', isUtc: true }));
console.log(dateReadFromUi('30.01.2000 00:00:00', { type: 'germany' }));
console.log(dateReadFromUi('30.01.2000 00:00:00', { type: 'germany', isUtc: true }));
console.log(dateReadFromUi('01/30/2000 00:00:00', { type: 'usa' }));
console.log(dateReadFromUi('01/30/2000 00:00:00', { type: 'usa', isUtc: true }));

# Darien Valentine (7 years ago)

I think there is a pretty good case to be made for native support for pattern-based formatting as the OP described. I believe it has prior art in other languages and, while the highly configurable, internationalized formatting provided by Intl.DateTimeFormat.prototype.format is very valuable, pattern-based formatting serves different purposes.

@ Maggie I’m very excited to see ES getting distinct, hack-free types for representing the different kinds of date/date time values that we often awkwardly conflate with the generic datetime Date. The immutability is refreshing — I cannot think of a time I ever thought "gee, I’m glad date objects are mutable".

@ kai zhu, I appreciate the perspective you try to bring to things on these forums, but I don’t think that your campaign for simplicity is well-served by arguing that people should roll their own date manipulation utilities. :) In many cases, it’s true that one needs nothing more than Date, and it’s also been my experience that sometimes when people think "but timezones!" they are creating, not solving, problems. However, cases that do demand more nuance aren’t uncommon, and when you wrote that date arithmetic is "trivial to do with 3-lines of vanilla javascript code", ten thousand dead developers briefly reanimated in their graves and hissed demonic curses.

# Caridy Patiño (7 years ago)

Intl.DateTimeFormat is a low level API that is very powerful, and we will continue evolving it. Intl.DateTimeFormat.prototype.formatToParts is just one more thing that we have added recently (stage 4). Patterns, and Skeletons (which are not the same) have been a topic of discussion many times, e.g.: tc39/ecma402#108, tc39/ecma402#108, and I hope we can continue those discussions.

Unfortunately, pattern seems to be a foot gun. It is only really useful to folks with a very specific requirement per locale, which means they need to define the pattern that they want per locale, and that sort of conflicts with the philosophy behind DateTimeFormat, and not many people realize that.

OTOH, skeleton is pretty much equivalent to what we have today via options, whether it has better ergonomics is debatable IMO. Additionally, Mozilla folks have been proposing some reforms to introduce a more simple form of skeleton that matches the OS configuration.

Bottom line, we are progressing in various fronts (date manipulation and formatting), and these are very exciting times for anyone who is interested in these topics, just come and help us in tc39/ecma402, tc39/ecma402 :)

./caridy

# Barret Furton (7 years ago)

@kai zhu Your implementation isn’t ISO 8601 compliant. Separators are optional, dates don’t need to include any time information, and there are additional formats for week number and headless dates that your implementation doesn’t support. Furthermore, at least one of your RegExp matchers has bugs in its implementation (matching all characters instead of a literal “.”). Moment.js is one of the most well-tested libraries in the JS community, which is part of why it sees such widespread adoption. And it’s not for lack of trying, either. I’ve worked with devs who are hesitant to add another lib to their stack and attempt date manipulation themselves, working out all the bugs, then finally giving up when they realize that their implementation breaks 3 different ways in Firefox, Safari, and IE.