How do I set a timezone in my Jest config?
Asked Answered
N

10

131
✗ npx jest --version
24.5.0

Got a set of jest tests that are timezone sensitive. We typically run them with an npm script: "jest": "TZ=utc jest"

With the TZ set to utc I get values like this in snapshots:

modificationDate="2019-01-08T00:00:00.000Z" 

Without it I get:

modificationDate="2019-01-08T08:00:00.000Z"

Is there a way to set that in my jest config so I can run npx jest at the command line without having to go through the NPM script? There's nothing in config docs about this.

I tried adding these two to my jest.config.js. Neither one worked:

  TZ: 'utc',

  globals: {
    TZ: 'utc',
  },

Sure, it seems trivial to work around but I'm surprised Jest doesn't have a way to configure this for tests.

Nicolenicolea answered 22/5, 2019 at 16:33 Comment(0)
K
213

This does not work on windows prior to node 16.2.0 (and prior to 17.0.1 in node 17) - see https://github.com/nodejs/node/issues/4230


The problem with process.env.TZ = 'UTC'; is, that if something runs before this line and uses Date, the value will be cached in Date. Therefore process.env is in general not suitable for setting the timezone. See https://github.com/nodejs/node/issues/3449

So a better way is to use an actual env variable, but for tests this will work:

1. Add this to your package.json

  "jest": {
     ...
     // depending on your paths it can also be './global-setup.js' 
    "globalSetup": "../global-setup.js"
  }
}

2. Put this file besides package.json as global-setup.js

module.exports = async () => {
    process.env.TZ = 'UTC';
};

3. Optional: Add a test that ensures UTC execution

describe('Timezones', () => {
    it('should always be UTC', () => {
        expect(new Date().getTimezoneOffset()).toBe(0);
    });
});

The normal setupFiles did not work for me, since they run too late (jest: ^23.5.0). So it is mandatory to use the globalSetup file.

Karmen answered 6/6, 2019 at 17:38 Comment(18)
@OliverWatkins, can you verify whether global-setup.js is called or not by adding some console logs?Karmen
I tried on Jest 24.8.0 . I put consoles everywhere, and they all get called (ie. also inside the asynch - the line process.env.TZ = 'UTC';). My timezoneoffset is -120, and not zero.Sisterinlaw
I call it using yarn like this : yarn test TZ.test.jsSisterinlaw
I even put a console inside the test : console.info("process.env.TZ " + process.env.TZ ), and that is correctly set to UTC. So the env.TZ value is definately being set in the "globalSetup": "../global-setup.js"Sisterinlaw
@OliverWatkins sorry to hear that, I just confirmed that this is not a global issue. My test works with [email protected]. This means, before your test runs, maybe before jest at all, something is likely using Date. Probably the fastest way to debug is to remove parts of your code and test again.Karmen
@Can It does not work for me too. Logging process.env.TZ gives me UTC. What's your OS? Mine is Win10.Shama
@ImanMahmoudinasab that is really weird, I'm on Ubuntu. If it gives you UTC, what do you want to achieve? Or logging works, but the given example test doesn't?Karmen
@Can It gives UTC but .getTimezoneOffset() returns me 480 not 0. I think it may be related to OS.Shama
@Can, hey)) I am also trying to set timezone globally and did all of the three steps. But, how can I actually use globalSetup variable? Should I use the variable like this: jest $npm_package_scripts_jest_globalSetup in package.json?Handwriting
@QQQQQ, it should work if you set it in the package.json file under the path: "jest"."globalSetup" as you can see in step 1. If this does not work, maybe this behavior was changed in recent Jest versions - would be great if you can answer if you got it to workKarmen
This solution worked great on my Mac but doesn't seem to work on my Windows 10 machine. My global-setup.js is definitely being called and has ``` process.env.TZ = 'UTC'; console.log('JEST SETUP TZ OFFSET: ' + new Date().getTimezoneOffset()) ``` My test has ``` console.log('IN TEST TZ OFFSET' + new Date().getTimezoneOffset()); console.log('IN TEST TZ' + process.env.TZ); expect(new Date().getTimezoneOffset()).toBe(0); ``` The output of this is ` JEST SETUP TZ OFFSET: 0 IN TEST TZ OFFSET-60 IN TEST TZ UTC Expected: 0 Received: -60 ```Jordans
@JoseSolorzano would you mind testing if cross-env would work? "test": "cross-env TZ=\"UTC\" npm run jest-task",Karmen
"This does not work on windows" -- this sure does not make me miss my brief contract where I was writing Node code in Windows (we switched to Macs after we ran into numerous issues)Nicolenicolea
This is brilliant! I used to do it in setupFiles and didn't understand why it was not working anymore. Thanks man!Sheepshearing
@Can looks like this is fixed "I can confirm this problem is solved in node version 17.0.1 using Windows 10" -- last comment in that github ticketNicolenicolea
how to set the time zone for a single test , not for whole project?Torrez
@Can I was able to get this to work using "cross-env TZ=UTC jest ..."Inadvisable
don't forget to restart your jest runner when you update the configArose
S
67

