Momentjs Date Mutation

March 14, 2019 by mtchavez

If you’ve ever had to work with a lot of date logic in javascript you’ve likely used or at least seen Momentjs. It can be convenient for moving dates to other timezones or date math. I ran into another today I learned moment trying to write some tests in Jest while using moment to handle dates in test setup and fixtures.

I had a simplified version of some Jest tests like this that were setting up some user data with timestamps.

import moment from 'moment';

describe('User', () => {
  describe('#report_for_yesterday', () => {
    const yesterdayPST = moment()
      .tz('America/Los_Angeles')
      .startOf('day')
      .subtract(1, 'days')
      .hour(10);
    const yesterdayDate = yesterdayPST.format('YYYY-MM-DD');

    it('returns users for yesterday', async () => {
      const users = [
        buildUser({
          timestamp: yesterdayPST.hour(0)
        }),
        buildUser({
          timestamp: yesterdayPST
        }),
        buildUser({
          timestamp: yesterdayPST
            .add(1, 'day')
            .hour(1)
        })
      ];
      const expectedUsers = [
        {
          created_date: yesterdayDate,
          // ... other user data
        }
      ];

      expect(User.report_for_yesterday()).toEqual(expectedUsers);
    });
  });
});

The issue here is when you have yesterdayPST being called in the test and setting it to the beginning of the day it will actually modify the yesterdayPST moment date which will mess up the following uses of the date variable.

The way that moment allows you to deal with this is with the Moment#clone function. This will allow you to make a copy of the expected date and do the mutations on that date without affecting the original. The same test with the fixes would look like this using .clone()

import moment from 'moment';

describe('User', () => {
  describe('#report_for_yesterday', () => {
    const yesterdayPST = moment()
      .tz('America/Los_Angeles')
      .startOf('day')
      .subtract(1, 'days')
      .hour(10);
    const yesterdayDate = yesterdayPST.format('YYYY-MM-DD');

    it('returns users for yesterday', async () => {
      const users = [
        buildUser({
          timestamp: yesterdayPST.clone().hour(0)
        }),
        buildUser({
          timestamp: yesterdayPST
        }),
        buildUser({
          timestamp: yesterdayPST
            .clone()
            .add(1, 'day')
            .hour(1)
        })
      ];
      const expectedUsers = [
        {
          created_date: yesterdayDate,
          // ... other user data
        }
      ];

      expect(User.report_for_yesterday()).toEqual(expectedUsers);
    });
  });
});

The Moment#clone docs even have a call out that all moments are mutable.

All moments are mutable. If you want a clone of a moment, you can do so implicitly or explicitly.

So if you are writing tests and want to DRY up some of your variables and setup with dates don’t forget to use .clone() where applicable for your Moment date objects.

mtchavez All rights reserved.