PHP互換のJavaScript日付フォーマット関数

[追記3]
モジュール化したので、こちらがおすすめです。

[追記2]
id:spiritlooseさんの指摘を勘違いしてしまった私だけど、こいつはこのままにしておくことにする。一応、出来るだけfunctionを配列にしたり、クロージャを減らしてswitch文の中にロジックを書いたりして工夫はしてみたものの、ベンチとると現状の方が速いので(勿論、各メソッドをprototype化した方が高速だとは思うけれど)。
下のコードは一度に1000回以上ループするような局面ではあまり向いていないので、必要に応じて改変するのが良いでしょう。
[追記]
id:spiritlooseさんの指摘がもっともだと思ったので、Dateに引っ付けたバージョンに変更した。うん。こっちの方が無駄がない感じ。今回の使用例はこんな感じになる。

var now = new Date();
alert( Date.format(now, "Y/m/d(J) H:i:s") );

上記の結果は「2006/12/02(土) 16:20:40」となる。
コードは以下。利用・改変は自由なので気に入った方は利用してみて下さい。

/**
* Date formater
*
* @author clonedoppelganger(at)gmail.com
* @class Date
* @param {Date} target date object
* {String} pattern
* - Y 年。4桁。
* - y 年。2桁。
* - m 月。前ゼロあり。
* - n 月。前ゼロなし。
* - F 月。フルスペル。
* - M 月。3文字形式。
* - O 月。旧暦日本語。
* - d 日。前ゼロあり。
* - j 日。前ゼロなし。
* - w 曜日。数値。
* - l 曜日。フルスペル。
* - D 曜日。3文字形式。
* - N 曜日。ISO-8601形式の数値。
* - J 曜日。日本語。
* - g 時。12時間単位。前ゼロなし。
* - G 時。24時間単位。前ゼロなし。
* - h 時。12時間単位。前ゼロあり。
* - H 時。24時間単位。前ゼロあり。
* - i 分。前ゼロあり。
* - s 秒。前ゼロあり。
* - a 午前または午後。(am/pm)
* - A 午前または午後。(AM/PM)
* - S 英語形式の序数を表すサフィックス。2文字。
* - z 年間の通算日。数字。(ゼロから開始)
* - t 指定した月の日数。
* - L 閏年であるかどうか。
* 上記の予約パラメータを通常の文字としたい場合には直前に#を付けて下さい。
* @return {String} formatted date
*/
Date.format = function(d, pattern) {

if (typeof pattern != "string") return;

var dYear = d.getFullYear();
var dMonth = d.getMonth();
var dDate = d.getDate();
var dDay = d.getDay();
var dHours = d.getHours();
var dMinutes = d.getMinutes();
var dSeconds = d.getSeconds();

var preZero = function(value) {
return (parseInt(value) < 10) ? "0" + value : value
};
var from24to12 = function(hours) {
return (hours > 12) ? hours - 12 : hours;
}
var ampm = function() {
return (dHours < 12) ? "am" : "pm";
}
var isoDay = function() {
return (dDay == 0) ? "7" : dDay;
}
var weekFullEn = function() {
var week = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
return week[dDay];
}
var weekJp = function() {
var week = ["日","月","火","水","木","金","土"];
return week[dDay];
}
var monthFullEn = function() {
var month = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"];
return month[dMonth];
}
var monthOldJp = function() {
var month = ["睦月", "如月", "弥生", "卯月", "皐月", "水無月",
"文月", "葉月", "長月", "神無月", "霜月", "師走"];
return month[dMonth];
}
var lastDayOfMonth = function(dateObj) {
var tmp = new Date(dateObj.getFullYear(), dateObj.getMonth() + 1, 1);
tmp.setTime(tmp.getTime() - 1);
return tmp.getDate();
}
var isLeapYear = function() {
var tmp = new Date(dYear, 0, 1);
var sum = 0;
for (var i = 0; i < 12; i++) {
tmp.setMonth(i);
sum += lastDayOfMonth(tmp);
}
return (sum == 365) ? "0" : "1";
}
var dateCount = function() {
var tmp = new Date(dYear, 0, 1);
var sum = -1;
for (var i = 0; i < dMonth; i++) {
tmp.setMonth(i);
sum += lastDayOfMonth(tmp);
}
return sum + dDate;
}
var dateSuffix = function() {
var suffix = [
"st", "nd", "rd", "th", "th", "th", "th", "th", "th", "th",
"th", "th", "th", "th", "th", "th", "th", "th", "th", "th",
"st", "nd", "rd", "th", "th", "th", "th", "th", "th", "th", "st"
];
return suffix[dDate - 1];
}

var res = "";
for (var i = 0, len = pattern.length; i < len; i++) {
var c = pattern.charAt(i);
switch (c) {
case "#":
if (i == len - 1) break;
res += pattern.charAt(++i);
break;
case "Y": res += dYear; break;
case "y": res += dYear.toString().substr(2, 2); break;
case "m": res += preZero(dMonth + 1); break;
case "n": res += dMonth + 1; break;
case "d": res += preZero(dDate); break;
case "j": res += dDate; break;
case "w": res += dDay; break;
case "N": res += isoDay(); break
case "l": res += weekFullEn(); break;
case "D": res += weekFullEn().substr(0, 3); break;
case "J": res += weekJp(); break;
case "F": res += monthFullEn(); break;
case "O": res += monthOldJp(); break;
case "a": res += ampm(); break;
case "A": res += ampm().toUpperCase(); break;
case "H": res += preZero(dHours); break;
case "h": res += preZero(from24to12(dHours)); break;
case "g": res += from24to12(dHours); break;
case "G": res += dHours; break;
case "i": res += preZero(dMinutes); break;
case "s": res += preZero(dSeconds); break;
case "M": res += monthFullEn().substr(0, 3); break;
case "t": res += lastDayOfMonth(d); break;
case "L": res += isLeapYear(); break;
case "z": res += dateCount(); break;
case "S": res += dateSuffix(); break;
default : res += c; break;
}
}
return res;
}

