Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Post History

81%
+7 −0
Q&A Why is this client code getting the wrong date for a few hours a day?

First of all, we need to understand what a JavaScript Date actually is. And surprisingly, it's not exactly a date (at least not in terms of having unique values for day, month, year, hour, minute a...

posted 3y ago by hkotsubo‭  ·  edited 3y ago by hkotsubo‭

Answer
#3: Post edited by user avatar hkotsubo‭ · 2021-06-04T13:44:22Z (over 3 years ago)
  • First of all, we need to understand what a [JavaScript `Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) actually is. And surprisingly, it's not exactly a date (at least not in terms of having unique values for day, month, year, hour, minute and second).
  • A JavaScript `Date` actually represents a [timestamp](https://www.unixtimestamp.com/). More precisely, [according to the language specification](https://tc39.es/ecma262/#sec-date-objects), a `Date`'s value is the number of milliseconds since [Unix Epoch](https://en.wikipedia.org/wiki/Unix_time) (which, in turn, corresponds to `1970-01-01T00:00:00Z` - January 1<sup>st</sup>, 1970, at midnight in [UTC](https://en.wikipedia.org/wiki/UTC)).
  • Hence, a `Date` represents a specific instant: a point in the timeline. And this same instant corresponds to a different date and time, depending on the timezone you are.
  • Example: by calling `new Date().valueOf()`, you'll get the current timestamp. I've just called that and the result was `1622722404062`, which means that more than 1.6 trillion miliseconds has elapsed since Unix Epoch. If you had ran that code in any computer in the world (assuming they're not misconfigured), at the same instant I did, you'd get this same value.
  • But that same timestamp value corresponds to a different date and time, depending on the timezone you use. So `1622722404062` corresponds to all of the dates/times below:
  • | Where | Corresponds to |
  • |:-------------------|:----------------------------------------------|
  • | São Paulo (Brazil) | June 3<sup>rd</sup> 2021, 09:13:24 AM |
  • | Tokyo (Japan) | June 3<sup>rd</sup> 2021, 09:13:24 **PM** |
  • | Apia (Samoa) | June **4<sup>th</sup>** 2021, **01**:13:24 AM |
  • | UTC | June 3<sup>rd</sup> 2021, **12**:13:24 **PM** |
  • Note that, depending on the timezone, the date and/or time can be completely different. That's a crucial thing to understand how `Date` works: all the date/times above correspond to the same timestamp (`1622722404062`). Hence, a `Date` object with such timestamp value actually represents all of them.
  • And that's the confusing part: when you use the _getters_ (such as `getDate()`, `getHours()`, etc), the value returned considers the browser's timezone (whatever it's configured in it: some might get that config from the OS, some might override it - it doesn't matter how it's done, that config will be used in the end).
  • But some methods return the value in UTC: that's the case of `toISOString()`. There are also "UTC getters", such as `getUTCDate()` and `getUTCHours`, which return the values according to UTC.
  • Anyway, mixing UTC and non-UTC methods is what probably caused this confusion, specially if you're in that range of hours when your local time corresponds to the next - or previous - UTC day (based on the [other answer](https://software.codidact.com/posts/281996#answer-281996), that seems to be the case).
  • ---
  • Just for the sake of completeness, to check if it's after 08:00 PM, you should test `now.getHours() >= 20` (not `>`). Hours are zero based, but zero means midnight, 1 means 01:00 AM, and so on (hence, 20 means 08:00 PM, not 09:00 PM as the [other answer](https://software.codidact.com/posts/281996#answer-281996) said).
  • And to get the date formatted, using local timezone values instead of UTC, you'll have to do it manually:
  • ```javascript
  • function pad(value) {
  • return value.toString().padStart(2, '0');
  • }
  • let now = new Date();
  • if (now.getHours() >= 20) {
  • now.setDate(now.getDate() + 1);
  • }
  • let formatted = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
  • ```
  • Don't forget to add 1 to the month, as months in JavaScript's `Date` are annoyingly zero-based (January is zero, February is 1, etc).
  • ---
  • ### How to NOT do it
  • If you search enough in the internet, you'll certainly find someone suggesting to change the timestamp value, by adding or subtracting the respective timezone offset (the difference from UTC). Something like this:
  • ```javascript
  • // don't do this
  • function format(date) {
  • let offset = date.getTimezoneOffset() / 60;
  • date.setTime(date.getTime() - offset * 3600 * 1000);
  • return date.toISOString().slice(0, 10);
  • }
  • ```
  • This code changes the timestamp, by subtracting the offset from it, making `toISOString` return the "correct" string. Although it "works", this is not the best solution, because when you change the timestamp, you're changing the instant that the `Date` represents.
  • To make an analogy, now it's 10 AM in Brazil and and 2 PM in London (it's [British Summer Time (BST)](https://www.timeanddate.com/time/zones/bst)). Let's suppose that my computer is misconfigured with London's timezone, so it displays 2 PM. There are two ways I could fix it:
  • 1. by setting the timezone to Brazil (actually, to São Paulo, because the country has more than one timezone)
  • 2. by setting the clock 4 hours back
  • Both will make my computer's clock display 10 AM, but if I use option 2, I actually set my clock to an instant 4 hours in the past (10 AM in London). Not only this is wrong, it'll also lead to more problems when BST ends.
  • That's what happens when I change the timestamp. It might "work" at a first glance, but changing the `Date` to a different instant can affect other parts of your code that relies on that (such as code that thinks it's the current time).
  • ---
  • ## Alternatives
  • If you don't mind using an external lib, there are plenty of options, such as [Moment.js](https://momentjs.com/), [date-fns](https://date-fns.org/), [Luxon](https://moment.github.io/luxon/), etc, all with better timezone and formatting support, if compared to `Date`.
  • And in the (near?) future, we'll have the [Temporal API](https://tc39.es/proposal-temporal/docs/index.html). AFAICT, it's not available in any browser yet, but using the current version of the [polyfill](https://www.npmjs.com/package/proposal-temporal), we can see how it's gonna work:
  • ```javascript
  • // current date/time in browser's timezone
  • let now = Temporal.now.zonedDateTimeISO();
  • if (now.hour >= 20) {
  • // add is a timezone aware, DST-safe operation
  • now = now.add({ days: 1 });
  • }
  • // toString() by default returns the date in ISO 8601 format
  • let formatted = now.toPlainDate().toString();
  • ```
  • Although it looks similar to `Date`, it internally deals with all the troubles of date arithmetic when timezones (and DST changeovers) are involved.
  • <sup>But this proposal is still in experimental stage. As soon as most major browsers implement it, I'll update this answer accordingly.</sup>
  • First of all, we need to understand what a [JavaScript `Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) actually is. And surprisingly, it's not exactly a date (at least not in terms of having unique values for day, month, year, hour, minute and second).
  • A JavaScript `Date` actually represents a [timestamp](https://www.unixtimestamp.com/). More precisely, [according to the language specification](https://tc39.es/ecma262/#sec-date-objects), a `Date`'s value is the number of milliseconds since [Unix Epoch](https://en.wikipedia.org/wiki/Unix_time) (which, in turn, corresponds to `1970-01-01T00:00:00Z` - January 1<sup>st</sup>, 1970, at midnight in [UTC](https://en.wikipedia.org/wiki/UTC)).
  • Hence, a `Date` represents a specific instant: a point in the timeline. And this same instant corresponds to a different date and time, depending on the timezone you are.
  • Example: by calling `new Date().valueOf()`, you'll get the current timestamp. I've just called that and the result was `1622722404062`, which means that more than 1.6 trillion miliseconds has elapsed since Unix Epoch. If you had ran that code in any computer in the world (assuming they're not misconfigured), at the same instant I did, you'd get this same value.
  • But that same timestamp value corresponds to a different date and time, depending on the timezone you use. So `1622722404062` corresponds to all of the dates/times below:
  • | Where | Corresponds to |
  • |:-------------------|:----------------------------------------------|
  • | São Paulo (Brazil) | June 3<sup>rd</sup> 2021, 09:13:24 AM |
  • | Tokyo (Japan) | June 3<sup>rd</sup> 2021, 09:13:24 **PM** |
  • | Apia (Samoa) | June **4<sup>th</sup>** 2021, **01**:13:24 AM |
  • | UTC | June 3<sup>rd</sup> 2021, **12**:13:24 **PM** |
  • Note that, depending on the timezone, the date and/or time can be completely different. That's a crucial thing to understand how `Date` works: all the date/times above correspond to the same timestamp (`1622722404062`). Hence, a `Date` object with such timestamp value actually represents all of them.
  • And that's the confusing part: when you use the _getters_ (such as `getDate()`, `getHours()`, etc), the value returned considers the browser's timezone (whatever it's configured in it: some might get that config from the OS, some might override it - it doesn't matter how it's done, that config will be used in the end).
  • But some methods return the value in UTC: that's the case of `toISOString()`. There are also "UTC getters", such as `getUTCDate()` and `getUTCHours`, which return the values according to UTC.
  • Anyway, mixing UTC and non-UTC methods is what probably caused this confusion, specially if you're in that range of hours when your local time corresponds to the next - or previous - UTC day (based on the [other answer](https://software.codidact.com/posts/281996#answer-281996), that seems to be the case).
  • ---
  • Just for the sake of completeness, to check if it's after 08:00 PM, you should test `now.getHours() >= 20` (not `>`). Hours are zero based, but zero means midnight, 1 means 01:00 AM, and so on (hence, 20 means 08:00 PM, not 09:00 PM as the [other answer](https://software.codidact.com/posts/281996#answer-281996) said).
  • And to get the date formatted, using local timezone values instead of UTC, you'll have to do it manually:
  • ```javascript
  • function pad(value) {
  • return value.toString().padStart(2, '0');
  • }
  • let now = new Date();
  • if (now.getHours() >= 20) {
  • now.setDate(now.getDate() + 1);
  • }
  • let formatted = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
  • ```
  • Don't forget to add 1 to the month, as months in JavaScript's `Date` are annoyingly zero-based (January is zero, February is 1, etc).
  • ---
  • ### How to NOT do it
  • If you search enough in the internet, you'll certainly find someone suggesting to change the timestamp value, by adding or subtracting the respective timezone offset (the difference from UTC). Something like this:
  • ```javascript
  • // don't do this
  • function format(date) {
  • let offset = date.getTimezoneOffset() / 60;
  • date.setTime(date.getTime() - offset * 3600 * 1000);
  • return date.toISOString().slice(0, 10);
  • }
  • ```
  • This code changes the timestamp, by subtracting the offset from it, making `toISOString` return the "correct" string. Although it "works", this is not the best solution, because when you change the timestamp, you're changing the instant that the `Date` represents.
  • To make an analogy, now it's 10 AM in Brazil and and 2 PM in London (it's [British Summer Time (BST)](https://www.timeanddate.com/time/zones/bst)). Let's suppose that my computer is misconfigured with London's timezone, so it displays 2 PM. There are two ways I could fix it:
  • 1. by setting the timezone to Brazil (actually, to São Paulo, because the country has more than one timezone)
  • 2. by setting the clock 4 hours back
  • Both will make my computer's clock display 10 AM, but if I use option 2, I actually set my clock to an instant 4 hours in the past (10 AM in London). Not only this is wrong, it'll also lead to more problems when BST ends.
  • That's what happens when I change the timestamp. It might "work" at a first glance, but changing the `Date` to a different instant can affect other parts of your code that relies on that (such as code that thinks it's the current time).
  • ---
  • ## Alternatives
  • If you don't mind using an external lib, there are plenty of options, such as [Moment.js](https://momentjs.com/), [date-fns](https://date-fns.org/), [Luxon](https://moment.github.io/luxon/), etc, all with better timezone and formatting support, if compared to `Date`.
  • And in the (near?) future, JavaScript will have the [native Temporal API](https://tc39.es/proposal-temporal/docs/index.html). AFAICT, it's not available in any browser yet, but using the current version of the [polyfill](https://www.npmjs.com/package/proposal-temporal), we can see how it's gonna work:
  • ```javascript
  • // current date/time in browser's timezone
  • let now = Temporal.now.zonedDateTimeISO();
  • if (now.hour >= 20) {
  • // add is a timezone aware, DST-safe operation
  • now = now.add({ days: 1 });
  • }
  • // toString() by default returns the date in ISO 8601 format
  • let formatted = now.toPlainDate().toString();
  • ```
  • Although it looks similar to `Date`, it internally deals with all the troubles of date arithmetic when timezones (and DST changeovers) are involved.
  • <sup>But this proposal is still in experimental stage. As soon as most major browsers implement it, I'll update this answer accordingly.</sup>
#2: Post edited by user avatar hkotsubo‭ · 2021-06-03T14:31:16Z (over 3 years ago)
  • First of all, we need to understand what a JavaScript `Date` actually is. And surprisingly, it's not exactly a date (at least not in terms of having unique values for day, month, year, hour, minute and second).
  • A JavaScript `Date` actually represents a [timestamp](https://www.unixtimestamp.com/). More precisely, [according to the language specification](https://tc39.es/ecma262/#sec-date-objects), a `Date`'s value is the number of milliseconds since [Unix Epoch](https://en.wikipedia.org/wiki/Unix_time) (which, in turn, corresponds to `1970-01-01T00:00:00Z` - January 1<sup>st</sup>, 1970, at midnight in [UTC](https://en.wikipedia.org/wiki/UTC)).
  • Hence, a `Date` represents a specific instant: a point in the timeline. And this same instant corresponds to a different date and time, depending on the timezone you are.
  • Example: by calling `new Date().valueOf()`, you'll get the current timestamp. I've just called that and the result was `1622722404062`, which means that more than 1.6 trillion miliseconds has elapsed since Unix Epoch. If you had ran that code in any computer in the world (assuming they're not misconfigured), at the same instant I did, you'd get this same value.
  • But that same timestamp value corresponds to a different date and time, depending on the timezone you use. So `1622722404062` corresponds to all of the dates/times below:
  • | Where | Corresponds to |
  • |:-------------------|:----------------------------------------------|
  • | São Paulo (Brazil) | June 3<sup>rd</sup> 2021, 09:13:24 AM |
  • | Tokyo (Japan) | June 3<sup>rd</sup> 2021, 09:13:24 **PM** |
  • | Apia (Samoa) | June **4<sup>th</sup>** 2021, **01**:13:24 AM |
  • | UTC | June 3<sup>rd</sup> 2021, **12**:13:24 **PM** |
  • Note that, depending on the timezone, the date and/or time can be completely different. That's a crucial thing to understand how `Date` works: all the date/times above correspond to the same timestamp (`1622722404062`). Hence, a `Date` object with such timestamp value actually represents all of them.
  • And that's the confusing part: when you use the _getters_ (such as `getDate()`, `getHours()`, etc), the value returned considers the browser's timezone (whatever it's configured in it: some might get that config from the OS, some might override it - it doesn't matter how it's done, that config will be used in the end).
  • But some methods return the value in UTC: that's the case of `toISOString()`. There are also "UTC getters", such as `getUTCDate()` and `getUTCHours`, which return the values according to UTC.
  • Anyway, mixing UTC and non-UTC methods is what probably caused this confusion, specially if you're in that range of hours when your local time corresponds to the next UTC day (based on the [other answer](https://software.codidact.com/posts/281996#answer-281996), that seems to be the case).
  • ---
  • Just for the sake of completeness, to check if it's after 08:00 PM, you should test `now.getHours() >= 20`. Hours are zero based, but zero means midnight, 1 means 01:00 AM, and so on (so 20 means 08:00 PM, not 09:00 PM as the [other answer](https://software.codidact.com/posts/281996#answer-281996) said).
  • And to get the date formatted, using local timezone values instead of UTC, you'll have to do it manually:
  • ```javascript
  • function pad(value) {
  • return value.toString().padStart(2, '0');
  • }
  • let now = new Date();
  • if (now.getHours() >= 20) {
  • now.setDate(now.getDate() + 1);
  • }
  • let formatted = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
  • ```
  • Don't forget to add 1 to the month, as months in JavaScript's `Date` are annoyingly zero-based (January is zero, February is 1, etc).
  • ---
  • ### How to NOT do it
  • If you search enough in the internet, you'll certainly find someone suggesting to change the timestamp value, by adding or subtracting the respective timezone offset (the difference from UTC). Something like this:
  • ```javascript
  • // don't do this
  • function format(date) {
  • let offset = date.getTimezoneOffset() / 60;
  • date.setTime(date.getTime() - offset * 3600 * 1000);
  • return date.toISOString().slice(0, 10);
  • }
  • ```
  • This code changes the timestamp, by subtracting the offset from it, making `toISOString` return the "correct" string. Although it "works", this is not the best solution, because when you change the timestamp, you're changing the instant that the `Date` represents.
  • To make an analogy, now it's 10 AM in Brazil and and 2 PM in London (it's [British Summer Time (BST)](https://www.timeanddate.com/time/zones/bst)). Let's suppose that my computer is misconfigured with London's timezone, so it displays 2 PM. There's 2 ways I could fix it:
  • 1. by setting the timezone to Brazil (actually, to São Paulo, because the country has more than one timezone)
  • 2. by changing the clock 4 hours back
  • Both will make my computer's clock display 10 AM, but if I use option 2, I actually set my clock to an instant 4 hours in the past (10 AM in London). Not only this is wrong, it'll also lead to more problems when BST ends.
  • That's what happens when I change the timestamp. It might "work", but changing the date to a different instant can affect other parts of your code that relies on that (such as code that thinks it's the current time).
  • ---
  • ## Alternatives
  • If you're willing to use an external lib, there are plenty of options, such as [Moment.js](https://momentjs.com/), [date-fns](https://date-fns.org/), [Luxon](https://moment.github.io/luxon/), etc, all with better timezone and formatting support, if compared to `Date`.
  • And in the (near?) future, we'll have the [Temporal API](https://tc39.es/proposal-temporal/docs/index.html). AFAICT, it's not available in any browser yet, but using the current version of the [polyfill](https://www.npmjs.com/package/proposal-temporal), we can see how it's gonna work:
  • ```javascript
  • // current date/time in browser's timezone
  • let now = Temporal.now.zonedDateTimeISO();
  • if (now.hour >= 20) {
  • // add is a timezone aware, DST-safe operation
  • now = now.add({ days: 1 });
  • }
  • // toString() by default returns the date in ISO 8601 format
  • let formatted = now.toPlainDate().toString();
  • ```
  • Although it looks similar to `Date`, it deals with all the troubles of date arithmetic when timezones (and DST changeovers) are involved.
  • <sup>But this proposal is still in experimental stage. As soon as most major browsers implement it, I'll update this answer accordingly.</sup>
  • First of all, we need to understand what a [JavaScript `Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) actually is. And surprisingly, it's not exactly a date (at least not in terms of having unique values for day, month, year, hour, minute and second).
  • A JavaScript `Date` actually represents a [timestamp](https://www.unixtimestamp.com/). More precisely, [according to the language specification](https://tc39.es/ecma262/#sec-date-objects), a `Date`'s value is the number of milliseconds since [Unix Epoch](https://en.wikipedia.org/wiki/Unix_time) (which, in turn, corresponds to `1970-01-01T00:00:00Z` - January 1<sup>st</sup>, 1970, at midnight in [UTC](https://en.wikipedia.org/wiki/UTC)).
  • Hence, a `Date` represents a specific instant: a point in the timeline. And this same instant corresponds to a different date and time, depending on the timezone you are.
  • Example: by calling `new Date().valueOf()`, you'll get the current timestamp. I've just called that and the result was `1622722404062`, which means that more than 1.6 trillion miliseconds has elapsed since Unix Epoch. If you had ran that code in any computer in the world (assuming they're not misconfigured), at the same instant I did, you'd get this same value.
  • But that same timestamp value corresponds to a different date and time, depending on the timezone you use. So `1622722404062` corresponds to all of the dates/times below:
  • | Where | Corresponds to |
  • |:-------------------|:----------------------------------------------|
  • | São Paulo (Brazil) | June 3<sup>rd</sup> 2021, 09:13:24 AM |
  • | Tokyo (Japan) | June 3<sup>rd</sup> 2021, 09:13:24 **PM** |
  • | Apia (Samoa) | June **4<sup>th</sup>** 2021, **01**:13:24 AM |
  • | UTC | June 3<sup>rd</sup> 2021, **12**:13:24 **PM** |
  • Note that, depending on the timezone, the date and/or time can be completely different. That's a crucial thing to understand how `Date` works: all the date/times above correspond to the same timestamp (`1622722404062`). Hence, a `Date` object with such timestamp value actually represents all of them.
  • And that's the confusing part: when you use the _getters_ (such as `getDate()`, `getHours()`, etc), the value returned considers the browser's timezone (whatever it's configured in it: some might get that config from the OS, some might override it - it doesn't matter how it's done, that config will be used in the end).
  • But some methods return the value in UTC: that's the case of `toISOString()`. There are also "UTC getters", such as `getUTCDate()` and `getUTCHours`, which return the values according to UTC.
  • Anyway, mixing UTC and non-UTC methods is what probably caused this confusion, specially if you're in that range of hours when your local time corresponds to the next - or previous - UTC day (based on the [other answer](https://software.codidact.com/posts/281996#answer-281996), that seems to be the case).
  • ---
  • Just for the sake of completeness, to check if it's after 08:00 PM, you should test `now.getHours() >= 20` (not `>`). Hours are zero based, but zero means midnight, 1 means 01:00 AM, and so on (hence, 20 means 08:00 PM, not 09:00 PM as the [other answer](https://software.codidact.com/posts/281996#answer-281996) said).
  • And to get the date formatted, using local timezone values instead of UTC, you'll have to do it manually:
  • ```javascript
  • function pad(value) {
  • return value.toString().padStart(2, '0');
  • }
  • let now = new Date();
  • if (now.getHours() >= 20) {
  • now.setDate(now.getDate() + 1);
  • }
  • let formatted = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
  • ```
  • Don't forget to add 1 to the month, as months in JavaScript's `Date` are annoyingly zero-based (January is zero, February is 1, etc).
  • ---
  • ### How to NOT do it
  • If you search enough in the internet, you'll certainly find someone suggesting to change the timestamp value, by adding or subtracting the respective timezone offset (the difference from UTC). Something like this:
  • ```javascript
  • // don't do this
  • function format(date) {
  • let offset = date.getTimezoneOffset() / 60;
  • date.setTime(date.getTime() - offset * 3600 * 1000);
  • return date.toISOString().slice(0, 10);
  • }
  • ```
  • This code changes the timestamp, by subtracting the offset from it, making `toISOString` return the "correct" string. Although it "works", this is not the best solution, because when you change the timestamp, you're changing the instant that the `Date` represents.
  • To make an analogy, now it's 10 AM in Brazil and and 2 PM in London (it's [British Summer Time (BST)](https://www.timeanddate.com/time/zones/bst)). Let's suppose that my computer is misconfigured with London's timezone, so it displays 2 PM. There are two ways I could fix it:
  • 1. by setting the timezone to Brazil (actually, to São Paulo, because the country has more than one timezone)
  • 2. by setting the clock 4 hours back
  • Both will make my computer's clock display 10 AM, but if I use option 2, I actually set my clock to an instant 4 hours in the past (10 AM in London). Not only this is wrong, it'll also lead to more problems when BST ends.
  • That's what happens when I change the timestamp. It might "work" at a first glance, but changing the `Date` to a different instant can affect other parts of your code that relies on that (such as code that thinks it's the current time).
  • ---
  • ## Alternatives
  • If you don't mind using an external lib, there are plenty of options, such as [Moment.js](https://momentjs.com/), [date-fns](https://date-fns.org/), [Luxon](https://moment.github.io/luxon/), etc, all with better timezone and formatting support, if compared to `Date`.
  • And in the (near?) future, we'll have the [Temporal API](https://tc39.es/proposal-temporal/docs/index.html). AFAICT, it's not available in any browser yet, but using the current version of the [polyfill](https://www.npmjs.com/package/proposal-temporal), we can see how it's gonna work:
  • ```javascript
  • // current date/time in browser's timezone
  • let now = Temporal.now.zonedDateTimeISO();
  • if (now.hour >= 20) {
  • // add is a timezone aware, DST-safe operation
  • now = now.add({ days: 1 });
  • }
  • // toString() by default returns the date in ISO 8601 format
  • let formatted = now.toPlainDate().toString();
  • ```
  • Although it looks similar to `Date`, it internally deals with all the troubles of date arithmetic when timezones (and DST changeovers) are involved.
  • <sup>But this proposal is still in experimental stage. As soon as most major browsers implement it, I'll update this answer accordingly.</sup>
#1: Initial revision by user avatar hkotsubo‭ · 2021-06-03T13:30:45Z (over 3 years ago)
First of all, we need to understand what a JavaScript `Date` actually is. And surprisingly, it's not exactly a date (at least not in terms of having unique values for day, month, year, hour, minute and second).

A JavaScript `Date` actually represents a [timestamp](https://www.unixtimestamp.com/). More precisely, [according to the language specification](https://tc39.es/ecma262/#sec-date-objects), a `Date`'s value is the number of milliseconds since [Unix Epoch](https://en.wikipedia.org/wiki/Unix_time) (which, in turn, corresponds to `1970-01-01T00:00:00Z` - January 1<sup>st</sup>, 1970, at midnight in [UTC](https://en.wikipedia.org/wiki/UTC)).

Hence, a `Date` represents a specific instant: a point in the timeline. And this same instant corresponds to a different date and time, depending on the timezone you are.

Example: by calling `new Date().valueOf()`, you'll get the current timestamp. I've just called that and the result was `1622722404062`, which means that more than 1.6 trillion miliseconds has elapsed since Unix Epoch. If you had ran that code in any computer in the world (assuming they're not misconfigured), at the same instant I did, you'd get this same value.

But that same timestamp value corresponds to a different date and time, depending on the timezone you use. So `1622722404062` corresponds to all of the dates/times below:

| Where              | Corresponds to                                |
|:-------------------|:----------------------------------------------|
| São Paulo (Brazil) | June 3<sup>rd</sup> 2021, 09:13:24 AM         |
| Tokyo (Japan)      | June 3<sup>rd</sup> 2021, 09:13:24 **PM**     |
| Apia (Samoa)       | June **4<sup>th</sup>** 2021, **01**:13:24 AM |
| UTC                | June 3<sup>rd</sup> 2021, **12**:13:24 **PM** |

Note that, depending on the timezone, the date and/or time can be completely different. That's a crucial thing to understand how `Date` works: all the date/times above correspond to the same timestamp (`1622722404062`). Hence, a `Date` object with such timestamp value actually represents all of them.

And that's the confusing part: when you use the _getters_ (such as `getDate()`, `getHours()`, etc), the value returned considers the browser's timezone (whatever it's configured in it: some might get that config from the OS, some might override it - it doesn't matter how it's done, that config will be used in the end).

But some methods return the value in UTC: that's the case of `toISOString()`. There are also "UTC getters", such as `getUTCDate()` and `getUTCHours`, which return the values according to UTC.

Anyway, mixing UTC and non-UTC methods is what probably caused this confusion, specially if you're in that range of hours when your local time corresponds to the next UTC day (based on the [other answer](https://software.codidact.com/posts/281996#answer-281996), that seems to be the case).

---
Just for the sake of completeness, to check if it's after 08:00 PM, you should test `now.getHours() >= 20`. Hours are zero based, but zero means midnight, 1 means 01:00 AM, and so on (so 20 means 08:00 PM, not 09:00 PM as the [other answer](https://software.codidact.com/posts/281996#answer-281996) said).

And to get the date formatted, using local timezone values instead of UTC, you'll have to do it manually:

```javascript
function pad(value) {
    return value.toString().padStart(2, '0');
}

let now = new Date();
if (now.getHours() >= 20) {
   now.setDate(now.getDate() + 1);
}

let formatted = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
```

Don't forget to add 1 to the month, as months in JavaScript's `Date` are annoyingly zero-based (January is zero, February is 1, etc).

---
### How to NOT do it

If you search enough in the internet, you'll certainly find someone suggesting to change the timestamp value, by adding or subtracting the respective timezone offset (the difference from UTC). Something like this:

```javascript
// don't do this
function format(date) {
    let offset = date.getTimezoneOffset() / 60;
    date.setTime(date.getTime() - offset * 3600 * 1000);
    return date.toISOString().slice(0, 10);
}
```

This code changes the timestamp, by subtracting the offset from it, making `toISOString` return the "correct" string. Although it "works", this is not the best solution, because when you change the timestamp, you're changing the instant that the `Date` represents.

To make an analogy, now it's 10 AM in Brazil and and 2 PM in London (it's [British Summer Time (BST)](https://www.timeanddate.com/time/zones/bst)). Let's suppose that my computer is misconfigured with London's timezone, so it displays 2 PM. There's 2 ways I could fix it:

1. by setting the timezone to Brazil (actually, to São Paulo, because the country has more than one timezone)
2. by changing the clock 4 hours back

Both will make my computer's clock display 10 AM, but if I use option 2, I actually set my clock to an instant 4 hours in the past (10 AM in London). Not only this is wrong, it'll also lead to more problems when BST ends.

That's what happens when I change the timestamp. It might "work", but changing the date to a different instant can affect other parts of your code that relies on that (such as code that thinks it's the current time).


---

## Alternatives

If you're willing to use an external lib, there are plenty of options, such as [Moment.js](https://momentjs.com/), [date-fns](https://date-fns.org/), [Luxon](https://moment.github.io/luxon/), etc, all with better timezone and formatting support, if compared to `Date`.

And in the (near?) future, we'll have the [Temporal API](https://tc39.es/proposal-temporal/docs/index.html). AFAICT, it's not available in any browser yet, but using the current version of the [polyfill](https://www.npmjs.com/package/proposal-temporal), we can see how it's gonna work:

```javascript
// current date/time in browser's timezone
let now = Temporal.now.zonedDateTimeISO();
if (now.hour >= 20) {
    // add is a timezone aware, DST-safe operation
    now = now.add({ days: 1 });
}
// toString() by default returns the date in ISO 8601 format
let formatted = now.toPlainDate().toString();
```

Although it looks similar to `Date`, it deals with all the troubles of date arithmetic when timezones (and DST changeovers) are involved.

<sup>But this proposal is still in experimental stage. As soon as most major browsers implement it, I'll update this answer accordingly.</sup>