予定表アイテムの取得終了日に日本時間 3 月 1 日 (UTC 2 月 29 日) を指定する際の注意点

こんにちは。Exchange サポートの小間です。今回は EWS Managed API で「うるう日」の翌日 3 月 1 日までの予定表アイテムを取得する際の注意点について紹介します。

本ブログ執筆時点の次のうるう日は 2020/02/29 なので、例として 2018/03/01 からうるう日の翌日の 2020/03/01 までの 2 年分の予定表アイテムを取得するシナリオを考えます。C# と EWS Managed API で実装する場合は、以下のようなコードがまずは思い浮かびます。

ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
service.Credentials = new WebCredentials("UPN", "password");
service.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");

DateTime startDate = new DateTime(2018, 3, 1, 0, 0, 0);
DateTime endDate = startDate.AddYears(2);
const int NUM_APPTS = 1000;

CalendarFolder calendar = CalendarFolder.Bind(service, WellKnownFolderName.Calendar, new PropertySet(BasePropertySet.IdOnly));
CalendarView cView = new CalendarView(startDate, endDate, NUM_APPTS);
cView.PropertySet = new PropertySet(
AppointmentSchema.Subject,
AppointmentSchema.Start,
AppointmentSchema.End);

FindItemsResults<Appointment> appointments = calendar.FindAppointments(cView);

一見このコードは問題がなく、2018/03/01 から 2020/03/01 までの 2 年分の予定表アイテムを取得するように思えますが、実際にこのコードをタイムゾーンが日本時間に設定された端末上で実行すると「The specified view range exceeds the maximum range of two years.」というメッセージとともに失敗します。FindAppointments の実行時には開始日と終了日が最大 2 年となる制限がありますが、その制限を超えたリクエストが行われているためです。

タイムゾーンの変換の影響を受けた結果、このような動作になっています。コード上で指定した時間がローカル時間として扱われ、EWS Management API が UTC 時間に変換をして SOAP リクエストを送信した結果、2 年を超えてしまっています。もう少し詳しく見てみましょう。

開始日として指定したのは「new DateTime(2018, 3, 1, 0, 0, 0)」で、これをローカル時刻である日本時間であると仮定すると日本時間 2018/03/01 0:00:00 ですが、UTC 時間では 2018/02/28 15:00:00 となります。そして終了日として指定したのは開始日の 2 年後ですが、これも日本時間であると仮定すると日本時間 2020/03/01 0:00:00 になります。これを UTC 時間で表現するとうるう日である 2020/02/29 15:00:00 となります。つまり実際に送信される SOAP リクエスト上では 2018/02/28 15:00:00 から 2020/02/29 15:00:00 までの予定表アイテムをリクエストしていることになり、これは 2 年と 1 日ということになります。結果として 2 年を超えているためエラーになります。

対処策としては、タイムゾーンを意識した時間の指定を行うことです。コードを以下のように変更することでエラーなく 2018/03/01 から 2 年分の予定表アイテムを取得できます。

ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
service.Credentials = new WebCredentials("UPN", "password");
service.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");

DateTime localStartDate = new DateTime(2018, 3, 1, 0, 0, 0, DateTimeKind.Local);
DateTime utcStartDate = localStartDate.ToUniversalTime();
DateTime utcEndDate = utcStartDate.AddYears(2);
const int NUM_APPTS = 1000;

CalendarFolder calendar = CalendarFolder.Bind(service, WellKnownFolderName.Calendar, new PropertySet(BasePropertySet.IdOnly));
CalendarView cView = new CalendarView(utcStartDate, utcEndDate, NUM_APPTS);
cView.PropertySet = new PropertySet(
AppointmentSchema.Subject,
AppointmentSchema.Start,
AppointmentSchema.End);

FindItemsResults<Appointment> appointments = calendar.FindAppointments(cView);

このコードでは、まずは時間がローカル時間であることを明示して DateTime のオブジェクトを作成し、その上で UTC 時間に変換したものを開始日として使用しています。終了日についても、UTC 時間に変換した開始日に 2 年足した値を使っています。そのため SOAP リクエスト上では 2018/02/28 15:00:00から 2020/02/28 15:00:00 までのちょうど 2 年の予定表アイテムを取得するリクエストになり、エラーは発生しなくなります。なおユーザーの視点では、日本時間の 2018/03/01 から 2 年分の予定表アイテムを取得したにも関わらず 日本時間の 2020/02/28 までの情報しか取得できていないように見えてしまいます。そのため追加で日本時間 2020/02/29 の予定も取得する必要があります。

予定表アイテムを扱う際にはタイムゾーンの違いを考慮した開発を行うことで、ご紹介したようなうるう日のシナリオ以外でも予期せぬ問題を未然に防ぐことができますので参考になりましたら幸いです。

今後も当ブログおよびサポート チームをよろしくお願いいたします。

※サンプル コードについて
掲載されているサンプル コードは本ブログ記事の説明のための部分コードであり、弊社にてその動作を保証するものではございません。
ご使用の際にはお客様のシステムに合わせて修正していただき、十分なテストを実施していただきますようお願いいたします。
なお、サンプル内で使用しておりますコードの詳細な情報に関しては、Microsoft Docs や MSDN などをご参照くださいますようお願いいたします。

※ 本情報の内容 (添付文書、リンク先などを含む) は、作成日時点でのものであり、予告なく変更される場合があります。