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,请查看我们的从零开始指南或如何添加到现有项目。