新しいことにはウェルカム

技術 | 電子工作 | ガジェット | ゲーム のメモ書き

JavaScriptの日付演算ライブラリMoment.jsの使い方メモ

JavaScriptで日付演算をする際、組み込みのDate関数は扱いが難しいため、一般的に何かしらのライブラリを使います。

ライブラリは、よく見かける「Moment.js」を使っています。

何度やっても使い方を忘れるし、同じ所でハマっているので、自分用使い方メモを残します。

インストール・読み込み

日本時間(JST)をよく使うので、サーバーのロケールを設定しなくても動くよう、moment-timezoneを使っています。

インストール

npm install moment-timezone

読み込み

import * as moment from 'moment-timezone';
moment.tz.setDefault('Asia/Tokyo'); // タイムゾーンをJSTに設定

// TypeScript用型
// 関数の引数に渡す時に指定すると補完してくれるので便利
import { Moment } from 'moment-timezone';

// 型使用例
function test(date: Moment){

}

生成・出力

設定したタイムゾーンは

  • オフセットを指定しなかった時の「生成する時の日付」
  • 「出力する時の日付」

に影響します。

出力は設定したタイムゾーンになるのですが、ピンポイントでUTC出力したい時は、moment.utc()で一時的にUTCに変換して出力します。

import * as moment from 'moment-timezone';
moment.tz.setDefault('Asia/Tokyo'); 

// 現在時刻を取得
const date_now = moment(); 

// 出力はJSTになる
date_now.format('YYYY-MM-DD HH:mm:ss');

// 値を全て指定して作成
// オフセットを指定しない入力値はJSTとして扱われる
const date_jst = moment('2019-01-01 00:00:00.000');  

// フルフォーマット出力
// 入力値がJSTとして扱われている
date_jst.format('YYYY-MM-DD HH:mm:ss.SSSZ');
// 2019-01-01 00:00:00.000+09:00

// 入力値にオフセットを指定できる(例:UTC)
// msの指定は無くても構わない
const date_utc = moment('2019-01-01 00:00:00.000+00:00');  

// 入力はUTCにしたので、出力のJSTは9時間進んで9時になっている
date_utc.format('YYYY-MM-DD HH:mm:ss.SSSZ');
// 2019-01-01 09:00:00.000+09:00

// UTCに変換して出力
// 入力はUTCだったので、出力も同じ値になっている
date_utc.utc().format('YYYY-MM-DD HH:mm:ss.SSSZ');
// 2019-01-01 00:00:00.000+00:00

format

moment.format()で指定したフォーマットで文字列出力します。

とりあえずフルフォーマットをメモしておいて、必要に応じて切り出して使うといいんじゃないでしょうか。

momentを作成するには、最低限「年月日」の情報があればできます。

出力は、満たない桁はゼロ埋めにして、「年月日」間は「-」でつなぐフォーマットにしておくと、ソートがしやすいし、データベースにロードする時にエラーになりにくいのでオススメです。

const date = moment('2019-01-01');
// フルフォーマット
datel.format('YYYY-MM-DD HH:mm:ss.SSSZ');
// 2019-01-01 00:00:00.000+09:00

要素の取得・変更はformat経由で行う

moment.~()で値を取得。moment.~(<number>)で値を変更できるのですが、挙動にクセがあるので、それらは使わず、 多少手間ですが、format経由で行うようにした方がミスが少ないです。

挙動のクセ

  • moment.month()は月を示しますが、範囲は「1~12」ではなく「0~11」です
  • 月の日付はmoment.day()ではなくmoment.date()です

これらのクセを知っていれば問題ないのですが、しばらく使っていないとついつい忘れてしまうので、割り切って、要素取得・変更関数は使わないようにしています。

format経由での取得・変更

const date = moment('2019-02-03');

// 取得
// formatでString出力して型変換する
Number.parseInt(date.format('M');
// 月:範囲「1~12」

// 変更
// formatでString出力して、そのStringでmomentを作りなおす
const date_last_year = moment(date.format('2018-MM-DD'));

曜日取得

例外として、週を求める時はmoment.isoWeekday()を使っています。

「月曜~日曜」が数字の「1~7」になります。

const date = moment('2019-01-01');
date.isoWeekday());
// 1(月曜)

