Help us understand the problem. What is going on with this article?

JavaScriptの日付(Date)をyyyy-MM-dd HH:mm:ss のようにフォーマットする関数

image.png

概要

  • とある事情で、JavaScriptで日付(Dateオブジェクト)をyyyy-MM-dd'T'HH:mm:ssXXXのようにパターン指定でフォーマットする関数を作りました
  • 外部ライブラリ不要で、そこそこ欲張っています
    • ISO8601形式、RFC1123形式、タイムゾーンRFC822など表現可能
  • パターン仕様はJavaのSimpleDateFormatライクにしています

コード

以下が、作成した関数formatDate

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 date = new Date();

//パターンを指定してフォーマットできる
console.log(formatDate("yyyy年MM月dd日 HH時mm分ss秒SSS",date));
//->2020年02月18日 01時50分20秒789

//曜日も表示。日本語で曜日表示するときは曜日名を与える
console.log(formatDate("yyyy年MM月dd日(E曜日) HH時mm分ss秒SSS",date,{
    days:['','','','','','','']
}));
//->2020年02月18日(火曜日) 01時50分20秒789

//ISO8601形式(1)でフォーマット
console.log(formatDate("yyyyMMdd'T'HHmmssXX",date));
//->20200218T015020+0900

//ISO8601形式(2)でフォーマット
console.log(formatDate("yyyy-MM-dd'T'HH:mm:ssXXX",date));
//->2020-02-18T01:50:20+09:00

//RFC1123(RFC822)形式でフォーマット
console.log(formatDate("E, dd MMM yyyy HH:mm:ss Z",date));
//->Tue, 18 Feb 2020 01:50:20 +0900

//2019を「'19」みたいにフォーマットするときはyyにする
console.log(formatDate("EEE, MMM d, ''yy",date));
//->Tue, Feb 18, '20 01:50:20 +0900

//かぶる文字列を混ぜる場合はエスケープ(対象をシングルクォート"'"で囲む。シングルクォート自体は"''"で表現)
console.log(formatDate("'Hello!' It'''s' H:m:s.",date));
//->Hello! It's 1:50:20.

See the Pen eYNzpBO by Tom Misawa (@riversun) on CodePen.

パターン指定方法の詳細

  • パターンとしてH:m:sのように1文字指定した場合、時間が7時なら7のみ、17時なら17のように表示されるが、HH:mm:ssのように2文字指定した場合には7時なら07のように左から0埋め(zero padding)される。
  • パターン内に文字列を含めたい場合は 'Hello' hh:mm:ss のように文字列部をシングルクォート(')で文字列を囲むとエスケープされる。
  • 記法はJavaのSimpleDateFormatライク

パターン指定方法一覧

文字 内容
yyyy 2018
yy (年を2桁表示にする) 18
M 7
MM 月(0埋め) 07
MMM 月名(英語圏等向け) Jul
d 日付 17
dd 日付(0埋め) 17
a AM/PMのマーカー PM
H 時(0-23) 12
HH 時(0-23)(0埋め) 12
E 曜日 Tue
h 時(午前午後を1-12で) 12
hh 時(0埋め) 12
m 8
mm 分(0埋め) 08
s 56
ss 秒(0埋め) 56
S ミリ秒 789
Z RFC822形式のタイムゾーン +0900
X ISO8601形式のタイムゾーン(1) +09
XX ISO8601形式のタイムゾーン(2) +0900
XXX ISO8601形式のタイムゾーン(3) +09:00
' ' シングルクォートで囲むと中の文字をエスケープする
'' シングルクォート自体を表示したいとき

パターン指定いろいろな例

パターン
yyyyMMdd'T'HHmmssXX 20180717T120856+0900
yyyy-MM-dd'T'HH:mm:ssXXX 2018-07-17T12:08:56+09:00
E, dd MMM yyyy HH:mm:ss Z Tue, 17 Jul 2018 12:08:56 +0900
yyyy.MM.dd 'at' hh:mm:ss Z 2018.07.17 at 12:08:56 +0900
EEE, MMM d, ''yy Tue, Jul 17, '18
h:mm a 12:08 PM
hh 'o''''clock' a, X 12 o'clock PM, +09
yyyy年M月d日 H時m分s秒 2018年7月17日 12時8分56秒
yyyy年MM月dd日 HH時mm分ss秒 2018年07月17日 12時08分56秒
yyyyMMddHHmmssSSS 20180717120856789

ライブデモ(pen)

See the Pen ZEGWdKE by Tom Misawa (@riversun) on CodePen.

おまけ

間違いやすいAM,PMの概念

アメリカなど12時制が慣用されている国はhh:mm:ss(hは1-12を表すパターン)を使うことが多い。
以下のようにAM/PMは夜(midnight)の12時が12:00AM。昼(noon)の12時が12:00PMとなるので、実装にもそれを反映した。

24時制 12時制
0 12AM
1 1AM
2 2AM
3 3AM
4 4AM
5 5AM
6 6AM
7 7AM
8 8AM
9 9AM
10 10AM
11 11AM
12 12PM
13 1PM
14 2PM
15 3PM
16 4PM
17 5PM
18 6PM
19 7PM
20 8PM
21 9PM
22 10PM
23 11PM

まとめ

riversun
UX producer and Full-Stack developer with more than 15 years of experience.
https://github.com/riversun
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした