実践
第1回 Moment.js

ピクセルグリッドのエンジニアが、実務に関わる中で出会い、実装に使ったことのあるお気に入りのJavaScriptライブラリ1つをピックアップし、解説します。第1回目は外村がおすすめする日時の扱いを簡便にするライブラリです。

2012年10月25日発行

目次

Dateの日時処理は面倒なことも

プログラムでは日付や時間のデータを取り扱うことは頻繁に行います。JavaScriptではDateを使うことで日付や時間のデータを扱うことができます。Dateは次のように使います*。

var d = new Date('2012/10/20 15:30:50');
 
// 年を参照する
d.getFullYear(); //結果は 2012
 
// 月を参照する(0から始まる)
d.getMonth(); //結果は 9
 
// 日を参照する
d.getDate(); //結果は 20
*注:Dateでの日時情報の取得

この他にもさまざまなメソッドで日時の情報を取得できるので、詳しくはドキュメントを参照してください。

Date - JavaScript | MDN

このようにDateを使うと日時のデータを扱うことができるのですが、Dateでは面倒なことがいくつかあります。

例えばAPIから取得した2012/10/20 15:30:50という日時の文字列からDateオブジェクトを作り、2012年10月20日 15時30分のような形式で出力したいという場合、やり方はいくつかありますが、例えば次のように書きます。

var d = new Date('2012/10/20 15:30:50');
var formatDate =
  d.getFullYear() + '年' +
  (d.getMonth() + 1) + '月' +
  d.getDate() + '日 ' +
  d.getHours() + '時' +
  d.getMinutes() + '分';

日時を上記のようにフォーマットして出力するというのはよく行うことですが、このように面倒な処理をする必要があるのです。その他にも、日時を扱う上でDateオブジェクトはやや物足りないと思うことがいくつかあります。それを解決するのがMoment.jsというライブラリです。

Moment.jsとは

Moment.jsサイト
圧縮(Minified)ソースが5KB、Fullソースが40.3KBと軽量。

Moment.jsはJavaScriptで日時を扱う上で、標準のDateの機能だけでは足りない機能を提供してくれます。また、圧縮したバージョンは5KBととても軽量であること、ドキュメントが充実していること、テストケースが大量に書かれていること、Node.jsなどでも同じように使用できることもあり、最近好んで使っています。

Moment.jsのおすすめポイント

また、現在でも活発に開発が続いており、執筆現時点での最新バージョンは1.7.2ですので、今回の記事はこちらのバージョンに基づいて解説します。

momentオブジェクトを作る

まずはMoment.jsで日時のオブジェクトの作り方を解説します。Moment.jsではmoment()としてmomentのオブジェクトを作成することができます。Dateからはnew Date()として日時のオブジェクト(インスタンス)を作るのと同じです。次のように、引数を指定しない場合、現在の日時が設定されます。

var m = moment();

Dateと同じように文字列で特定の日時を指定することもできます。

var m = moment('2012/10/20 15:30:50');

Dateは年、月、日などを数値で引数に指定することができますが、momentではそのような場合は配列で指定します。

// Dateの場合:数値
var d = new Date(2012, 10, 20, 15, 30, 50);
// momentの場合:配列
var m = moment([2012, 10, 20, 15, 30, 50]);

Dateのインスタンスを引数にすることも可能です。

var d = new Date('2012/10/20 15:30:50');
var m = moment(d);

これだけの機能であればDateと同じですが、Moment.jsは入力値のフォーマットを指定することができます。

var m = moment('2012年10月20日', 'YYYY年MM月DD日');

このように、2012年10月10日という文字列からmomentオブジェクトを指定することができます。この機能を使うと、例えばAPIが返す日時形式が独自のものになっていても、そのフォーマットを指定することができます。独自の日時形式をパースできる形式にするために、改めて置換する処理は必要はありません。

文字列のフォーマットに指定できるYYYYMMなどは以下の表や、ドキュメントを参照してください。