If you are running tests with npm scripts, ie: npm run test, you can pass in the timezone like so:

  "scripts": {
    "test": "TZ=UTC jest"
  },

I also personally feel that this (vs the process.env methods) is cleaner and easier to identify the timezone when debugging issues on remote CI servers.

Saleme answered 2/5, 2020 at 10:3 Comment(10)
and if I'm running via command line without npm scripts I have to remember to do that every time -- the accepted answer is betterNicolenicolea
True. Though I would recommend moving towards using npm scripts. It's easier to keep track of how you and others should run and maintain your application(s). IE: npm run test will run the tests for every node project that I have. You don't have to do a thorough reading of the README (that's hopefully up to date) to know what's expected.or what quirks that app has.Saleme
Bad decision. This way you will depend on running tests with your custom script command. Use global setup instead.Jurkoic
Disagree. You should only be running your automated tests with one command, preferably one that you and your CI control, like a npm script command.Saleme
some of the time you don't want to alter that script command in order to run a subset of tests, making the accepted answer betterNicolenicolea
@Nicolenicolea If you're okay with your teammates' tests running on a different timezone than your computer, go for it. Have fun figuring out why your tests pass and theirs fail :) If you have a specific component that needs to work with local timezones, mock that out in that specific test instead of globally.Saleme
@Saleme "okay with your teammates' tests running on a different timezone" -- except there's a setup script that accounts for that, see the accepted answer. Also see thisNicolenicolea
This solution doesn't work on Windows. There's another way that does, but then your project is OS dependent.Mayamayakovski
Also note when using eg webstorm, it has its own commands to start tests - here, the global setup file is a better proposition that also works in these circumstancesGath
if you are on windows, use "test": "set TZ=UTC && jest"Stralsund
R
35

I just run into the same issue and I was able to resolve it by adding process.env.TZ = 'your/timezone'; to my jest.config.js.

Maybe this helps in your case as well :)

process.env.TZ = 'UTC';

module.exports = {
  ...
};
Rianna answered 23/5, 2019 at 14:16 Comment(5)
This is sadly wrong. See github.com/nodejs/node/issues/3449 "Closing, setting process.env.TZ is not the right way to set the default timezone." Verify with ` expect(new Date().getTimezoneOffset()).toBe(0);`Karmen
@CanK. this is weird... I just updated to angular 8 and changed a few dependencies and now my tests are not working anymore... I will let you know if i find another solution.Rianna
@Rianna yes I did, I'll put it into an answer due to the contents.Karmen
@CanK. I realised that in my case it was just because of a change in the config path that my solution stopped working, but your solution is working as well so I switched to this one, seems to be the better way to set the timezone! Thank you!Rianna
This should be the accepted answer. Especially for those of us using monorepos, by far the easiest solutionMalayan
J
16

Up until recently I used the following to mock being in a different timezone:

  beforeEach(() => {
    // Temporarily allow us to alter timezone calculation for testing
    /*eslint no-extend-native: "off"*/
    Date.prototype.getTimezoneOffset = jest.fn(() => 73);
  });

  afterEach(() => {
    jest.resetAllMocks();
  });

