Web Fundamentals: Avoid These JavaScript Date Object Pitfalls
The JavaScript Date
object is notoriously broken. Until it is replaced with a modern alternative, frontend developers have to deal with its quirks. This post explains two quirks that every frontend developer should know even if you are using TypeScript
and a date utility library like date-fns
, zod
, orDay.js
.
tldr;
new Date(null)
returns the Unix epoch1970-01-01T00:00:00.000Z
;- When parsing a value to a date, consider manually checking whether
value === null
before passing the value to library likedate-fns
orzod
- Here is a repo with safe parse examples in (i) plain TypeScript, (ii) date-fns, and (iii) zod
- Avoid one gotcha when using zod to parse ISO 8601 Date strings
1. new Date(null) Unexpectedly Returns a Valid Date
In JavaScript, creating a Date by passing null to the constructor generates the Unix epoch zoned to your local timezone:
new Date(null);
// Wed Dec 31 1969 18:00:00 GMT-0600
This result is unexpected and could easily cause frontend bugs. By definition, null
means a value that is empty or doesn’t exist. If an api returns { lastModified: null }
, I’d bet that no backend developer expects the client to set lastModified to the Unix epoch.
Here’s one way this bug can sneak into your code: let’s say you’re using a date utility library like date-fns
and you’re trying to ensure that something is a valid date. The new Date(null)
issue would still slip through the cracks because new Date(null)
returns a valid date:
import { isValid } from 'date-fns';
const data = { lastModified: null };
isValid(new Date(data.lastModified)) // true??
You might think to yourself, “I’m not worried about this because I use TypeScript!” And to an extent you’d be right. If you’re using TypeScript with “strict” set to true you’ll get a compilation error when creating a date with new Date(null)
.
/* tsconfig.json
"compilerOptions": {
"strict": true
}
*/
const date = new Date(null);
However, there’s still a lingering problem. Because new Date(null)
returns a valid date, libraries like zod coerce it to a Date even if your project uses TypeScript:
z.coerce.date().safeParse(null);
/*
{
success: true,
date: '1970-01-01T00:00:00.000Z
}
*/
i. The Solution
So what is a frontend developer to do?
One solution is to filter null values before they get to date utilities. Here’s an example that wraps zod’s parsing method and filters null and undefined:
export const zodSaferDateParse = (
value: string | number | null | Date | undefined
): Date | undefined => {
if (value === null || value === undefined) {
return undefined;
}
const maybeDate = value instanceof Date
? z.date().safeParse(value)
: z.coerce.date().safeParse(value);
return maybeDate.success
? maybeDate.data
: undefined;
}
This is a repository I created with similar date parsing examples using plain TypeScript
and date-fns
. The repo also includes a Jest test suite if you want to check your own date parse methods.
2. Bonus: Watch Out For ‘YYYY-MM-DD’ Date Strings When Using Zod
Below is a short example showing one ‘gotcha’ when using zod to parse date strings:
// 2023-01-01T00:00:00.000Z // NOT zoned
new Date('2023-01-01');
z.coerce.date().safeParse('2023-01-01'); // zod
// 2023-01-01T06:00:00.000Z // zoned to local timezone
parseISO('2023-01-01'); // date-fns
new Date('2023/01/01');
The gotcha is that, unlike the date-fns parseISO
method, zod does not automatically parse ISO Dates to local timezones.
The ECMAScript standard for parsing ISO 8601 Date strings (’2023-01-01’) is flawed:
- The ECMAScript standard provides that such strings default to UTC.
- However, the actual ISO 8601 standard specifies that absent a timezone such strings should default to local timezone.
Why does ECMAScript work this way? The t39 committee’s original specification was flawed and they couldn’t revise it before it became a ‘Web Reality’.
This issue is well known and date utility libraries like date-fns
provide utility methods that automatically parse ISO Date strings to your local timezone. If you’re used to this convenience from your date utility library, just be warned that zod does not currently take care of this for you.
Finally, here is a cheat sheet for Date strings in JavaScript:
// ISO 8601 without time: defaults to midnight UTC time
const withHyphen = new Date('2023-01-01') // Sat Dec 31 2022 18:00:00 GMT-0600 (Central Standard Time)
withHyphens.getTime() // 1672531200000
// ISO 8601 set to UTC timezone (same as not providing time)
const noOffset = new Date('2023-01-01T00:00:00.000Z') // Sat Dec 31 2022 18:00:00 GMT-0600 (Central Standard Time)
noOffset.getTime() // 1672531200000
// ISO 8601 set to Local Timezone
const withLocalOffset = new Date('2023-01-01T06:00:00.000Z') // Sun Jan 01 2023 00:00:00 GMT-0600 (Central Standard Time)
withHyphens.getTime() // 1672552800000
// Well Recognized String Format - Defaults to Local Timezone
const withSlashes = new Date('2023/01/01') // Sun Jan 01 2023 00:00:00 GMT-0600 (Central Standard Time)
withSlashes.getTime() // 1672552800000
// Number Arguments - Defaults to Local Timezone
const withArgs = new Date(2023, 0, 1) // Sun Jan 01 2023 00:00:00 GMT-0600 (Central Standard Time)
withArgs.getTime() // 1672552800000
Conclusion
Thank you for reading my post about Dates in JavaScript. To recap, watch out for null constructor values when dealing with Dates and pay special attention to date strings in this format: ‘YYYY-MM-DD’. Date utility libraries and schema validators are excellent tools, but there are still rough edges that warrant special attention.