日報を Outlook のメールで作成するとき、VBA を使って Outlook の予定表に登録されている予定を取得して、あらかじめ用意したメール テンプレートに貼り付けられたら、手作業が減って便利かも?と思い、作成しました。
もくじ
環境
- OS:Windows 8.1
- Outlook 2013
プログラムを作成する前の準備
メール テンプレートを作成する
Outlook で、日報のテンプレートとなるメールを作成します。今回は次のようにしました。
“<<Appointments>>” の部分は、後でマクロから予定の件名を埋め込む際のしるしとなります。
メールを作成したら、「Outlook テンプレート形式」(.oft)で、適当なファイル名を付けて保存しておきます。保存先はどこでも OK です。
予定を登録する
適当な予定を数件登録しておきます。目的の日付(2015 年 12 月 24 日)の予定だけがメールに貼り付けられているのを確認するために、あえて他の日付の予定も登録しました。
日付 | 開始時刻 | 終了時刻 | 件名 |
---|---|---|---|
2015-12-24 | 13:00 | 13:30 | 昼寝 |
2015-12-24 | 15:00 | 16:00 | おやつ会 |
2015-12-24 | 終日 | (N/A) | クリスマスイブ |
2015-12-25 | 9:00 | 12: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
上のプログラムは、次の流れで処理を行っています。
- 予定表を取得する。
- 予定表に登録されているすべての予定を取得する。
- 日付でフィルターをかける。
- テンプレート ファイルからメールを作成する。
- メールの本文に予定の件名を埋めこむ。
予定表を取得する
予定表を取得するには、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 日の予定だけが出力されています。
予定の抽出条件の指定(フィルター文字列の作成)
予定にフィルターをかける際に肝心となる、フィルター文字列について説明します。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 年のクリスマスイブ限定となっているため、実用的ではありません。そこで、以下の機能を追加して、完成とします。
- 当日の日付からメールを作成する。
- メールの件名と本文に日付を出力する。
- 明日の予定も出力する。
- 会議は他の予定と分けて出力する。
- 「定期的な予定」として登録されている予定も出力する。
テンプレートは次のように変更しました。
プログラム全体です。
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