そういえば、コードの配色はhttp://vimcolor.spiritloose.net/を利用してみた。ただ、日本語が化けてしまうので整形手作業でやってたり。あと、車輪の再開発的なところは、自分で自由利用宣言した方がペタペタ好き放題使えて気軽だよねってのと、知らない日付フォーマットを覚えるのが億劫だという後ろ向きな理由で、全然気にしていなかったりする。

あと、一応、追記前の内容も消さずに下に置いておきます。


以前に月の最終日を取得する関数を書いたけど、独り立ちしている関数だと実用的ではなかったので、結局こうなってしまった。タイトルに書いておいてなんだけど、PHP互換は言い過ぎでフォーマットの形式が同じという意味で捕らえて欲しい(PHPのdate関数が提供する機能を全て実装している訳ではないということ)。ただ、PHPに書き慣れた人だと、かなり使い勝手が良いのではと思う。
使用例はこんな感じになる。

alert(
new Date().format("Y月m月d日(J) H時i分s秒")
);

出力結果は

2006月12月02日(土) 00時43分59秒

となる。
PHPにない実装としては、日本語の曜日の出力ができるのと、日本語の旧暦月名(旧暦自体を計算している訳ではないので注意)が出力ができる。
予約語エスケープが\ではなく#になっているのは、\自体がJavaScriptエスケープ文字なので、\だらけになるのが嫌だったため。

利用や改変は自由なので、気に入った方は使ってみて下さい(バグがあった場合は教えてもらえると助かります)。

