6 ms·
This article lists several of the absurdities of the Date constructor, but only barely touches on the most unforgivable one. The example from the article is:
by tshaddox 9mo ago
This article lists several of the absurdities of the Date constructor, but only barely touches on the most unforgivable one. The example from the article is:
// Unless, of course, you separate the year, month, and date with hyphens.
// Then it gets the _day_ wrong.
console.log( new Date('2026-01-02') );
// Result: Date Thu Jan 01 2026 19:00:00 GMT-0500 (Eastern Standard Time)
In this example, the day is "wrong" because the constructor input is being interpreted as midnight UTC on January 2nd, and at that instantaneous point in time, it is 7pm on January 1st in Eastern Standard Time (which is the author's local time zone).
What's actually happening here is a comedy of errors. JavaScript is interpreting that particular string format ("YYYY-MM-DD") as an ISO 8601 date-only form. ISO 8601 specifies that if no time zone designator is provided, the time is assumed to be in local time. The ES5 spec authors intended to match ISO 8601 behavior, but somehow accidentally changed this to 'The value of an absent time zone offset is “Z”' (UTC).
Years later, they had realized their mistakes, and attempted to correct it in ES2015. And you can probably predict what happened. When browsers shipped the correct behavior, they got too many reports about websites which were relying on the previous incorrect behavior. So it got completely rolled back, sacrificed to the altar of "web compatibility."
For more info, see the "Broken Parser" section towards the bottom of this article:
https://maggiepint.com/2017/04/11/fixing-javascript-date-web-compatibility-and-reality/ https://maggiepint.com/2017/04/11/fixing-javascript-date-web...
- teiferer 9mo ago> sacrificed to the altar of "web compatibility." What should they have done instead? Force everybody to detect browser versions and branch based on that, like in the olden days of IE5? (Serious question, maybe I'm overlooking some smart trick.)
- tshaddox 9mo agoI agree with the "don't break the web" design principle, but I sometimes disagree with precisely where TC39 draws the line. There is obviously a cost to breaking old, unchanging websites. But there's also a cost to allowing old, unchanging websites to hold the entire web hostage. Balancing those costs is a subjective matter. As far as I know, TC39 doesn't have any clear guidelines about how many websites or how many users must be affected in order to reject a proposed change to JavaScript behavior. Clearly there are breaking changes that are so insignificant that TC39 should ignore them (imagine a website with some JavaScript that simply iterates over every built-in API and crashes if any of them ever change).
- mejutoco 9mo agoHave an optional parameter to opt in to the old behaviour and keep the new correct behaviour the default (without the parameter) seems like a decent choice.
- stevula 9mo agoTo preserve backwards compatibility and not require all those old sites to update, the legacy behavior would have to be the default, with opt-in for the new behavior.
- mejutoco 9mo agoThat is the opposite approach. Also an option. One could also deprecate the call without parameter and force always a parameter with which behaviour. The deprecation could last enough time that those websites would have been rewritten multiple times ;)
- sfink 9mo agoThe control interface burned into your hardware device will not have been rewritten. And it's not like you can have a flag day where everyone switches over, so the lifespan of those hardware devices isn't that relevant. Backwards compatibility is a large part of the point of the Web. You could version everything at whatever granularity you like, but over time that accumulates ("bug 3718938: JS gen24 code incorrectly handles Date math as if it were gen25-34", not to mention libraries that handle some versions but not others and then implicitly pass their expectations around via the objects they create so your dependency resolver has to look at the cross product of versions from all your depencies...)
- mejutoco 9mo agoThere is no free lunch. A deprecation warning lasting a decade before erroring will break less that some css boxing models and strict mode in many browsers.
- marcosdumay 9mo agoBrowsers should version their languages. They should say "if you use <html version="5.2"> or bigger, this is the behavior". Somehow, the standard groups decided to remove the versioning that was there.
- cogman10 9mo agoThe decided not to have it there because they didn't like the idea of maintaining version 4.0 forever in their engines. That's basically why they never did anything like "use strict" again. IMO, that's a bad choice. Giving yourself the ability to have new behavior and features based on a version is pretty natural and how most programming languages evolve. Having perpetual backwards and fowards compatibility at all times is both hard to maintain and makes it really hard to fix old mistakes. The only other reason they might have chosen this route is because it's pretty hard to integrate the notion of compatibility levels into minifiers.
- dahauns 9mo agoNah, that's not a "sacrifice", but the only sane way. In the ideal case, clearly document the constructor with a warning that it's not ISO conformant and offer a ISO conformant alternative. In my (unfortunate) experience, DateTime/Timezone handling is one of the things most prone to introduce sneaky, but far-reaching bugs as it is. Introducing such a behaviour change that (usually) won't fail-fast, will often seemingly continue working as before until it doesn't and is deceptively tricky to debug/pinpoint/fix ist just asking for a fast lane into chaos. And even with JS going the extra mile on backwards compatibility, I don't think most other languages would introduce that kind of breaking change in that way either.
- netghost 9mo agoIf this is comedy, sign me up for tragedy. This feels like something that must be the root of innumerable small and easily overlooked bugs out there.
- tshaddox 9mo agoIt's a common source of off-by-one date formatting bugs in client-rendered web apps, particularly ones that pass around "YYYY-MM-DD" date strings (common for OpenAPI JSON APIs). const dateStringFromApiResponse = "2026-01-12"; const date = new Date(dateStringFromApiResponse); const formatter = new Intl.DateTimeFormat('en-US', { dateStyle: 'long' }); formatter.format(new Date("2026-01-12")); // 'January 11, 2026'
- jazzyjackson 9mo agoI'm having flashbacks to writing Power Automate expressions to reconcile Dates passed from Outlook metadata to Excel Basically just extracting numbers from string index or regex and rearranging them to a string Excel would recognize
- OptionOfT 9mo agoI very much remember coding a function that split the string on their components and then rebuild them to ensure the date was created without time zone. Sometimes a date is just a date. Your birthday is on a date, it doesn't shift by x hours because you moved to another state. The old Outlook marked birthdays as all-day events, but stored the value with time-zone, meaning all birthdays of people whose birthday I stored in Belgium were now shifted as I moved to California...
- eszed 9mo agoI mean... That's kinda how it works? More than once I've halfway forgotten birthdays of friends who live in timezones to my east, and then sent them a message saying "Happy birthday! (It still is where I am, lol)". I'm not necessarily defending the implementation, just pointing out another way in which time is irreducibly ambiguous and cursed.
- whiskey-one 9mo agoA reminder associated with the birthday can and should be changed if I change time zones. But the birthday date didn’t change so it shouldn’t move to a different day.
- skissane 9mo ago> But the birthday date didn’t change so it shouldn’t move to a different day. But it does. My brother moved to the US for a few years. So we’d send him birthday wishes on the day of his birthday (Australia time), and he’d get them the day before his birthday (his time). Now he’s moved back to Australia, the same thing happens in reverse-he gets birthday wishes from his American friends the day after his birthday. My wife has lots of American friends on Facebook (none of whom she knows personally, all people she used to play Farmville with)-and she has them wishing her a happy birthday the day after her birthday too. Maybe she’s doing the same to them in reverse.
- thayne 9mo ago
- JeanMeche 9mo agoYou might want to play with https://jsdate.wtf/ https://jsdate.wtf/ One can't fathom how weird JS Date can be.
- publicdebates 9mo agoGuessed 2 of the first 3 questions. Got to question 4 and gave up: new Date("not a date") 1) Invalid Date 2) undefined 3) Throws an error 4) null There's literally no way of guessing this crap. It's all random.
- winstonp 9mo agothe new Date() constructor is an amalgamation of like 5 different specs, and unless the input matches one of them, which one kicks in is up to the implementer's choice
- marcosdumay 9mo agoThe choice here is really surprising. I was half-expecting NaN, that you omitted. Is there any other instance of the standard JS library returning an error object instead of throwing one? I can't think of any.
- jazzyjackson 9mo agoI think NaN itself is a bit of an error object, especially in how it's passed through subsequent math functions, which is a different choice than throwing up. But besides that I think you're right, Invalid Date is pretty weird and I somehow never ran into it. One consequence is you can still call Date methods on the invalid date object and then you get NaN from the numeric results.
- WorldMaker 9mo agoThe fun trick is that Invalid Date is still a Date: > let invalid = new Date('not a date') > invalid Invalid Date > invalid instanceof Date true You were half-correct on expecting NaN, it's the low level storage of Invalid Date: > invalid.getTime() NaN Invalid Date is just a Date with the "Unix epoch timestamp" of NaN. It also follows NaN comparison logic: > invalid === new Date(NaN) false It's an interesting curio directly related to NaN.
- sholladay 9mo agoPersonally, I like that UTC is the default time zone. Processing of dates should happen in a standardized time zone. It’s only when you want to display it that the date should become local.
- lysium 9mo agoThis will result in incorrect behavior when, between converting to UTC and back to the original timezone, the timezone database has changed, which happens more often than you think.
- GoblinSlayer 9mo agoIf time is stored in UTC, the result is correct even if the timezone database is corrupted, because timezone is only metadata and doesn't affect time.
- drysart 9mo agoDepends what you're actually storing. There are plenty of cases where the timezone is not metadata; it defines how the datetime should be interpreted. For example: your local mom and pop corner store's daily opening and closing times. Storing those in UTC is not correct because mom and pop don't open and close their store based on UTC time. They open and close it based on the local time zone.
- ovao 9mo agoStoring them in UTC is valid here also, but their IANA time zone string should also be stored ‘somewhere’.
- just6979 9mo agoYou don't need to store the timezone anywhere, you just need to know the current local timezone when the stored UTC time is used. And that's why storing in UTC is better, because it only takes one conversion to represent it in some arbitrary local time. If you stored it as a local time (ie: with TZ), then if it's ever later translated to a different local time (different TZ), you're now dealing with all the quirks of 2 different timezones. It's great way to be off by some multiple of 15 minutes, or even a day or two! Heck, even if it's the same exact location, storing in local time can still require conversion if that location uses daylight savings! You're never safe from needing to adapt to timezones, so storing datetimes in the most universal format is pretty much always the best thing to do.
- no_wizard 9mo ago>So it got completely rolled back, sacrificed to the altar of "web compatibility." This is why I don't understand the lack of directives. 'use strict'; at the top of a file was ubiquitous for a long time and it worked. It didn't force rolling back incompatibilities, it let you opt into a stricter parsing of JavaScript. It would have been nice for other wide changes like this to have like a 'strict datetime'; directive which would opt you into using this corrected behavior. They couldn't and shouldn't do this sort of thing for all changes, but for really major changes to the platform this would be an improvement. Or they could go all in on internal modules, like how you can import `node:fs` now. They could include corrected versions of globals like `import Date from 'browser:date';` has corrected behavior, for example
- hnlmorg 9mo agoThis was the approach Perl took and much as I love(d) that language, it do get pretty out of hand after a while if you wanted to adopt any newer or stricter language features.
- AceJohnny2 9mo ago> it do get pretty out of hand after a while if you wanted to adopt any newer or stricter language features. How does it get out of hand? FWIW, I just do `use v5.32;` or similar to opt-in to everything from that version. https://perldoc.perl.org/functions/use#use-VERSION https://perldoc.perl.org/functions/use#use-VERSION Of course, if you instead want to pick-and-choose features, then I can see the list growing large.
- thayne 9mo agoMaybe something like rust's editions, where you can opt into a set of breaking changes made at a certain time.
- deleted 9mo ago[deleted]
- WorldMaker 9mo agoTo be fair, the new opt-in "use strict" here is "switch to Temporal". It's a new, stricter namespace object. Old Date code gets the old Date code quirks, new code gets the nice new Temporal API. Internal modules would be handy in theory to maybe keep from having to dig through a thesaurus every time browsers decide to add a new, stricter version of an older API. Internal modules have even been proposed to TC-39 as a recommended way to continue to expand the JS API. Last I checked on that proposal it was stuck behind several concerns including: 1. Feature detection: detecting if Temporal available is as easy as `if ('Temporal' in globalThis) {}`, but detecting if a module import is missing is a bit harder. Right now the standard is that loading a module fails with an Error if one of its imports fails. You can work around that by doing a dynamic import inside a try/catch, but that's a lot of extra boilerplate compared to `const thingINeed = 'someApi' in globalThis ? someApi() : someApiPolyfill()`. I've seen multiple proposals on that front from extensions to import maps and `with { }` options on the import itself. 2. Bikeshedding (and lots of it): defining a URI scheme like `browser:` or `standard:` takes a bunch of thought on how you expand it. If it is just `browser:some-api` you run the risk of eventually polluting all the easy names in the exact way people worry about the risk of over-polluting `globalThis` (and in the way that it can be weirdly hard to find an available one-word name on npm), you've just moved the naming problem from one place to the other. On the other side, if you go down the road of something like `es-standard:https://tc39.es/ecma262/2025/v1/final-draft/Temporal https://tc39.es/ecma262/2025/v1/final-draft/Temporal`, even (especially) assuming users would mostly importmap that to something shorter you've recreated XMLNS URIs in a funny new hat and people who use JS all certainly have plenty of opinions on XMLNS URIs, many are very vocal in their hatred of it, but also they came out of a strong backwards incompatibility fixing desire exactly like this. (As they say time is a flat circle.)
- cush 9mo agoMaggie is a killer dev. Momentjs probably saved humanity millions of hours of collective coding and debugging
- deleted 9mo ago[deleted]
- GoblinSlayer 9mo agoLocal time is unparsable, and this case is only human readable, because humans can handle ambiguity ad hoc. Parsing it as UTC is a reasonable default for a machine parser, at least the only workable one.