This didn't place the testing code in a particular timezone, but made sure that any timezone offset calculations were correctly made. For example:

new Date("2010-10-01") would be 73 minutes earlier than new Date("2010-10-01T00:00:00"), with the former being equivalent to new Date("2010-10-01T00:00:00Z") (UTC timezone) and the latter being in the 'local timezone'

I say "up until recently" because a recent update to date-fns seems to no longer work

Jeanajeanbaptiste answered 13/3, 2021 at 19:3 Comment(2)
I'm using luxon and this code works properly. I imagine it depends on whether the lib relies on getTimezoneOffset to know the TZ differenceJerad
I should add that I now make use of npmjs.com/package/timezone-mock when testing code in a specific timezoneJeanajeanbaptiste
L
9

Update: This doesn't produce deterministic results if you want to dynamically change the TZ but it will work if you only want one TZ. It could be an alternative to specifying at the script level, but I think that would be the better answer here.

The issue is that by setting process.env.TZ at runtime, it will bleed across tests creating non-deterministic behavior (side effects) during a regular Jest test run. It may work if you use --runInBand which runs tests serially but I wouldn't count on it.

I also found an old archived issue about dynamic timezones in Node and it looks like dynamically adjusting it won't work in general.

Instead I will probably end up with multiple scripts that each set TZ before launching jest.


For my use case, I actually wanted to run tests under different timezones for specific date-based edge cases. Sometimes users run into timezone-based bugs and we want to cover that easily within our test suites.

