Skip to main content

Autor: ce@neckar.it

Things I learned about JavaScript’s Date

JavaScript’s Date does not stop to amaze me.
Probably every developer has encountered lots of problems when working with it.
Most projects use other date libraries like Moment.js or JS-Joda to cope with its shortcomings.
In case you want to or have to stick to JavaScript’s Date here are some things I find useful to remember.

Time-Zones

The most important thing of all is that you can’t determine the time-zone of a Date-object by simply looking at it.

You can only retrieve information related to the browser’s local time-zone or to the UTC-time-zone.
Date provides various getters to do that:

  • getUTCHours / getHours
  • getUTCMinutes / getMinutes
  • getUTCSeconds / getSeconds

The result of the UTC-getters will be the same all over the world whereas the result of the non-UTC-getters depends on the browser’s local time-zone.

Hence if you want to compare dates compare them using the UTC-time zone. This can be achieved by using the toISOString-function.

If you want to compare dates always use toISOString!

Date-constructors

You need to take extra care when creating Date-objects. Date provides several constructor-signatures:

ConstructortoStringtoISOStringgetHoursgetUTCHoursis a UTC date
new Date(1692967999168)Fri Aug 25 2023 14:53:19 GMT+0200 (Central European Summer Time)2023-08-25T12:53:19.168Z1412yes
new Date(‚2023-08-25T12:53:19.168Z‘)Fri Aug 25 2023 14:53:19 GMT+0200 (Central European Summer Time)2023-08-25T12:53:19.168Z1412yes
new Date(‚2023-08-25 12:53:19.168 UTC‘)Fri Aug 25 2023 14:53:19 GMT+0200 (Central European Summer Time)2023-08-25T12:53:19.168Z1412yes
new Date(2023, 7, 25, 12, 53, 19, 168)Fri Aug 25 2023 12:53:19 GMT+0200 (Central European Summer Time)2023-08-25T10:53:19.168Z1210no

As you can see there are constructors that interpret their arguments as a UTC date and constructors that interpret their arguments as a local date.

I recommend to use the constructors that interpret their arguments as a UTC date. Combined with the UTC-getters you can avoid the conversion to the browser’s local time-zone altogether.

Stick to UTC-dates as far as possible!

Formatting Dates

Most of the time human beings want to view the date in their time-zone (which is probably not UTC).

In that case try not to convert the Date-object programmatically but use the Intl.DateTimeFormat to format the date:

console.log(
  new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'long', timeZone: 'Australia/Sydney' }).format(new Date(1692967999168)),
);
// result: Friday, 25 August 2023 at 22:53:19 GMT+10

Use Intl.DateTimeFormat to format a date with a certain time-zone.

Invalid Dates

Sometimes you may encounter invalid Date-objects. That may happen on purpose or by accident (e.g. an invalid user input).

An invalid Date-object is still an object of type Date but you may run into errors if you try to do something useful with it:

new Date('13-13-13').toISOString();
// will throw: Uncaught RangeError: Invalid time value at Date.toISOString

Fortunately, there is an easy way to detect an invalid date. Calling getTime on an invalid date will return NaN (not a number):

const invalidDate = new Date('13-13-13');
const timestamp = invalidDate.getTime();
Number.isNaN(timestamp); // evaluates to true!

getTime will evaluate to Number.NaN if the date is invalid.

This implies that you can create an invalid date with new Date(Number.NaN).

Conclusion

It’s hard to work with JavaScript-Date objects. Try to stick to UTC dates and use other time-zones only when formatting the date.

I hope you can use some of the provided insights.

Let me know if you find a bug or want something to be added.

Until then, happy coding!

Function overloading with a variable number of arguments.

Let’s assume we need to write a function in TypeScript that adds samples to some kind of store.

Additionally, we want to be able to pass a single sample, several samples or an array of samples to that function.

That is, we want to be able to write the following code:

// sample1, ..., sample5 are of type Sample
addSamples(sample1);
addSamples([sample4, sample5]);
addSamples(sample2, sample3);

How would we do that? A function that takes a single sample can be easily written:

function addSamples(sample: Sample): void {
    const store = [];
    store.push(sample);
    // do something with store
}
addSamples(sample1); // works
addSamples([sample4, sample5]); // error
addSamples(sample2, sample3); // error

So far so good. A function that takes an array of samples would look like this:

function addSamples(samples: Array<Sample>): void {
    const store = [];
    store.push(...samples);
    // do something with store
}
addSamples(sample1); // error
addSamples([sample4, sample5]); // works
addSamples(sample2, sample3); // error

Still not difficult. How would a function look like that accepts an arbitrary number of samples?
This is where „varargs“ come in handy:

function addSamples(...samples: Array<Sample>): void {
    const store = [];
    store.push(...samples);
    // do something with store
}
addSamples(sample1); // works
addSamples([sample4, sample5]); // error
addSamples(sample2, sample3); // works

Alright, but the goal is to have a single function that does everything at once.

How would such a function be implemented?

For this we need function overloading:

function addSamples(sample: Sample): void;
function addSamples(...samples: Array<Sample>): void;
function addSamples(sample: Array<Sample> | Sample, ...samples: Array<Sample>): void {
    const store = [];
    if (Array.isArray(sample)) {
        store.push(...sample); 
    } else {
        store.push(sample);
    }
    store.push(...samples)
    // do something with store
}

addSamples(sample1); // works
addSamples([sample4, sample5]); // works
addSamples(sample2, sample3); // works

With function overloading the signature of the implementing function must be the union of all signatures.

One can certainly argue if this solution is something to strive for.
Perhaps a more straight forward approach would be to have several functions with different names.
But in the end this was a requirement from a real-world project.

Feel free to adopt it to your needs.