Next.jsでの営業時間・日数計算のコード例
1. はじめに
この記事では、以下の内容について学ぶことができます。
- date-fns ライブラリと holiday_jp-js ライブラリの設定方法
- Next.jsプロジェクトでの営業日計算のさまざまなコード例
使用するライブラリ
- date-fns
- holiday_jp-js
2. 営業日計算のコード例
2.1 ライブラリのインストール
まずは、ライブラリをインストールします。
npm install date-fns @holiday-jp/holiday_jp
2.2 初期設定
app/lib/business-config.ts
ファイルを作成し、以下の設定を行います。
import holidays from "@holiday-jp/holiday_jp";
import {
addDays,
isBefore,
isEqual,
isWithinInterval,
set,
startOfDay,
} from "date-fns";
// 営業時間の設定
const BUSINESS_HOURS = {
start: { hours: 10, minutes: 0 },
end: { hours: 20, minutes: 0 },
};
// 営業日(月曜から金曜)の設定
const BUSINESS_DAYS = [1, 2, 3, 4, 5];
/**
* 指定された日時が営業時間内かどうかを判定します。
* @param date 判定する日時
* @returns 営業時間内の場合はtrue、それ以外はfalse
*/
export function isBusinessHour(date: Date): boolean {
const day = date.getDay();
if (!BUSINESS_DAYS.includes(day)) return false;
const startTime = set(date, BUSINESS_HOURS.start);
const endTime = set(date, BUSINESS_HOURS.end);
return isWithinInterval(date, { start: startTime, end: endTime });
}
/**
* 指定された日付が祝日かどうかを判定します。
* @param date 判定する日付
* @returns 祝日の場合はtrue、それ以外はfalse
*/
export function isHoliday(date: Date): boolean {
return holidays.isHoliday(date);
}
/**
* 指定された日付が営業日かどうかを判定します。
* 営業日は平日(月曜から金曜)かつ祝日でない日を指します。
* @param date 判定する日付
* @returns 営業日の場合はtrue、それ以外はfalse
*/
export function isBusinessDay(date: Date): boolean {
return BUSINESS_DAYS.includes(date.getDay()) && !isHoliday(date);
}
/**
* 指定された日付の次の営業日を取得します。
* 次の営業日が見つかるまで日付を1日ずつ進めます。
* @param date 基準となる日付
* @returns 次の営業日
*/
export function getNextBusinessDay(date: Date): Date {
let nextDay = addDays(date, 1);
while (!isBusinessDay(nextDay)) {
nextDay = addDays(nextDay, 1);
}
return nextDay;
}
/**
* 指定された日付から指定された営業日数後の日付を取得します。
* 土日と祝日はスキップされます。
* @param date 基準となる日付
* @param days 追加する営業日数
* @returns 指定された営業日数後の日付
*/
export function addBusinessDays(date: Date, days: number): Date {
let result = new Date(date);
let remainingDays = days;
const direction = days >= 0 ? 1 : -1;
while (remainingDays !== 0) {
result = addDays(result, direction);
if (isBusinessDay(result)) {
remainingDays -= direction;
}
}
return result;
}
/**
* 指定された期間内の営業日数を計算します。
* 開始日と終了日を含む期間内の営業日をカウントします。
* @param startDate 期間の開始日
* @param endDate 期間の終了日
* @returns 期間内の営業日数
*/
export function getBusinessDaysBetween(startDate: Date, endDate: Date): number {
let currentDate = startDate;
let businessDays = 0;
while (currentDate <= endDate) {
if (isBusinessDay(currentDate)) {
businessDays++;
}
currentDate = addDays(currentDate, 1);
}
return businessDays;
}
/**
* 次の営業時間の開始時刻を計算します。
*
* この関数は以下のように動作します。
* 1. 現在が営業時間内の場合、翌営業日の開始時刻を返します。
* 2. 現在が営業時間外の場合
* a. 同日が営業日で、まだ営業開始時刻前なら、同日の開始時刻を返します。
* b. それ以外の場合は、次の営業日の開始時刻を返します。
*
* @param {Date} date - 基準となる日時
* @returns {Date} 次の営業時間の開始時刻
*/
export function getNextBusinessHourStart(date: Date): Date {
const currentDay = startOfDay(date);
const currentDayBusinessStart = set(currentDay, BUSINESS_HOURS.start);
const currentDayBusinessEnd = set(currentDay, BUSINESS_HOURS.end);
// 現在の日が営業日かつ、まだ営業開始時刻前の場合
if (isBusinessDay(currentDay) && isBefore(date, currentDayBusinessStart)) {
return currentDayBusinessStart;
}
// 現在の日が営業日かつ、営業時間内の場合
if (
isBusinessDay(currentDay) &&
(isEqual(date, currentDayBusinessStart) ||
isWithinInterval(date, {
start: currentDayBusinessStart,
end: currentDayBusinessEnd,
}))
) {
// 翌日を取得
let nextDay = addDays(currentDay, 1);
// 翌日が営業日でない場合、次の営業日を探す
while (!isBusinessDay(nextDay)) {
nextDay = addDays(nextDay, 1);
}
return set(nextDay, BUSINESS_HOURS.start);
}
// それ以外の場合(営業時間後や休日)
let nextBusinessDay = addDays(currentDay, 1);
while (!isBusinessDay(nextBusinessDay)) {
nextBusinessDay = addDays(nextBusinessDay, 1);
}
return set(nextBusinessDay, BUSINESS_HOURS.start);
}
2.3 様々な営業日計算のコード例
次に、app/business-time/page.tsx
を作成し、以下のコードを記述します。
"use client";
import holidays from "@holiday-jp/holiday_jp";
import {
addDays,
addHours,
addMonths,
addWeeks,
endOfMonth,
endOfYear,
format,
setDay,
startOfYear,
subDays,
subHours,
subYears,
} from "date-fns";
import { useEffect, useState } from "react";
import {
addBusinessDays,
getBusinessDaysBetween,
getNextBusinessDay,
getNextBusinessHourStart,
isBusinessDay,
isBusinessHour,
} from "../lib/business-config";
export default function Example() {
const [calculations, setCalculations] = useState<any>({});
useEffect(() => {
const now = new Date();
// 現在日から1営業日後を計算
const nextBusinessDay = getNextBusinessDay(now);
// 現在から10営業日後の日付を計算
const tenBusinessDaysLater = addBusinessDays(now, 10);
// 現在から1営業日前の日付を計算
const oneBusinessDayBefore = subDays(now, 1);
// 現在から2営業時間前の時刻を計算
const twoBusinessHoursBefore = subHours(now, 2);
// 現在から1ヶ月後までの営業日数を計算
const oneMonthLater = addMonths(now, 1);
const businessDaysBetween = getBusinessDaysBetween(now, oneMonthLater);
// 去年の営業日数を計算
const lastYearStart = startOfYear(subYears(now, 1));
const lastYearEnd = endOfYear(subYears(now, 1));
const businessDaysLastYear = getBusinessDaysBetween(
lastYearStart,
lastYearEnd
);
// 特定の日付(例:2024年4月1日)から3営業日前の日付を計算
const specificDate = new Date(2024, 3, 1);
const threeBusinessDaysBefore = addBusinessDays(specificDate, -3);
// 次の営業日の開始時刻を計算
const nextBusinessDayStart = getNextBusinessHourStart(now);
// 2週間後の金曜日の終業時刻を計算
const twoWeeksFriday = setDay(addWeeks(now, 2), 5);
const twoWeeksFridayEnd = new Date(twoWeeksFriday.setHours(20, 0, 0, 0));
// 今月の最後の営業日を計算
const lastDayOfMonth = endOfMonth(now);
let lastBusinessDayOfMonth = lastDayOfMonth;
while (!isBusinessDay(lastBusinessDayOfMonth)) {
lastBusinessDayOfMonth = subDays(lastBusinessDayOfMonth, 1);
}
// 直前の営業日の終了時刻を計算
let previousBusinessDay = subDays(now, 1);
while (!isBusinessDay(previousBusinessDay)) {
previousBusinessDay = subDays(previousBusinessDay, 1);
}
const previousBusinessDayEnd = new Date(
previousBusinessDay.setHours(20, 0, 0, 0)
);
// 次の営業時間の開始時刻を計算(現在が営業時間外の場合)
const nextBusinessHourStart = getNextBusinessHourStart(now);
// 現在から100営業時間後の日時を計算
let hundredBusinessHoursLater = now;
for (let i = 0; i < 100; i++) {
hundredBusinessHoursLater = addHours(hundredBusinessHoursLater, 1);
while (!isBusinessHour(hundredBusinessHoursLater)) {
hundredBusinessHoursLater = addHours(hundredBusinessHoursLater, 1);
}
}
// 現在時刻から24営業時間後が週末や祝日を跨ぐ場合の計算
let twentyFourBusinessHoursLater = now;
for (let i = 0; i < 24; i++) {
twentyFourBusinessHoursLater = addHours(twentyFourBusinessHoursLater, 1);
while (!isBusinessHour(twentyFourBusinessHoursLater)) {
twentyFourBusinessHoursLater = addHours(
twentyFourBusinessHoursLater,
1
);
}
}
// 現在の日時が営業時間内かどうかを判定
const isDuringBusinessHours = isBusinessHour(now);
// 特定の期間内の営業日のみの日付配列を生成
const startDate = now;
const endDate = addMonths(now, 1);
const businessDaysArray = [];
let currentDate = startDate;
while (currentDate <= endDate) {
if (isBusinessDay(currentDate)) {
businessDaysArray.push(new Date(currentDate));
}
currentDate = addDays(currentDate, 1);
}
// 次の祝日を取得
const nextHoliday = holidays.between(now, addMonths(now, 12))[0];
setCalculations({
nextBusinessDay,
tenBusinessDaysLater,
oneBusinessDayBefore,
twoBusinessHoursBefore,
businessDaysBetween,
businessDaysLastYear,
threeBusinessDaysBefore,
nextBusinessDayStart,
twoWeeksFridayEnd,
lastBusinessDayOfMonth,
previousBusinessDayEnd,
nextBusinessHourStart,
hundredBusinessHoursLater,
twentyFourBusinessHoursLater,
isDuringBusinessHours,
businessDaysArray,
nextHoliday,
});
}, []);
return (
<div className="mx-auto max-w-xl rounded-lg bg-white p-6 shadow-lg">
<h1 className="mb-6 text-3xl font-bold text-gray-800">営業日計算の例</h1>
<ul className="space-y-4">
{[
{
label: "次の営業日",
value: calculations.nextBusinessDay,
format: "yyyy-MM-dd",
},
{
label: "10営業日後",
value: calculations.tenBusinessDaysLater,
format: "yyyy-MM-dd",
},
{
label: "1営業日前",
value: calculations.oneBusinessDayBefore,
format: "yyyy-MM-dd",
},
{
label: "2営業時間前",
value: calculations.twoBusinessHoursBefore,
format: "yyyy-MM-dd HH:mm:ss",
},
{
label: "1ヶ月後までの営業日数",
value: calculations.businessDaysBetween,
},
{ label: "去年の営業日数", value: calculations.businessDaysLastYear },
{
label: "2024年4月1日の3営業日前",
value: calculations.threeBusinessDaysBefore,
format: "yyyy-MM-dd",
},
{
label: "次の営業日の開始時刻",
value: calculations.nextBusinessDayStart,
format: "yyyy-MM-dd HH:mm:ss",
},
{
label: "2週間後の金曜日の終業時刻",
value: calculations.twoWeeksFridayEnd,
format: "yyyy-MM-dd HH:mm:ss",
},
{
label: "今月の最後の営業日",
value: calculations.lastBusinessDayOfMonth,
format: "yyyy-MM-dd",
},
{
label: "直前の営業日の終了時刻",
value: calculations.previousBusinessDayEnd,
format: "yyyy-MM-dd HH:mm:ss",
},
{
label: "次の営業時間の開始時刻",
value: calculations.nextBusinessHourStart,
format: "yyyy-MM-dd HH:mm:ss",
},
{
label: "100営業時間後",
value: calculations.hundredBusinessHoursLater,
format: "yyyy-MM-dd HH:mm:ss",
},
{
label: "24営業時間後",
value: calculations.twentyFourBusinessHoursLater,
format: "yyyy-MM-dd HH:mm:ss",
},
{
label: "現在営業時間内か",
value: calculations.isDuringBusinessHours ? "はい" : "いいえ",
},
{
label: "次の1ヶ月の営業日数",
value: calculations.businessDaysArray?.length,
},
{
label: "次の祝日",
value: calculations.nextHoliday,
format: "holiday",
},
].map((item, index) => (
<li
key={index}
className="flex flex-col border-b border-gray-200 pb-2 sm:flex-row sm:justify-between"
>
<span className="font-medium text-gray-700">{item.label}:</span>
<span className="text-gray-600">
{item.format && item.value
? item.format === "holiday"
? `${format(item.value.date, "yyyy-MM-dd")} (${item.value.name})`
: format(item.value, item.format)
: item.value}
</span>
</li>
))}
</ul>
</div>
);
}
2.4 コード例の実行画面(2024/09/05 19:51:59に実行)
ローカル環境だと、http://localhost:8080/business-time
にアクセスすると以下のような実行画面が表示されます。
3. コード例の解説
-
次の営業時間・営業日の計算
const nextBusinessHour = addHours(now, 1); const nextBusinessDay = getNextBusinessDay(now);
addHours
関数で1時間後を計算し、getNextBusinessDay
関数で次の営業日を取得します。 -
指定した営業日数後の日付計算
const tenBusinessDaysLater = addBusinessDays(now, 10);
addBusinessDays
関数を使用して、10営業日後の日付を計算します。 -
期間内の営業日数の計算
const oneMonthLater = addMonths(now, 1); const businessDaysBetween = getBusinessDaysBetween(now, oneMonthLater);
getBusinessDaysBetween
関数を使用して、現在から1ヶ月後までの営業日数を計算します。 -
特定の日付の営業日判定
const lastDayOfMonth = endOfMonth(now); let lastBusinessDayOfMonth = lastDayOfMonth; while (!isBusinessDay(lastBusinessDayOfMonth)) { lastBusinessDayOfMonth = subDays(lastBusinessDayOfMonth, 1); }
isBusinessDay
関数を使用して、月末から逆算して最後の営業日を特定します。 -
営業時間内かどうかの判定
const isDuringBusinessHours = isBusinessHour(now);
isBusinessHour
関数を使用して、現在が営業時間内かどうかを判定します。 -
祝日の取得
const nextHoliday = holidays.between(now, addMonths(now, 12))[0];
holiday_jp-js
ライブラリのbetween
メソッドを使用して、次の祝日を取得します。 -
複雑な営業時間計算
let hundredBusinessHoursLater = now; for (let i = 0; i < 100; i++) { hundredBusinessHoursLater = addHours(hundredBusinessHoursLater, 1); while (!isBusinessHour(hundredBusinessHoursLater)) { hundredBusinessHoursLater = addHours(hundredBusinessHoursLater, 1); } }
- 100営業時間後の日時を計算します
- この方法は、24営業時間後の計算にも同様に使用されています
-
日付配列の生成特定の期間内の営業日のみを含む日付配列を生成します。
const businessDaysArray = []; let currentDate = startDate; while (currentDate <= endDate) { if (isBusinessDay(currentDate)) { businessDaysArray.push(new Date(currentDate)); } currentDate = addDays(currentDate, 1); }
4. まとめ
この記事では、Next.jsアプリケーションにおいてdate-fns
ライブラリとholiday_jp-js
ライブラリを使用して、日本の祝日と休日を考慮した営業日計算を行う方法を解説しました。
この記事のポイントは以下の通りです。
-
date-fns
とholiday_jp-js
ライブラリの適切な設定と初期化 - カスタム関数を使用した営業日・時間の判定と計算
-
date-fns
ライブラリの様々な関数を使用した営業日・営業時間の計算 - Next.jsのApp Router構造に適合したコンポーネントの実装と表示
この記事が、Next.jsを使用した営業時間・日数計算の実装の参考になれば幸いです。
最後までお読みいただきありがとうございました!
もしお役に立ちましたら、記事に「いいね!」お願いします!
Discussion
記事記載の
isBusinessDay
関数を使用して営業日カレンダーにチャレンジしてみましたおぉ。すごい。
カレンダーのデザインがかわいくて良い感じですね!
ご活用いただきありがとうございます!