シェア

はじめに

日付と時刻データは、データベースシステムによって一般的に管理され、非常に重要ですが、最初は思われるよりも正しく処理するのが難しい場合があります。データベースは、日付と時刻データを明確で曖昧さのない形式で保存し、そのデータをクライアントアプリケーションと対話するためのユーザーフレンドリーな形式に変換し、さまざまなタイムゾーンや夏時間変更のような複雑さを考慮して時間ベースの操作を実行できる必要があります。

このガイドでは、MongoDB が日付と時刻データを効果的に操作するために提供するツールの一部について説明します。関連するデータ型を探求し、演算子とメソッドを見て、これらのツールを最大限に活用して日付と時刻データを良好な状態に保つ方法について説明します。

MongoDB の Date 型と Timestamp

MongoDB の DATEは、日付と時刻の値を組み合わせてユニットとして保存できます。

ここで、左側の列はデータ型の BSON (バイナリ JSON) 名を表し、2 番目の列はその型に関連付けられた ID 番号を表します。最後の「エイリアス」列は、MongoDB が型を表すために使用する文字列を表します。

Type | Number | Alias |
------------------ | ------ | ------------ |
Date | 9 | "date" |

BSON Date 型は、Unix エポック (1970 年 1 月 1 日) からのミリ秒数を表す符号付き 64 ビット整数です。正の数はエポックからの経過時間を表し、負の数はエポックから遡る時間を表します。

日付と時刻データを大きな整数として保存することには利点があります。それは

  • MongoDB が日付をミリ秒単位の精度で保存できるようにする
  • 日付と時刻の表示方法に柔軟性を提供する

日付型はタイムゾーンのような追加情報を保存しないため、関連する場合はそのコンテキストを別途保存する必要があります。MongoDB は日付と時刻情報を内部的には UTC を使用して保存しますが、必要に応じて取得時に他のタイムゾーンに簡単に変換できます。

MongoDB は、主に内部的に使用される Timestampも提供しています

Type | Number | Alias |
------------------ | ------ | ------------ |
Timestamp | 17 | "timestamp" |

これは主にレプリケーションやシャーディングのような内部プロセスを調整するために実装されているため、独自のアプリケーションロジックでは使用しない方がよいでしょう。日付型は、考えられる時間に関する要件を通常は満たすことができます。

新しい日付の作成方法

Date オブジェクトは、次の 2 つの異なる方法で作成できます

  • new Date(): 日付と時刻を Date オブジェクトとして返します。
  • ISODate(): 日付と時刻を Date オブジェクトとして返します。

new Date() メソッドと ISODate() メソッドはどちらも、ISODate() ヘルパー関数でラップされた Date オブジェクトを生成します。

さらに、Date() 関数を new コンストラクターなしで呼び出すと、日付と時刻は Date オブジェクトではなく文字列として返されます

  • Date(): 日付と時刻を文字列として返します。

利用可能な操作、情報の保存方法、および得られる柔軟性の量に影響するため、これら 2 つの型間の区別を念頭に置いておくことが重要です。一般に、日付情報は常に Date 型を使用して保存し、必要に応じて出力用にフォーマットするのが最善です。

MongoDB シェルセッションでこれがどのように機能するかを見てみましょう。

まず、新しい一時データベースに切り替えて、それぞれに date フィールドを持つ 3 つのドキュメントを作成できます。オブジェクトごとに date フィールドを設定するために異なる方法を使用します

use temp_db
db.dates.insertMany([
{
name: "Created with `Date()`",
date: Date(),
},
{
name: "Created with `new Date()`",
date: new Date(),
},
{
name: "Created with `ISODate()`",
date: ISODate(),
},
])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("62726af5a3dc7398b97e6e93"),
ObjectId("62726af5a3dc7398b97e6e94"),
ObjectId("62726af5a3dc7398b97e6e95")
]
}

デフォルトでは、これらのメカニズムはそれぞれ現在の日付と時刻を保存します。 ISO 8601 形式の日付文字列を引数として追加することで、異なる日付と時刻を保存できます

db.dates.insertMany([
{
name: 'Future date',
date: ISODate('2040-10-28T23:58:18Z'),
},
{
name: 'Past date',
date: new Date('1852-01-15T11:25'),
},
])

