Dart の DateTime の扱いあれこれ

mono 
Flutter 🇯🇵
Published in
7 min readApr 30, 2019

Dartで日時を表現するクラスはDateTimeです。

基本的にシンプルで扱いやすいものの、タイムゾーン周りの扱いに少し戸惑ったのでそのあたり中心にざっと整理します。

現在日時

DateTimeでもっともよく使う名前付きコンストラクタの1つであろう now を使うと現在日時を取れます。

final now = DateTime.now();

タイムゾーンの扱い

DartのDateTimeはタイムゾーン情報を持っているものの、それはローカルかUTCかのいずれかとなります(ちょっと変わった作りな気がします)。

DateTimeで生成したインスタンスのタイムゾーンは基本的にローカルで、この場合もisUtcの結果はfalseになります。

print(now.isUtc);
// -> false
print(now.timeZoneName);
// -> JST (日本の場合)

ISO8601文字列を出力してみると、次のようになります。末尾にタイムゾーン表記が無いですね🤔

print(now.toIso8601String());
// -> 2019-04-30 10:48:27.701406

この文字を受け取った側はどのタイムゾーンかの判断が不可能です。次のようにparseしてみるとローカルタイムゾーン扱いとなります。

print(DateTime.parse('2019-04-30 10:48:27.701406').isUtc);
// -> false
// ちなみに、ローカルタイムゾーンをISO8601変換した時は時差表記が付かないが、逆に時差表記の含まれている文字列の解釈はきちんとなされる
print(DateTime.parse('2019-04-30 10:48:27.701406+0100'));
// -> 2019-04-30 09:48:27.701406Z

日本国内でのやり取りならこの日時文字列を使ったデータのやり取りでも何とかなるかもしれませんが、タイムゾーンの違う国にもサービス展開するとなると困ります。

toUtcメソッドでUTCタイムゾーンに変換してあげれば、元々のローカルタイムゾーンが日本の場合9時間前にずれて最後にUTCであることを示す Z が付いて全世界共通で扱えるISO8601文字列表現となります。

print(now.toUtc().toIso8601String());
// -> 2019-04-30 01:48:27.701406Z
print(DateTime.parse('2019-04-30 01:48:27.701406Z').isUtc);
// -> true

ローカルタイムゾーンに戻したい場合、toLocalメソッドを使います。

print(DateTime.parse('2019-04-30 01:48:27.701406Z').toLocal());
// -> 2019-04-30 10:48:27.701406

UNIX時間の扱い

なお、次のUNIX時間(1970–01–01T00:00:00Z からの経過時間)を使ってもタイムゾーンによらず扱えます。

逆にUNIX時間からDateTimeを生成するためには、以下のコンストラクタを利用します。

やり取りする相手の都合次第で、ISO8601、UNIX時間、あるいはその他のフォーマットから適した表現を使い分けることになるはずです。

タイムゾーンのオフセット

例えば、日にち指定で作ったDateTimeを .toUtc() すると、標準時とローカルの時差の分ずれてしまいます。

// ローカルタイム 2019年5月1日 で初期化
print(DateTime(2019, DateTime.may, 1).toUtc());
// -> 2019-04-30 15:00:00.000Z

この場合、次のようにすると、世界標準時での「2019年5月1日」のDateTimeを得られます。誕生日など時刻情報を考慮せず純粋に特定の日にちを表したい時などに使うと思います。

final date = DateTime(2019, DateTime.may, 1);
print(date.add(date.timeZoneOffset).toUtc());
// -> 2019-05-01 00:00:00.000Z
print(date.timeZoneOffset)
// -> 9:00:00.000000 (9時間進んでいるという意味)

timeZoneOffsetDuratio型で、同様に任意の値を指定することで任意の時間操作が可能です。

ただ、DateTime.utc コンストラクタがあるので、上の例の場合は以下の方がベターです(外部から受け取ったDateTimeインスタンスのタイムゾーンを揃えたい時などでは上の方法が有用だと思いますが)。

print(DateTime.utc(2019, DateTime.may, 1));

なお、上で DateTime() コンストラクタ引数に指定した DateTime.may は、以下で定義されたものを使っています。

// Weekday constants that are returned by [weekday] method:
static const int monday = 1;
static const int tuesday = 2;
static const int wednesday = 3;
static const int thursday = 4;
static const int friday = 5;
static const int saturday = 6;
static const int sunday = 7;
static const int daysPerWeek = 7;
// Month constants that are returned by the [month] getter.
static const int january = 1;
static const int february = 2;
static const int march = 3;
static const int april = 4;
static const int may = 5;
static const int june = 6;
static const int july = 7;
static const int august = 8;
static const int september = 9;
static const int october = 10;
static const int november = 11;
static const int december = 12;
static const int monthsPerYear = 12;

月も曜日も、0始まりではなく1始まりとなっています。ただ、上の定数をきちんと使えばそれを意識せざるを得ない場面はほとんどないはずです。

assert(DateTime.january == 1);
assert(DateTime.december == 12);
assert(DateTime.monday == 1);
assert(DateTime.sunday == 7);

以上、DateTimeクラスをざっとなぞりました。日時データのやり取りではDateTimeだけで済むはずですが、アプリ上で日時表記をする場合は intl パッケージがほぼ必須になってきます。

次の記事ではそのintlパッケージの使い方やFlutterでの実践的な多言語対応について説明していきます。

--

--