YYYYMMDD everywhere

Other date formats are too confusing.

// ==UserScript==
// @name        YYYYMMDD everywhere
// @version     3
// @grant       none
// @namespace   tz
// @include     *
// @description Other date formats are too confusing.
// ==/UserScript==

const month_by_name = {
    "Jan": "01",
    "Feb": "02",
    "Mar": "03",
    "Apr": "04",
    "May": "05",
    "Jun": "06",
    "Jul": "07",
    "Aug": "08",
    "Sep": "09",
    "Oct": "10",
    "Nov": "11",
    "Dec": "12",
    "January": "01",
    "February": "02",
    "March": "03",
    "April": "04",
    "May": "05",
    "June": "06",
    "July": "07",
    "August": "08",
    "September": "09",
    "October": "10",
    "November": "11",
    "December": "12",
};
const month_re = (
    '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec'
    + '|January|February|March|April|May|June|July|August|September|October|November|December)'
);
const day_re = '(\\d\\d?)(?:,|th|st|nd)?';

const replacer_list = [
    {
        // mdy
        pattern: new RegExp(month_re + '  ?' + day_re + ' (\\d\\d\\d\\d)'),
        func: (_, month, day, year) => {
            day = pad2(day);
            month = month_by_name[month];
            return year + '-' + month + '-' + day;
        },
    },
    {
        // dmy
        pattern: new RegExp(day_re + ' ' + month_re + ' (\\d\\d\\d\\d)'),
        func: (_, day, month, year) => {
            day = pad2(day);
            month = month_by_name[month];
            return year + '-' + month + '-' + day;
        },
    },
    {
        // md
        pattern: new RegExp(month_re + '  ?' + day_re),
        func: (_, month, day) => {
            day = pad2(day);
            month = month_by_name[month];
            return '2021-' + month + '-' + day;  // FIXME: hardcode 2021
        },
    },
];

function fix(node) {
    for (const kid of node.childNodes) {
        if (kid.nodeType != Node.TEXT_NODE) {
            continue
        }
        let txt = kid.nodeValue;
        for (const {pattern, func} of replacer_list) {
            txt = txt.replace(pattern, func);
        }
        kid.nodeValue = txt;
    }
}

function fix_by_attr(attr) {
    return (node) => {
        const txt = node.getAttribute(attr);
        if (!txt) {
            return;
        }
        node.textContent = date_fmt(txt);
    };
}

function fix_so(node) {
    node.textContent = date_fmt(node.getAttribute('title').substr(0, '1111-11-11 11:11:11Z'.length));
}

function pad2(n) {
    n = n.toString();
    if (n.length == 1) {
        n = '0' + n;
    }
    return n;
}

// display using local timezone
function date_fmt(str) {
    let date;
    let unix = false;
    if (/^\d{10}$/.test(str)) {
        date = new Date(+str * 1000);
        unix = true;
    } else if (/^\d{13}$/.test(str)) {
        date = new Date(+str);
        unix = true;
    } else {
        date = new Date(str);
    }
    const yyyy = date.getFullYear();
    const mm = pad2(date.getMonth() + 1);
    const dd = pad2(date.getDate());
    const HH = pad2(date.getHours());
    const MM = pad2(date.getMinutes());
    const SS = pad2(date.getSeconds());
    if (isNaN(yyyy)) {
        console.error('yyyymmdd.user.js: unable to convert ' + str);
        return str;
    }

    let r = yyyy + '-' + mm + '-' + dd;
    if (unix || str.includes(':')) {
        r += ' ' + HH + ':' + MM + ':' + SS;
    }
    return r;
}

function execute(fixer) {
    for (const rule of fixer.rules) {
        const fix_fn = rule.fix || fix;
        const nodes = document.querySelectorAll(rule.selector);
        for (let node of nodes) {
            fix_fn(node);
        }
    }
}

function main() {
    const url = new URL(window.location.href);
    for (const fixer of fixer_list) {
        let matched = false;
        if (typeof fixer.domain === 'string') {
            matched = url.hostname.includes(fixer.domain);
        } else if (fixer.domain instanceof RegExp) {
            matched = fixer.domain.test(url.hostname);
        }
        if (matched && fixer.selector) {
            matched = !!document.querySelector(fixer.selector);
        }
        if (!matched) {
            continue
        }

        if (fixer.css) {
            const el = document.createElement('style');
            el.type = 'text/css';
            el.appendChild(document.createTextNode(fixer.css));
            document.head.appendChild(el);
        }

        execute(fixer);
        if (fixer.observe) {
            const to_observe = [];
            for (const selector of fixer.observe) {
                for (const node of document.querySelectorAll(selector)) {
                    to_observe.push(node);
                }
            }
            const observer = new MutationObserver(() => {
                // pause observing
                for (const node of to_observe) {
                    observer.disconnect(node);
                }
                // perform modifications
                execute(fixer);
                // resume observing
                for (const node of to_observe) {
                    observer.observe(node, {childList: true,  subtree: true});
                }
            });
            // start observing
            for (const node of to_observe) {
                observer.observe(node, {childList: true,  subtree: true});
            }
        }
    }
}

