[MongoDB]文字列でJSTの日時情報を扱う
前回の補足記事。
はじめに
MongoDBにおける日時は、ISODate型かつUTCが基本となります。
ですが、string型かつJSTで扱いたいケースもあるかもしれません。その場合、若干の加工が必要になります。
いくつかやり方はあるかと思いますが、その一つを紹介します。
環境・前提
- 環境
- MacOS 10.13.6
- MySQL 5.6
- MongoDB 4.0.6
- 前提
- データは前回の記事で使用したもの。
uid
,device
,created_at
: すべて文字列
- created_atの格納データは、文字列(string型)かつJST。
2019-04-22 00:00:00
のような形式 - やりたいことも前回と同様、
uidとdeviceのユニークな組み合わせのリストを、日別で取得したい。
加えて、日別の日時がきちんとJSTになっていることを目的とする。
- データは前回の記事で使用したもの。
まずはcol.find()して文字列を確認
最初にデータの中身を確認してみます。
# クエリ
col.find()
# 結果
{ "_id" : ObjectId("5cbc9f47bc27db275f687fad"), "uid" : "hoge", "device" : "xxx", "created_at" : "2019-02-01 00:00:00" }
{ "_id" : ObjectId("5cbc9f47bc27db275f687fae"), "uid" : "fuga", "device" : "yyy", "created_at" : "2019-02-01 00:00:01" }
{ "_id" : ObjectId("5cbc9f47bc27db275f687faf"), "uid" : "hoge", "device" : "xxx", "created_at" : "2019-02-01 00:00:02" }
...
現状では、created_atはただの文字列です。
なので、aggregateでこのフィールドをISODate型と同じノリで扱おうとするとエラーになります。
$toDate
で変換しましょう。
※ちなみに、$lteや$gteなどの比較自体は文字列のままでも大丈夫です。
文字列を$toDateでシンプルに変換
以下のクエリでは、文字列をISODate型に変換していますが、JSTではなくUTCになってしまいます。(末尾のZ
はUTCを意味しています)
# クエリ
col.aggregate(
[
{$project:
{
_id: 0,
uid: 1,
device: 1,
created_at:
{
$toDate: "$created_at"
}
}
}
]);
# 結果
{ "uid" : "hoge", "device" : "xxx", "created_at" : ISODate("2019-02-01T00:00:00Z") }
{ "uid" : "fuga", "device" : "yyy", "created_at" : ISODate("2019-02-01T00:00:01Z") }
{ "uid" : "hoge", "device" : "xxx", "created_at" : ISODate("2019-02-01T00:00:02Z") }
...
時間差を考慮して変換する
時間差を考慮した変換をするためには、
変換する前に、$concat
を使い、JSTの時間差分を文字列に追記します。
例えば 2019-02-01 00:00:00+0900
という文字列にしてから$toDateで変換する、ということです。
# クエリ
col.aggregate(
[
{$project:
{
_id: 0,
uid: 1,
device: 1,
created_at:
{
$toDate:
{
$concat: ["$created_at", "+0900"]
}
}
}
}
]);
# 結果
{ "uid" : "hoge", "device" : "xxx", "created_at" : ISODate("2019-01-31T15:00:00Z") }
{ "uid" : "fuga", "device" : "yyy", "created_at" : ISODate("2019-01-31T15:00:01Z") }
{ "uid" : "hoge", "device" : "xxx", "created_at" : ISODate("2019-01-31T15:00:02Z") }
...
これで、JSTの時間をUTCで表記した結果になりました。
$groupでまとめる
次に、日別の結果を取得するために $dateToString
でグルーピングします。
しかし、このままではUTCでまとめられてしまいます。
なので、まとめる前に時間差分を $add
で足します。
$dateToString
の引数dateに、$created_at
(前項のISODate型データ)+9時間した値を渡します。
ISODate型はUNIXTIMEのミリ秒なので、60(秒)*60(分)*9(時間)*1000(ミリ秒)
を足しています。
ついでに$sortも指定。
# クエリ
col.aggregate(
[
{$project:
{
_id: 0,
uid: 1,
device: 1,
created_at:
{
$toDate:
{
$concat: ["$created_at", "+0900"]
}
}
}
},
{$group:
{
_id:
{
uid: "$uid",
device: "$device",
created_at:
{
$dateToString:
{
"format" : "%Y-%m-%d",
"date":
{
$add: ["$created_at", 60 * 60 * 9 * 1000]
}
}
}
}
}
},
{$sort:
{
"_id.created_at": 1, "_id.uid": 1
}
}
]);
# 結果
{ "_id" : { "uid" : "fuga", "device" : "yyy", "created_at" : "2019-02-01" } }
{ "_id" : { "uid" : "hoge", "device" : "yyy", "created_at" : "2019-02-01" } }
{ "_id" : { "uid" : "hoge", "device" : "xxx", "created_at" : "2019-02-01" } }
...
これで目的の値が取れました!
まとめ
かなりまどろっこしい処理ですが、Pipelineの途中で日付を使った$matchなどの処理が入った時に対応できなくなりそうなので、面倒ですが対応してみました。
とはいえ、可能ならばデータを格納する段階で文字列ではなくISODate型にしておくのが無難だと思います。
ディスカッション
コメント一覧
まだ、コメントがありません