はじめに
日付と時刻のデータはデータベースシステムで一般的に管理されており、非常に重要ですが、最初は思えるよりも正しく扱うのが難しい場合があります。データベースは、日付と時刻のデータを明確で曖昧でない形式で保存し、そのデータをクライアントアプリケーションと対話するためにユーザーフレンドリーな形式に変換し、異なるタイムゾーンや夏時間の変更などの複雑さを考慮して時間ベースの操作を実行できる必要があります。
このガイドでは、MongoDBが日付と時刻のデータを効果的に操作するために提供するいくつかのツールについて説明します。関連するデータ型を探り、演算子とメソッドを確認し、これらのツールを最大限に活用して日付と時刻のデータを適切に管理する方法を詳しく説明します。
PrismaでMongoDBを使用している場合、MongoDBコネクタを使用してデータベースに接続し、管理できます。Prismaのdate
型は、MongoDBのDate
型に直接マッピングされます。
MongoDBのDate
型とTimestamp
型
MongoDBのDATE
型は、日付と時刻の値を結合された単位として格納できます。
ここで、左の列はデータ型のBSON(バイナリJSON)名を表し、2番目の列はその型に関連付けられたID番号を表します。最後の「Alias」列は、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" |
これは主にレプリケーションやシャーディングなどの内部プロセスを調整するために実装されているため、独自のアプリケーションロジックでこれを使用すべきではありません。日付型は、通常、時刻に関するあらゆる要件を満たすことができます。
PrismaでMongoDBデータベースを管理する場合、MongoDBのDate
型はPrisma内のdate
型に直接マッピングされます。
新しい日付を作成する方法
新しいDate
オブジェクトを2つの異なる方法で作成できます。
new Date()
: 日付と時刻をDate
オブジェクトとして返します。ISODate()
: 日付と時刻をDate
オブジェクトとして返します。
new Date()
メソッドとISODate()
メソッドは両方とも、ISODate()
ヘルパー関数でラップされたDate
オブジェクトを生成します。
さらに、new
コンストラクタなしでDate()
関数を呼び出すと、Date
オブジェクトではなく文字列として日付と時刻が返されます。
Date()
: 日付と時刻を文字列として返します。
これら2つの型の違いを念頭に置くことが重要です。なぜなら、利用可能な操作、情報の保存方法、そしてどれだけの柔軟性が得られるかに影響するからです。一般的に、日付情報はDate
型を使用して保存し、必要に応じて出力用にフォーマットするのがほとんど常に最善です。
MongoDBシェルセッションでこれがどのように機能するかを見てみましょう。
まず、新しい一時データベースに切り替え、それぞれdate
フィールドを持つ3つのドキュメントを作成します。各オブジェクトのdate
フィールドに値を設定するために、異なるメソッドを使用します。
use temp_dbdb.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
フィールドには、Date
オブジェクト(ISODate
ヘルパーにラップされています)が含まれています。対照的に、生の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 Datedate_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
演算子を使用して、一致する型を持つフィールドをクエリできます。
例えば、date
がDate
オブジェクトであるすべてのドキュメントをクエリするには、次のように入力します。
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 // year9 // month28 // date23 // hour58 // minutes18 // 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 valueSat 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()
集計関数です。Date
オブジェクト、書式指定文字列、タイムゾーンインジケーターを$dateToString()
に渡すことができます。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
データ型で保存するのがおそらく最適です。これにより、データの操作や表示においてかなりの柔軟性が得られます。
日付と時刻のデータが内部でどのように保存されるか、出力時に望ましい形式に強制変換する方法、そしてデータを比較、変更、有用なチャンクに分解する方法に慣れることで、さまざまな問題を解決できます。日付情報の扱いは難しい場合がありますが、利用可能なメソッドや演算子を活用することで、一部の重労働を軽減できます。
MongoDBを使用している場合は、PrismaのMongoDBコネクタをぜひチェックしてください!Prisma Clientを使用すると、本番環境のMongoDBデータベースを自信を持って管理できます。
MongoDBとPrismaの作業を始めるには、弊社のゼロから始めるガイド、または既存プロジェクトへの追加方法をご覧ください。