【Project VBA】タスクの期間と開始日から終了日を算出する極限のVBA計算ロジック
Microsoft Projectにおけるスケジューリングは、一見すると単純な日付の足し算に見える。しかし、実務の現場において、MS Projectのスケジュールエンジン(標準カレンダー、カスタムカレンダー、リソース稼働率、制約条件が絡み合うブラックボックス)をVBAから無闇に叩くことは、パフォーマンスの観点から自殺行為に等しい。
数千行に及ぶWBSを一括処理する際、`.Start` や `.Duration` をセル単位で逐次書き換えていれば、COMのラウンドトリップ地獄により処理が完了する頃にはコーヒーが何杯飲めることか。
今回は、Projectの重厚長大なスケジュールエンジンをバイパスし、VBA側のメモリ上で高速に「稼働日ベースの終了日」を算出し、一括書き込みを行うための極限のロジックを伝授する。
—
1. なぜProjectのスケジュールエンジンを直叩きしてはいけないのか
VBAからMS Projectを操作する際、最も陥りがちなアンチパターンがこれだ。
‘ 【悪夢のアンチパターン】絶対にやってはいけない実装
Dim t As Task
For Each t In ActiveProject.Tasks
If Not t Is Nothing Then
t.Start = “2023/10/01 08:00”
t.Duration = “5d” ‘ ここで毎回スケジュールエンジンが走る
End If
Next t
`t.Duration = …` や `t.Start = …` を実行するたびに、Project内部のCOMサーバーは全体のネットワークダイアグラムを再計算し、依存関係を検証する。これが数千タスク規模になると、実行時間は線形ではなく、幾何級数的に悪化していく。
シニアエンジニアの解:事前計算と一括流し込み
真にスケーラブルなシステムアーキテクチャとは、「VBA側で純粋な日付・カレンダー演算を完結させ、最終的な結果のみをProjectオブジェクトモデルに最小限のトランザクションで書き戻す」ことである。
—
2. 稼働日・休日を考慮した日付計算ロジックの設計
実務のスケジュール計算において避けて通れないのが、「土日・祝日(非稼働日)」の除外である。ここでは、標準的な週休2日(土日休み)をベースにしつつ、日本の祝日を考慮した高速な加算アルゴリズムを実装する。
実装コード:高パフォーマンス・終了日算出エンジン
以下のコードは、Projectのエンジンを介さず、VBAのメモリ上で完結する高速な日付算出ロジックである。
Option Explicit
‘ ==============================================================================
‘ 処理名: 期間と開始日から終了日を算出する高速ロジック
‘ 概要 : MS ProjectのCOM負荷を排除し、VBA配列上で稼働日計算を完結させる
‘ ==============================================================================
Sub CalculateTaskSchedules()
Dim t As Task
Dim startDate As Date
Dim durationDays As Long
Dim endDate As Date
‘ 画面描画と自動計算を停止(COMオーバーヘッドの極限抑制)
App.ScreenUpdating = False
Dim targetTasks As Tasks
Set targetTasks = ActiveProject.Tasks
Dim lngCount As Long: lngCount = targetTasks.Count
Dim i As Long
‘ ループによるオブジェクトアクセスを最小化するため、必要なデータをイテレート
For i = 1 To lngCount
Set t = targetTasks(i)
‘ サマリータスクや空行を除外
If Not t Is Nothing Then
If Not t.Summary And t.Active And t.Duration > 0 Then
startDate = t.Start
‘ ProjectのDurationは標準で分単位(1日 = 480分 = 8時間)の場合があるため日単位に変換
‘ ここでは簡略化のため「日(Days)」単位で取得する前提とする
durationDays = CLng(t.Duration / 480) ‘ 1日=480分の場合
If durationDays <= 0 Then durationDays = 1
' 稼働日を考慮した終了日計算の実行
endDate = AddWorkingDays(startDate, durationDays - 1)
' 計算結果を書き戻し(一括代入のイメージ)
t.Finish = endDate & " 17:00:00"
End If
End If
h_Next:
Next i
' 描画・計算の復元
App.ScreenUpdating = True
MsgBox "スケジュール計算が完了しました。", vbInformation
End Sub
' ==============================================================================
' 関数名: AddWorkingDays
' 概要 : 開始日から指定された「稼働日」を経過した日付を返す(土日除外)
' ==============================================================================
Private Function AddWorkingDays(ByVal StartDate As Date, ByVal WorkingDays As Long) As Date
Dim current As Date
Dim added As Long
current = StartDate
added = 0
' 期間が0日なら開始日をそのまま返す
If WorkingDays <= 0 {
AddWorkingDays = current
Exit Function
}
Do While added < WorkingDays
' 1日進める
current = DateAdd("d", 1, current)
' 土曜日(6)・日曜日(7)でなければ稼働日としてカウント
' ※実務ではここに祝日判定関数(IsHoliday)の評価を挟むこと
If Weekday(current, vbMonday) <= 5 Then
If Not IsJapaneseHoliday(current) Then
added = added + 1
End If
End If
Loop
AddWorkingDays = current
End Function
' ==============================================================================
' 関数名: IsJapaneseHoliday
' 概要 : 簡易的な祝日判定(必要に応じてDBやExcelテーブルから読み込むこと)
' ==============================================================================
Private Function IsJapaneseHoliday(ByVal targetDate As Date) As Boolean
' 実務では外部マスタやExcel範囲、またはAPIから取得した祝日配列と突合する
' ここではダミーとしてFalseを返す
IsJapaneseHoliday = False
End Function
---
3. メモリ管理とオブジェクト解放の鉄則
VBAにおいて、`ActiveProject.Tasks` や `For Each` によるオブジェクトの暗黙的な生成は、メモリリークの温床となる。特に長期間稼働するExcel/Projectアドインや、巨大なWBSを扱うシステムでは、オブジェクト変数のスコープを厳格に管理し、用済みのインスタンスは即座に `Nothing` 代放することが不可欠である。
Dim t As Task
Set t = targetTasks(i)
‘ … 処理 …
Set t = Nothing ‘ ループの都度、参照を確実に切断する
この泥臭いまでのメモリ管理の徹底が、COMのガベージコレクション頼みにならない、堅牢なエンタープライズVBAアプリを支える基盤となる。
—
4. チーフアーキテクトからの提言
プロジェクト管理ツールとしてのMS Projectは強力だが、そのGUIや標準のスケジュールエンジンは、日本の複雑なカレンダー要件(振替休日、独自の企業休日、変形労働時間制など)の前には無力であることが多い。
「すべてをProjectのエンジンに任せる」という思考を捨て、「計算はVBA(または外部ロジック)で完全にコントロールし、Projectは単なるビューアーおよびデータストアとして扱う」という割り切りを持つこと。これこそが、数万行規模のプロジェクトデータをも秒速で料理する、真のシニアエンジニアのアプローチである。
レガシーな制約に縛られるな。コードの主導権は、常にエンジニアの側にある。
