【入門編】Taskの「Duration」単位(分・時間・日)の揺らぎをVBAで吸収する正規化処理 – Project VBA解析バイブル

スポンサーリンク

こんにちは!プロジェクトマネジメントの現場で、日々Microsoft Projectと格闘お疲れ様です。
マクロの記録ボタンを押すだけの世界から一歩踏み出し、「自分の手でProjectを意のままに操りたい」と願うあなたへ。

今回は、現場のデータ管理で誰もが一度は頭を抱える「Task(タスク)の工数(Duration)の単位揺らぎ」を、VBAで美しくねじ伏せる極意をお伝えします。

ここをクリアすれば、Project VBAのオブジェクトモデルの癖と、データクレンジングの基本はバッチリです。優しく、そして本質的に解説していきますね。

1. なぜ「Duration(期間)」の単位で苦しめられるのか?

Microsoft Projectのタスクには `Duration`(期間)というプロパティがあります。例えば「3日」でも、人によって、あるいは外部からインポートしたデータによって、以下のようにバラバラに入力されてくることがあります。

  • `3d` (3日:Days)
  • `24h` (24時間:Hours)
  • `1440m` (1440分:Minutes)

これらは人間が見ればすべて「実質3日(8時間/日として)」と同じですが、VBAでそのまま足し算したり比較したりすると、Project内部では「分(Minutes)」を基準にした絶対値で保持されているため、予期せぬバグや集計ミスを引き起こします。

さらに厄介なことに、Projectの裏側では、プロジェクト自体のカレンダー設定(1日何時間労働か?)によって「1日」の重みが変わります。

この混沌とした単位の揺らぎを、VBAで綺麗に「分(Minutes)」に正規化(ノーマライズ)するロジックを作ってみましょう。

2. 押さえておくべきProject VBAの基本構造

まずは、今回操作するオブジェクトの階層を確認しておきます。

Application (全体を統括する親)
┗ ActiveProject (現在開いているプロジェクト)
┗ Tasks (タスクの集合体)
┗ Task (個々のタスク。DurationやTextフィールドを持つ)

VBAでタスクの期間を扱うとき、`Task.Duration` プロパティはデフォルトで「分(Minutes)」の整数を返します。しかし、画面上の表示形式や入力文字列(文字列として渡された場合など)を扱うときは注意が必要です。

今回は、「ユーザーが自由に入力した、単位付きの文字列(例:`2d`, `4h`, `90m`)」を想定し、それを安全にパースしてProjectの標準である「分」に変換するプロシージャを構築します。

3. 実装コード:単位揺らぎを完全に吸収する正規化エンジン

以下のコードを、VBAエディタ(Alt + F11)の標準モジュールに貼り付けてみてください。現場でそのまま使える、堅牢なエラーハンドリング付きのコードです。

Option Explicit

‘ ==============================================================================
‘ 処理名 : NormalizeTaskDurations
‘ 概要 : アクティブプロジェクト内の全タスクの期間表記ゆらぎを検出し、
‘ 「分(Minutes)」単位の数値へとクレンジングする
‘ ==============================================================================
Sub NormalizeTaskDurations()
Dim t As Task
Dim rawDurationStr As String
Dim normalizedMinutes As Long
Dim processedCount As Long

processedCount = 0

‘ エラーハンドリングの準備
On Error GoTo ErrorHandler

‘ アクティブプロジェクトの存在確認
If ActiveProject Is Nothing Then
MsgBox “有効なプロジェクトが開かれていません。”, vbCritical, “エラー”
Exit Sub
End If

‘ プロジェクト内の全タスクをループ処理
For Each t In ActiveProject.Tasks
‘ 概要タスク(サマリー)や削除済みタスクはスキップ
If Not t Is Nothing Then
If Not t.Summary And t.Active Then

‘ ここでは例として「Text1」にユーザが自由入力した文字列があると仮定
‘ ※実際の Task.Duration を直接触る場合は、Projectが勝手に解釈してくれますが、
‘ 外部CSV等から取り込んだ「テキスト形式の工数」を補正するシチュエーションを想定しています。
rawDurationStr = Trim(t.Text1)

