DEV Community

nghialv
nghialv

Posted on

Format Date with Locale in JavaScript | without a Library

const CONST_LOCALES = {
    'en-US': {
        weekDays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // Starts from 'Sunday'
        weekDaysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
        monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
        monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
    }, // en-US
    'vi-VN': {
        weekDays: ['Chủ nhật', 'Thứ hai', 'Thứ ba', 'Thứ tư', 'Thứ năm', 'Thứ sáu', 'Thứ bảy'], // Starts from 'Chủ nhật'
        weekDaysShort: ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7'],
        monthNames: ['Tháng một', 'Tháng hai', 'Tháng ba', 'Tháng tư', 'Tháng năm', 'Tháng sáu', 'Tháng bảy', 'Tháng tám', 'Tháng chín', 'Tháng mười', 'Tháng mười một', 'Tháng mười hai'],
        monthNamesShort: ['Thg 1', 'Thg 2', 'Thg 3', 'Thg 4', 'Thg 5', 'Thg 6', 'Thg 7', 'Thg 8', 'Thg 9', 'Thg 10', 'Thg 11', 'Thg 12'],
        datePartDecorators: ['ngày', 'tháng', 'năm']
    }, // vi-VN
}; // CONST_LOCALES
const CONST_DATE_SEPARATORS = ['.', ',', '-', '/', ' '];
const CONST_DATE_PARTS_MAX = 4;
const CONST_DATE_PART_COMBINATIONS_YEAR = ['YYYY', 'YY'];
const CONST_DATE_PART_COMBINATIONS_MONTH = ['MMMM', 'MMM', 'MM', 'M'];
const CONST_DATE_PART_COMBINATIONS_DAY = ['DDDD', 'DDD', 'DD', 'D'];

/**
 * **USAGE**:
 * - `formatDateWithLocale()`
 * - `formatDateWithLocale({ format: 'foo bar' })` // Invalid date part(s)
 * - `formatDateWithLocale({ format: 'DDD D MMMM, YYYY', input: new Date(2022, 1, 1) })` // Tue 1 February, 2022
 * - `formatDateWithLocale({ format: 'DDDD ngày DD tháng MM năm YYYY', input: new Date('2022-12-02'), locale: 'vi-VN' })` // Thứ sáu ngày 02 tháng 12 năm 2022
 * @param {{format?: string, input?: Date, locale?: string}} options Options passed as an object { format, input, locale }:
 * 
 * 👉 Default format is `'YYYY-MM-DD'`
 * 
 * 👉 Default input is current date
 * 
 * 👉 Default locale is `'en-US'`
 * @returns {string} Date string in the given format and locale
 */
function formatDateWithLocale({ format = 'YYYY-MM-DD', input = new Date(), locale = 'en-US' } = {}) { // Default values for object parameter
    const localeSettings = CONST_LOCALES[locale];
    if (!localeSettings)
        return `Invalid locale`;

    const {
        weekDays, weekDaysShort,
        monthNames, monthNamesShort,
        datePartDecorators
    } = localeSettings;
    const datePartDECORATORS = datePartDecorators?.map(item => item.toUpperCase()) || []; // ?. is optional chaining operator

    const day = input.getDate();
    const dayOfWeek = input.getDay();   // Counts from 0
    const month = input.getMonth();     // Counts from 0
    const year = input.getFullYear();

    //#region Tellers
    const getTeller = (pattern) => (
        CONST_DATE_PART_COMBINATIONS_YEAR.includes(pattern) ?
            yearTeller(pattern) :
            CONST_DATE_PART_COMBINATIONS_MONTH.includes(pattern) ?
                monthTeller(pattern) :
                CONST_DATE_PART_COMBINATIONS_DAY.includes(pattern) ?
                    dayTeller(pattern) :
                    false
    ) // getTeller

    const yearTeller = (yearPattern) => (
        yearPattern === 'YYYY' ?
            year :
            `${year}`.substring(2)
    ) // yearTeller

    const monthTeller = (monthPattern) => (
        monthPattern === 'MMMM' ?
            monthNames[month] :
            monthPattern === 'MMM' ?
                monthNamesShort[month] :
                monthPattern === 'MM' ?
                    `${month + 1}`.padStart(2, '0') :
                    month + 1
    ) // monthTeller

    const dayTeller = (dayPattern) => (
        dayPattern === 'DDDD' ?
            weekDays[dayOfWeek] :
            dayPattern === 'DDD' ?
                weekDaysShort[dayOfWeek] :
                dayPattern === 'DD' ?
                    `${day}`.padStart(2, '0') :
                    day
    ) // dayTeller
    //#endregion Tellers

    const dateParts = format.split(/[.,-\/\s]/)
        .filter(part => part && !datePartDECORATORS.includes(part.toUpperCase())); // Remove empties and dateDecorators
    if (dateParts.length < 2 || dateParts.length > CONST_DATE_PARTS_MAX)
        return `Invalid length of parts`;

    const datePartsParsed = dateParts.map(part => getTeller(part));
    if (datePartsParsed.some(part => !part))
        return `Invalid date part(s)`;

    return format
        .replace(dateParts[0], datePartsParsed[0])
        .replace(dateParts[1], datePartsParsed[1])
        .replace(dateParts[2], datePartsParsed[2])  // do NOTHING if dateParts < 3
        .replace(dateParts[3], datePartsParsed[3])  // do NOTHING if dateParts < 4
} // formatDateWithLocale
Enter fullscreen mode Exit fullscreen mode

Top comments (0)

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, cherished by the supportive DEV Community. Coders of every background are encouraged to bring their perspectives and bolster our collective wisdom.

A sincere “thank you” often brightens someone’s day—share yours in the comments below!

On DEV, the act of sharing knowledge eases our journey and forges stronger community ties. Found value in this? A quick thank-you to the author can make a world of difference.

Okay