JavaScript -- 日付変更線をまたぐデータを翌日の00:00:00で分割する

概要

  • [睡眠開始時刻, 睡眠終了時刻] のようなデータを扱いたい
  • が、データが日付をまたいでいると d3.js などでプロットしにくい
  • 前日の睡眠開始時刻 ~ 前日の 23:59:59、翌日の 00:00:00 ~ 翌日の睡眠終了時刻、のようにデータを分割したい

下準備

JavaScript の Date オブジェクトに時/分/秒を設定する関数を用意する。
さらに抽象化できそうではあるが、キリがないのでそのまま定義する。

/* さらに抽象化できそうだが.. */

/* 時/分/秒 を 23:59:59 に設定する (コピーは行わない) */
function set235959(d) {
  d.setHours(23);
  d.setMinutes(59);
  d.setSeconds(59);
  return d;
}

/* 時/分/秒 を 00:00:00 に設定する (コピーは行わない) */
function set000000(d) {
  d.setHours(0);
  d.setMinutes(0);
  d.setSeconds(0);
  return d;
}

日付変更線でレコード 1 件を分割する

[睡眠開始時刻, 睡眠終了時刻] をレコード 1 件としています(Django側でそのように定義している)。

複数の日時の年月日が全て同じなら→日付をまたいでいない & それ以外.. としている部分がミソ(だと思われる)。

/* レコード 1 件を日付変更線で分割する */
function divideIfTomorrow(record) {

  var start = record[0];    // 睡眠開始時刻
  var finish = record[1];    // 睡眠終了時刻

  /* 日付をまたいでいない場合は終了 */
  var isSameYear = start.getFullYear() === finish.getFullYear();
  var isSameMonth = start.getMonth() === finish.getMonth();
  var isSameDate = start.getDate() === finish.getDate();
  if (isSameYear && isSameMonth && isSameDate) {
    return record;
  } else {
    /* 日付をまたいでいる場合 */
      
    /* 開始日の 23:59:59 までのレコードを作成 */
    var todayEnd = new Date(start.getTime());    // 開始日をコピー
    todayEnd = set235959(todayEnd);

    /* 開始日 ~ 当日の 23:59:59 までのセット */
    var firstHalf = [start, todayEnd];

    /* 終了日(翌日)の 00:00:00 からのレコードを作成 */
    var tomorrowStart = new Date(finish.getTime());    // 終了日をコピー
    tomorrowStart = set000000(tomorrowStart);

    /* 終了日(翌日)の 00:00:00 ~ 終了日、までのセット */
    var secondHalf = [tomorrowStart, finish];

    return [
      firstHalf,
      secondHalf,
    ];
  }
}

レコード全体に対して分割処理を行う(divideIfTomorrow()を繰り返す)

/* 日付変更線をまたぐデータが存在する場合に、レコードを分割する */
function divideByDateLine(sleepList) {
  var divided = [];

  for (var i = 0; i < sleepList.length; i++) {
    record = sleepList[i];
    record = divideIfTomorrow(record);    // 日付変更線で分割する

    /* 分割されなかった場合 */
    if (record[0] instanceof Date) {
      divided.push(record);
    } else {
      /* 分割された場合 (連想配列になっているはず) */

      for (var j = 0; j < 2; j++) {
        divided.push(record[j]);
      }
    }
  }

  return divided;
}

動作イメージ

/* 入力データ */
var sleepList = [
  [
    new Date("2018-02-12 00:30:00"),    // 睡眠開始時刻
    new Date("2018-02-12 06:02:01"),    // 睡眠終了時刻
  ],
  [
    new Date("2018-02-12 23:31:00"),
    new Date("2018-02-13 05:55:55"),    // 日付をまたいでいる
  ],
];

/* 日付をまたいでいれば分割する */
var sleepListDivided = divideByDateLine(sleepList);

/* 見やすく表示する */
sleepListDivided.forEach(function (record) {
  var msg = record[0].toLocaleString();
  msg += " ~ ";
  msg += record[1].toLocaleString();
  console.log(msg);
});

/*

2018/2/12 0:30:00 ~ 2018/2/12 6:02:01
2018/2/12 23:31:00 ~ 2018/2/12 23:59:59
2018/2/13 0:00:00 ~ 2018/2/13 5:55:55
*/