0

So I couldn't find a good answer for how to get a numeric date format in client-side JavaScript specific to the users locale. E.g. if your language is 'en-US' I would like to get 'MM/DD/YYYY'.

Oscar
  • 11
  • 3
  • FYI, this question is specifically about getting the format _with_ placeholders as a string, not formatting dates. – Oscar Apr 12 '21 at 13:09

2 Answers2

0

Date.toLocaleDateString can be passed the options {year: 'numeric', month: '2-digit', day: '2-digit',} to format a date in a numeric format that nearest matches the user's locale.

To get a date format with "YYYY", "MM" and "DD" placeholders, you can replace a specific year, month and day with the respective placeholder string.

// Replace with date format e.g. 'MM/DD/YYYY'
const FALLBACK_DATE_FORMAT = 'your fallback date format';

// Memoize return value
let cachedNumericFixedWidthDateFormat = null;

function getNumericFixedWidthDateFormat() {
    if (cachedNumericFixedWidthDateFormat !== null) {
        return cachedNumericFixedWidthDateFormat;
    }

    let dateFormat;
    try {
        const dummyDate = new Date();
        dummyDate.setFullYear(1984);
        dummyDate.setMonth(0);
        dummyDate.setDate(23);

        dateFormat = dummyDate.toLocaleDateString(undefined, {
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
        }).replace('1984', 'YYYY').replace('01', 'MM').replace('23', 'DD');
    } catch (err) {
        // TODO: Monitor errors!

        return FALLBACK_DATE_FORMAT;
    }

    if (checkIsValidDateFormat(dateFormat)) {
        cachedNumericFixedWidthDateFormat = dateFormat;

        return dateFormat;
    } else {
        // TODO: Add monitoring!

        return FALLBACK_DATE_FORMAT;
    }
}

function checkIsValidDateFormat(dateFormat) {
    const yearCharsMatches = dateFormat.match(/YYYY/g);
    const monthCharsMatches = dateFormat.match(/MM/g);
    const dayCharsMatches = dateFormat.match(/DD/g);
    const digitCharMatches = dateFormat.match(/[0-9]/g);

    return yearCharsMatches !== null && yearCharsMatches.length === 1
        && monthCharsMatches !== null && monthCharsMatches.length === 1
        && dayCharsMatches !== null && dayCharsMatches.length === 1
        && digitCharMatches === null;
}

// Output for Mozilla Firefox 87.0 on Ubuntu 20.04:
//     en-US: 'MM/DD/YYYY'
//     de-DE: 'DD.MM.YYYY'
//     es-ES, es-AR, en-GB: 'DD/MM/YYYY'
console.log(getNumericFixedWidthDateFormat());
original TypeScript playground
Oscar
  • 11
  • 3
  • *navigator.language* may or may not return the user's preferred language, and users may expect a format other than that associated with the returned language. Far better to use an unambiguous format. – RobG Apr 11 '21 at 20:45
  • Thanks for the info. Seems like a fair assessment :) It kind of sucks that there isn't any way of getting a format that is better tailored to the user's settings though – Oscar Apr 12 '21 at 12:30
  • @RobG I just found out that you can directly pass options to `Date.toLocaleDateString` eliminating the use of `navigator.language`. I haven't done enough research to tell you whether `toLocaleDateString` is reliable - it may have the same problems as `navigator.language` – Oscar Apr 12 '21 at 14:25
0

While it is very hard to determine the the locale of the user, we can determine the user's preferred languages and pass these to a Intl.DateTimeFormat() constructor to create a date formatter.

Pass a Date object to the format function of the formatter to format the date to the pre-specified conditions.

// Function that returns an instance of the DateTimeFormat constructor.
const createDateFormatter = () => new Intl.DateTimeFormat([], {
  year: '2-digit',
  month: '2-digit',
  day: '2-digit'
});

// Current date of today.
const date = new Date();

// Create a new formatter.
const dateFormatter = createDateFormatter();

// Format the date.
const formattedDate = dateFormatter.format(date);
console.log(formattedDate);
Emiel Zuurbier
  • 19,095
  • 3
  • 17
  • 32
  • Is there any reason to use *navigator.languages* rather than 'default'? – RobG Apr 11 '21 at 20:46
  • Hmm, no not really. I've overlooked the empty array possibility. Though, because of your question I did find out about [locale negotiation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locale_negotiation), which is something new I've learned today. Though it doesn't apply to this situation as the `navigator.languages` might be the same as *the locales it has available* in the browser. – Emiel Zuurbier Apr 11 '21 at 21:13
  • Noting that the use of "locale" in regard to the Intl object is unfortunately confused with "language and culture" in [ECMA-402](https://www.ecma-international.org/publications-and-standards/standards/ecma-402/), it actually represents a language (and ignoring the common coexistence of more than one culture per language and place). I think the only reasonable use of *Intl.DateTimeFormat* is to get date parts in an appropriate language and calendar, then format them in an unambiguous order. Relying on the language with extensions to do that is fraught. – RobG Apr 12 '21 at 02:17
  • @RobG I was also initially hoping that the default would work. For some reason it didn't for me... it choose en-US even though I have en-GB set in my preferences – Oscar Apr 12 '21 at 12:28
  • i think if you just need a date formatted in the users preferred format then `toLocaleString` is the best solution. In this case I specifically needed the format with placeholders – Oscar Apr 12 '21 at 12:41