skip to Main Content

I have this collection in my Mongo database:

id    | place | local time
--------------------------
3     | A     | 12pm
4     | A     | 11pm
5     | B     | 4pm
6     |       | 7pm

The local times are stored as ISODates with UTC timezone. A local time of 12 is represented as 2022-01-01T12:00:00.000Z.

Outside of the database I have a partially complete mapping between places and timezones.

place | timezone
A     | Europe/London
B     | Europe/Brussels

Using this information I want to make my database rows timezone-aware, by adding new fields that capture the timezone and fix the offset for the date. This would be my ideal result:

id    | place | local time | newDate                         | timezone
----------------------------------------------------------------------------
3     | A     | 12pm       | ISODate("2022-07-01T11:00:00Z") | Europe/London
4     | A     | 11pm       | ISODate("2022-07-01T22:00:00Z") | Europe/London
5     | B     | 4pm        | ISODate("2022-07-01T14:00:00Z") | Europe/Brussels
6     |       | 7pm        | ISODate("2022-07-01T07:00:00Z") | UTC

Adding the timezone column is straight forward, I iterate over my mapping’s table and for each one build a single update statement like this:

db.testcollection.updateMany({ place: "A" }, { $set: { "timezone" : "Europe/London" }});

Dealing with locations where the timezone for the place isn’t known (and we fallback to just using UTC) is also straight forward:

db.testcollection.updateMany({ "timezone" : { $exists: false } }, { $set: { "timezone" : "UTC" }});

But what I cannot seem to do is apply the timezone change itself, to multiple rows (with the same place) at once, using a Mongo update statement. I’m not entirely sure if it’s possible.

I have tried:

rs0 [direct: primary] test> db.testcollection.updateMany({ place: "A" }, { $set: { "utctime" : { $dateAdd: { startDate: "$localtime", amount: 1, unit: "hour" }}}});

which can change the times by adding/subtracting an hour or so, but actually applying a timezone change like I want is too difficult for $dateAdd function. Europe/London has GMT/BST depending on daylight savings, certain years have had and abandoned different historic values: https://en.wikipedia.org/wiki/Daylight_saving_time_by_country

Even a series of time based filter clauses seems incredibly complicated.

The update I want to apply is essentially the same as the Java function:

correctUTC = localTimeMarkedAsUTC          // 9pm local time
    .withZoneSameLocal(portTimezone)       // 9pm Europe/London
    .withZoneSameInstant(UTC)              // 8pm UTC

I can use the Date object in an update function, but I am still struggling on how I can manipulate $dateToString and friends to generate the output I want:

test> db.testcollections.updateMany({ place: "A" }, [{ $set: { "utctime" : { $convert : { input: { $dateToString: { date: "$localtime", format: "%Y-%m-%dT%H:%M:%S%z", timezone: "UTC" }}, to: "date" }}}}]);

Sets to the same thing it’s already set to. Using timezone London merely adds an hour. I want the opposite, which is to subtract an hour from the UTC variant.

Because of the size of the DB, I’d prefer a solution that operates as updates on whole places in bulk, or doesn’t use .forEach(function(e){ ... }) to hit every record, unless that’s the last possible solution that can possibly work?

I’m on Mongo 5.0

2

Answers


  1. You just need to wrap it with [] to create a pipeline:

    db.collection.update({
      place: "A"
    },
    [{
      $set: {
        "utctime": {
          $dateAdd: {
            startDate: "$localtime",
            amount: 1,
            unit: "hour"
          }
        }
      }
    }],
    {
      multi: true
    })
    

    As you can see here.
    Which can off course work with a bulk operation

    Login or Signup to reply.
  2. Try this one:

    db.collection.updateMany(
      {timezone: {$ne: "UTC"} }, // UTC times do not need any correction
      [
        {
          $set: {
            newDate: {
              $dateFromString: {
                // "cut" wrong time zone
                dateString: {
                  $dateToString: {
                    date: "$newDate",
                    format: "%Y-%m-%dT%H:%M:%S.%L"
                  }
                },
                format: "%Y-%m-%dT%H:%M:%S.%L",
                timezone: "$timezone" // "attach" correct time zone
              }
            }
          }
        }
      ]
    )
    

    Mongo Playground

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search