トークン指定の意味
MあるいはMM月の数字(1 - 12)
MMMあるいはMMMM月の名前(言語はmoment.lang()で指定)
DあるいはDD日(月:たとえば10月の何日目)
DDDあるいはDDDD日(年:たとえば2010年の何日目)
d, dd, dddあるいはdddd曜日(注:これらのトークンは日付を得ることには使えない。1ヶ月は4〜5週あり、曜日を元に日付を取得することは不可能だから)
YY2ケタの年数(もし70以上であれば、1900年代か、そうでなければ2000年代を返す)
YYYY4ケタの年数
aあるいはAAM/PM
H, HH時間(24時制)
hあるいはhh時間(12時制、aあるいはAと組み合わせて使用)
mあるいはmm
sあるいはss
Sデシ秒(1/10秒)
SSセンチ秒(1/100秒)
SSSミリ秒(1/1000秒)
ZあるいはZZ+0700あるいは+07:30などタイムゾーンの時差。タイムゾーンが指定されていない限り、文字のパースは現在のタイムゾーンでdateを作る。

ISO 8601のパースにも対応

また、ISO 8601*という日時の形式があるのですが、IE8やiOS4、Android2.2などはこの日時の形式のパースに対応していません。ISO 8601は2012-10-20T15:30:50+09:00のような形式です。この文字列をDateの引数に指定してパースしようとすると、対応していないブラウザではInvalid DateNaNとなってしまいます。

// Android2.2などでInvalid Dateとなる
var d = new Date('2012-10-20T15:30:50+09:00');
*注:ISO 8601

ISO 8601は日時の表記に関する国際規格。最新は2004年版ISO 8601:2004です。

この形式が採用されていることは少なくなく、APIのデータがこの形式で返す場合などは、一度Dateオブジェクトに渡す前に置換するなどして、ISO 8601以外の対応している形式に変換する必要があります。

しかしMoment.jsはこのISO 8601形式のパースに対応しており、前述のフォーマットを指定する方法を使わなくても、ISO 8601形式をパースできるので、以下のように書いてもクロスブラウザで動作します。

var m = moment('2012-10-20T15:30:50+09:00');

日時のフォーマット

それではmomentオブジェクトにどのような機能があるかを見ていきましょう。先ほどの例の2012年10月20日 15時30分というフォーマットで出力するという処理をMoment.jsを使うと次のようになります。

var m = moment('2012/10/20 15:30:50');
var formatDate = m.format('YYYY年MM月DD日 HH時mm分');

format()メソッドを使うことで、とても直感的に簡潔に書くことができるのが分かると思います。format()の引数のYYYYが年に、MMが月に変換されるというのはドキュメントに記述されています。

次のように、さまざまな形式に変換することができます。

m.format('YYYY-MM-DD'); //表示は 2012-10-20
m.format('dddd, MMMM Do YYYY, h:mm:ss a'); //表示は Saturday, October 20th 2012, 3:30:50 pm
m.format('MMM Do YY ddd'); //表示は Oct 20th 12 Sat

format()の引数に何も指定しない場合はmoment.defaultFormatの値が適用されます。moment.defaultFormatの初期値はYYYY-MM-DDTHH:mm:ssZとなっています。

日時の計算

Moment.jsでは日時の足し算や引き算を簡単に行うことができる機能もあります。足し算にはadd()、引き算にはsubtract()メソッドを使います。

このadd()subtract()メソッドを使う際は、少し注意が必要です。次の例を見てください。

moment.defaultFormat = 'YYYY/MM/DD HH:mm:ss';
var m = moment('2012/10/20 15:30:50');
 
// 5日足す
m.add('days', 5).format(); //表示は 2012/10/25 15:30:50
 
// 10時間足す
m.add('hours', 10).format(); //表示は 2012/10/26 01:30:50
 
// 3ヶ月引く
m.subtract('months', 3).format(); //表示は 2012/07/26 01:30:50

上記の例を見て分かるように、add()subtract()は破壊的(自分自身のデータを変更してしまう)なので、5日足した後に10時間足したときの値は、元データ(2012/10/20 15:30:50)から比べて5日と10時間足したデータになっています。

自分自身のデータは変更せず計算結果だけほしい場合はclone()でコピーしてからadd()subtract()を呼びます。

moment.defaultFormat = 'YYYY/MM/DD HH:mm:ss';
var m = moment('2012/10/20 15:30:50');
 
// 5日足す
m.clone().add('days', 5).format(); //表示は 2012/10/25 15:30:50
 
// 10時間足す
m.clone().add('hours', 10).format(); //表示は 2012/10/21 01:30:50

