mongodb: Zulu dates - mongodb

Question is straightforward.
It's not clear to me if mongodb is ALWAYS storing dates in UTC, so in zulu(Z) zone, regardless if date string contains a zone?
I've tested with:
> db.products.updateOne( { _id: 1 }, { $set: { item: "apple" }, $setOnInsert: { dateAdded: new Date() } }, { upsert: true });
> db.products.updateOne( { _id: 2 }, { $set: { item: "jordi" }, $setOnInsert: { dateAdded: new Date("1982-05-19T14:00:00.000+05:00") } }, { upsert: true });
I detected that second date is stored a Zulu(Z):
db.products.find();
[
{
_id: 1,
dateAdded: ISODate("2022-02-02T15:40:02.457Z"),
item: 'apple'
},
{
_id: 2,
dateAdded: ISODate("1982-05-19T09:00:00.000Z"),
item: 'jordi'
}
]
Related question is, how do I need to make date range queries?
I mean, ranged dates queries have to have dates using Zulu zone?
I've tested a bit. It seems I'm able to set range queries using whichever timezone and they are transalted to Zulu:
db.products.find({ dateAdded: { $gt: ISODate("1982-05-19T13:00:00.000+05:00"), $lt: ISODate("1982-05-20T00:00:00.000Z") } });
[
{
_id: 2,
dateAdded: ISODate("1982-05-19T09:00:00.000Z"),
item: 'jordi'
}
]

The internal representation of a date doesn't refer to UTC or any other time zone, but represents a specific instant in the history of the world. Specifically, the MongoDB manual says:
BSON Date is a 64-bit integer that represents the number of milliseconds since the Unix epoch (Jan 1, 1970). This results in a representable date range of about 290 million years into the past and future.
So, a date and time of "1969-12-31T16:00:00 America/Los_Angeles" or 1970-01-01T03:00:00 Africa/Nairobi" would both be stored as the number zero, because they correspond to the arbitrary "epoch", chosen to fall at "1970-01-01T00:00 UTC".
For input, dates constructed from any of those date strings would result in the same internal value, so compare as equal.
For output, you can choose the timezone to display (e.g. which of the three strings above you want to show for an internal value of zero) by using the timezone argument to $dateToString. If you don't specify it, UTC will be used as a default, but that doesn't reflect the internal storage, just an arbitrary default for that parameter.

Related

MongoDB query based on date plus time

i've to run a query like this (sql) in MongoDb 4:
SELECT * FROM log WHERE DATE_ADD(created_at, INTERVAL 2 HOUR) < NOW()
Basically, I want to find all the documents, in the PENDING state, whose creation date PLUS TWO HOURS is less than now .. Let me explain: I want to find all the documents in the PENDING state that have been in PENDING for more than two hours.
I feel stupid, but I am failing to do this with MongoDb.
I also created a playground:
https://mongoplayground.net/p/4bifqiX2KMJ
Can you help me?
You can add hours in ISO date using $add, convert string date to ISO date using dateFromString,
let date = new Date();
db.collection.find({
status: "pending",
$expr: {
$lt: [
{
$add: [
// convert string date to ISOdate, if its already then use only "$inserted_at"
{ $dateFromString: { dateString: "$inserted_at" } },
// add milliseconds
7200000 // (60*60*2000)
]
},
date
]
}
})
Playground
Or subtract from current date and then compare the condition,
let date = new Date();
date = new Date(date.getHours()-2); //subtract 2 hours
db.collection.find({
status: "pending",
$expr: {
$lt: [
{ $dateFromString: { dateString: "$inserted_at" } },
date
]
}
})
Playground

Mongodb group epoch to YYYY-MM-DD