これらは、適切な日付と時刻に Date オブジェクトを作成します。

注意すべき点の 1 つは、上記の最初の新しいドキュメントに末尾の Z が含まれていることです。これは、日付と時刻が UTC として提供されていることを示します。Z なしで日付を指定すると、MongoDB は入力を現在のローカルタイムに関連して解釈します (ただし、常に UTC 日付として内部的に変換および保存します)。

日付オブジェクトの型の検証

次に、結果のドキュメントを表示して、MongoDB が日付データをどのように保存したかを確認できます

db.dates.find().pretty()
{
"_id" : ObjectId("62726af5a3dc7398b97e6e93"),
"name" : "Created with `Date()`",
"date" : "Wed May 04 2022 12:00:53 GMT+0000 (UTC)"
}
{
"_id" : ObjectId("62726af5a3dc7398b97e6e94"),
"name" : "Created with `new Date()`",
"date" : ISODate("2022-05-04T12:00:53.307Z")
}
{
"_id" : ObjectId("62726af5a3dc7398b97e6e95"),
"name" : "Created with `ISODate()`",
"date" : ISODate("2022-05-04T12:00:53.307Z")
}
{
"_id" : ObjectId("62728b57a3dc7398b97e6e96"),
"name" : "Future date",
"date" : ISODate("2040-10-28T23:58:18Z")
}
{
"_id" : ObjectId("62728c5ca3dc7398b97e6e97"),
"name" : "Past date",
"date" : ISODate("1852-01-15T11:25:00Z")
}

予想どおり、ISODate() および new Date() で設定された date フィールドには、(ISODate ヘルパーでラップされた) Date オブジェクトが含まれています。対照的に、生の Date() 関数呼び出しによって設定されたフィールドは文字列として保存されます。

コレクション全体で map 関数を呼び出すことで、date フィールドのどれに実際の Date オブジェクトが含まれているかを確認できます。map は、各 date フィールドをチェックして、そこに保存されているオブジェクトが Date 型のインスタンスであるかどうかを確認し、結果を is_a_Date_object という新しいフィールドに表示します。さらに、valueOf() メソッドを使用して、各 date フィールドが MongoDB によって実際にどのように保存されているかを示します

db.dates.find().map(function (date_doc) {
date_doc['is_a_Date_object'] = date_doc.date instanceof Date
date_doc['date_storage_value'] = date_doc.date.valueOf()
return date_doc
})
;[
{
_id: ObjectId('62726af5a3dc7398b97e6e93'),
name: 'Created with `Date()`',
date: 'Wed May 04 2022 12:00:53 GMT+0000 (UTC)',
is_a_Date_object: false,
date_storage_value: 'Wed May 04 2022 12:00:53 GMT+0000 (UTC)',
},
{
_id: ObjectId('62726af5a3dc7398b97e6e94'),
name: 'Created with `new Date()`',
date: ISODate('2022-05-04T12:00:53.307Z'),
is_a_Date_object: true,
date_storage_value: 1651665653307,
},
{
_id: ObjectId('62726af5a3dc7398b97e6e95'),
name: 'Created with `ISODate()`',
date: ISODate('2022-05-04T12:00:53.307Z'),
is_a_Date_object: true,
date_storage_value: 1651665653307,
},
{
_id: ObjectId('62728b57a3dc7398b97e6e96'),
name: 'Future date',
date: ISODate('2040-10-28T23:58:18Z'),
is_a_Date_object: true,
date_storage_value: 2235081498000,
},
{
_id: ObjectId('62728c5ca3dc7398b97e6e97'),
name: 'Past date',
date: ISODate('1852-01-15T11:25:00Z'),
is_a_Date_object: true,
date_storage_value: -3722502900000,
},
]

これにより、ISODATE(...) として表示されるフィールドが Date 型のインスタンスであり、生の Date() 関数で作成された date はそうではないことが確認されます。

さらに、上記の出力は、Date 型で保存されたオブジェクトが符号付き整数として記録されていることを示しています。予想どおり、1852 年の日付に関連付けられた日付オブジェクトは、1970 年 1 月から遡ってカウントしているため、負になります。

日付オブジェクトのクエリ

