Hướng dẫn string to date mongodb

If you have a MongoDB collection with dates stored as strings, you can convert those into the Date BSON type if required.

Below are three ways to convert a string into a Date in MongoDB.

Sample Data

The examples in this article use the following document in a collection called dogs:

{ "_id" : 1, "name" : "Fetch", "born" : "2021-01-03T23:30:15.123" }

We can see that the born field contains a date, but that date is stored as a string.

We can use the following aggregation pipeline operators to convert that string into a date.

The $dateFromString Operator

The $dateFromString aggregation pipeline operator was designed specifically for converting a date to a string.

It requires a dateString argument, which is the string that you want to be converted to a date.

Here’s an example of converting the string in the above collection to a date:

db.dogs.aggregate([ 
  {
    $project: {
        born: {
          $dateFromString: {
              dateString: '$born'
          }
        }
    }
  } 
])

Result:

{ "_id" : 1, "born" : ISODate("2021-01-03T23:30:15.123Z") }

We can see that the date is now wrapped in the ISODate helper, which means that it’s a Date object.

The $dateFromString operator accepts a handful of other arguments, all of which are optional. See MongoDB $dateFromString for more information and examples.

The $toDate Operator

The $toDate aggregation pipeline operator converts a value to a date. The value can be any type that that can be converted to a date, which is basically numbers, strings, and objectIds.

This article is about converting strings to dates, and so here’s an example of using $toDate to do just that:

db.dogs.aggregate([ 
  {
    $project: {
        "born": { 
          $toDate: "$born" 
        }
    }
  } 
])

Result:

{ "_id" : 1, "born" : ISODate("2021-01-03T23:30:15.123Z") }

See Mongo $toDate for more information and examples.

The $convert Operator

The $convert operator was specifically designed for converting between one type and another.

The input parameter specifies the field you want to convert, and the to parameter specifies the type you want to convert it to.

Example:

db.cats.aggregate(
  [
    {
      $project:
        { 
          result: 
          {
            $convert: { 
              input: "$born", 
              to: "date",
              onError: "An error occurred",
              onNull: "Input was null or empty" 
            }
          }
        }
    }
  ]
)

Result:

{ "_id" : 1, "result" : ISODate("2021-01-03T23:30:15.123Z") }

You might have noticed that the $convert operator also accepts onError and onNull parameters, which allows us to provide a message to use in the event of an error or a null value. This prevents the whole aggregation operation from halting, and it allows for a user-friendly error message within the output document.

See MongoDB $convert for more information and examples.

Using MongoDB 4.0 and newer

The $toDate operator will convert the value to a date. If the value cannot be converted to a date, $toDate errors. If the value is null or missing, $toDate returns null:

You can use it within an aggregate pipeline as follows:

db.collection.aggregate([
    { "$addFields": {
        "created_at": {
            "$toDate": "$created_at"
        }
    } }
])

The above is equivalent to using the $convert operator as follows:

db.collection.aggregate([
    { "$addFields": {
        "created_at": { 
            "$convert": { 
                "input": "$created_at", 
                "to": "date" 
            } 
        }
    } }
])

Using MongoDB 3.6 and newer

You cab also use the $dateFromString operator which converts the date/time string to a date object and has options for specifying the date format as well as the timezone:

db.collection.aggregate([
    { "$addFields": {
        "created_at": { 
            "$dateFromString": { 
                "dateString": "$created_at",
                "format": "%m-%d-%Y" /* <-- option available only in version 4.0. and newer */
            } 
        }
    } }
])

Using MongoDB versions >= 2.6 and < 3.2

If MongoDB version does not have the native operators that do the conversion, you would need to manually iterate the cursor returned by the find() method by either using the forEach() method or the cursor method next() to access the documents. Withing the loop, convert the field to an ISODate object and then update the field using the $set operator, as in the following example where the field is called created_at and currently holds the date in string format:

var cursor = db.collection.find({"created_at": {"$exists": true, "$type": 2 }}); 
while (cursor.hasNext()) { 
    var doc = cursor.next(); 
    db.collection.update(
        {"_id" : doc._id}, 
        {"$set" : {"created_at" : new ISODate(doc.created_at)}}
    ) 
};

For improved performance especially when dealing with large collections, take advantage of using the Bulk API for bulk updates as you will be sending the operations to the server in batches of say 1000 which gives you a better performance as you are not sending every request to the server, just once in every 1000 requests.

The following demonstrates this approach, the first example uses the Bulk API available in MongoDB versions >= 2.6 and < 3.2. It updates all the documents in the collection by changing the created_at fields to date fields:

var bulk = db.collection.initializeUnorderedBulkOp(),
    counter = 0;

db.collection.find({"created_at": {"$exists": true, "$type": 2 }}).forEach(function (doc) {
    var newDate = new ISODate(doc.created_at);
    bulk.find({ "_id": doc._id }).updateOne({ 
        "$set": { "created_at": newDate}
    });

    counter++;
    if (counter % 1000 == 0) {
        bulk.execute(); // Execute per 1000 operations and re-initialize every 1000 update statements
        bulk = db.collection.initializeUnorderedBulkOp();
    }
})
// Clean up remaining operations in queue
if (counter % 1000 != 0) { bulk.execute(); }

Using MongoDB 3.2

The next example applies to the new MongoDB version 3.2 which has since deprecated the Bulk API and provided a newer set of apis using bulkWrite():

var bulkOps = [],
    cursor = db.collection.find({"created_at": {"$exists": true, "$type": 2 }});

cursor.forEach(function (doc) { 
    var newDate = new ISODate(doc.created_at);
    bulkOps.push(         
        { 
            "updateOne": { 
                "filter": { "_id": doc._id } ,              
                "update": { "$set": { "created_at": newDate } } 
            }         
        }           
    );

    if (bulkOps.length === 500) {
        db.collection.bulkWrite(bulkOps);
        bulkOps = [];
    }     
});

if (bulkOps.length > 0) db.collection.bulkWrite(bulkOps);