$dateSubtract (aggregation)
On this page
Definition
$dateSubtractNew in version 5.0.
Decrements a
Date()object by a specified number of time units.The
$dateSubtractexpression has the following syntax:{ $dateSubtract: { startDate: <Expression>, unit: <Expression>, amount: <Expression>, timezone: <tzExpression> } }Returns a
Date(). ThestartDatecan be any expression that resolves to type Date, Timestamp or ObjectId. No matter which data type is used as input, the value returned will be aDate()object.Field Required/Optional Description startDateRequired The beginning date, in UTC, for the subtraction operation. The startDatecan be any expression that resolves to a Date, a Timestamp, or an ObjectID.unitRequired The unitused to measure theamountof time subtracted from thestartDate. Theunitis an expression that resolves to one of the following strings:yearquarterweekmonthdayhourminutesecondmillisecond
amountRequired The number of unitssubtracted from thestartDate. Theamountis an expression that resolves to an integer or long. Theamountcan also resolve to an integral decimal and or a double if that value can be converted to a long without loss of precision.timezoneOptional The timezone to carry out the operation. <tzExpression>must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If notimezoneis provided, the result is displayed inUTC.Format Examples Olson Timezone Identifier "America/New_York" "Europe/London" "GMT"
UTC Offset +/-[hh]:[mm], e.g. "+04:45" +/-[hh][mm], e.g. "-0530" +/-[hh], e.g. "+03"
For more information on expressions and types see Expressions and BSON Types.
Behavior
Time Measurement
MongoDB follows prevaling database usage and works with time in UTC. The dateSubtract expression always takes a startDate in UTC and returns a result in UTC. If the timezone is specified, the calculation will be done using the specified timezone. The timezone is especially important when a calculation involves Daylight Savings Time (DST).
If the unit is a month, or larger the operation adjusts to account for the last day of the month. Subtracting one month on the last day of March, for example, demonstrates the "last-day-of-the-month" adjustment.
{
$dateSubtract:
{
startDate: ISODate("2021-03-31T12:10:05Z"),
unit: "month",
amount: 1
}
}
Notice that the date returned, ISODate("2021-02-28T12:10:05Z"), is the 28th and not the 31st since February has fewer days than March.
Time Zone
When using an Olson Timezone Identifier in the <timezone> field, MongoDB applies the DST offset if applicable for the specified timezone.
For example, consider a sales collection with the following document:
{
"_id" : 1,
"item" : "abc",
"price" : 20,
"quantity" : 5,
"date" : ISODate("2017-05-20T10:24:51.303Z")
}
The following aggregation illustrates how MongoDB handles the DST offset for the Olson Timezone Identifier. The example uses the $hour and $minute operators to return the corresponding portions of the date field:
db.sales.aggregate([ { $project: { "nycHour": { $hour: { date: "$date", timezone: "-05:00" } }, "nycMinute": { $minute: { date: "$date", timezone: "-05:00" } }, "gmtHour": { $hour: { date: "$date", timezone: "GMT" } }, "gmtMinute": { $minute: { date: "$date", timezone: "GMT" } }, "nycOlsonHour": { $hour: { date: "$date", timezone: "America/New_York" } }, "nycOlsonMinute": { $minute: { date: "$date", timezone: "America/New_York" } } } }])
The operation returns the following result:
{
"_id": 1,
"nycHour" : 5,
"nycMinute" : 24,
"gmtHour" : 10,
"gmtMinute" : 24,
"nycOlsonHour" : 6,
"nycOlsonMinute" : 24
}
Examples
Subtract A Fixed Amount
Consider a collection of system connection times like these:
db.connectionTime.insertMany( [ { custId: 457, login: ISODate("2020-12-25T19:04:00"), logout: ISODate("2020-12-28T09:04:00") }, { custId: 457, login: ISODate("2021-01-27T05:12:00"), logout: ISODate("2021-01-28T13:05:00") }, { custId: 458, login: ISODate("2021-01-22T06:27:00"), logout: ISODate("2021-01-31T11:00:00") }, { custId: 459, login: ISODate("2021-02-14T20:14:00"), logout: ISODate("2021-02-17T16:05:00") }, { custId: 460, login: ISODate("2021-02-26T02:44:00"), logout: ISODate("2021-02-18T14:13:00") } ] )
Due to a service issue you need to subtract 3 hours from each of the January 2021 logout times. You can use $dateSubtract in an aggregation pipeline to decrement the logoutTime.
db.connectionTime.aggregate( [ { $match: { $expr: { $eq: [ { $year: "$logout" }, 2021 ] }, $expr: { $eq: [ { $month: "$logout" }, 1 ] } } }, { $project: { logoutTime: { $dateSubtract: { startDate: "$logout", unit: "hour", amount: 3 } } } }, { $merge: "connectionTime" } ] )
Two similar comparisons are made in the $match stage. First the $year and $month operators extract the year and month, respectively, from the logoutTime Date object. Then the month and year are checked to see if they match the selection targets. Since "January" is encoded as "1", $expr is true when the year and month are equal ($eq) to "2021" and "1".
The $project stage uses $dateSubtract to subtract 3 hours from the logoutTime of each selected dcoument.
Finaly, the $merge stage updates the collection, writing the new logoutTime for the modified documents.
Note
Unlike $out, the $merge stage only updates the matched documents and preserves the rest of the collection. For more details see: $out compared with $merge.
The resulting documents look like this:
{
"_id" : ObjectId("603dd94b044b995ad331c0b5"),
"custId" : 457,
"login" : ISODate("2020-12-25T19:04:00Z"),
"logout" : ISODate("2020-12-28T09:04:00Z")
}
{
"_id" : ObjectId("603dd94b044b995ad331c0b6"),
"custId" : 457,
"login" : ISODate("2021-01-27T05:12:00Z"),
"logout" : ISODate("2021-01-28T13:05:00Z"),
"logoutTime" : ISODate("2021-01-28T10:05:00Z")
}
{
"_id" : ObjectId("603dd94b044b995ad331c0b7"),
"custId" : 458,
"login" : ISODate("2021-01-22T06:27:00Z"),
"logout" : ISODate("2021-01-31T11:00:00Z"),
"logoutTime" : ISODate("2021-01-31T08:00:00Z")
}
{
"_id" : ObjectId("603dd94b044b995ad331c0b8"),
"custId" : 459,
"login" : ISODate("2021-02-14T20:14:00Z"),
"logout" : ISODate("2021-02-17T16:05:00Z")
}
{
"_id" : ObjectId("603dd94b044b995ad331c0b9"),
"custId" : 460,
"login" : ISODate("2021-02-26T02:44:00Z"),
"logout" : ISODate("2021-02-18T14:13:00Z")
}Filter by Relative Dates
You want to send a survey to clients who have used your service in the past week. The $dateSubtract expression can create a range filter relative to the time the query is executed.
db.connectionTime.aggregate( [ { $match: { $expr: { $gt: [ "$logoutTime", { $dateSubtract: { startDate: "$$NOW", unit: "week", amount: 1 } } ] } } }, { $project: { _id: 0, custId: 1, loggedOut: { $dateToString: { format: "%Y-%m-%d", date: "$logoutTime" } } } } ] )
The built in aggregation variable $$NOW returns the current datetime in ISODate format. The $match stage uses the value in $$NOW to get today's date. Then the comparison expression ($expr) filters the collection using greater than ($gt) and $dateSubtract to match documents that have a logoutTime in the past week.
The $project stage uses the $dateToString expression to convert the dates to a more readable format. Without the conversion MongoDB would return the date in ISODate format. The output shows two customers have logged out in the last week.
{ "custId" : 459, "loggedOut" : "2021-02-17" }
{ "custId" : 460, "loggedOut" : "2021-02-18" }Adjust for Daylight Savings Time
All dates are stored internally in UTC time. When a timezone is specified, $dateSubtract uses local time to carry out the calculations. The results are displayed in UTC.
You have customers in several timezones and you want to see what effect daylight savings time might have on your billing periods if you bill by day or by hour.
Create this collection of connection times:
db.billing.insertMany( [ { location: "America/New_York", login: ISODate("2021-03-14T10:00:00-0500"), logout: ISODate("2021-03-14T18:00:00-0500") }, { location: "America/Mexico_City", login: ISODate("2021-03-14T10:00:00-00:00"), logout: ISODate("2021-03-15T08:00:00-0500") } ] )
First subtract 1 day, then subtract 24 hours from the login dates in each document.
db.billing.aggregate( [ { $project: { _id: 0, location: 1, start: { $dateToString: { format: "%Y-%m-%d %H:%M", date: "$login" } }, days: { $dateToString: { format: "%Y-%m-%d %H:%M", date: { $dateSubtract: { startDate: "$login", unit: "day", amount: 1, timezone: "$location" } } } }, hours: { $dateToString: { format: "%Y-%m-%d %H:%M", date: { $dateSubtract: { startDate: "$login", unit: "hour", amount: 24, timezone: "$location" } } } }, startTZInfo: { $dateToString: { format: "%Y-%m-%d %H:%M", date: "$login", timezone: "$location" } }, daysTZInfo: { $dateToString: { format: "%Y-%m-%d %H:%M", date: { $dateSubtract: { startDate: "$login", unit: "day", amount: 1, timezone: "$location" } }, timezone: "$location" } }, hoursTZInfo: { $dateToString: { format: "%Y-%m-%d %H:%M", date: { $dateSubtract: { startDate: "$login", unit: "hour", amount: 24, timezone: "$location" } }, timezone: "$location" } }, } } ] ).pretty()
The $dateToString expression reformats the output for readability. Results are summarized here:
| Field | New York | Mexico City |
|---|---|---|
| Start | 2021-03-14 15:00 | 2021-03-14 15:00 |
| Start, TZ Info | 2021-03-14 11:00 | 2021-03-14 04:00 |
| 1 Day | 2021-03-13 16:00 | 2021-03-13 15:00 |
| 1 Day, TZInfo | 2021-03-13 11:00 | 2021-03-13 09:00 |
| 24 Hours | 2021-03-13 15:00 | 2021-03-13 15:00 |
| 24 Hours, TZInfo | 2021-03-13 10:00 | 2021-03-13 09:00 |
The chart highlights several points:
-
Unformatted dates are returned in UTC. The
$loginfor New York is UTC -5, however thestart,days, andhoursrows display the time in UTC. -
March 14th is the start of DST in New York, but not in Mexico. The calculated time is adjusted when a location switches to DST and crosses from one
dayto the next. -
DST modifies the length of the
day, not thehour. There is no DST change forhours. There is an only an adjustment for DST when the measurementunitisdayor larger and the computation crosses a clock change in the specifiedtimezone.