Skip to content

How to best deal with timezone issues with the Date method? #26

@opauloh

Description

@opauloh

Hello EpicWeb team!

I'm going through the testing-fundamentals workshop (liked the idea of building my own assertion library to really understand how they work under the hoods). While going through the exercises, I faced an issue where some tests were failing due to timezones differences

One of those was the greet function:

export function greet(name: string) {
	const weekday = new Date().toLocaleDateString('en-US', { weekday: 'long' })

	return `Hello, ${name}! Happy, ${weekday}.`
}

Where in the test we override the globalThis.Date on the greet.test.ts file:

beforeAll(() => {
	globalThis.Date = new Proxy(globalThis.Date, {
		construct: () => new OriginalDate('2024-01-01'),
	})
})

However, when executing npx tsx --import ./setup.ts greet.test.ts, I got the test fails with the message: Error: Expected Hello, John! Happy, Sunday. to equal to Hello, John! Happy, Monday.

Full error:

npx tsx --import ./setup.ts greet.test.ts
✗ returns a greeting message for the given name
Error: Expected Hello, John! Happy, Sunday. to equal to Hello, John! Happy, Monday.
    at Object.toBe (testing-fundamentals/exercises/02.test-structure/04.solution.hooks/setup.ts:16:11)
    at <anonymous> (testing-fundamentals/exercises/02.test-structure/04.solution.hooks/greet.test.ts:21:24)
    at globalThis.test testing-fundamentals/exercises/02.test-structure/04.solution.hooks/setup.ts:24:3)
    at <anonymous> (testing-fundamentals/exercises/02.test-structure/04.solution.hooks/greet.test.ts:20:1)
    at ModuleJob.run (node:internal/modules/esm/module_job:222:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:316:24)
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:123:5) 

✓ returns a congratulation message for the given name

When executing new Date('2024-01-01') in the console, it indeed returns Sunday

image

After digging it a little bit, I found out that was because the given date format (YYYY-MM-DD), makes javaScript's Date object interpret the date as UTC time, so basically the problem was that in the test we set the date in UTC but the greet function displays it in local time, so it might lead to a test falling depending on the timezone where it was executed.

I found a couple of different solutions for this issue, but I would like to ask what could be the best way to handle it?

One solution was to set the timezone to UTC when overriding

beforeAll(() => {
	globalThis.Date = new Proxy(globalThis.Date, {
		construct: (target, args) => {
			const instance = new OriginalDate('2024-01-01')

			// Override the toLocaleDateString method to always return UTC or it will fail when running in different timezones
			instance.toLocaleDateString = function (locale?: any, options?: any) {
				return OriginalDate.prototype.toLocaleDateString.call(this, locale, {
					...options,
					timeZone: 'UTC',
				})
			}

			return instance
		},
	})
})

But that sounds overkill to me, as the test setup became far more complex this way.

Another solution I found was to set the timezone to UTC with the TZ Node environment variable.

TZ=UTC npx tsx --import ./setup.ts greet.test.ts

That looks like an efficient approach to me, but I didn't find a mention of this in the workshop, would it be a good practice to have the timezone overridden like this?

Then, in a real-world project, to have everyone using the project to run the tests in the same timezone, the environment override could be added to the package.json as in the example below:

"scripts": {
    "test": "TZ=UTC vitest"
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions