2021年5月30日日曜日

【JavaScript】Day.jsで日付演算処理を行う。

 元となるソースはGitHub上にあります。

共通

 共通的に必要となる定義を示します。フォーマット変換するので、プラグインを入れるのがポイントです。


const dayjs = require("dayjs");
// プラグインの追加
dayjs.extend(require("dayjs/plugin/customParseFormat"));

// フォーマット
const YYYYMMDD = "YYYYMMDD";
const YYYYMMDD_HHmmss = "YYYYMMDD HHmmss";
const YYYYMMDD_HHmmss_SSS = "YYYYMMDD HHmmss.SSS";

エラー判定処理

 Day.jsの癖として、日付処理ができない場合は「Invalid Date」という文字列を返します(細かいことはわかりませんが。)。それを利用したエラー判定を行います。これは下で示していく自作APIが処理エラー時にnullを返したいために作られました。


function isValidDate(day) {
  if (typeof day === "string") {
    return day === "Invalid Date" ? null : day;
  }

  return day.isValid() ? day : null;
}

年齢計算

 diff()のポイントは、基準日に対し過去が正の値・未来が負の値となることです。つまり基準日.diff(過去日) > 0 で、基準日.diff(未来日) < 0 です。


/**
 * 年齢計算
 * @param {string} baseDate  年齢計算の基準日 YYYYMMDD
 * @param {string} birthday  生年月日 YYYYMMDD
 * @returns 満年齢 or null(計算不可).誕生日に年をとる.
 */
exports.calcAge = (baseDate, birthday) => {
  let base = dayjs(baseDate, YYYYMMDD, true);
  let birth = dayjs(birthday, YYYYMMDD, true);

  // 日付をパースできない.
  if (!isValidDate(base) || !isValidDate(birth)) {
    return null;
  }

  let ibase = Number( base.format(YYYYMMDD) );
  let ibirth = Number( birth.format(YYYYMMDD) );

  return Math.floor( (ibase - ibirth)/10000 );
};

日付計算(年)

 日付計算において、年・月・日の加減算はadd()の引数に"year"、"month"、"day"を指定することで計算を行う。

/**
 * 日付計算(年)
 * @param {string} baseDate 基準日 YYYYMMDD
 * @param {number} year 加減算する年
 * @returns {string|null} 加減算後の日付 YYYYMMDD
 */
exports.addYears = (baseDate, year) => {
  return isValidDate(
    dayjs(baseDate, YYYYMMDD, true).add(year, "year").format(YYYYMMDD)
  );
};

日付計算(月)

/**
 * 日付計算(月)
 * @param {string} baseDate 基準日 YYYYMMDD
 * @param {number} month 加減算する月
 * @returns {string|null} 加減算後の日付 YYYYMMDD
 */
exports.addMonths = (baseDate, month) => {
  return isValidDate(
    dayjs(baseDate, YYYYMMDD, true).add(month, "month").format(YYYYMMDD)
  );
};

日付計算(日)

/**
 * 日付計算(日)
 * @param {string} baseDate 基準日 YYYYMMDD
 * @param {number} day 加減算する日
 * @returns {string|null} 加減算後の日付 YYYYMMDD
 */
exports.addDays = (baseDate, day) => {
  return isValidDate(
    dayjs(baseDate, YYYYMMDD, true).add(day, "day").format(YYYYMMDD)
  );
};

日付フォーマッター

/**
 * 日付フォーマット
 * @param {string} baseDate 日付日時
 * @param {string} format フォーマット. フォーマット形式はDay.js参照.
 * @returns {string|null} フォーマット後日付
 */
exports.format = (baseDate, format) => {
  return isValidDate(dayjs(baseDate).format(format));
};

月末算出

 ライブラリが優秀なので、うるう年も対応しています。


/**
 * 月末の日付を返す.
 * @param {string} baseDate 日付 YYYYMMDD or YYYYMM
 * @returns {string|null} 月末日付 YYYYMMDD
 */
exports.endOfMonth = (baseDate) => {
  return isValidDate(
    dayjs(baseDate, [YYYYMMDD, "YYYYMM"], true).endOf("month").format(YYYYMMDD)
  );
};

日付比較

 isBefore()は基準日と比較して比較対象日付が未来ならばtrue、過去ならばfalseとなります。つまり、基準日.isBefore(過去日 "day") はtrue、基準日.isBefore(未来日, "day")はfalseです。

/**
 * 日付比較
 * @param {string} baseDate 基準日 YYYYMMDD
 * @param {string} compDate 対象日 YYYYMMDD
 * @returns {number|null} 比較結果. 基準日 < 対象日 は -1, 基準日 = 対象日 は 0, 基準日 > 対象日 は 1
 */