最初の例と違って10時間足した処理は、元データから10時間足した日時になっていることがわかります。

このような時間の足し算や引き算は、今日から1ヶ月後の日時を表示したいといったときに便利です。

日時の差

Moment.jsでは日時の差分をとることができます。Dateオブジェクトも数値にキャストするとミリ秒に変換することができるので、単純な差分であればMoment.jsを使わずとも可能です。

var d1 = new Date('2012/10/20 15:30:50');
var d2 = new Date('2012/10/21 15:30:50');
 
// d2とd1の差分
d2 - d1; // 86400000

8640000024 * 60 * 60 * 1000なので、一日をミリ秒にした値になります。このようなDateオブジェクトで差分をとる方法は、ベンチマークなどの際によく使われる手法です。

var start = new Date();
...(ベンチマークをとりたい処理)
new Date() - start; // 処理にかかった時間

このように単純に差分をミリ秒で求めるだけでよいケースであれば、Dateオブジェクトだけで事足ります。しかし、例えば、現在の日時から3日以内のエントリーにnewというclassを付けたいときなどは、差分のミリ秒から日数を求めなければいけないので面倒です。

Moment.jsのdiff()メソッドを使うと、このようなケースを、とてもシンプルに書くことができるようになります。

var now = moment();
 
if (now.diff(entry.date, 'days') <= 3) {
  entry.addNew();
}

diff()の第一引数には日時の文字列やDateオブジェクト、momentオブジェクトなどを指定することができます。第二引数にはyears、months、daysなどの差分がほしい単位を指定することができます。第二引数を省略するとミリ秒が返ります。

var m1 = moment('2012/10/20 15:30:50');
var m2 = moment('2011/09/20 15:30:50');
 
m1.diff(m2); // 34214400000
m1.diff(m2, 'years'); // 1
m1.diff(m2, 'months'); // 13
m1.diff(m2, 'weeks'); // 57
m1.diff(m2, 'days'); // 396
m1.diff(m2, 'hours'); // 9504
m1.diff(m2, 'minutes'); // 570240
m1.diff(m2, 'seconds'); // 34214400

多言語対応

Moment.jsは国際化対応も簡単に行うことができます。Moment.jsの公式GitHubリポジトリにアップされているものだけで30以上の言語に対応しており、自分で特定の言語に対応するのも難しくありません。

対応したい言語を上記からダウンロードして読み込み、moment.lang()に言語を指定します。デフォルトでは英語(en)になっています。

moment.lang('ja');
var m = moment('2012/10/20 15:30:50');
m.format('MMM'); // 10月

また、format()に指定できるものにL、LL、LLL、LLLL、LTというローカルな日時形式への変換に使うトークンがあり、これを言語の設定と組み合わせて使うことで、言語設定ごとに表示を切り替えるという実装も簡単に行うことができます。

moment.lang(navigator.language);
var m = moment('2012/10/20 15:30:50');
m.format('LL');
// 言語設定が日本語の場合  2012年10月20日
// 言語設定が英語語の場合  20 octobre 2012
// 言語設定がドイツ語の場合  20. Oktober 2012 

まとめ

Moment.jsについて紹介しましたが、いかがだったでしょうか。小さいライブラリなのでjQueryのように劇的に生産性が上がるというわけではないですが、痒いところに手がとどく渋いライブラリだと思っています。

このような日時を処理するライブラリはいろいろあり、使いやすさなどは人それぞれであることは否めません。人によってはMoment.jsより使いやすいものがあるかもしれませんし、自作で同じようなものを作っている人もいるかもしれません。もしMoment.jsよりいいのを知ってるよ!という人はぜひ@codegridまでお知らせください!

外村 和仁
外村 和仁
フロントエンド・エンジニア

HTMLコーダー、JavaScriptプログラマ、PHP、Perlのプログラマといった職務を経験後、2010年に株式会社ピクセルグリッドに入社。大規模サイトの運用、開発の経験を活かしてバックエンドからフロントエンドまで幅広く担当。2015年、退社。好きな言語はJavaScriptとRuby。 著書に『ノンプログラマのためのJavaScriptはじめの一歩』(単著:技術評論社、2012年11月7日)があり、共著も多数。このほか、WEB+DB PRESS、Software Designなどにも寄稿。