1987 1897 1465 1758 1697 1350 1714 1871 1470 1213 1073 1910 1205 1930 1324 1179 1209 1860 1042 1202 1454 1893 1067 1837 1222 1985 1576 1061 1120 1866 1871 1668 1398 1727 1481 1440 1461 1847 1194 1793 1918 1489 1620 1230 1192 1453 1960 1287 1690 1837 1090 1612 1229 1271 1213 1635 1933 1445 1858 1679 1131 1779 1938 1823 1333 1231 1627 1717 1940 1503 1697 1590 1022 1350 1348 1800 1742 1900 1627 1072 1966 1017 1479 1719 1134 1700 1160 1922 1959 1232 1789 1116 1134 1354 1482 1737 1277 1001 1540 Why did Safari go batman? [blog] | PHPnews.io

PHPnews.io

Why did Safari go batman? [blog]

Written by Remy Sharp / Original link on Jun. 29, 2022

This week I released the first round of tickets for ffconf 2022 an in a last minute change (literally 20 minutes before the tickets went live), I decided to add a live countdown which would switch out and then show the "Buy Now" button at 10am.

Except, of course, Safari didn't work.

But feel free to skip all this to the TLDR!

I maintain JavaScript date API is terrible!

This kind of problem isn't entirely Safari's fault (though I'm no fan), but it's because the Date object, specifically parsing strings, is terrible and inconsistent and it's been terrible from the outset (or certainly in my opinion!).

In particularly though, is when you want to set a date from a string. The documentation you'll find around the web says to avoid Date.parse but going through new Date("…") doesn't come with the same warning - though it has the same pitfalls.

For instance, can you spot which of these values result in null?

new Date("Sept 13 2022 9:00");
new Date("Sept 13, 2022 9:00");
new Date("Sept 13, 2022 9:00am");
new Date("Sept 13 22 9:00");
new Date("Sept 13 '22 9:00");
new Date("13 09 22 9:00");
new Date("12 09 22 9:00");

It's probably not obvious - plus, if you're American you'll read those last dates differently to a European (whom I'll go ahead and say "are reading it right"!).

Use one date string, and only one

Being a developer, I tend to represent my dates as 2022-06-29. JavaScript can handle that.

So I set the tickets to go live with:

new Date('2022-06-27 10:00:00')

This worked in my browser of choice (Firefox) and I gave it a test on mobile (Brave) which given the simplicity (oh…such a fool) and Brave using the Chromium browser engine, I figured it was safe (aka: foreshadowing).

I immediately spotted the first bug - timezones! Those people not in the UK, say perhaps, Germany, were already past 10am, so it would display the button.

A quick change, test and release later:

new Date('2022-06-27 10:00:00+0100')

Now it parses as (effectively) "10am at BST". And yes, if you're reading this spotting the problems, it's easy in retrospect!

First things first: the string format is … a fluke. It's readable, but not a standard that JavaScript ideally wants.

The other issue is dates really just want to be in UTC - I'll return to this.

My date constructor above, in Safari failed with:

new Date('2022-06-27 10:00:00+0100') === null

This is because that space, between the date and time, is invalid. It's missing a T. The world of difference.

new Date('2022-06-27T10:00:00+0100') !== null

So the take away, if anything, is that the date format must follow the ISO 8601 extended format - importantly, that T needs to be in between.

Using UTC over timezone adjust

The +0100 always felt brittle to me, but the closest to "right way" is to use UTC dates across the board (since the time is location dependent).

This is solved by adding a simple Z at the end of the string (and not with the +nnnn at the end).

It's worth noting that extracting the date data will then localise itself, i.e. the following call gives a value of 10 when run in the UK during British Summer Time:

new Date('2022-06-27T09:00:00Z').getHours() // 10

More importantly, I can use this feature for date comparison. So my countdown code would look like this:

const target = Date.parse("2022-06-27T09:00:00Z");
function update() {
  const now = Date.now();

  if (now > target) {
    showBuyButton();
  } else {
    displayRemainingTime(target - now);
    requestAnimationFrame(update);
  }
}
update();

Regardless which country this page is loaded in, UK included, it will run a countdown to the right time - oh, and it'll work in Safari!

Originally published on Remy Sharp's b:log

remysharp

« What’s New in React Native 0.69 - Improve Git monorepo performance with a file system monitor »