C#によるgRPC通信でのTimeStampとDateTimeの解説

「C#によるgRPC通信のサンプルコード」に戻る

(注意:2024年1月15日更新→.NET5.0はサポート終了しました。サンプルコードは.NET8.0に更新しました)

WPFアプリとgRPCサービスで、日時を通信する処理を作成してみたところ、案外分かり難い点が多いので、ハマりやすい部分を重点的に解説したいと思う。

Protocol Buffers のC#ライブラリのTimestampと、.NETのDateTimeの仕様の違いから、単純な両者の変換処理では使用できない。

両者を正しく使用するにはUTC,UNIX時刻(Timestamp),タイムゾーン,DateTimeKindの知識が必要となる。

その点を踏まえて、Protocol Buffers の Timestamp と、.NETの DateTime・DateTimeOffsetを使用したクライアント・サーバー型アプリの使い方を解説する。

サンプルコードとして以下のGitHubへのリンクを掲載しておく。

二つの単項メソッドを追加している。

Release v1.7.1.0 · motoi-tsushima/Sample_GrpcService
.NET 8.0 により、リビルドしました。
Release v1.7.1.0 · motoi-tsushima/Sample_gRPC_WpfApp
.NET 8.0 により、リビルドしました。

日時についての基礎知識

Protocol Buffers の Timestamp と、.NETの DateTime の扱い方を解説する前に、日付と日付型(時刻を含む)についての基礎知識の解説をする。

UTCとUNIX時刻(Timestamp)

Protocol Buffers の Timestamp は、その名に違わずUNIZタイムスタンプの値を持つ。

だから gRPCで日付型を扱う為にはUNIXタイムスタンプの知識が必要であり、更にUTCの知識も必要になる。

この認識がないと gRPCで日付型を扱う事ができない。

UNIX時刻(Timestamp)

UNIXタイムスタンプは1970年1月1日0時0分0秒からの経過秒数を整数で有する値である。

年月日時分の値は無い。秒数だけだ。

昔は32bitで表していたが、近年は64bit整数で表すものが多くなってきている。

Protocol Buffers の Timestamp も64bit整数で値を有する。

UTC(協定世界時)

UTとは国際的に定められた、英国の標準時である「グリニッジ標準時」を表す「世界時」の規格である。

UT1・UT2・UTC の三種類があり、一般市民が使用する「世界時」の規格が「UTC」である。

UTCは、1970年1月1日0時0分0秒からの経過秒数を整数で保持し、それを基に年月日時分秒に変換する規格を定めている。

多くのOSやコンピュータ言語やDBMSで、UNIXタイムスタンプからUTCに変換する機能を有し、世界各国の時差に対応する為に基準となる日付時刻になっている。

多くの場合、日本時間への対応もUTCから変換する事で行っている。

時差とタイムゾーン

地球は丸いので世界各国の標準時には時差がある。

各国にはそれぞれの「標準時」が存在する。

この中で東西方向の位置を基準に、南北に重なる国々の標準時は同じになる。

この同じ標準時ごとの分類をタイムゾーンと呼ぶ。

タイムゾーンはUTCとの時差で表す。

日本の場合は、UTCより9時間早いので「UTC+9:0:0」と表す。

台湾やシンガポールなら、UTCより8時間早いので「UTC+8:0:0」と表す。

ニューヨーク(米国東部時間)なら、UTCより5時間遅いので「UTC-5:0:0」と表す。

DateTimeとタイムゾーン

.NET で、国内専用の業務システムを開発していると意識しないが、DateTime型は自国とUTCを識別する情報を保有している。

タイムゾーンの情報は保有していないが、その日時がUTCであるか自国であるかを識別するフラグを有する。

この情報はコンストラクタで指定する事ができ、DateTimeKindをコンストラクタ引数に渡す事によって行う。

DateTimeKindは、Local, Unspecified, Utc の三つを区別する。

DateTimeKind Enum (System)
Specifies whether a DateTime object represents a local time, a Coordinated Universal Time (UTC), or is not specified as ...

Protocol Buffers の Timestamp は DateTimeKind.Utc を有するDateTime型でなければ使用できない。

それ以外は変換エラーとなる。

DateTime 構造体 (System)
特定の時点を表します。通常、日時形式で表されます。

DateTimeOffsetとタイムゾーン