このように日付の表現が混在するコレクションがある場合は、$type 演算子を使用して、型が一致するフィールドをクエリできます。

たとえば、dateDate オブジェクトであるすべてのドキュメントをクエリするには、次のように入力します

db.dates
.find({
date: { $type: 'date' },
})
.pretty()
{
"_id" : ObjectId("62726af5a3dc7398b97e6e94"),
"name" : "Created with `new Date()`",
"date" : ISODate("2022-05-04T12:00:53.307Z")
}
{
"_id" : ObjectId("62726af5a3dc7398b97e6e95"),
"name" : "Created with `ISODate()`",
"date" : ISODate("2022-05-04T12:00:53.307Z")
}
{
"_id" : ObjectId("62728b57a3dc7398b97e6e96"),
"name" : "Future date",
"date" : ISODate("2040-10-28T23:58:18Z")
}
{
"_id" : ObjectId("62728c5ca3dc7398b97e6e97"),
"name" : "Past date",
"date" : ISODate("1852-01-15T11:25:00Z")
}

date フィールドが文字列として保存されているインスタンスを見つけるには、次のように入力します

db.dates
.find({
date: { $type: 'string' },
})
.pretty()
{
"_id" : ObjectId("62726af5a3dc7398b97e6e93"),
"name" : "Created with `Date()`",
"date" : "Wed May 04 2022 12:00:53 GMT+0000 (UTC)"
}

Date 型を使用すると、時間単位間の関係を理解するクエリを実行できます。

たとえば、他の型と同様に、Date オブジェクトを順序的に比較できます。将来の日付を確認するには、次のように入力します

db.dates
.find({
date: {
$gt: new Date(),
},
})
.pretty()
{
"_id" : ObjectId("62728b57a3dc7398b97e6e96"),
"name" : "Future date",
"date" : ISODate("2040-10-28T23:58:18Z")
}

Date 型メソッドの使用方法

さまざまな組み込みメソッドと演算子を使用して、Date オブジェクトを操作できます。たとえば、日付からさまざまな日付と時刻のコンポーネントを抽出し、さまざまな形式で出力できます。

デモンストレーションは、おそらくこの機能を最も早く紹介する方法です。

まず、日付オブジェクトを含むドキュメントから日付を選択しましょう

date_obj = db.dates.findOne({ name: 'Future date' }).date

次に、date フィールドを選択し、オブジェクトでさまざまなメソッドを呼び出すことで、そこからさまざまなコンポーネントを抽出できます

date_obj.getUTCFullYear()
date_obj.getUTCMonth()
date_obj.getUTCDate()
date_obj.getUTCHours()
date_obj.getUTCMinutes()
date_obj.getUTCSeconds()
2040 // year
9 // month
28 // date
23 // hour
58 // minutes
18 // seconds

さまざまな時間および日付コンポーネントを提供することで時間を設定するために使用できるコンパニオンメソッドもあります。たとえば、.setUTCFullYear() メソッドを呼び出すことで、年を変更できます

date_obj.toString()
date_obj.setUTCFullYear(2028)
date_obj.toString()
date_obj.setUTCFullYear(2040)
Sun Oct 28 2040 23:58:18 GMT+0000 (UTC)
1856390298000 // integer stored for the new date value
Sat Oct 28 2028 23:58:18 GMT+0000 (UTC)
2235081498000 // integer stored for the restored date value

日付をさまざまな表示形式にキャストすることもできます

date_obj.toDateString()
date_obj.toUTCString()
date_obj.toISOString()
date_obj.toLocaleDateString()
date_obj.toLocaleTimeString()
date_obj.toString()
date_obj.toTimeString()
Sun Oct 28 2040 // .toDateString()
Sun, 28 Oct 2040 23:58:18 GMT // .toUTCString()
2040-10-28T23:58:18.000Z // .toISOString()
10/28/2040 // .toLocaleDateString()
23:58:18 // .toLocaleTimeString()
Sun Oct 28 2040 23:58:18 GMT+0000 (UTC) // .toString()
23:58:18 GMT+0000 (UTC) // .toTimeString()

これらはすべて、主に JavaScript の Date 型に関連付けられたメソッドです。