If rawDurationStr <> “” Then
‘ 文字列を解析して「分」に変換する関数を呼び出す
normalizedMinutes = ParseDurationToMinutes(rawDurationStr)

If normalizedMinutes >= 0 Then
‘ 正規化された「分」を本来の Duration プロパティにセット
t.Duration = normalizedMinutes

‘ 処理したログを Text2 に残す(デバッグ・確認用)
t.Text2 = “Normalized from: ” & rawDurationStr
processedCount = processedCount + 1
End If
End If

End If
End If
Next t

MsgBox “データの正規化が完了しました。” & vbCrLf & _
“処理件数: ” & processedCount & ” 件”, vbInformation, “完了”
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “エラー”
End Sub

‘ ==============================================================================
‘ 関数名 : ParseDurationToMinutes
‘ 概要 : “3d”, “5h”, “90m” などの文字列を解析し、総「分」を返すヘルパー関数
‘ ==============================================================================
Function ParseDurationToMinutes(ByVal durationStr As String) As Long
Dim unit As String
Dim numPart As Double
Dim lastChar As String

‘ 小文字に統一して空白を削除
durationStr = LCase(Replace(durationStr, ” “, “”))

If durationStr = “” Then
ParseDurationToMinutes = 0
Exit Function
End If

‘ 末尾の文字(単位)を取得
lastChar = Right(durationStr, 1)

‘ 数字部分を抽出するための処理
Select Case lastChar
Case “d” ‘ 日 (Days) -> 1日 = 480分 (8時間労働と仮定した場合)
numPart = Val(Left(durationStr, Len(durationStr) – 1))
ParseDurationToMinutes = numPart 480

Case “h” ‘ 時間 (Hours) -> 1時間 = 60分
numPart = Val(Left(durationStr, Len(durationStr) – 1))
ParseDurationToMinutes = numPart 60

Case “m” ‘ 分 (Minutes)
numPart = Val(Left(durationStr, Len(durationStr) – 1))
ParseDurationToMinutes = numPart

Case Else
‘ 単位が省略されている場合は、デフォルトで「日」とみなす、あるいはエラー処理
If IsNumeric(durationStr) Then
ParseDurationToMinutes = Val(durationStr) 480
Else
‘ 不明なフォーマット
ParseDurationToMinutes = -1
End If
End Select
End Function

4. コードのポイントとエンジニアの知見

ここで、シニアエンジニアとしてのこだわりポイントをいくつか解説しておきます。

① サマリータスク(概要タスク)の除外

`If Not t.Summary Then` という判定を入れています。
Projectのサマリータスクの期間は、配下のサブタスクから自動計算される「読み取り専用に近い」振る舞いをします。ここに無理やり値を代入しようとすると、VBAが機嫌を損ねて実行時エラーを起こします。「実体のタスク(Leaf Task)だけを操作する」のはProject VBAの鉄則です。

② 1日の定義(480分という魔法の数字)

コード内の `480` という数字は、「1日 = 8時間 × 60分 = 480分」という前提に基づいています。
もしあなたのプロジェクトの設定が「1日 = 7時間(420分)」であれば、ここを環境に合わせて調整する必要があります。本来は `ActiveProject.MinutesPerDay` などのプロパティ動的に取得するのが最もプロフェッショナルな実装です。

③ 堅牢なパース処理

ユーザーは時に余計なスペースを入れたり(` 3 d `など)、大文字で入力してきたりします。`LCase` と `Replace` であらかじめ入力を「 sanitizing(無害化・整形)」してから評価するのが、トラブルを防ぐ秘訣です。

5. ここをクリアすれば、Project VBAの基本はバッチリ!

いかがでしょうか?
一見バラバラに見える文字列を、独自のヘルパー関数(`ParseDurationToMinutes`)に噛み砕かせて整然とした数値に変換する――このアプローチができるようになると、Excelからのデータインポートや、他システム連携バッチの精度が劇的に向上します。

「マクロの記録」では絶対に作れない、こうした実用的なデータクレンジングのロジックが書けるようになったあなたなら、もうProject VBAの初級者は卒業です。

現場の面倒な手作業をコードで自動化し、スマートにプロジェクトを推進していってくださいね。それでは、次のステップでお会いしましょう!

タイトルとURLをコピーしました