-
Notifications
You must be signed in to change notification settings - Fork 43
Add typings for JSDate #484
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
No tests? 😁 |
srujzs
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks so much for adding this Natalie!
+1 to a quick test just to make sure we have the right bindings.
js_interop/lib/src/date.dart
Outdated
| /// The [Date constructor] that returns the current date and time. | ||
| /// | ||
| /// [Date constructor]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date | ||
| external JSDate.now(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, this might be a little confusing as intuitively, one would expect the static method Date.now to be called and not a constructor. What about JSDate.nowAsDate so that we don't need to rename now below?
| external JSDate(int year, int month, | ||
| [int? day, int? hours, int? minutes, int? seconds, int? milliseconds]); | ||
|
|
||
| /// Dee [`Date.now()`]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: See
js_interop/lib/src/date.dart
Outdated
| /// | ||
| /// [Date constructor]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date | ||
| /// [from a string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date#date_string | ||
| external JSDate.parse(String dateString); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to now, what about parseAsDate instead so we can keep parse below?
js_interop/lib/src/date.dart
Outdated
| /// | ||
| /// [`Date.utc()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC | ||
| @JS('UTC') | ||
| external static int utc(int year, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We avoid renaming to lowercase with package:web e.g. URL, maybe worth doing the same?
Same comment on toIsoString, toUtcString, and toDartUtc.
| /// [`Date.getDate()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getDate | ||
| /// [`Date.setDate()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate | ||
| int get date => _getDate(); | ||
| set date(int value) => _setDate(value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should keep the syntax the same as JS here by just exposing the methods instead of trying to be more Dart-y like we do in package:web.
Same comment everywhere else.
|
Do you have an example of tests for thin wrapper declarations like this? Since we're still defining the exact shape and conventions of this package, I'd like to make a bit of an impassioned plea that this not match But More importantly, though, these APIs are going to be foundational to the programming environment and commonly-used by authors doing any more than cursory Dart/JS interaction. Making them ergonomic to Dart authors and Dart conventions has an outsized benefit relative to the Along that line, I'd also make the case that The final case I'll make for using a more Dart-native style is for the ecosystem as a whole. Since their inception with |
|
re: tests, we do this for some of the I think there's a valid argument to say this package is a better candidate to be Dart-y than I'd still argue there are some benefits to having a model that translates directly to JS. I want and expect users to look for JS documentation when dealing with interop types, and I think it's easier for users to directly write those method/member names and have them exist/autocomplete properly. It's a (small) tax to have to look at the API and figure out how to make the equivalent JS call. I bet this tax goes up as the APIs get more complex and we get more opinionated.
The instances where we do this are usually out of necessity. All of this being said, helpers are great. That's partially why we have some of the "helpers" in
I think I feel slightly different about interop-related packages because they're inherently dealing with another language. This argument makes sense for any helpers we add, but they shouldn't be complete replacements.
My above comment is relevant here: I expect the tax of trying to match the JS call to the Dart API we expose will go up as we look at the more complex cases but we can talk about those when we get there. |
|
I think the tax of having to remember not to use Dart naming conventions when writing Dart code is likely to be higher than the tax of not exactly matching the JS API names, especially since no matter what we can't match JS names exactly—as you point out, there will always be cases like I feel more strongly about this for places where the JS names outright violate Dart naming conventions (like |
I agree that there will always be exceptions due to the nature of keywords/Dart semantics, and I can see the argument that e.g. reaching for the all-uppercase version is less intuitive than the camel-cased version, but I think a strict adherence to Dart style for interop, e.g.
Sounds good, I don't have a strong opinion on capitalization. |
f779434 to
ef7e339
Compare
| int? seconds, | ||
| int? milliseconds, | ||
| ]) { | ||
| var ms = switch ((day, hours, minutes, seconds, milliseconds)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love me some switch!
|
|
||
| import 'package:js_interop/js_interop.dart'; | ||
|
|
||
| final Matcher _isDate = predicate( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the isA matcher w/ having will give nicer errors when this fails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think isA works with JS interop types, unfortunately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gah! Right. Ugh.
|
I've added tests, updated member names, and added a new
I wonder if this is the core disconnect here. When I'm writing interop code, the biggest point of friction is usually context switching. This is especially true when I'm dealing with APIs that are weird shapes for historical reasons—remembering that The thing that eases this pain is API consistency, which is why I'm advocating for it so fervently. If I can know that when I'm writing Dart code, APIs will be Dart-shaped, historical artifacts will be smoothed out, and names will follow Dart conventions, my life becomes much easier. By contrast, having the APIs match precisely across languages is relatively low value to me (as long as the correspondence is documented). I rarely access APIs entirely by memory, even when I'm working in a single language that I'm familiar with (even when I'm using APIs I personally created!). I rely on documentation, either through the web or through my editor, to remind me the precise names or arguments of whatever I'm interacting with. If I'm writing JS interop code in Dart, I'd be looking those up in the Dart documentation anyway, so having them be a little more different from the JS in exchange for them being a little more ergonomic at every use-site is a tradeoff that's well worth its cost to me. |
Probably. :D I also think this will differ between folks who will use these types regularly versus folks who use these once.
I suppose one benefit types in this package will have over But okay, so that we make progress, let's go with the strategy that you propose and make things more Dart-y and ergonomic where it makes sense. In this PR that means capitalization modifications and moving obvious methods to getters/setters. I expect us to do a pre-release, and we can get more input (if any) from other users on the conventions then before releasing a 1.0.0. |
| /// | ||
| /// [Date constructor]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date | ||
| /// [individual component integers]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date#individual_date_and_time_component_values | ||
| factory JSDate.utcDate( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we think JSDate.fromMillisecondsSinceEpoch(JSDate.utcAsMillisecondsSinceEpoch(...)) is bad enough that we should include this? I mostly ask because our treatment of nullability makes this factory a bit more complex. If we do want this we should document that treatment (e.g. "if day is null, we only pass year and month. if not and if hours is nullable,...").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think given that there's already a local-time constructor, having a symmetrical one for UTC makes sense (especially because it's often going to be the safer option).
If we do want this we should document that treatment (e.g. "if
dayis null, we only pass year and month. if not and ifhoursis nullable,...").
It actually works in the opposite way: it passes all arguments through the last non-null. This makes the observable behavior identical to localDate: if you pass any intermediate nulls, you get an "Invalid Date". Since the behaviors match (and match the behavior of calling JSDate.fromMillisecondsSinceEpoch(JSDate.utcAsMillisecondsSinceEpoch(...)) I don't think it needs special documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, I thought you were trying to avoid passing null regardless of whether the user passed it.
My follow-up question would've been "why not implement the body as JSDate.fromMillisecondsSinceEpoch(JSDate.utcAsMillisecondsSinceEpoch(...))", but it looks like someone decided to make the default for days 1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date#parameters, so there is an observable difference between passing null and not passing anything. This LGTM then.
if you pass any intermediate nulls, you get an "Invalid Date"
Is this true? I'm constructing e.g. Date(2025, 11, null, 1) and that results in a valid date.
Sounds good, collecting feedback from users at large is definitely the most robust way to answer this question 🙂. |
Contribution guidelines:
dart format.Many Dart repos have a weekly cadence for reviewing PRs - please allow for some latency before initial review feedback.
Note: The Dart team is trialing Gemini Code Assist. Don't take its comments as final Dart team feedback. Use the suggestions if they're helpful; otherwise, wait for a human reviewer.