14
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeScriptで定休日から営業日を自動整形する関数を作ってみた

Last updated at Posted at 2025-04-02

はじめに

実務で病院やクリニック、サロンなど、定休日を指定して、それ以外の営業日を整形して表示したいという場面がありました。

たとえば、

  • 定休日が日曜だけなら「月曜 ~ 土曜」
  • 定休日が水曜だけなら「日曜 ~ 火曜、木曜 ~ 土曜」
  • 定休日が不定休なら「定休日なし(不定休)」

といった表示が求められるケースです。

そこで今回は、定休日の配列から営業日を "◯曜 ~ ◯曜、◯曜" のように整形して返す TypeScript 関数を紹介します。

🎯 ゴール

以下のような enum をベースに:

enum ClinicClosedDays {
  SUNDAY = "日曜日",
  MONDAY = "月曜日",
  TUESDAY = "火曜日",
  WEDNESDAY = "水曜日",
  THURSDAY = "木曜日",
  FRIDAY = "金曜日",
  SATURDAY = "土曜日",
  IRREGULAR = "不定休"
}

このような関数を実装します

```ts
getOpenDayRanges([ClinicClosedDays.SUNDAY, ClinicClosedDays.WEDNESDAY, ClinicClosedDays.SATURDAY]);
// → "月曜 ~ 火曜、木曜 ~ 金曜"

🔧 実装コード

function getOpenDayRanges(closedDays: ClinicClosedDays[]): string {
  const allDays = [
    ClinicClosedDays.SUNDAY,
    ClinicClosedDays.MONDAY,
    ClinicClosedDays.TUESDAY,
    ClinicClosedDays.WEDNESDAY,
    ClinicClosedDays.THURSDAY,
    ClinicClosedDays.FRIDAY,
    ClinicClosedDays.SATURDAY,
  ];

  if (closedDays.includes(ClinicClosedDays.IRREGULAR)) {
    return "定休日なし(不定休)";
  }

  const openDays = allDays.filter(day => !closedDays.includes(day));

  if (openDays.length === 0) {
    return "全日休診";
  }

  const dayIndexMap = new Map<ClinicClosedDays, number>([
    [ClinicClosedDays.SUNDAY, 0],
    [ClinicClosedDays.MONDAY, 1],
    [ClinicClosedDays.TUESDAY, 2],
    [ClinicClosedDays.WEDNESDAY, 3],
    [ClinicClosedDays.THURSDAY, 4],
    [ClinicClosedDays.FRIDAY, 5],
    [ClinicClosedDays.SATURDAY, 6],
  ]);

  const sortedOpenDays = openDays.sort((a, b) => (dayIndexMap.get(a) ?? 0) - (dayIndexMap.get(b) ?? 0));

  const ranges: string[] = [];
  let rangeStart = sortedOpenDays[0];
  let prevIndex = dayIndexMap.get(rangeStart)!;

  for (let i = 1; i <= sortedOpenDays.length; i++) {
    const currentDay = sortedOpenDays[i];
    const currentIndex = dayIndexMap.get(currentDay);

    if (currentIndex !== prevIndex + 1) {
      const rangeEnd = sortedOpenDays[i - 1];
      ranges.push(formatRange(rangeStart, rangeEnd));
      rangeStart = currentDay;
    }

    prevIndex = currentIndex ?? -999;
  }

  return ranges.join("");

  function formatRange(start: ClinicClosedDays | undefined, end: ClinicClosedDays | undefined): string {
    const formatDay = (day: ClinicClosedDays | undefined) =>
      day ? day.replace("曜日", "") : "";
    return start === end ? formatDay(start) : `${formatDay(start)} ~ ${formatDay(end)}`;
  }
}

処理の流れイメージ

たとえば以下のような入力:

const closedDays = [SUNDAY, WEDNESDAY, SATURDAY];

開いている曜日は:

[MONDAY, TUESDAY, THURSDAY, FRIDAY]

この場合、範囲分けは次のように行われます:

image.png

解説付きのコードとテストケース

// 定休日を表す enum(日本語表記)
enum ClinicClosedDays {
  SUNDAY = "日曜日",
  MONDAY = "月曜日",
  TUESDAY = "火曜日",
  WEDNESDAY = "水曜日",
  THURSDAY = "木曜日",
  FRIDAY = "金曜日",
  SATURDAY = "土曜日",
  IRREGULAR = "不定休"
}

/**
 * 定休日の配列から営業日を整形し、"月曜 ~ 火曜、木曜 ~ 金曜" のように返す関数
 *
 * @param closedDays - 定休日の配列
 * @returns 営業日を範囲形式で整形した文字列
 */
function getOpenDayRanges(closedDays: ClinicClosedDays[]): string {
  // 全曜日を定義(日曜始まり)
  const allDays = [
    ClinicClosedDays.SUNDAY,
    ClinicClosedDays.MONDAY,
    ClinicClosedDays.TUESDAY,
    ClinicClosedDays.WEDNESDAY,
    ClinicClosedDays.THURSDAY,
    ClinicClosedDays.FRIDAY,
    ClinicClosedDays.SATURDAY,
  ];

  // 不定休が含まれる場合は特別なメッセージを返す
  if (closedDays.includes(ClinicClosedDays.IRREGULAR)) {
    return "定休日なし(不定休)";
  }

  // 定休日を除いた曜日が営業日となる
  const openDays = allDays.filter(day => !closedDays.includes(day));

  // 営業日が0日の場合(毎日休診)
  if (openDays.length === 0) {
    return "全日休診";
  }

  // 各曜日に対応するインデックス(日曜: 0 ~ 土曜: 6)
  const dayIndexMap = new Map<ClinicClosedDays, number>([
    [ClinicClosedDays.SUNDAY, 0],
    [ClinicClosedDays.MONDAY, 1],
    [ClinicClosedDays.TUESDAY, 2],
    [ClinicClosedDays.WEDNESDAY, 3],
    [ClinicClosedDays.THURSDAY, 4],
    [ClinicClosedDays.FRIDAY, 5],
    [ClinicClosedDays.SATURDAY, 6],
  ]);

  // 営業日をインデックス順に並べ替え
  const sortedOpenDays = openDays.sort(
    (a, b) => (dayIndexMap.get(a) ?? 0) - (dayIndexMap.get(b) ?? 0)
  );

  // 営業日の連続範囲を格納する配列
  const ranges: string[] = [];

  // 最初の範囲の開始点
  let rangeStart = sortedOpenDays[0];
  // 前の曜日のインデックスを保持
  let prevIndex = dayIndexMap.get(rangeStart)!;

  // 営業日をループして範囲に分割
  for (let i = 1; i <= sortedOpenDays.length; i++) {
    const currentDay = sortedOpenDays[i];
    const currentIndex = dayIndexMap.get(currentDay);

    // 前の曜日と連続していない場合 → 範囲の区切り
    if (currentIndex !== prevIndex + 1) {
      const rangeEnd = sortedOpenDays[i - 1];
      ranges.push(formatRange(rangeStart, rangeEnd));
      // 次の範囲のスタートを更新
      rangeStart = currentDay;
    }

    // インデックスを更新(undefined の場合はダミー)
    prevIndex = currentIndex ?? -999;
  }

  // 最終的に「月曜 ~ 水曜、金曜」形式で返す
  return ranges.join("");

  // 単体または範囲を "月曜" または "月曜 ~ 水曜" 形式に整形
  function formatRange(start: ClinicClosedDays | undefined, end: ClinicClosedDays | undefined): string {
    const formatDay = (day: ClinicClosedDays | undefined) =>
      day ? day.replace("曜日", "") : "";

    return start === end ? formatDay(start) : `${formatDay(start)} ~ ${formatDay(end)}`;
  }
}

// テスト用ラッパー関数:定休日を渡して結果を表示
function test(closed: ClinicClosedDays[], label: string) {
  const result = getOpenDayRanges(closed);
  const formattedClosed = closed.map(d => d.replace("曜日", "")).join("");
  console.log(`🧪 ${label}`);
  console.log(`定休日: ${formattedClosed || "なし"}\n→ 営業日: ${result}\n`);
}

// 豊富なケースを用意
test([], "全て営業(定休日なし)");
test([ClinicClosedDays.SUNDAY], "日曜休み");
test([ClinicClosedDays.WEDNESDAY], "水曜休み");
test([ClinicClosedDays.SUNDAY, ClinicClosedDays.SATURDAY], "土日休み");
test([ClinicClosedDays.MONDAY, ClinicClosedDays.TUESDAY, ClinicClosedDays.THURSDAY, ClinicClosedDays.FRIDAY], "月・火・木・金休み");
test([ClinicClosedDays.SUNDAY, ClinicClosedDays.WEDNESDAY, ClinicClosedDays.SATURDAY], "日・水・土休み");
test([
  ClinicClosedDays.SUNDAY,
  ClinicClosedDays.MONDAY,
  ClinicClosedDays.TUESDAY,
  ClinicClosedDays.WEDNESDAY,
  ClinicClosedDays.THURSDAY,
  ClinicClosedDays.FRIDAY,
  ClinicClosedDays.SATURDAY
], "全休診");
test([ClinicClosedDays.IRREGULAR], "不定休");
test([ClinicClosedDays.TUESDAY, ClinicClosedDays.THURSDAY], "火・木休み");
test([ClinicClosedDays.SUNDAY, ClinicClosedDays.MONDAY], "日・月休み");
test([ClinicClosedDays.FRIDAY], "金曜休み");
🧪 全て営業(定休日なし)
定休日: なし
→ 営業日: 日曜 ~ 土曜

🧪 日曜休み
定休日: 日曜
→ 営業日: 月曜 ~ 土曜

🧪 水曜休み
定休日: 水曜
→ 営業日: 日曜 ~ 火曜、木曜 ~ 土曜

🧪 土日休み
定休日: 日曜・土曜
→ 営業日: 月曜 ~ 金曜

🧪 月・火・木・金休み
定休日: 月曜・火曜・木曜・金曜
→ 営業日: 日曜、水曜、土曜

🧪 日・水・土休み
定休日: 日曜・水曜・土曜
→ 営業日: 月曜 ~ 火曜、木曜 ~ 金曜

🧪 全休診
定休日: 日曜・月曜・火曜・水曜・木曜・金曜・土曜
→ 営業日: 全日休診

🧪 不定休
定休日: 不定休
→ 営業日: 定休日なし(不定休)

🧪 火・木休み
定休日: 火曜・木曜
→ 営業日: 日曜 ~ 月曜、水曜、金曜 ~ 土曜

🧪 日・月休み
定休日: 日曜・月曜
→ 営業日: 火曜 ~ 土曜

🧪 金曜休み
定休日: 金曜
→ 営業日: 日曜 ~ 木曜、土曜

処理一部を解説(日本語解説)

  1. 営業日を「連続する範囲」に分割
    例:
  • 月・火・水・金・土 → 月 ~ 水、金 ~ 土
  • 日・水・金 → 日曜、水曜、金曜

処理手順:

  1. 最初の営業日を rangeStart とする
  2. ループで次の営業日と前の営業日を比較
    3.「連続してない」と判断されたら、そこまでの範囲を切り出して ranges に追加
    4.次の営業日を新しい rangeStart にする
    5.最後の要素まで確認したら join("、") して返す

終わりに

株式会社シンシアでは、実務未経験のエンジニアの方や学生エンジニアインターンを採用し一緒に働いています。
※ シンシアにおける働き方の様子はこちら

弊社には年間100人程度の実務未経験の方に応募いただき、技術面接を実施しております。
この記事が少しでも学びになったという方は、ぜひ wantedly のストーリーもご覧いただけるととても嬉しいです!

14
8
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?