VBA で Outlook 予定表から日報メールを作成する

日報を Outlook のメールで作成するとき、VBA を使って Outlook の予定表に登録されている予定を取得して、あらかじめ用意したメール テンプレートに貼り付けられたら、手作業が減って便利かも?と思い、作成しました。

環境

  • OS:Windows 8.1
  • Outlook 2013

プログラムを作成する前の準備

メール テンプレートを作成する

Outlook で、日報のテンプレートとなるメールを作成します。今回は次のようにしました。

[Outlook] メール テンプレート
メール テンプレート

“<<Appointments>>” の部分は、後でマクロから予定の件名を埋め込む際のしるしとなります。

メールを作成したら、「Outlook テンプレート形式」(.oft)で、適当なファイル名を付けて保存しておきます。保存先はどこでも OK です。

予定を登録する

適当な予定を数件登録しておきます。目的の日付(2015 年 12 月 24 日)の予定だけがメールに貼り付けられているのを確認するために、あえて他の日付の予定も登録しました。

日付開始時刻終了時刻件名
2015-12-2413:0013:30昼寝
2015-12-2415:0016:00おやつ会
2015-12-24終日(N/A)クリスマスイブ
2015-12-259:0012:00大掃除
2015-12-25終日(N/A)クリスマス

プログラムを作成する(日付限定バージョン)

プログラム全体は次のようになります。

Public Sub CreateDailyMail()
    Dim l_calendar As Outlook.Folder
    Set l_calendar = Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar)

    Dim l_appointments As Outlook.Items
    Set l_appointments = l_calendar.Items

    Set l_appointments = l_appointments.Restrict( _
        "(([Start] = '2015/12/24 0:00') And ([AllDayEvent] = True)) Or " & _
        "(([Start] <= '2015/12/24 0:00') And ([End] < '2015/12/25 0:00'))")

    Dim l_appointmentList As String
    Dim l_appointment As Outlook.AppointmentItem

    For Each l_appointment In l_appointments
        l_appointmentList = l_appointmentList & l_appointment.Subject & vbCrLf
    Next

    ' テンプレート ファイルのパスの部分は、環境に応じて設定してください。
    Dim l_mail As Outlook.MailItem
    Set l_mail = Application.CreateItemFromTemplate("C:\...\DailyReport.oft")

    l_mail.Body = Replace(l_mail.Body, "<<Appointments>>", l_appointmentList)

    l_mail.Display
End Sub

上のプログラムは、次の流れで処理を行っています。

  1. 予定表を取得する。
  2. 予定表に登録されているすべての予定を取得する。
  3. 日付でフィルターをかける。
  4. テンプレート ファイルからメールを作成する。
  5. メールの本文に予定の件名を埋めこむ。

予定表を取得する

予定表を取得するには、Outlook アプリケーションの現在のセッションに対して、GetDefaultFolder メソッドを実行します。このメソッドは、取得するフォルダーの種類を指定する必要がありますので、ここでは予定表フォルダー (OlDefaultFolders.olFolderCalendar) を指定します。

Dim l_calendar As Outlook.Folder
Set l_calendar = Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar)

予定表に登録されているすべての予定を取得する

予定表に登録されているすべての予定は、予定表 (Folder オブジェクト) の、Items プロパティに設定されています。

Dim l_appointments As Outlook.Items
Set l_appointments = l_calendar.Items

日付でフィルターをかける

フィルターをかけるためには、すべての予定 (Items オブジェクト) の Restrict メソッドを実行します。このメソッドを実行すると、フィルターに一致する予定のみが取得されます。フィルター文字列については後述します。

Set l_appointments = l_appointments.Restrict( _
    "(([Start] = '2015/12/24 0:00') And ([AllDayEvent] = True)) Or " & _
    "(([Start] >= '2015/12/24 0:00') And ([End] < '2015/12/25 0:00'))")

テンプレートからメールを作成する

テンプレート ファイルのパスを指定して、メールを新規作成します。

Dim l_mail As Outlook.MailItem
Set l_mail = Application.CreateItemFromTemplate("C:\...\DailyReport.oft")

メールの本文に予定の件名を埋め込む

予定の件名の頭に “・” を付けて、さらに改行でつなげることで、箇条書き風にします。そして、テンプレート上の “<<Appointments>>” の部分に埋め込みます。

Dim l_appointmentList As String
Dim l_appointment As Outlook.AppointmentItem

For Each l_appointment In l_appointments
    l_appointmentList = l_appointmentList & l_appointment.Subject & vbCrLf
Next

l_mail.Body = Replace(l_mail.Body, "<<Appointments>>", l_appointmentList)

l_mail.Display

実行結果

プログラムを実行すると、次のようなメールが作成されました。2015 年 12 月 24 日の予定だけが出力されています。

[Outlook] メール テンプレートに予定の件名が埋め込まれている
メール テンプレートに予定の件名が埋め込まれている

予定の抽出条件の指定(フィルター文字列の作成)

