見出し画像

Microsoft.Azure.Cosmos.Table.TableQuery でローカル時間の検索クエリ生成


背景

  • とあるログ管理サービスを開発中

    • 開発環境:visual studio 2022

    • Azure Functions

    • .Net 6.0

  • ログ管理テーブルには次のデータが格納されている

    • 顧客No. (PartitionKey, String)

    • ログアップロード時刻 (RowKey, String)

    • 接続開始時間 (DateTime)

    • IPアドレス (String)

    • etc.

  • クライアントから渡されたパラメータを取得し、クエリに基づくデータを返すAPIを開発する

ログ管理テーブル


やりたいこと

  • クライアント側で指定した日付フィルタを取得し、接続開始時間による絞り込み検索

    • フィルタの開始日、終了日はGetパラメータで与えられる
      (ここに載せるプログラムはこのプロセスは省略)

日付フィルタをクエリにし、データを取得したい

困りごと

TableQueryを作ろうとすると、日付がUTC変換されてしまう…

// 検索日
DateTime startDateTime = DateTime.Parse("20231010");
  // ここでは startDateTime = '2023-10-10T00:00:00.0000000Z'
DateTime endDateTime = DateTime.Parse("20231015").AddDays(1);
  // ここでは endDateTime = '2023-10-16T00:00:00.0000000Z'

// 検索開始日より日付が後のものを取得するフィルタ
string startFilter = TableQuery.GenerateFilterConditionForDate("ConnectionStartTime", QueryComparisons.GreaterThanOrEqual, startDateTime);
  // これが startFilter = 'ConnectionStartTime ge datetime'2023-10-09T15:00:00.0000000Z' となってしまう

// 検索終了日より日付が前のものを取得するフィルタ
string endFilter = TableQuery.GenerateFilterConditionForDate("ConnectionStartTime", QueryComparisons.LessThan, endDateTime);
  // これが endFilter = 'ConnectionStartTime lt datetime'2023-10-14T15:00:00.0000000Z' となってしまう

// フィルタを結合
string combinedFilter = TableQuery.CombineFilters(startFilter, TableOperators.And, endFilter);

// TableQuery生成
TableQuery<Entity> query = new TableQuery<Entity>().Where(combinedFilter);
  // ここのQueryが((ConnectionStartTime ge datetime'2023-10-09T15:00:00.0000000Z') and (ConnectionStartTime lt datetime'2023-10-15T15:00:00.0000000Z'))

例えば、2023/10/10 0:00:00 ~ 2023/10/15 23:59:59… で検索したいのに、
2023/10/9 20:00:00 のデータを取得してしまったり、
2023/10/15 22:00:00 のデータが取得できない問題が発生します。
(接続開始時間がUTCで保存されていたら問題ないのですが…)

生成されるクエリのイメージ

解決方法

何度かChatGPTと議論を繰り返し、ようやくたどり着いたのがTableQuery.GenerateFilterConditionForDate を使わず文字列で直指定する方法。

// 検索日
string startDate = "20231010";
string endDate = "20231015";

// タイムゾーン情報を取得(例:日本標準時)
TimeZoneInfo jstZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
// DateTimeOffset に変換
DateTimeOffset startDateTime = TimeZoneInfo.ConvertTime(DateTime.ParseExact(startDate, "yyyyMMdd", null), jstZone);
DateTimeOffset endDateTime = TimeZoneInfo.ConvertTime(DateTime.ParseExact(endDate, "yyyyMMdd", null).AddDays(1), jstZone);

// 検索開始日より日付が後のものを取得するフィルタ
string startFilter = $"(ConnectionStartTime ge datetime'{startDateTime:yyyy-MM-ddTHH:mm:ss.fffZ}')";

// 検索終了日より日付が前のものを取得するフィルタ
string endFilter = $"(ConnectionStartTime lt datetime'{endDateTime:yyyy-MM-ddTHH:mm:ss.fffZ}')";

// フィルタを結合
string combinedFilter = TableQuery.CombineFilters(startFilter, TableOperators.And, endFilter);

// TableQuery生成
TableQuery<DynamicTableEntity> query = new TableQuery<DynamicTableEntity>().Where(combinedFilter);

DateTimeは世界標準時で検索せよと言うMicrosoft & OpenAI v.s. 日本時間で登録されてるデータは日本時間で検索してよと思う私の対決は、5時間の激闘の末、私が勝利を収めました。


参考までに

DateTimeOffsetで時差の情報も持たせたのに、TableQueryで無効化されるのは納得いかない…
↓ChatGPTの間違った回答

// タイムゾーン情報を取得(例:日本標準時)
TimeZoneInfo jstZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");

// クエリで与えられた文字列をDateTimeに変換
string startDate = "20231010";
string endDate = "20231015";
DateTimeOffset startDateTime = TimeZoneInfo.ConvertTime(DateTime.ParseExact(startDate, "yyyyMMdd", null), jstZone);
DateTimeOffset endDateTime = TimeZoneInfo.ConvertTime(DateTime.ParseExact(endDate, "yyyyMMdd", null).AddDays(1), jstZone);

// 開始日 < ログ < 終了日
var queryB = TableQuery.CombineFilters(
    TableQuery.GenerateFilterConditionForDate("ConnectionStartTime", QueryComparisons.GreaterThan, startDateTime.UtcDateTime),
    TableOperators.And,
    TableQuery.GenerateFilterConditionForDate("ConnectionStartTime", QueryComparisons.LessThan, endDateTime.UtcDateTime));

// このクエリが(ConnectionStartTime gt datetime'2023-10-10T00:00:00.0000000Z') and (ConnectionStartTime lt datetime'2023-10-16T00:00:00.0000000Z')のようにローカル時間で変換される

いいえ、これでは残念ながら、クエリは(ConnectionStartTime gt datetime'2023-10-09T15:00:00.0000000Z') and (ConnectionStartTime lt datetime'2023-10-15T15:00:00.0000000Z')となってしまいます。

編集後記

結局、同PJメンバーがなんか勝手に接続開始時間をUTCで保存することに決めたらしく、このコードが使われることは無くなりました…TT

この記事が気に入ったらサポートをしてみませんか?