exports.compare = (baseDate, compDate) => {
  const base = dayjs(baseDate, YYYYMMDD, true);
  const comp = dayjs(compDate, YYYYMMDD, true);

  if (!isValidDate(base) || !isValidDate(comp)) {
    return null;
  }

  if (base.isSame(comp, "days")) {
    return 0;
  }

  return base.isBefore(comp, "days") ? -1 : 1;
};

日付差分日数

 やっていることは年齢計算と同じで、年を計算するか日数を計算するかの違いです。

/**
 * 差分日数
 * @param {string} fromDate 基準日 YYYYMMDD
 * @param {string} toDate 対象日 YYYYMMDD
 * @returns {number|null} 計算結果. 基準日 < 対象日は 正の値、基準日 > 対象日は 負の値.
 */
exports.diffDays = (fromDate, toDate) => {
  let from = dayjs(fromDate, YYYYMMDD, true);
  let to = dayjs(toDate, YYYYMMDD, true);

  if (!isValidDate(from) || !isValidDate(to)) {
    return null;
  }

  return to.diff(from, "day");
};

エポック <-> UTC時間変換

 UTC時間を使う場合、プラグインを追加します。またエポック時間には秒とミリ秒で使い方が違うので注意します。

エポック秒 to UTC時間

/**
 * エポック(秒) to UTC変換
 * @param {number} seconds Unixエポック
 * @returns {string|null} UTC時間 YYYYMMDD hhmmss
 */
exports.epocSecToUtc = (seconds) => {
  dayjs.extend(require("dayjs/plugin/utc"));  // 本来はファイルの先頭で宣言
  return isValidDate(dayjs.unix(seconds).utc().format(YYYYMMDD_HHmmss));
};

エポックミリ秒 to UTC変換

/**
 * エポック(ミリ秒) to UTC変換
 * @param {number} miliseconds
 * @returns {string|null} UTC時間 YYYYMMDD hhmmss.SSS
 */
exports.epocMilliSecToUtc = (miliseconds) => {
  dayjs.extend(require("dayjs/plugin/utc"));  // 本来はファイルの先頭で宣言
  return isValidDate(dayjs(miliseconds).utc().format(YYYYMMDD_HHmmss_SSS));
};

UTC to エポック秒

/**
 * UTC to エポック(秒)
 * @param {string} baseDate 日付
 * @returns {number|null} エポック(秒)
 */
exports.utcToEpocSec = (baseDate) => {
  dayjs.extend(require("dayjs/plugin/utc"));  // 本来はファイルの先頭で宣言
  let utc = dayjs.utc(baseDate);
  if (!isValidDate(utc)) {
    return null;
  }
  return utc.unix();
};

UTC to エポックミリ秒

/**
 * UTC to エポック(ミリ秒)
 * @param {string} baseDate YYYYMMDD HHmmss.SSS
 * @returns {number|null} エポック(ミリ秒)
 */
exports.utcToEpocMilliSec = (baseDate) => {
  dayjs.extend(require("dayjs/plugin/utc"));  // 本来はファイルの先頭で宣言
  let utc = dayjs.utc(baseDate);
  if (!isValidDate(utc)) {
    return null;
  }
  return utc.valueOf();
};

UTC <-> JST 変換

 Day.jsオブジェクトは実行環境のロケールに合わせてしまうので、クラウドのリージョンなどの環境では注意が必要ですが、今回は環境に依存しないように実装しています。

UTC to JST

/**
 * UTC to JST変換
 * @param {string} baseUtc UTC時間. YYYYMMDD HHmmss
 * @returns {string|null} 日本時間. YYYYMMDD HHmmss
 */
exports.utcToJst = (baseUtc) => {
  dayjs.extend(require("dayjs/plugin/timezone"));  // 本来はファイルの先頭で宣言
  dayjs.extend(require("dayjs/plugin/utc"));  // 本来はファイルの先頭で宣言
  dayjs.tz.setDefault("Asia/Tokyo");

  let utc = dayjs.utc(baseUtc, YYYYMMDD_HHmmss);
  if (!isValidDate(utc)) {
    return null;
  }

  return utc.tz().format(YYYYMMDD_HHmmss);
};

JST to UTC

/**
 * JST to UTC変換
 * @param {string} baseJst YYYYMMDD HHmmss
 * @returns {string|null} UTC. YYYYMMDD HHmmss
 */
exports.jstToUtc = (baseJst) => {
  dayjs.extend(require("dayjs/plugin/timezone"));  // 本来はファイルの先頭で宣言
  dayjs.extend(require("dayjs/plugin/utc"));  // 本来はファイルの先頭で宣言
  dayjs.tz.setDefault("Asia/Tokyo");
  let jst = dayjs(baseJst, YYYYMMDD_HHmmss, true);
  if (!isValidDate(jst)) {
    return null;
  }

  return jst.utc().format(YYYYMMDD_HHmmss);
};