<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>@riversun/formatDate</title>
</head>
<body>
<h3>@riversun/formatDate demo</h3>
<div id="preview"></div>
</body>
</html>
function formatDate(formatStr, date, opts) {

    if (!date) {
        date = new Date();
    }

    opts = opts || {};

    let _days = opts.days;

    if (!_days) {
        _days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
    }

    let _months = opts.months;

    if (!_months) {
        _months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    }

    const pad = (number, strDigits, isUnpad) => {
        const strNum = Math.abs(number).toString();
        if (!isUnpad && strNum.length > strDigits.length) {
            return strNum;
        } else {
            return ('0000' + strNum).slice(-strDigits.length);
        }
    };

    const timezone = (date, letter) => {
        const chunk = [];
        const offset = -date.getTimezoneOffset();
        chunk.push(offset === 0 ? 'Z' : offset > 0 ? '+' : '-');//add Z or +,-
        if (offset === 0) return chunk;
        chunk.push(pad(Math.floor(offset / 60), '00'));//hour
        if (letter === 'X') return chunk.join('');
        if (letter === 'XXX') chunk.push(':');
        chunk.push(pad((offset % 60), '00'));//min
        return chunk.join('');
    };

    const DELIM = '\0\0';
    const escapeStack = [];

    const escapedFmtStr = formatStr.replace(/'.*?'/g, m => {
        escapeStack.push(m.replace(/'/g, ''));
        return `${DELIM}${escapeStack.length - 1}${DELIM}`;
    });

    const formattedStr = escapedFmtStr
        .replace(/y{4}|y{2}/g, m => pad(date.getFullYear(), m, true))
        .replace(/M{3}/g, m => _months[date.getMonth()])
        .replace(/M{1,2}/g, m => pad(date.getMonth() + 1, m))
        .replace(/M{1,2}/g, m => pad(date.getMonth() + 1, m))
        .replace(/d{1,2}/g, m => pad(date.getDate(), m))
        .replace(/H{1,2}/g, m => pad(date.getHours(), m))
        .replace(/h{1,2}/g, m => {
            const hours = date.getHours();
            return pad(hours === 0 ? 12 : hours > 12 ? hours - 12 : hours, m);
        })
        .replace(/a{1,2}/g, m => date.getHours() >= 12 ? 'PM' : 'AM')
        .replace(/m{1,2}/g, m => pad(date.getMinutes(), m))
        .replace(/s{1,2}/g, m => pad(date.getSeconds(), m))
        .replace(/S{3}/g, m => pad(date.getMilliseconds(), m))
        .replace(/[E]+/g, m => _days[date.getDay()])
        .replace(/[Z]+/g, m => timezone(date, m))
        .replace(/X{1,3}/g, m => timezone(date, m));

    const unescapedStr = formattedStr.replace(new RegExp(`${DELIM}\\d+${DELIM}`, 'g'),
        m => {
            const unescaped = escapeStack.shift();
            return unescaped.length > 0 ? unescaped : '\'';
        });

    return unescapedStr;
}

const patterns = [
    "yyyyMMdd'T'HHmmssXX",//ISO8601
    "yyyy-MM-dd'T'HH:mm:ssXXX",//ISO8601EX
    "E, dd MMM yyyy HH:mm:ss Z",//RFC1123(RFC822) like email
    "yyyy.MM.dd 'at' hh:mm:ss Z",//hh shows 1-12
    "EEE, MMM d, ''yy",
    "h:mm a",
    "hh 'o''''clock' a, X",
    "yyyy年M月d日 H時m分s秒",
    "yyyy年MM月dd日 HH時mm分ss秒",
    "yyyyMMddHHmmssSSS"

];

const preview = document.querySelector('#preview');

const updateDate = () => {

    const now = new Date();

    preview.innerHTML = `Now : ${now}<br><br>`;

    for (const pattern of patterns) {
        preview.innerHTML += `${pattern}  → ${formatDate(pattern, now)}<br>`;
    }
    
    setTimeout(updateDate, 500);
}

updateDate();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.