この記事でわかること

  • 2つの日付の列から、間の日数を列に足す方法
  • 土日と自分で決めた休みの日を除いて、平日の数を数える方法
  • 完了日が空欄の行があっても止まらずに数える書き方
  • 終わりの日が数えられない点など、数え方のずれに気づく方法

受付から完了までに何日かかったかを、Excelの明細から1件ずつ数えて報告することがあります。行が増えると、カレンダーを見ながら数えるのは手間で、土日や休みの日を除く数え方では間違いも起きやすくなります。この記事では、pandasとNumPyで、受付日と完了日の2つの列から日数と平日の数を出し、新しい列として足す手順を紹介します。

よりどころは、pandas 3.1.0のSeries.dt.days・pandas.to_datetimeと、NumPy v2.5のnumpy.busday_countの公式の資料です。どれも2026年11月2日に原文を読みました。例に出てくる受付番号と日付は、試すために作った架空の明細です。

先に結論: 引き算とdt.daysで日数、busday_countで平日の数

完了日から受付日を引いてdt.daysを付けると、間の日数になります。 平日の数は、NumPyのnp.busday_countに受付日と完了日を渡すと出ます。このとき、終わりの日(完了日)そのものは数えません。2つの数え方の違いは、次のとおりです。

数え方 使うもの 試した表(11月2日の月曜〜11月6日の金曜)
間の日数 完了日 - 受付日 と dt.days 4
平日の数 np.busday_count 4(休みの日を1日入れると3)
完了日も含めた平日の数 完了日の翌日を終わりとして渡す 5

日数と平日の数のしくみ

pandasでは、日付どうしを引き算すると、日の差を表す値になります。pandasの資料では、dt.daysはその差のうちの丸ごとの日数で、差を日数に直した長さそのものではないとしています。

NumPyの資料では、busday_countは始まりの日と終わりの日の間の有効な日を数え、終わりの日そのものは数に入れないとしています。どの曜日を数えるかはweekmaskで決め、初期の設定は月曜から金曜です。holidaysに渡した日は、平日でも数えません。

busday_countには日付をdatetime64[D]という日単位の形で渡し、日付が空(NaT)の行は渡せません。

手順: 受付から完了までの日数と平日の数を列に足す

次のコードは、架空の受付の明細 orders.xlsx(受付番号・受付日・完了日の3列)を読み、日数と平日の数の2つの列を足して、orders_days.xlsx に保存します。元の orders.xlsx には何も書き込まない作りです。

# 受付日と完了日から、日数と平日の数の2列を足す
import numpy as np   # 平日の数を数えるのに使う
import pandas as pd

tickets = pd.read_excel("orders.xlsx")   # 受付の明細(元のファイル)は開くだけ
start = pd.to_datetime(tickets["受付日"], errors="coerce")   # 読めない値は空に
end = pd.to_datetime(tickets["完了日"], errors="coerce")

tickets["日数"] = (end - start).dt.days.astype("Int64")   # 暦の上の日数

done = start.notna() & end.notna()   # 両方の日付がそろった行だけ数える
holidays = ["2026-11-03", "2026-11-23"]   # 会社の休みの日を自分で並べる
counts = np.busday_count(
    start[done].to_numpy().astype("datetime64[D]"),
    end[done].to_numpy().astype("datetime64[D]"),
    holidays=holidays,
)
tickets["平日の数"] = pd.Series(counts, index=tickets.index[done]).astype("Int64")

print(tickets)
tickets.to_excel("orders_days.xlsx", index=False)   # 結果は新しいファイルへ

pandasのほかに、NumPyと、xlsxを読み書きするopenpyxlを入れておきます。astype("Int64")は、空の行があっても数を整数のまま持つための指定です。

試した結果

架空の明細(8行)で動かしました。完了日には、空欄の行と「未定」と書いた行を1つずつ入れています。休みの日には、架空の会社の休みとして11月3日と11月23日を並べました。

  • 11月2日の月曜から11月6日の金曜までは、日数が4、平日の数が3でした。休みの日を外すと4になります
  • 11月6日の金曜から11月9日の月曜までは、日数が3、平日の数が1でした
  • 受付日と完了日が同じ行は、日数も平日の数も0でした
  • 完了日が空欄の行と「未定」の行は、日数も平日の数も空のまま残り、エラーにはなりませんでした
  • orders_days.xlsx には、元の3列のあとに日数と平日の数の2列が入りました。元の orders.xlsx は、実行の前後でハッシュ値が変わりませんでした

試した環境は、Mac の作業PCで、Python 3.12.15・pandas 3.1.0・NumPy 2.5.4・openpyxl 3.1.5です。書き出したファイルはopenpyxlで開き直して中身を見ただけで、Excelのアプリでの表示は見ていません。

自分で決めるか、相談するかの判断

目安は、「どの日を数えて、どの日を数えないかが、社内で決まっているか」です。報告の決まりで「受付日は数えず、完了日は数える」のように書いてあれば、その決まりに合わせて終わりの日を1日ずらすだけで済みます。

休みの日の一覧や、土曜を営業日に入れるかどうかは、会社や部署で違います。決まりが見つからないときは、自分で決めずに、数字を使う人や予定表を管理している人に確かめてから数えます。

つまずきやすいところ

  • 完了日が数えられていない: busday_countは終わりの日を数えません。試したときも、月曜から金曜を渡すと4でした。完了日も含めたいときは、完了日の翌日を終わりとして渡します。試した表では、金曜の翌日の土曜を終わりとして渡すと5になりました
  • 時刻が入った日付で、日数が1つ少ない: dt.daysは丸ごとの日数だけを数えます。試したときも、11月2日の18時から11月4日の9時までは、日をまたいで2日でも、日数は1でした。日だけで数えたいときは、先にdt.normalize()で時刻を0時にそろえます
  • 日付の型のエラーで止まる: pandasの日付をそのままbusday_countに渡すと、型を変えられないというTypeErrorで止まりました。.astype("datetime64[D]")で日単位に直してから渡します
  • 空欄の日付でエラーになる: 完了日が空の行を含めたまま渡すと、NaTの日付では数えられないというValueErrorで止まりました。記事のコードのように、両方の日付がそろった行だけを選んで数えます
  • 平日の数が4.0のような小数で出る: 一部の行にだけ値を入れると、空の行に合わせて小数の列になりました。astype("Int64")を付けると、空の行を残したまま整数で持てます
  • 日付の書き方がそろっていない: スラッシュで区切った日付と、漢字で月と日を書いた日付が混ざった列は、先にそろえておきます。日付の列を直す方法は、PythonでExcelの日付を正しく読み込む手順で紹介しています
  • かかった日数が長い行だけを取り出したい: 足した日数の列で条件を決めて行を絞る方法は、PythonでExcelの表から条件に合う行を抽出する手順にまとめています
  • 曜日ごとに件数や合計を見たい: 日付から曜日を取り出して、月曜から日曜の順に集計する方法は、PythonでExcelの日付から曜日ごとの合計を出す手順が参考になります

まとめ

  • 日数は、完了日から受付日を引いてdt.daysを付けると出ます
  • 平日の数は、np.busday_countにdatetime64[D]の日付と休みの日を渡します
  • busday_countは終わりの日を数えないので、完了日を含めたいときは翌日を渡します
  • 空欄の日付がある行は、両方の日付がそろった行だけを選んでから数えます