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
特殊フォーマットの文字列から生成
日付文字列が「YYYY-MM-DD HH:mm:ss」形式でない場合は、2番目の引数にフォーマットを指定してあげることで、生成することができます。
例えば、TwitterのAPIで取得される日付は「Fri Mar 05 14:11:26 +0000 2010」といった形式なのですが、それからmoment
を生成するには下記のようになります。
const date_twitter = moment( 'Fri Mar 05 14:11:26 +0000 2010', 'ddd MMM DD HH:mm:ss ZZ YYYY' ); console.log(date_twitter.utc().format('YYYY-MM-DD HH:mm:ss')); // 2010-03-05 14:11:26
同様の方法で、unixtimeからの生成も可能です。
フォーマットの記述方法は出力のものと同じで、下記のtokensに説明があります。
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 |
差分(diff)
差分の単位を指定できます。
const date_a = moment('2020-01-01'); const date_b = moment('2019-01-01'); date_a.diff(date_b, 'year'); // a - b // 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.jsは、そういった単位を切り詰めての演算手段が用意されていないため、一旦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にメモしたりしているのですが、経緯・過程を端折っていたりして、パッと見ても「何だっけ?」となることがあります。
こうやって記事にすれば、何でそうなっているのかも書くので、後で自分で使う分にもいいかも。