Today we got bitten by this behavior, is this to be expected?
(update {:timestamp "2018-01-01"} :timestamp js/Date.)
; =>
{:timestamp
"Wed Aug 27 2025 12:36:42 GMT+0000 (Coordinated Universal Time)"}
(update {:timestamp "2018-01-01"} :timestamp #(js/Date. %))
; =>
{:timestamp #inst "2018-01-01T00:00:00.000-00:00"}Oh btw this is with: org.clojure/clojurescript 1.12.42
cljs.user=> (identical? js/Date. js/Date)
true
So the former in your code works as a function and the latter works as a constructor.In CLJ, something like java.util.Date. is a syntax error. So I'd say js/Date. should never be used as a value.
It is still really hard to not get bitten by this because this does work:
(-> "2018-01-01" js/Date.)
; =>
#inst "2018-01-01T00:00:00.000-00:00"Ah now I get what you are saying:
(js/Date "2018-01-01")
; =>
"Wed Aug 27 2025 12:52:08 GMT+0000 (Coordinated Universal Time)"
(js/Date. "2018-01-01")
; =>
#inst "2018-01-01T00:00:00.000-00:00"
Still weird that update uses the function and not the constructor
-> is a macro, it rewrites the code in a way so that js/Date. is put in the function call position and is treated as a constructor.
update is a function itself, so it can only use the arguments it receives
I agree that js/Date. here likely should be an error. surprised that it isn't to be honest.
#(js/Date. %) “expands” to (fn [x] (js/Date. x)), which is admittedly non-obvious (which many things are).
The shorthand fn syntax is a tricky thing. That’s often a personal preference, but I frequently avoid it because it’s too noisy on my eyes.
that really isn't relevant here. maybe the issue becomes more apparent written like this. the update call does not expand to (js/Date. "2018-01-01") like -> does. instead it only receives what js/Date. evals to, so more like (let [x js/Date.] (x "2018-01-01"))
so update receives the x and calls it as a function. which I would have expected to fail, but I guess it has some .call semantics which just returns it unmodified or something
(js/Date. ...) is syntax sugar for (new js/Date ...), but js/Date. is just js/Date (not actually creating one, only the reference to the type)
Yeah I guess the actual tricky bit is the fact that non-CLJS “things” might have a different behavior vs “proper” CLJS functions, example:
cljs.user=> (defn my-date [x] "I am a date")
#'cljs.user/my-date
cljs.user=> (type my-date)
#object[Function]
cljs.user=> (update {:timestamp "2018-01-01"} :timestamp my-date)
{:timestamp "I am a date"}
cljs.user=> (update {:timestamp "2018-01-01"} :timestamp #(my-date %))
{:timestamp "I am a date"}
So in that case, both work exactly the same
Also:
cljs.user=> (type js/Date)
#object[Function]
So on the surface, one might assume they would work the same.well, in all cases my-date is a function. so not sure that is comparing the same thing. your statement about "non-cljs things" is also wrong. works exactly the same for cljs
ok
> The `Date()` constructor creates Date objects.
Is that the difference? (My JS fundamentals are not at expert level)
(from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date )
I guess that bit is important: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date#return_value
(js/Date "2018-01-01")
=> "Thu Aug 28 2025 08:55:07 GMT+0200 (Central European Summer Time)"Right
(js/Date. "2018-01-01")
=> #inst "2018-01-01T00:00:00.000-00:00"Yup, so that’s what I meant by “non-cljs” things 🙂
You would not typically encounter such “subtle” creativity in CLJS-proper
these are not the same thing though
Well, sure
(deftype Foo [])
=> cljs.user/Foo
(Foo "x")
------ WARNING - :invoke-ctor --------------------------------------------------
Resource: <eval>:1:1
Cannot invoke type constructor cljs.user/Foo as function
--------------------------------------------------------------------------------
=> nil
(.call Foo "x")
=> nil
(set! Foo -call (fn [x] "oops"))
=> #object [Function]
(.call Foo "x")
=> "oops"something like that is going on I guess
I'm unsure why (js/Date ...) doesn't do what it says in the docs
ah nvm
All I was saying is that it’s not obvious. I don’t have the mental capacity to internalize all the JS subtleties 🙂
> Calling the Date() function (without the new keyword) returns a string representation of the current date and time, exactly as new Date().toString() does.
it does say 🙂
> All I was saying is that it’s not obvious. I don’t have the mental capacity to internalize all the JS subtleties 🙂 Which is why I do Clojure(Script) 😜
> it does say 🙂 They do have exhaustive docs, I’ll give them that 😅
Real page turner, those manuals.
> All I was saying is that it’s not obvious. And to be extra clear, there can be concepts and essential details in many areas (including Clojure) that are non-obvious, but when something is thoughtfully well-designed, there’s often a good reason and rationale behind it. For organically evolved things like JavaScript, many things “just are”, sorta like biological evolution.
Related: you should be careful with using local dates in the date constructor (e.g new Date("2020-01-01")) as it will in fact not result in a local date, but instead a utc date:
> When the time zone offset is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time. The interpretation as a UTC time is due to a historical spec error that was not consistent with ISO 8601 but could not be changed due to web compatibility. See https://maggiepint.com/2017/04/11/fixing-javascript-date-web-compatibility-and-reality/.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format
The "correct" way to handle local dates is to parse the date yourself and use the new Date(year, month, day) constructor - or use a library.
Well, for once, I like the UTC status quo of that detail, at least you get something consistent 🙂
Oh… it differs for date-only and date-time… oh my
Scratch what I said 😅
the closure library has a lot of stuff to help with all this 😉
oh for sure
I like the Closure Library
> Well, for once, I like the UTC status quo of that detail, at least you get something consistent
If your backend returns a LocalDate and you do new Date(dateStr) and then do date.toISOString() when you're posting back to the server, well, then you're screwed.
No debate here, like I corrected myself: > Oh… it differs for date-only and date-time… oh my > Scratch what I said
Yep 🙂 I've been bitten by this before, in subtle ways. The constructor should emit huge warning signs, imo. Footguns everywhere. 😄
I guess I can catch this in clj-kondo
I mean the (update {} :a js/Date.) thing
forget the update. just js/Date., but aware of context since its fine in macros like ->
That would be pretty amazing
maybe there should be a discussion whether this should at least have a compiler warning
Well, the thing is, aren’t both valid, as per the JS docs:
(js/Date)
"Thu Aug 28 2025 01:04:20 GMT-0700 (Pacific Daylight Time)"
cljs.user=> (js/Date.)
#inst "2025-08-28T08:04:20.938-00:00"
cljs.user=> > Calling the Date() function (without the new keyword) returns a string representation of the current date and time, exactly as new Date().toString() does.
valid yes, but completely different semantics
I'm specifically referring to the . at the end. not related to js/Date in any way
Right.
could just be a bug that this even compiles
well… not sure, but is this right, both of those are the same: (one is syntax sugar for the other)
(new js/Date)
#inst "2025-08-28T08:09:42.473-00:00"
cljs.user=> (js/Date.)
#inst "2025-08-28T08:09:44.842-00:00"
cljs.user=> ^^^ this being the “constructor/object” variant
this one is the “function variant”:
cljs.user=> (js/Date)
"Thu Aug 28 2025 01:10:43 GMT-0700 (Pacific Daylight Time)"
Seems to match what the JS docs say?
I'm confused. yes, that is all correct. I'm not talking about (js/Date.) I'm talking about js/Date.. the symbol alone, where the syntax sugar does not trigger
the symbol as the first item in a list is fine with a dot. elsewhere it should likely warn. but because of macros that can't be reliably established and needs compiler help
I mean that (-> "2018" js/Date.) is fine because during macro expansion this turns into (js/Date. "2018"), so the js/Date. is never eval'd alone, but any macro can do that. so not limited to ->
Clj-kondo expands the most common macros but agree that the compiler should detect this
Noted in the issue, Date/new would be nice sugar for these cases
should Date not be referenced as js/Date indicating that it's a global thing?
well js/ is not going to be necessary soon - that stuff already works in the WIP branch
assuming you referred it of course
that's what I was not assuming :)
anyways this is not an important detail for this report 🙂
@dnolen are you talking about referring global things? Have you finally decided on the syntax for this? Would like to know more (in other thread not to hijack this one)
posted a message in #cljs-dev
(ns foo.bar (:refer-global [Date])) IIRC
https://github.com/clojure/clojurescript/tree/cljs-3233/globals