clojurescript

mitchelkuijpers 2025-08-27T12:38:20.319249Z

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"}

mitchelkuijpers 2025-08-27T12:44:02.497349Z

Oh btw this is with: org.clojure/clojurescript 1.12.42

p-himik 2025-08-27T12:46:48.010629Z

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.

p-himik 2025-08-27T12:47:44.920709Z

In CLJ, something like java.util.Date. is a syntax error. So I'd say js/Date. should never be used as a value.

mitchelkuijpers 2025-08-27T12:48:39.841759Z

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"

mitchelkuijpers 2025-08-27T12:52:59.440539Z

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"

mitchelkuijpers 2025-08-27T12:54:34.663449Z

Still weird that update uses the function and not the constructor

p-himik 2025-08-27T13:21:06.045149Z

-> 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.

thheller 2025-08-27T13:21:07.078329Z

update is a function itself, so it can only use the arguments it receives

thheller 2025-08-27T13:21:33.098939Z

I agree that js/Date. here likely should be an error. surprised that it isn't to be honest.

mitchelkuijpers 2025-08-27T13:33:19.362399Z

At least I learned something thanks @p-himik and @thheller

raspasov 2025-08-28T03:10:42.802129Z

#(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.

thheller 2025-08-28T06:41:30.425539Z

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"))

thheller 2025-08-28T06:43:10.254579Z

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

thheller 2025-08-28T06:45:44.392869Z

(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)

👍 1
raspasov 2025-08-28T06:50:57.821509Z

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"}

raspasov 2025-08-28T06:51:19.883409Z

So in that case, both work exactly the same

raspasov 2025-08-28T06:53:00.084889Z

Also:

cljs.user=> (type js/Date)
#object[Function]
So on the surface, one might assume they would work the same.

thheller 2025-08-28T06:53:06.141949Z

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

raspasov 2025-08-28T06:53:43.268459Z

ok

raspasov 2025-08-28T06:54:04.810839Z

> The `Date()` constructor creates Date objects. Is that the difference? (My JS fundamentals are not at expert level)

thheller 2025-08-28T06:55:20.634009Z

(js/Date "2018-01-01")
=> "Thu Aug 28 2025 08:55:07 GMT+0200 (Central European Summer Time)"

raspasov 2025-08-28T06:55:30.980669Z

Right

thheller 2025-08-28T06:55:34.782879Z

(js/Date. "2018-01-01")
=> #inst "2018-01-01T00:00:00.000-00:00"

raspasov 2025-08-28T06:55:47.803589Z

Yup, so that’s what I meant by “non-cljs” things 🙂

raspasov 2025-08-28T06:56:18.193239Z

You would not typically encounter such “subtle” creativity in CLJS-proper

thheller 2025-08-28T06:56:20.321669Z

these are not the same thing though

raspasov 2025-08-28T06:56:25.388909Z

Well, sure

thheller 2025-08-28T06:57:45.351769Z

(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"

thheller 2025-08-28T06:58:12.750159Z

something like that is going on I guess

thheller 2025-08-28T06:58:42.376339Z

I'm unsure why (js/Date ...) doesn't do what it says in the docs

thheller 2025-08-28T06:59:10.146869Z

ah nvm

raspasov 2025-08-28T06:59:11.596719Z

All I was saying is that it’s not obvious. I don’t have the mental capacity to internalize all the JS subtleties 🙂

thheller 2025-08-28T06:59:14.218659Z

> Calling the Date() function (without the new keyword) returns a string representation of the current date and time, exactly as new Date().toString() does.

thheller 2025-08-28T06:59:17.213099Z

it does say 🙂

raspasov 2025-08-28T06:59:40.632089Z

> 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) 😜

raspasov 2025-08-28T07:00:38.308039Z

> it does say 🙂 They do have exhaustive docs, I’ll give them that 😅

raspasov 2025-08-28T07:01:06.027379Z

Real page turner, those manuals.

raspasov 2025-08-28T07:07:23.494659Z

> 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.

gunnar 2025-08-28T07:37:39.359349Z

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.