const fixer_list = [
    {
        // for all sites
        domain: '',
        rules: [
            {selector: 'time[datetime]', fix: fix_by_attr('datetime')},
        ],
    },
    {
        // for discourse bbs
        domain: '',
        selector: 'meta[name="discourse_theme_ids"]',
        rules: [
            {selector: '.relative-date', fix: fix_by_attr('data-time')},
            {selector: '.timeline-ago'},    // FIXME: not working
            {selector: '.d-label'},
        ],
        observe: ['section#main'],
    },
    {
        domain: '.google.',
        rules: [
            {selector: '.WZ8Tjf'},
            {selector: '.uo4vr'},
            {selector: '.wrBvFf span'},
        ],
    },
    {
        domain: 'news.ycombinator.com',
        rules: [
            {selector: '.age a'},
        ],
    },
    {
        domain: 'hckrnews.com',
        rules: [
            {selector: '.tab'},
        ],
        observe: ['#entries'],
    },
    {
        domain: /(stackoverflow|serverfault|superuser|stackexchange|askubuntu|mathoverflow|stackapps)\.(com|net)/,
        rules: [
            // https://meta.stackoverflow.com/questions/288674/custom-date-format
            {selector: '.relativetime', fix: fix_so},
            {selector: '.relativetime-clean', fix: fix_so},
        ],
        observe: ['.js-comments-list'],
    },
    {
        domain: 'github.com',
        rules: [
            {selector: 'relative-time', fix: fix_by_attr('datetime')},
        ],
        observe: ['.js-discussion', '#js-repo-pjax-container'],
    },
    {
        domain: 'lwn.net',
        rules: [
            {selector: '.CommentPoster'},
            {selector: '.FeatureByline'},
            {selector: '.Byline'},
            {selector: '.GAByline p'},
        ],
    },
    {
        domain: 'blog.golang.org',
        rules: [
            {selector: '.author'},
        ],
    },
    {
        domain: 'wordpress.com',
        rules: [
            {selector: '.author'},
            {selector: 'time[datetime]', fix: fix_by_attr('datetime')},
        ],
        observe: ['.jp-relatedposts'],
    },
    {
        domain: 'blog.cloudflare.com',
        rules: [
            {selector: 'p[datetime]', fix: fix_by_attr('datetime')},
            {selector: 'p[data-iso-date]', fix: fix_by_attr('data-iso-date')},
        ],
        observe: ['p[datetime]'],
    },
    {
        domain: 'goodreads.com',
        rules: [
            {selector: '.reviewDate'},
            {selector: '#details .row'},
        ],
    },
    {
        domain: 'digitalocean.com',
        rules: [
            {selector: '.post-time-link'},
            {selector: '.timestamp'},
            {selector: '.date'},
        ],
        observe: ['#aurora-container'],
    },
    {
        domain: 'wikipedia.org',
        rules: [
            {selector: '#footer-info-lastmod'},
        ],
    },
    {
        domain: 'nytimes.com',
        rules: [
            {selector: 'time[datetime]', fix: fix_by_attr('datetime')},
        ],
        observe: ['#story'],
    },
    {
        domain: 'probablydance.com',
        rules: [
            {selector: '.published a'},
            {selector: '.comment-meta a'},
        ],
        observe: ['#core-content'],
    },
    {
        domain: 'phoronix.com',
        rules: [
            {selector: '.author'},
            {selector: '.time'},
        ],
    },
    {
        domain: 'realworldtech.com',
        rules: [
            {selector: '.rwtforum-post-by'},
            {selector: '.time', fix: fix_by_attr('title')},
        ],
    },
    {
        domain: 'blogspot.com',
        rules: [
            {selector: '.date-header span'},
            {selector: '.comment-header a'},
            {selector: '.datetime a'},
            {selector: 'abbr.time', fix: fix_by_attr('title')},
        ],
        observe: ['body'],
    },
    {
        domain: 'code.google.com',
        rules: [
            {selector: 'p[ng-if="projectCtrl.project.creationTime"]'},
        ],
        observe: ['body'],
    },
    {
        domain: 'groups.google.com',
        rules: [
            {selector: '.zX2W9c'},
        ],
        observe: ['body'],
    },
    {
        domain: 'gf.qytechs.cn',
        rules: [
            {selector: 'gf-relative-time', fix: fix_by_attr('datetime')},
        ],
        observe: ['gf-relative-time'],
    },
    {
        domain: 'gitter.im',
        rules: [
            {selector: '.js-chat-time', fix: fix_by_attr('title')},
            {selector: '.js-chat-time', fix: fix_by_attr('aria-label')},
            {selector: '.activity-time', fix: fix_by_attr('aria-label')},   // FIXME: not working
        ],
        observe: ['body'],
    },
    {
        domain: 'writings.stephenwolfram.com',
        rules: [
            {selector: '.comment-permlink'},
        ],
    },
    {
        domain: 'slashdot.org',
        rules: [
            {selector: 'time[datetime]'},
            {selector: '.otherdetails'},
        ],
        observe: ['.otherdetails'],
    },
    {
        domain: 'imdb.com',
        rules: [
            {selector: '.review-date'},
            {selector: 'li > a'},
        ],
        observe: ['body'],
    },
    {
        domain: 'blogs.windows.com',
        rules: [
            {selector: '.article-header__intro_caption_content'},
        ],
    },
    {
        domain: 'arstechnica.com',
        rules: [
            {selector: '.reg-date'},
        ],
    },
    {
        domain: 'amazon.com',
        rules: [
            {selector: '.review-date'},
        ],
    },
    {
        domain: 'reddit.com',
        rules: [],
        css: `
            time.edited-timestamp:before {
                content: " Last edited ";
            }
        `
    },
];

main();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址