このページの本文へ

面倒なJavaScriptの日付の処理は「date-fns」でラクに片付けよう

2017年08月09日 13時00分更新

文●Edwin Reynoso

  • この記事をはてなブックマークに追加
本文印刷
単純そうに思えることでも意外と面倒なのがJavaScriptでの日付の処理です。素のJavaScriptよりも使いやすくラクに処理できるコンパクトなライブラリー「date-fns」を紹介します。

JavaScriptで日付を扱うと、素のDateメソッドは冗長な記述になり、整合せず、バグが発生しがちです。そこで日付操作に適したライブラリーが役立ちます。ライブラリーとJavaScriptのDateとの関係は、jQueryと素のDOM APIの関係のようなものです。

Stack Overflowで承認された回答から、月の最終日を取得する例で解説します。

var t = new Date();
alert( new Date(t.getFullYear(), t.getMonth() + 1, 0, 23, 59, 59) );

上記のコードは動くものの、getMonthに続く数字の意味を理解するのは困難です。次のコードを見てください。

const today = new Date();
console.log( lastDayOfMonth(today) );

lastDayOfMonthdate-fnsのメソッドです。date-fnsは、ブラウザーとNode.jsでJavaScriptの日付を扱うための総合的なツールセットです。

この記事ではdate-fnsの設定と使い方を説明します。date-fnsを自分のプロジェクトに取り入れて、複数のヘルパーメソッドで簡単に日付を操作し、t.getMonth()+1,0,23,59,59のようなコードから解放されます。

Moment.jsではダメな理由

日付を操作するカテゴリーの代表はMoment.jsで、広く普及しています。Moment.js以外のJavaScriptデータライブラリーが必要なのでしょうか?

date-fnsを開発したSasha Kossによると、Moment.jsには特有の問題があり、date-fnsを作成したとのことです。GitHubのプロジェクトページから、要点を記載します。

  • Moment.jsはmutableなのでバグを生む
  • OOP APIが複雑(これがmutabeの問題を悪化させる)
  • APIが複雑で、初回呼び出しのパフォーマンスが低い
  • webpackと一緒に使うとlocaleファイルがincludeされるため、ビルドサイズが大きくなる

date-fnsと比較します(出典:プロジェクトのWebページ)。

  • date-fnsはimmutableで、引数として渡した値を変更するのではなく、必ず新たなdateを返す
  • APIがシンプルで、れぞれの関数が持つ機能は1つだけ
  • 高速で、最高のユーザーエクスペリエンスを提供できる
  • webpackと相性が良い。1関数1ファイルのスタイルなので、必要なものだけを選べばよく、不要な機能でプロジェクトが肥大化するのを防げる

Webベースのプロジェクトで使うには、CDNからdate-fnsを入手してページにドロップするだけです。date-fnsのメリットは使いやすさにあります。使い方を解説します。

インストール

ライブラリーをインストールする方法は複数あります。

npmパッケージでインストール
npm install date-fns --save
Yarnでインストール
yarn add date-fns
Bowerでインストール
bower install date-fns
CDNでインストール
<script src="https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.28.5/date_fns.min.js"/>

dateFnsオブジェクトのnamespaceにライブラリーがインストールされます。jQueryがnamespace「$」を使うのと同じです。

<script>
  console.log(
    dateFns.isToday(new Date())
  );
</script>

date-fnsの基本的な使い方

モジュールローダーを使っている場合は、使用する部分だけrequireできます。たとえば形式を指定して日付を出力します。

const format = require('date-fns/format');
console.log(
  format(new Date(2017, 6, 6), 'MM/DD/YYYY')
);

// '06/07/2017'

localeを変更します。

const format = require('date-fns/format');
const deLocale = require('date-fns/locale/de') // German

console.log(
  format(new Date(2017, 6, 6), 'DD MMMM YYYY', { locale: deLocale })
);

// 06 Juli 2017

サポートされている言語の一覧はこちらです(編注:日本語にも対応している)。

ブラウザーで作業をしている場合は、ヘルパークラスはdateFnsオブジェクトから呼び出します。月の最終日をlastDayOfMonthメソッドで調べましょう。

<script src="https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.28.5/date_fns.min.js"></script>
<script>
  console.log(
    dateFns.lastDayOfMonth( new Date(2017, 6, 7) )
  );
  // Mon Jul 31 2017 00:00:00 GMT+0200 (CEST)
</script>

ここでは取り上げませんが、date-fnsには同様に四半期の最終日を調べるメソッドがあります。

Immutable、純粋関数、シンプル

date-fnsのメソッドは純粋関数で理解しやすいこともポイントです。読みやすいコードになり、デバッグで問題を特定しやすくなります。

Moment.jsのコードと比べて説明します。前述のとおりMomentのDateはmutableなので、予期しない挙動を示すことがあります。

const moment = require('moment');
const now = new Date(); 
const mNow = moment(now);

mNow.add('day', 3);
console.log(mNow.toDate());
mNow.add(3, 'day');
console.log(mNow.toDate());