raspasov 2025-08-28T07:48:06.706739Z

Well, for once, I like the UTC status quo of that detail, at least you get something consistent 🙂

raspasov 2025-08-28T07:48:32.585269Z

Oh… it differs for date-only and date-time… oh my

raspasov 2025-08-28T07:48:47.435579Z

Scratch what I said 😅

thheller 2025-08-28T07:49:13.106399Z

the closure library has a lot of stuff to help with all this 😉

raspasov 2025-08-28T07:49:18.166779Z

oh for sure

raspasov 2025-08-28T07:49:24.776839Z

I like the Closure Library

gunnar 2025-08-28T07:57:24.583289Z

> 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.

raspasov 2025-08-28T07:58:45.903209Z

No debate here, like I corrected myself: > Oh… it differs for date-only and date-time… oh my > Scratch what I said

gunnar 2025-08-28T07:59:59.723139Z

Yep 🙂 I've been bitten by this before, in subtle ways. The constructor should emit huge warning signs, imo. Footguns everywhere. 😄

borkdude 2025-08-28T08:01:11.324529Z

I guess I can catch this in clj-kondo

borkdude 2025-08-28T08:01:35.545379Z

I mean the (update {} :a js/Date.) thing

thheller 2025-08-28T08:02:36.692799Z

forget the update. just js/Date., but aware of context since its fine in macros like ->

mitchelkuijpers 2025-08-28T08:02:56.753329Z

That would be pretty amazing

thheller 2025-08-28T08:04:42.966969Z

maybe there should be a discussion whether this should at least have a compiler warning

raspasov 2025-08-28T08:04:52.394129Z

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=> 

thheller 2025-08-28T08:05:39.015109Z

> Calling the Date() function (without the new keyword) returns a string representation of the current date and time, exactly as new Date().toString() does.

thheller 2025-08-28T08:05:54.402679Z

valid yes, but completely different semantics

thheller 2025-08-28T08:06:38.422039Z

I'm specifically referring to the . at the end. not related to js/Date in any way

raspasov 2025-08-28T08:06:53.097629Z

Right.

thheller 2025-08-28T08:07:25.218909Z

could just be a bug that this even compiles

raspasov 2025-08-28T08:10:16.109429Z

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=> 

raspasov 2025-08-28T08:10:38.173649Z

^^^ this being the “constructor/object” variant

raspasov 2025-08-28T08:11:00.540179Z

this one is the “function variant”:

cljs.user=> (js/Date)
"Thu Aug 28 2025 01:10:43 GMT-0700 (Pacific Daylight Time)"

raspasov 2025-08-28T08:11:30.132739Z

Seems to match what the JS docs say?

thheller 2025-08-28T08:12:28.306529Z

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

👍 1
thheller 2025-08-28T08:13:21.119539Z

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

thheller 2025-08-28T08:15:44.445149Z

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 ->

borkdude 2025-08-28T10:23:26.869179Z

Clj-kondo expands the most common macros but agree that the compiler should detect this

dnolen 2025-08-28T12:26:22.720489Z

https://clojure.atlassian.net/browse/CLJS-3445

🙏 1
dnolen 2025-08-28T12:27:21.003759Z

Noted in the issue, Date/new would be nice sugar for these cases

borkdude 2025-08-28T12:27:55.036009Z

should Date not be referenced as js/Date indicating that it's a global thing?

dnolen 2025-08-28T12:28:30.759299Z

well js/ is not going to be necessary soon - that stuff already works in the WIP branch

❓ 1
dnolen 2025-08-28T12:29:39.231969Z

assuming you referred it of course

borkdude 2025-08-28T12:29:55.816489Z

that's what I was not assuming :)

dnolen 2025-08-28T12:29:59.547159Z

anyways this is not an important detail for this report 🙂

👍 1
borkdude 2025-08-28T12:30:44.216269Z

@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)

borkdude 2025-08-28T12:31:38.709619Z

posted a message in #cljs-dev

thheller 2025-08-28T12:32:12.715349Z

(ns foo.bar (:refer-global [Date])) IIRC