予定にフィルターをかける際に肝心となる、フィルター文字列について説明します。2015 年 12 月 24 日に登録されている予定を抽出するフィルター文字列を作成します。
開始時刻・終了時刻が設定されている予定と、開始時刻・終了時刻が設定されていない、いわゆる「終日」の予定の、いずれも対象とすることにします。

開始時刻・終了時刻が設定されている予定を取得するフィルター文字列

2015 年 12 月 24 日の、開始時刻・終了時刻が設定されている予定を取得するには、次のようにします。

([Start] >= '2015/12/24 0:00') And ([End] < '2015/12/25 0:00')

[Start] は開始日時、[End] は終了日時を表すフィールドです。注意点として、日時の値は、年・月・日・時・分までを指定する必要があります。秒を指定すると、エラーにはならないのですが、正しい結果を得ることができませんでした。

終日の予定を取得するフィルター文字列

2015 年 12 月 24 日の、終日の予定を取得するには、次のようにします。

([Start] = '2015/12/24 0:00') And ([AllDayEvent] = True)

[AllDayEvent] は終日の予定であるかどうかを表すフィールドです。True なら終日、False なら終日ではありません。予定を入力する画面の、[終日] チェック ボックスと連動しています。

注意点として、終日の予定であっても、開始日時 ([Start]) には時・分までを指定する必要があります。よって、0:00 を指定しています。

最後に、2 つの文字列を “Or” 演算子で結合することにより完成です。これを、Restrict メソッドの引数に指定しています。

(([Start] >= '2015/12/24 0:00') And ([End] < '2015/12/25 0:00')) Or 
(([Start] = '2015/12/24 0:00') And ([AllDayEvent] = True))

フィールドを指定する際の注意点

フィルター文字列における予定のフィールドは角括弧 [] で書きます。たとえば、開始時刻は [Start] と書きます。また、このフィールドと値を、等号(=)や不等号(> や <= など)で比較する式を書く際、フィールドは必ず左辺に書かなければならないようです。つまり、次の書き方は OK です。

[Start] >= '2015/12/24 0:00'

しかし、次の書き方では正しい結果が得られないようです。ランタイム エラーにもならないため、注意が必要です。

'2015/12/24 0:00' <= [Start]

ところで、開始日時や終了日時以外にもいろいろなフィールドがありますが、他のフィールドの値でフィルターをかけたい時、どのような名前を指定すれば良いのでしょうか。これはおそらく、予定のオブジェクトである AppointmentItem オブジェクトに定義されているプロパティの名前を指定すればよいのではないかと考えています (未検証)。

機能追加して完成

ここまでに作成したプログラムは日報と呼ぶには内容がシンプルすぎますし、何より 2015 年のクリスマスイブ限定となっているため、実用的ではありません。そこで、以下の機能を追加して、完成とします。

  • 当日の日付からメールを作成する。
  • メールの件名と本文に日付を出力する。
  • 明日の予定も出力する。
  • 会議は他の予定と分けて出力する。
  • 「定期的な予定」として登録されている予定も出力する。

テンプレートは次のように変更しました。

[Outlook] メール テンプレート改良版
メール テンプレート改良版

プログラム全体です。

Public Sub CreateDailyMail2()
    Dim l_today As Date
    l_today = Date
    
    Dim l_tomorrow As Date
    l_tomorrow = GetNextDate(l_today)
    
    ' テンプレートからメールを新規作成します。
    ' (この時点では、まだメールは画面には表示されません。)
    ' テンプレート ファイルのパスの部分は、環境に応じて設定してください。
    Dim l_mail As Outlook.MailItem
    Set l_mail = Application.CreateItemFromTemplate("C:\...\DailyReport2.oft")
    
    l_mail.Subject = Replace(l_mail.Subject, "<<Date>>", Format(l_today, "yyyy/mm/dd"))
    l_mail.Body = Replace(l_mail.Body, "<<Date>>", Format(l_today, "yyyy/mm/dd"))
    
    Dim l_appointments As Outlook.Items
    
    ' 本日の予定 (会議以外) を検索し、メールの本文に埋め込みます。
    Set l_appointments = FindAppointments(l_today, OlMeetingStatus.olNonMeeting)
    l_mail.Body = Replace(l_mail.Body, "<<Todays Appointments>>", CreateApplintmentList(l_appointments))
    
    ' 本日の予定 (会議) を検索し、メールの本文に埋め込みます。
    Set l_appointments = FindAppointments(l_today, OlMeetingStatus.olMeeting)
    l_mail.Body = Replace(l_mail.Body, "<<Todays Meetings>>", CreateApplintmentList(l_appointments))
    
    ' 明日の予定 (会議以外) を検索し、メールの本文に埋め込みます。
    Set l_appointments = FindAppointments(l_tomorrow, OlMeetingStatus.olNonMeeting)
    l_mail.Body = Replace(l_mail.Body, "<<Tomorrows Appointments>>", CreateApplintmentList(l_appointments))
    
    ' 明日の予定 (会議) を検索し、メールの本文に埋め込みます。
    Set l_appointments = FindAppointments(l_tomorrow, OlMeetingStatus.olMeeting)
    l_mail.Body = Replace(l_mail.Body, "<<Tomorrows Meetings>>", CreateApplintmentList(l_appointments))
    
    ' メールを表示します。
    l_mail.Display