So currently store start and end time that a user listens to our stations in epoch format, however now I need to do a query that groups listeners if they where listening with an hour block.
For example:
last 7 days Query would need to go back 7 days normally I would just do something like this
items.find({
history.$.starttime: {
$gte: ISODate("2020-07-10T00:00:00.000Z"),
$lt: ISODate("2020-009-17T24:59:59.000Z")
}})
However we don't store the date in this format we store it looking like this.
So how do a search between dates if the dates are in epoch format?
I have tried
{'history': {'$elemMatch':{'starttime': {'$gte':1592966066060}}}
UPDATE:
this works
{"history.starttime":{$gte:1593236606706}}
You can convert timestamp to ISO date using $toDate, like this,
db.collection.aggregate([
{
$addFields: {
startTimeDate: {
$toDate: "$startTime"
},
endTimeDate: {
$toDate: "$endTime"
}
}
},
{
$match: {
startTimeDate: {
$gte: ISODate("2020-01-20T21:20:00Z")
}
}
}
])
You can removed that added field using:
{
$project: {
startTimeDate: 0,
endTimeDate: 0
}
}
Working Playground: https://mongoplayground.net/p/XiEYVxvmfpv
Convert the times to Unix timestamps in your application, specify the timestamps in the query.

MongoDB: Convert Date String (mm/dd/yyyy) to Unix timestamp

just practicing my MongoDB queries and I've hit a wall with a field data type.
I'm currently using Robomongo as GUI for accessing the production database.
My document structure looks like:
Is there a MongoDB operator or way/method to convert the date field value, currently in mm/dd/yyyy format, to a Unix timestamp so we can perform filter operations?
You can iterate all your items and update one by one with the conversion to Date. Here is an example to convert your date from mm/dd/yyyy to ISODate :
db.test.find().forEach( function(res){
if (typeof(res.date)=="string"){
var arr = res.date.split("/");
res.date = new Date(arr[2], arr[0] - 1, arr[1]);
db.test.save(res)
}
}
)
For Unix timestamp (millis from epoch), you can call getTime() from Date :
db.test.find().forEach( function(res){
if (typeof(res.date)=="string"){
var arr = res.date.split("/");
res.date = new Date(arr[2], arr[0] - 1, arr[1]).getTime();
db.test.save(res)
}
}
)
Note that these dates will be converted into UTC format, so you may want to change temporarily your timezone before doing your conversion
You can also use bulk update if you want to optimize update performance
You can also just convert your date to yyyy-mm-dd which will preserve sorting (check this post). The following will decompose your date field into day,month and year, set date field with the new format and write output in a new collection named test2 :
db.test.aggregate([{
$project: {
startTime: 1,
endTime: 1,
date: {
$let: {
vars: {
year: { $substr: ["$date", 6, 10] },
month: { $substr: ["$date", 0, 2] },
dayOfMonth: { $substr: ["$date", 3, 2] }
},
in : { $concat: ["$$year", "-", "$$month", "-", "$$dayOfMonth"] }
}
}
}
},{
$out :"test2"
}])

Timezone in mongo query

I have data inserted in UTC time format in mongodb. I want timings to be converted based on the timezone. Is there any possibility to do so in mongo query?
In mongo version 3.6 timezone has been added, mongo doc
expression to extract date part with timezone is
{ date: <dateExpression>, timezone: <tzExpression> }
we can either specify the timezone or offset while getting the date parts.
see my answer posted here
to get date from date with timezone America/Chicago
{ $month: {
date: new Date(),
timezone: "America/Chicago"
} }
or with offset
{ $month: {
date: ISODate(),
timezone: "-0500"
} }
Let consider your document contains ISODate as below :
db.collection.insert({"date":new Date()})
Above query insert date in ISODate format now you want to convert this ISODate into give timeZone.
Suppose you want to convert above date to Eastern Daylight Saving Time ( EDT ) epoch time zone conertor then offset converted as 14400 * 1000. First convert ISODate to timeStamp and then use substract EDT OffsetintimeStampand then converttimeStamptoISODate` again.
Check below aggregation query :
db.collection.aggregate({
"$project": {
"timestamp": { //convert ISODate tom timestamp
"$subtract": [{
"$divide": [{
"$subtract": ["$date", new Date("1970-01-01")]
}, 1000]
}, {
"$mod": [{
"$divide": [{
"$subtract": ["$date", new Date("1970-01-01")]
}, 1000]
}, 1]
}]
}
}
}, {
"$project": {
"timeZoneTimeStamp": {
"$subtract": [{ //substract timestamp to given offset if offset will in postive then replace subtract to add
"$multiply": ["$timestamp", 1000]
}, 14400000]
}
}
}, {
"$project": {
"timeZoneTimeStamp": 1, //converted timeZoneTimeStamp if required
"_id": 0,
"newDate": { // newDate is converted timezone ISODate
"$add": [new Date(0), "$timeZoneTimeStamp"]
}
}
})
NOTE :
In above query conversion from ISODATE to timeStamp ref. here
In case if the dates are not changed and constant e.g. something like created_record_date then whichever timezone data you need it, you should pre-calculate and save (as String) along with the same document so that you don't have to run the huge processing at the runtime which could slow down the execution time. in case you have existing records and you want to store the various different timezone data along with the records, think about running a Map-Reduct job and update the documents separately. (let me know if you need the code for that). However, if this date field can be changed as per the business logic then its wise to calculate at runtime. both techniques have their different use cases and their pros and cons.
-$
If you are using mongoose (probably also works in native driver):
import moment from 'moment-timezone'; // this is needed to use .tz() method
import mongoMoment from 'mongodb-moment';
// Initalize mongodb-moment so you can use moment() object directly in mongo query
mongoMoment(moment);
// Add timezone to your_date
const date = moment(your_date)
.tz("Europe/Zagreb");
// Make $gte/$lte queries with date ...

Why are dates in match aggregate query being ignored?

I'm trying to run an aggregation statement in my mongo db. I have a document whose structure is (at least) as follows:
{
"_id": ObjectId,
"date": ISODate,
"keywordGroupId": NumberLong,
"ranking": NumberLong,
}
I would like to run an aggregation statement that aggregates the 'ranking' field for a given 'keywordGroupId' and a given 'date' interval.
I have been trying with the following aggregate command:
{
aggregate : "KeywordHistory",
pipeline : [
{ $match: { keywordGroupId: 75 , "$date": {$gte: ISODate("2013-01-01T00:00:00.0Z"), $lt: ISODate("2013-02-01T00:00:00.0Z")}} },
{ $group: { _id: { null }, count: { $sum: "$ranking" } } }
]
}
This command executes without errors and returns a result. If I try to change the value for the 'keywordGroupId' field, the command returns a different value, so I assume that the $match statement works for that field (NumberLong). Though, if I change the 'date' range and I specify a time interval for which I don't have any data in the database, it still returns a result (I would actually expect an empty result set). So I have to assume that the $match statement is ignoring the date interval specified.
Can anyone help me with this point?
Remove the $ prefix on the $date field of your $match:
{ $match: {
keywordGroupId: 75,
date: {$gte: ISODate("2013-01-01T00:00:00.0Z"), $lt: ISODate("2013-02-01T00:00:00.0Z")}
}},
You only use the $ prefix when the field name is used in a value, not as a key.
Sometimes ISodate does not works . so in Case if you want to match date using only "one" date the best way is:---
ex:-- Let a schema be:---
var storeOrder = new Schema({
store_name:{type:String, required:true},
date :{type:Date ,default:moment(new Date()).format('YYYY-MM-DD')},
orders : [{
vegetable : String,
quantity : Number,
price:Number
}]
});
mongoose.model('storeorder',storeOrder);
now to aggregate by matching date :--
storeOrder.aggregate([$match:{date :new Date("2016-12-26T00:00:00.000Z")} ])
**It is must to use new Date("2016-12-26T00:00:00.000z") instead of Date("2016-12-26T00:00:00.000z") because Date(your_date) !== new Date(your_date).
THANK YOU
The aggregate expects a Javascript Date Object and doesn't work otherwise.
new Date();
new Date(year, month, day);
Please note the month start with 0 and not 1 (Your January is 0 and December 11)