We run all tests in the project by default using one of the proposed answers here, by setting TZ=UTC in the npm script (e.g. TZ=UTC npm run jest. This runs all tests under the UTC timezone.

Then, we leverage the testEnvironment configuration which can be set at the test suite-level using the JSDoc pragma @jest-environment. Using this custom test environment, we can then read the suite's desired timezone using a "custom docblock pragma" like @timezone. This enables timezone customization per-test-suite which is not as ideal as per-test but good enough for our purposes.

jsdom-with-timezone.js

const JSDOMEnvironment = require('jest-environment-jsdom');

/**
 * Timezone-aware jsdom Jest environment. Supports `@timezone` JSDoc 
 * pragma within test suites to set timezone.
 *
 * You'd make another copy of this extending the Node environment, 
 * if needed for Node server environment-based tests.
 */
module.exports = class TimezoneAwareJSDOMEnvironment extends JSDOMEnvironment 
{
  constructor(config, context) {

    // Allow test suites to change timezone, even if TZ is passed in a script.
    // Falls back to existing TZ environment variable or UTC if no timezone is specified.
    // IMPORTANT: This must happen before super(config) is called, otherwise
    // it doesn't work.
    process.env.TZ = context.docblockPragmas.timezone || process.env.TZ || 'UTC';

    super(config);
  }
};

tz-eastern.test.js

/**
 * @timezone America/New_York
 */

describe('timezone: eastern', () => {
  it('should be America/New_York timezone', () => {
    expect(process.env.TZ).toBe('America/New_York');
    expect(new Date().getTimezoneOffset()).toBe(300 /* 5 hours */);
  });
});

jest.config.js

module.exports = {
  "testEnvironment": "<rootDir>/jsdom-with-timezone.js"
}

Using this with jest.useFakeTimers('modern'); and jest.setSystemTime() is sufficient for more robust date testing so I thought I'd share this approach for others to benefit from! Since the pragma handling is custom, you could customize this any way you like for your use case.

Sources:

Lillia answered 5/1, 2021 at 19:7 Comment(2)
Actually, following up (and I edited the answer). This produces non-deterministic behavior when running Jest normally (i.e. with multiple suites, in parallel). The problem is that process.env.TZ bleeds across tests since it is not part of the sandbox. It looks like Node.js doesn't support dynamic TZ at runtime :( github.com/nodejs/node-v0.x-archive/issues/3286Lillia
This is great, thank you very much! Just two small notes: --- 1. I also had to pass context to super to make it work - super(config, context); --- 2. expect(new Date().getTimezoneOffset()).toBe(300) will not work always work in timezones with DTS time shifts. To fix this, you can fix the date for which you compare offset, for example: expect(new Date(1683716277208).getTimezoneOffset()).toBe(240);Yoheaveho
C
0

I got the same problem.

at first my jest config was json: jest.config.json I changed this to jest.config.js

This is the file where also all the mapping happens so the timezone has te be set in this file.

Since i couldn't use commonJs anymore my config file looks like this:

process.env.TZ = 'GMT';

export default {
 ... config ...
}
Conflagration answered 24/8, 2022 at 9:6 Comment(0)
X
0

EDIT: while the test seemed to work when executed alone, it does not work in general.


from a mix of answers, this is the way I came up with, and the tests to ensure that Timezone change is actually working. I hope this helps someone !

first create a global setup file named 'jest.global-setup.js' (you can name it anything you like) with this content:

module.exports = async () => {
  function changeTimezone(tz) {
    if (tz) {
      process.env.TZ = tz;
    } else {
      delete process.env.TZ;
    }
  }

  if (typeof global !== 'undefined') {
    global.changeTimezone = changeTimezone;
  }

  if (typeof globalThis !== 'undefined') {
    globalThis.changeTimezone = changeTimezone;
  }
};

then add it to your jest config file:

module.exports = {
  // your other configs ...
  globalSetup: '<rootDir>/jest.global-setup.js',
};

now in your tests:

// please notice that I use typescript here,
// you don't have to

const timezones = {
  UTC: 'UTC',
  Afghanistan: 'Asia/Kabul',
};

function getTimezoneIdentity(tz: string) {
  if (tz === timezones.UTC) {
    return 'Coordinated Universal Time';
  }
  if (tz === timezones.Afghanistan) {
    return 'Afghanistan Time';
  }

  return 'time_zone_identity_not_found';
}

const tests = (timezone: string) =>
  describe('timezone is set to ' + timezone, () => {
    beforeAll(() => {
      // @ts-ignore
      changeTimezone(timezone);
    });

    afterAll(() => {
      // @ts-ignore
      changeTimezone('');
    });

    it('timezone change works', () => {
      expect(new Date().toString()).toContain(getTimezoneIdentity(timezone));
    });

    // Your other tests here 
  });

describe('my tests work properly over different timezones', () => {
  for (const tz in timezones) {
    tests(timezones[tz as keyof typeof timezones]);
  }
});
Xever answered 26/12, 2022 at 4:8 Comment(0)
S
0

Maybe this answer is not exactly what you asked for, but if you need to compare dates in small amount of tests, you can try from the opposite side. Set fake date with current time zone offset:

beforeEach(() => {
  const currentTimezoneOffset = (new Date()).getTimezoneOffset();
  jest.useFakeTimers();
  jest.setSystemTime(Date.parse('25 Apr 2024 11:00:59 GMT') + (currentTimezoneOffset * 60 * 1000));
});

afterEach(() => {
  jest.useRealTimers();
});
Sexennial answered 25/4 at 10:3 Comment(0)
C
-1

If you are using react scripts you can set timezone like this in your package.json:

"scripts": {        
    "test": "TZ=UTC react-scripts test",        
},
Conglutinate answered 13/2, 2023 at 11:29 Comment(1)
from the question: Is there a way to set that in my jest config so I can run npx jest at the command line without having to go through the NPM script? -- this answer does not answer that questionNicolenicolea
C
-1

https://github.com/Jimbly/timezone-mock is a great utility for running test cases against multiple different timezones. Example with Jest:

import timezoneMock from 'timezone-mock';

const testTimezones: timezoneMock.TimeZone[] = [
  'US/Pacific',
  'UTC',
  'Europe/London',
  // ...
];

describe.each(testTimezones)('tests in %s', (testTimezone) => {
  beforeAll(() => {
    timezoneMock.register(testTimezone);
  });

  afterAll(() => {
    timezoneMock.unregister();
  });

  it('...', () => {
    // Tests here
  })
});
Cady answered 5/12, 2023 at 3:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.