/**
* Date formater
*
* @author clonedoppelganger(at)gmail.com
* @class Date
* @param {String} pattern
* - Y 年。4桁。
* - y 年。2桁。
* - m 月。前ゼロあり。
* - n 月。前ゼロなし。
* - F 月。フルスペル。
* - M 月。3文字形式。
* - O 月。旧暦日本語。
* - d 日。前ゼロあり。
* - j 日。前ゼロなし。
* - w 曜日。数値。
* - l 曜日。フルスペル。
* - D 曜日。3文字形式。
* - N 曜日。ISO-8601形式の数値。
* - J 曜日。日本語。
* - g 時。12時間単位。前ゼロなし。
* - G 時。24時間単位。前ゼロなし。
* - h 時。12時間単位。前ゼロあり。
* - H 時。24時間単位。前ゼロあり。
* - i 分。前ゼロあり。
* - s 秒。前ゼロあり。
* - a 午前または午後。(am/pm)
* - A 午前または午後。(AM/PM)
* - S 英語形式の序数を表すサフィックス。2文字。
* - z 年間の通算日。数字。(ゼロから開始)
* - t 指定した月の日数。
* - L 閏年であるかどうか。
* 上記の予約パラメータを通常の文字としたい場合には直前に#を付けて下さい。
* @return {String} formatted date
*/
Date.prototype.format = function(pattern) {

if (typeof pattern != "string") return;

var d = new Date();
d.setTime(this.getTime());
var dYear = d.getFullYear();
var dMonth = d.getMonth();
var dDate = d.getDate();
var dDay = d.getDay();
var dHours = d.getHours();
var dMinutes = d.getMinutes();
var dSeconds = d.getSeconds();

var preZero = function(value) {
return (parseInt(value) < 10) ? "0" + value : value
};
var from24to12 = function(hours) {
return (hours > 12) ? hours - 12 : hours;
}
var ampm = function() {
return (dHours < 12) ? "am" : "pm";
}
var isoDay = function() {
return (dDay == 0) ? "7" : dDay;
}
var weekFullEn = function() {
var week = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
return week[dDay];
}
var weekJp = function() {
var week = ["日","月","火","水","木","金","土"];
return week[dDay];
}
var monthFullEn = function() {
var month = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"];
return month[dMonth];
}
var monthOldJp = function() {
var month = ["睦月", "如月", "弥生", "卯月", "皐月", "水無月",
"文月", "葉月", "長月", "神無月", "霜月", "師走"];
return month[dMonth];
}
var lastDayOfMonth = function(dateObj) {
var tmp = new Date(dateObj.getFullYear(), dateObj.getMonth() + 1, 1);
tmp.setTime(tmp.getTime() - 1);
return tmp.getDate();
}
var isLeapYear = function() {
var tmp = new Date(dYear, 0, 1);
var sum = 0;
for (var i = 0; i < 12; i++) {
tmp.setMonth(i);
sum += lastDayOfMonth(tmp);
}
return (sum == 365) ? "0" : "1";
}
var dateCount = function() {
var tmp = new Date(dYear, 0, 1);
var sum = -1;
for (var i = 0; i < dMonth; i++) {
tmp.setMonth(i);
sum += lastDayOfMonth(tmp);
}
return sum + dDate;
}
var dateSuffix = function() {
var suffix = [
"st", "nd", "rd", "th", "th", "th", "th", "th", "th", "th",
"th", "th", "th", "th", "th", "th", "th", "th", "th", "th",
"st", "nd", "rd", "th", "th", "th", "th", "th", "th", "th", "st"
];
return suffix[dDate - 1];
}

var res = "";
for (var i = 0, len = pattern.length; i < len; i++) {
var c = pattern.charAt(i);
switch (c) {
case "#":
if (i == len - 1) break;
res += pattern.charAt(++i);
break;
case "Y": res += dYear; break;
case "y": res += dYear.toString().substr(2, 2); break;
case "m": res += preZero(dMonth + 1); break;
case "n": res += dMonth + 1; break;
case "d": res += preZero(dDate); break;
case "j": res += dDate; break;
case "w": res += dDay; break;
case "N": res += isoDay(); break
case "l": res += weekFullEn(); break;
case "D": res += weekFullEn().substr(0, 3); break;
case "J": res += weekJp(); break;
case "F": res += monthFullEn(); break;
case "O": res += monthOldJp(); break;
case "a": res += ampm(); break;
case "A": res += ampm().toUpperCase(); break;
case "H": res += preZero(dHours); break;
case "h": res += preZero(from24to12(dHours)); break;
case "g": res += from24to12(dHours); break;
case "G": res += dHours; break;
case "i": res += preZero(dMinutes); break;
case "s": res += preZero(dSeconds); break;
case "M": res += monthFullEn().substr(0, 3); break;
case "t": res += lastDayOfMonth(d); break;
case "L": res += isLeapYear(); break;
case "z": res += dateCount(); break;
case "S": res += dateSuffix(); break;
default : res += c; break;
}
}
return res;
}

6件のコメント

  1. こんにちは。
    エントリに反映しました。何か勘違いしていたら突っ込み入れて下さい。

  2. あら。。。そういうことじゃなくって。formatがインスタンスメソッドなのはいいんだけど。
    途中でいっぱい作ってるクロージャ(weekFullEnとか)ってこれ呼ぶ必要ないよね。上で展開したデータしかこないんだし。
    var weekFullEn = [”Sunday”,”Monday”,”Tuesday”,”Wednesday”,”Thursday”,”Friday”,”Saturday”][dDay];
    でもいいわけで。
    でもそれはもったいないから、weekFullEnとかをDateにくっつけちゃったらってこと。
    Date.prototype.weekFullEn = function(dDay) {
    [”Sunday”,”Monday”,”Tuesday”,”Wednesday”,”Thursday”,”Friday”,”Saturday”][dDay];
    };
    とか。
    あと、文字化けはすんません。。。いつからだろ。。直します。

  3. そこかぁ。
    >途中でいっぱい作ってるクロージャ(weekFullEnとか)ってこれ呼ぶ必要ないよね。
    これはその通りです。。何とか工夫して直します。
    でも、
    >weekFullEnとかをDateにくっつけちゃったらってこと。
    これはやりたくないって発想があったんですよね。formatというシグネチャ以外は全部隠蔽したかった。他のJavaScriptコードの邪魔をしたくなかったというか。なので、
    var now = new Date();
    now.weekFullEn();
    みたいなことをやりたいときは、
    now.format(”l”);
    って書いてねって感じです。
    そういう作りじゃないと、後からコード見た人には易しくないかなぁと思いまして。

  4. あったところで邪魔にはならないし、便利だから定義しちゃってもいいと思うけどね。組み込みオブジェクトを大きく拡張するのが抵抗あるのかな?
    このケースだと定義しちゃったほうが効率もいいし、速くなるよ。

  5. メモリ効率とか速度はコーディングしているときから自明の理だった訳ですが、そもそもの発想が「これ貼るだけで、formatってメソッドで上手いこと表示できるよ」ってものだったので。。外出しメソッドが一杯あると、そもそも命名がとかあーだこーだとなりかねないので。
    プロジェクトとかで作るなら、ちゃんとprototypeプロパティに入れて使えるようにしてあげる方が当然ベストだと思いますけどね。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です