// 2017-07-08T22:00:00.000Z
// 2017-07-11T22:00:00.000Z

ここで重要なことがいくつかあります。Momentのオブジェクトはmutableのため、add関数は引数を任意の順番で渡せます(1つ目のメソッドはdeprecation警告がでます)。ただし連続してaddを呼び出すと呼び出すたびに結果が変わり、混乱する恐れがあります。

mNow.add(3, 'day'); // add 3 days
mNow.add(3, 'day'); // adds 3 **more** days

date-fnsは引数の順番が規定されているため、関数は呼び出されるごとに新たなDateオブジェクトを作成し、必ず同じ結果を返します。

let addDays = required('date-fns/add_days');
addDays(now, 3); // (April 30, 2017)
addDays(now, 3); // (April 30, 2017)
addDays(now, 3); // (April 30, 2017)

メソッドには分かりやすい名前が付いているので(ただのaddではなくaddDays)、整合が取れていて、1つメソッドには1つの機能しか与えられていません。

日付の比較

たとえば公開日を日付で書いたり、何日前と書いたりするのを素のJavaScriptで実装するのは手間がかかりますが、date-fnsのdistanceInWordsメソッドを使えば簡単です。

2つの異なる日付を比較します。

const distanceInWords = require('date-fns/distanceInWords');
const startDate = new Date(2017, 3, 27); // (April 27, 2017)
const endDate = new Date(2018, 3, 27); // (April 27, 2018)
console.log(
  distanceInWords(startDate, endDate)
);
// "about 1 year"

date-fnsでは次の単位で日付の差を出力できます。

  • ミリ秒
  • 時間

使用するメソッド名はdifferenceIn[any of the above here]で、differenceInDaysのように使います。

日付のCollectionを扱う

date-fnsには日付のCollectionを柔軟に操作できるヘルパーメソッドがあります。

日付のCollectionを並び替える

compareAscで日付を昇順に並び替えましょう。compareAscは1つ目の日付が2つ目より後なら1を、前なら-1を、等しければ0を返します。

const compareAsc = require('date-fns/compare_asc');
const date1 = new Date('2005-01-01');
const date2 = new Date('2010-01-01');
const date3 = new Date('2015-01-01');
const arr = [date3, date1, date2];
const sortedDates = arr.sort(compareAsc);

// [ 2005-01-01, 2010-01-01, 2015-01-01 ]

日付が昇順に並びました。
compareAscと対になるメソッドはcompareDescです。

const compareDesc = require('date-fns/compare_desc');
...
const sortedDates = arr.sort(compareDesc);
// [ 2015-01-01, 2010-01-01, 2005-01-01 ]

2つの日付の間にある日を取得する

2つの日付の間にある日を取得するには、前述のaddDaysメソッドか指定の範囲に入る日付の配列を返すeachDay helperを使います。

const addDays = require('date-fns/add_days');
const eachDay = require('date-fns/each_day');
const today = new Date();
const dayAWeekFromNow = addDays(today, 7);
const thisWeek = eachDay(today, dayAWeekFromNow); 
console.log(thisWeek);

// [ 2017-07-03, 2017-07-04, 2017-07-05, 2017-07-06, 2017-07-07, 2017-07-08, 2017-07-09, 2017-07-10 ]

もっとも近い日を調べる

日付の配列から指定する日付にもっとも近い日を取得するには、closestToメソッドを使います。次に示すコードは先ほどの例に続くものです。

const closestTo = require('date-fns/closest_to');
...
const christmas = new Date(2017, 11, 25);
const closestToChristmasDate = closestTo(christmas, thisWeek); 
// 2017-07-10T

ほかにも配列のインデックスを取得するときに使うclosestIndexToメソッドもあります。

タイムゾーン

date-fnsのデメリットの1つは、Moment.jsのようにタイムゾーンを扱うヘルパー関数がないことです。Stack Overflowの回答を読むと、素のDateオブジェクトには「本当のタイムゾーン」のデータが入っていない背景を理解できます。date-fnsはコードを実行している現地の時間を返します。

先ほどのStack Overflowのスレッドでは素のJavaScriptでタイムゾーンを設定する方法が紹介されています。限定的ではありますが、出力時にUTCか現地時間から特定のタイムゾーンに変換できます。

new Date().toLocaleString("en-US", {timeZone: "America/New_York"});

MomentJSにはタイムゾーンを扱う別のライブラリーがあります。date-fnsがタイムゾーンをサポートするという計画が進行中ですが、執筆時点では未完成です。

最後に

date-fnsは、JavaScriptで日付を扱うヘルパークラスがそろった小規模なライブラリーです。この記事がdate-fnsのことを知って使いたくなる一助になれば幸いです。date-fnsは現在も活発に開発が続いており、バージョン2がまもなくリリースされる予定です。

(原文:Introduction to date-fns – a Lightweight JavaScript Date Library

[翻訳:内藤 夏樹/編集:Livit

Web Professionalトップへ

WebProfessional 新着記事