End Sub

' 予定を検索します。
' <params>
'   p_date
'       検索対象の日付。
'   p_meetingStatus
'       検索対象の会議の状態。会議の場合は olMeeting を、会議以外の場合は olNonMeeting を指定します。
' <returns>
'   p_date および p_meetingStatus で指定された条件に一致する、0 個以上の予定。
Private Function FindAppointments( _
    ByVal p_date As Date, ByVal p_meetingStatus As Outlook.OlMeetingStatus) As Outlook.Items

    ' 予定表を取得します。
    Dim l_calendar As Outlook.Folder
    Set l_calendar = Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar)
    
    ' 予定表に登録されているすべての予定を取得します。
    Dim l_appointments As Outlook.Items
    Set l_appointments = l_calendar.Items
    
    ' 定期的な予定を検索に含めます。
    l_appointments.IncludeRecurrences = True
    l_appointments.Sort "[Start]"

    ' 終日の AppointmentItem を抽出するフィルターを作成します。
    Dim l_filterDate As String
    l_filterDate = "([Start] = " & FormatFilterDateTime(p_date) & ") And ([AllDayEvent] = True)"

    ' 開始時刻、終了時刻の指定がある AppointmentItem を抽出するフィルターを作成します。
    Dim l_filterDateTime As String
    l_filterDateTime = _
        "([Start] >= " & FormatFilterDateTime(p_date) & ")" & _
        " And ([End] < " & FormatFilterDateTime(GetNextDate(p_date)) & ")"
    
    ' 予定が会議か、会議以外かを区別するフィルターを作成します。
    Dim l_filterMeetingStatus As String
    l_filterMeetingStatus = "[MeetingStatus] = " & p_meetingStatus
    
    ' フィルターを組み立てます。
    Dim l_filter As String
    l_filter = "((" & l_filterDate & ") Or (" & l_filterDateTime & ")) And (" & l_filterMeetingStatus & ")"
    
    ' 予定表に登録されているすべての予定に対して、フィルターを適用します。
    Set l_appointments = l_appointments.Restrict(l_filter)
    
    Set FindAppointments = l_appointments
End Function

' 0 個以上の予定の内容が改行で結合された文字列を作成します。
' <params>
'   p_appointments
'       予定のコレクション。
' <returns>
'   p_appointmentItems の各項目の内容が改行で結合された文字列。
Private Function CreateApplintmentList(ByVal p_appointments As Items) As String
    Dim l_count As Integer
    l_count = GetItemCount(p_appointments)
 
    Dim l_appointmentList As String
    Dim i As Integer

    For i = 1 To l_count
        l_appointmentList = l_appointmentList & "・" & p_appointments(i).Subject
        
        If i < l_count Then
            l_appointmentList = l_appointmentList & vbCrLf
        End If
    Next
    
    CreateApplintmentList = l_appointmentList
End Function

' Outlook.Items オブジェクト内の項目の数を取得します。
' <params>
'   p_items
'       項目の数を取得する対象のコレクション。
' <returns>
'   p_items に含まれる項目の数。
' <remarks>
'   コレクション内に定期的な予定が存在する場合は、Items.Count の値は Long 型の最大値になるようです。
'   (たとえば、実際の予定は 3 件でも、Items.Count の値は 2,147,483,647 になっています。)
'   そのため、実際の項目数を調べるためのこのメソッドを用意しました。
Private Function GetItemCount(ByVal p_items As Outlook.Items) As Long
    Dim l_item As Object
    
    Dim l_count As Integer
    l_count = 0
 
    ' p_items の実際の項目数を調べます。
    For Each l_item In p_items
        l_count = l_count + 1
    Next
    
    GetItemCount = l_count
End Function

' フィルターに適した日時の書式を適用します。
' たとえば、指定した日時が #2015/5/1 5:06:07# であれば、2015/05/01 5:06" という文字列になります。
' <params>
'   p_date
'       フィルターに適した日時の書式を適用する対象の日時。
' <returns>
'   p_date に書式が設定された文字列。
Private Function FormatFilterDateTime(ByVal p_date As Date) As String
    FormatFilterDateTime = "'" & Format(p_date, "yyyy/mm/dd h:nn") & "'"
End Function

' 指定した日の翌日の日付を取得します。
' <params>
'   p_date
'       翌日の日付を取得する対象の日時。
' <returns>
'   p_date の翌日の日付。
Private Function GetNextDate(ByVal p_date As Date) As Date
    GetNextDate = DateAdd("d", 1, p_date)
End Function

kpdn

お寿司とゲームと動物が好きな、フリーランスのエンジニアです。フロントエンドからインフラまで日々奮闘中です。最近は物忘れがどんどんがひどくなってきました。

コメントを残す