MongoDB
在 MongoDB 中處理日期和時間
簡介
日期和時間資料通常由資料庫系統管理,它極其重要,但處理起來往往比最初看起來更棘手。資料庫必須能夠以清晰、明確的格式儲存日期和時間資料,將這些資料轉換為使用者友好的格式以便與客戶端應用程式互動,並執行基於時間的操作,同時考慮到不同的時區和夏令時變化等複雜性。
在本指南中,我們將討論 MongoDB 提供的一些工具,以有效處理日期和時間資料。我們將探索相關資料型別,檢視運算子和方法,並介紹如何最好地使用這些工具來保持日期和時間資料的良好秩序。
如果您將 MongoDB 與 Prisma 結合使用,您可以使用 MongoDB 聯結器來連線和管理您的資料庫。Prisma 的 date 型別直接對映到 MongoDB 的 Date 型別。
MongoDB 的 Date 和 Timestamp 型別
MongoDB 中的 DATE 型別可以儲存日期和時間值作為一個組合單元。
這裡,左欄代表資料型別的 BSON (二進位制 JSON) 名稱,第二列代表與該型別關聯的 ID 號。最後一列“別名”代表 MongoDB 用來表示該型別的字串。
Type | Number | Alias |------------------ | ------ | ------------ |Date | 9 | "date" |
BSON Date 型別是一個*帶符號的* 64 位整數,表示自 Unix 紀元(1970 年 1 月 1 日)以來的毫秒數。正數表示自紀元以來的時間,而負數表示從紀元倒退的時間。
將日期和時間資料儲存為大整數的好處是:
- 允許 MongoDB 儲存毫秒精度的日期
- 提供了日期和時間顯示方式的靈活性
因為日期型別不儲存諸如時區等額外資訊,所以如果相關,這些上下文必須單獨儲存。MongoDB 內部將使用 UTC 儲存日期和時間資訊,但可以在檢索時根據需要輕鬆轉換為其他時區。
MongoDB 還提供了一個主要用於內部的 Timestamp 型別
Type | Number | Alias |------------------ | ------ | ------------ |Timestamp | 17 | "timestamp" |
由於這主要是為了幫助協調複製和分片等內部程序而實現的,您可能不應該在自己的應用程式邏輯中使用它。日期型別通常可以滿足您可能對時間提出的任何要求。
當使用 Prisma 管理 MongoDB 資料庫時,MongoDB 的 Date 型別直接對映到 Prisma 中的 date 型別。
如何建立新日期
您可以透過兩種不同的方式建立一個新的 Date 物件
new Date(): 返回一個日期和時間作為Date物件。ISODate(): 返回一個日期和時間作為Date物件。
無論是 new Date() 還是 ISODate() 方法都生成一個被 ISODate() 輔助函式包裝的 Date 物件。
此外,不帶 new 建構函式地呼叫 Date() 函式會返回一個日期和時間字串,而不是一個 Date 物件。
Date(): 返回一個日期和時間作為字串。
重要的是要記住這兩種型別之間的區別,因為它會影響可用的操作、資訊的儲存方式以及它給您的靈活性。一般來說,幾乎總是最好使用 Date 型別儲存日期資訊,然後根據需要格式化輸出。
讓我們看看這在 MongoDB shell 會話中是如何工作的。
首先,我們可以切換到一個新的臨時資料庫並建立三個文件,每個文件都有一個 date 欄位。我們為每個物件填充 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 物件。
需要注意的是,上面第一個新文件中包含了末尾的 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 還提供了一些可以操作日期的其他函式。一個有用的例子是 $dateToString() 聚合函式。您可以將 $dateToString() 與一個 Date 物件、一個格式字串說明符和一個時區指示符一起呼叫。MongoDB 將使用格式字串作為模板,根據提供的時區正確偏移輸出,以確定如何輸出給定的 Date 物件。
在這裡,我們將使用任意字串格式化 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,請檢視我們的從零開始指南或如何新增到現有專案。