演算の注意

Moment.jsの演算はイミュータブルではありません。

オブジェクトを演算すると、オブジェクトの値が変更されてしまいます。

const date_original = moment('2019-01-01');
const date_result = date_original.add(2, 'day');

date_result.format('YYYY-MM-DD'));
// 2019-01-03

date_original.format('YYYY-MM-DD');
// 2019-01-03
// date_originalが加算されている

momentの代入は参照のコピーのため、コピーを変更すると、参照元も変更されます。

const date_original = moment('2019-01-01');
const date_ref = date_original;

date_ref.add(2, 'day');

date_original.format('YYYY-MM-DD');
// 2019-01-03
// date_originalが加算されている

このあたりの挙動、ついつい忘れてハマることが多いので、演算する時は、moment.clone()でコピーを作成して、コピーを使って演算するようにした方がミスが少ないです。

const date_original = moment('2019-01-01');

// コピー作成
const date_copy = date_original.clone();

// 演算
const date_add = date_copy.add(2, 'day');

date_add.format('YYYY-MM-DD');
// 2019-01-03

date_original.format('YYYY-MM-DD');
// 2019-01-01
// date_originalが元のまま

演算で使用する単位

Key
year
month
day
hour
minute
second
millisecond

差分

差分の単位を指定できます。

const date_a = moment('2019-01-01');
const date_b = moment('2020-01-01');

date_a.diff(date_b, 'year');
// 1

加算・減算

減算関数は別途あるのですが、加算にマイナス値を使っても行えるので、その方が覚えることが少なくて済みます。

const date = moment('2019-01-01');

date.add(1, 'day'); // 2019-01-02
date.add(-1, 'day'); // 2018-12-31

比較

moment.diff()で単位を指定しなかった場合は、ms単位(最小単位)での差を出します。

これを使って、momentどうしの比較を行います。

const date_a = moment('2019-01-01');
const date_b = moment('2020-01-01');

if(date_a.diff(date_b)>0){
   // date_a>date_b
}

if(date_a.diff(date_b)<0){
   // date_a<date_b
} 

if(date_a.diff(date_b)===0){
   // date_a=date_b
} 

しかし、厳密に比較をしたいわけではない時もあるので、ハマらないために、比較する時は、比較の単位を明確にしておいた方がいいです。

例えば、同じ日なのかをチェックするといった、日付レベルでの比較を行うにはmoment.diff()の単位を日(day)にします。

const date_a = moment('2019-01-01 00:00:00');
const date_b = moment('2019-01-01 23:59:59');

if( date_a.diff(date_b)===0 ){
    // date_aとdate_bは、同じじゃないので、ここは実行されない
}

if( date_a.diff(date_b, 'day')===0 ){
    // date_aとdate_bは、日付は同じなので、ここは実行される
}

できれば「0」との比較ではなく、比較演算子を使いたいです。

速度を気にしないなら、formatでStringにしてしまえば、比較演算子がそのまま使えて楽です。

const date_a = moment('2019-01-01');
const date_b = moment('2020-01-01');
const str_a = date_a.format('YYYY-MM-DD');
const str_b = date_a.format('YYYY-MM-DD');

if(str_a>str_b){
   // date_a>date_b
}

if(str_a<str_b){
   // date_a<date_b
} 

if(str_a===str_b){
   // date_a=date_b
} 

月末の日付取得

月末の日付取得は、月初から1日引いてもいいのですが、moment.endOf('month')を使うと楽です。

ただし、時間は12時なっているので注意が必要です。

const date = moment('2019-01-01');

const date_end_of_month = date.endOf('month');
date_end_of_month.format('YYYY-MM-DD hh:mm:ss');
// 2019-01-31 11:59:59 

感想など

こういった自分がよく使うコードは、既存コードからコピペしたり、Evernoteにメモしたりしているのですが、経緯・過程を端折っていたりして、パッと見ても「何だっけ?」となることがあります。

こうやって記事にすれば、何でそうなっているのかも書くので、後で自分で使う分にもいいかも。