MongoDB Date 集計関数の使用方法

MongoDB は、日付も操作できる他の関数も提供しています。この便利な例の 1 つは、$dateToString() 集計関数です。$dateToString()Date オブジェクト、フォーマット文字列指定子、およびタイムゾーンインジケーターを渡して呼び出すことができます。MongoDB は、フォーマット文字列をテンプレートとして使用して、指定された Date オブジェクトを出力する方法を把握し、タイムゾーンを使用して出力を UTC から正しくオフセットします。

ここでは、dates コレクションの日付を任意の文字列を使用してフォーマットします。また、日付をニューヨークタイムゾーンにキャストします。

まず、date フィールドを文字列として保存している可能性のある不要なドキュメントを削除する必要があります

db.dates.deleteMany({ date: { $type: 'string' } })

これで、$dateToString 関数を使用して集計を実行できます

db.dates
.aggregate([
{
$project: {
_id: 0,
date: '$date',
my_date: {
$dateToString: {
date: '$date',
format:
'Day %d of Month %m (Day %j of year %Y) at %H hours, %M minutes, and %S seconds (timezone offset: %z)',
timezone: 'America/New_York',
},
},
},
},
])
.pretty()
{
"date" : ISODate("2022-05-04T12:00:53.307Z"),
"my_date" : "Day 04 of Month 05 (Day 124 of year 2022) at 08 hours, 00 minutes, and 53 seconds (timezone offset: -0400)"
}
{
"date" : ISODate("2022-05-04T12:00:53.307Z"),
"my_date" : "Day 04 of Month 05 (Day 124 of year 2022) at 08 hours, 00 minutes, and 53 seconds (timezone offset: -0400)"
}
{
"date" : ISODate("2040-10-28T23:58:18Z"),
"my_date" : "Day 28 of Month 10 (Day 302 of year 2040) at 19 hours, 58 minutes, and 18 seconds (timezone offset: -0400)"
}
{
"date" : ISODate("1852-01-15T11:25:00Z"),
"my_date" : "Day 15 of Month 01 (Day 015 of year 1852) at 06 hours, 28 minutes, and 58 seconds (timezone offset: -0456)"
}

$dateToParts() 関数も同様に役立ちます。これは、Date フィールドを構成要素に分解するために使用できます。

たとえば、次のように入力できます

db.dates.aggregate([
{
$project: {
_id: 0,
date: {
$dateToParts: { date: '$date' },
},
},
},
])
{ "date" : { "year" : 2022, "month" : 5, "day" : 4, "hour" : 12, "minute" : 0, "second" : 53, "millisecond" : 307 } }
{ "date" : { "year" : 2022, "month" : 5, "day" : 4, "hour" : 12, "minute" : 0, "second" : 53, "millisecond" : 307 } }
{ "date" : { "year" : 2040, "month" : 10, "day" : 28, "hour" : 23, "minute" : 58, "second" : 18, "millisecond" : 0 } }
{ "date" : { "year" : 1852, "month" : 1, "day" : 15, "hour" : 11, "minute" : 25, "second" : 0, "millisecond" : 0 } }

集計関数に関する MongoDB ドキュメントには、表示または比較のために Date オブジェクトを操作するために使用できる追加の関数に関する情報があります。

結論

このガイドでは、MongoDB 内で日付と時刻データを操作できるさまざまな方法のいくつかについて説明しました。ほとんどの時間データは、MongoDB の Date データ型に保存するのが適切でしょう。これは、データの操作や表示時に優れた柔軟性を提供するためです。

日付と時刻データが内部的にどのように保存されるか、出力時に望ましい形式に強制変換する方法、およびデータを比較、変更、分解して役立つチャンクにする方法を理解することで、さまざまな問題を解決できます。日付情報は扱うのが難しい場合がありますが、利用可能なメソッドと演算子を利用することで、面倒な作業の一部を軽減できます。

著者について
Justin Ellingwood

Justin Ellingwood

Justin は、2013 年からデータベース、Linux、インフラストラクチャ、および開発者ツールについて執筆しています。彼は現在、妻と 2 羽のウサギと一緒にベルリンに住んでいます。彼は通常、三人称で書く必要はありません。これは関係者全員にとって安心です。