DateTimeOffset型 はDateTime型の拡張で、DateTime型に加えて、内部にタイムゾーンの情報を有する。

日本のタイムゾーンを有するDateTimeOffset型は、日本標準時と時差「UTC+9:0:0」の情報を持つ。

UTCを参照する時はこの時差情報から、UTCに変換する。

これにより世界中のタイムゾーンの日付型を、一つのシステムに共存できる。

Protocol Buffers の Timestamp は DateTimeOffset型 に対応しており、Timestamp へ変換する時UTCへ変換する。

DateTimeOffset 構造体 (System)
特定の時点を表します。通常、世界協定時刻 (UTC) を基準とする相対的な日時として表されます。

TimeSpan の使い方

TimeSpan は、日数、時間、分、秒、および秒の小数部の正または負の数として計測される時間間隔 (時間または経過時間) を表す。

異なる二つのDateTime型の日数時間差を表す時などに使用する。

DateTimeOffset型を生成する時にタイムゾーンをコンストラクタ引数に渡す器として使用する。

Protocol Buffers の異なる二つの Timestamp型の時間差を表す型に、Duration型 が有る。

これを .NETの型に変換する場合は、TimeSpan型のオブジェクトに変換する。

Timestamp型とTimeSpan型は、名前が似ているので最初は混乱するが、両者は全然違うものだ。

Timestamp と、.NETの 日付型との二つの扱い方

Protocol Buffers の Timestamp型は、UTC以外の標準時を受け付けない仕様の為、日本標準時などローカルの標準時を使用する場合は、工夫が必要になる。

Protocol Buffers の Timestamp型と、.NETの日付型を扱う方法は大きく二つになると思う。

一つは、全てDateTime型だけでシステムを開発する方法。

もう一つは、DateTimeOffset型を中心に使用し、UTCと日本標準時を明確に使い分ける扱い方だ。

日本国内のみDateTimeだけで扱う

全ての日付をDateTimeで扱うのなら、全ての日付型を「UTC」と解釈してシステム全体を開発する必要がある。

つまり、Protocol Buffers の Timestamp型を、日本標準時が「UTC」であると欺いて使用する事になる。

日本国内でのみ使用するシステムなら、この方がシンプルで良い。

特にペナルティーもない。

国際対応でDateTimeOffsetで扱う

正攻法で Protocol Buffers の Timestamp型を .NET で使用するなら、厳格にタイムゾーンを区別して日付型を扱う必要がある。

.NETの日付型には DateTimeOffset型を使用する。

初めから世界各国のタイムゾーンが共存できるように開発する。

gRPCサービス側はUTC中心に日付型を扱う

どちらの方法でも、シリアライズは全てUNIXタイムスタンプ(UTCと同じグリニッジ標準時)になるので、システム的には gRPCサービス側では「UTC」日付を中心に情報処理を行う事になる。

クライアント側はUNIXタイムスタンプを、ローカル標準時に変換して使用する。

gRPCサービスへローカル標準時を送る時は、UNIXタイムスタンプへ変換する。

gRPCサービス側は、常にUNIXタイムスタンプを受け取る。

日本国内のみDateTimeだけで扱うサンプル

日本国内のみ、C#のDateTimeだけで扱うgRPCサンプルコード

国際対応でDateTimeOffsetで扱うサンプル

国際対応で、C#のDateTimeOffsetで扱うgRPCサンプルコード

最初に国内のみか、国際対応するか、考える

gRPCサービスで日付型を使用するシステムを開発する時は、最初の設計段階から「国内のみで使用する」のか「国際的に使用できる」ようにするか、考えておくべきだ。

コーディング段階で考えてはいけない。

設計段階、できれば要件定義の段階で、どちらかに決めるべきだ。

コーディング段階で考えると、各担当者ごとにタイムゾーンの扱いがバラバラになり、「どうしよう、どうしよう」と散々議論と試行錯誤した挙げ句、結局「国際化対応するしかない」という結論に気がつき、膨大な手戻り作業を行う事になる。

前工程で決められる事は、決めておこう。

優柔不断と曖昧さは「敵」だ。

前工程こそ「コスト」にシビアであるべきだが、多くのリーダーやマネージャーは後工程でコストにシビアになり、赤字を膨らませる。

この悪習は排除すべきだと思う。

「C#によるgRPC通信のサンプルコード」に戻る

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