Skip to content
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

[Merged by Bors] - Safe wrapper for JsDate #2181

Closed
wants to merge 6 commits into from

Conversation

lameferret
Copy link
Contributor

@lameferret lameferret commented Jul 16, 2022

This PR adds a safe wrapper around JavaScript JsDate from builtins::date, and is being tracked at #2098.

Implements following methods

  • new Date()
  • Date.prototype.getDate()
  • Date.prototype.getDay()
  • Date.prototype.getFullYear()
  • Date.prototype.getHours()
  • Date.prototype.getMilliseconds()
  • Date.prototype.getMinutes()
  • Date.prototype.getMonth()
  • Date.prototype.getSeconds()
  • Date.prototype.getTime()
  • Date.prototype.getTimezoneOffset()
  • Date.prototype.getUTCDate()
  • Date.prototype.getUTCDay()
  • Date.prototype.getUTCFullYear()
  • Date.prototype.getUTCHours()
  • Date.prototype.getUTCMilliseconds()
  • Date.prototype.getUTCMinutes()
  • Date.prototype.getUTCMonth()
  • Date.prototype.getUTCSeconds()
  • Date.prototype.getYear()
  • Date.now()
  • Date.parse() Issue 4
  • Date.prototype.setDate()
  • Date.prototype.setFullYear()
  • Date.prototype.setHours() Issue 3
  • Date.prototype.setMilliseconds()
  • Date.prototype.setMinutes() Issue 3
  • Date.prototype.setMonth()
  • Date.prototype.setSeconds()
  • Date.prototype.setTime()
  • Date.prototype.setUTCDate()
  • Date.prototype.setUTCFullYear()
  • Date.prototype.setUTCHours()
  • Date.prototype.setUTCMilliseconds()
  • Date.prototype.setUTCMinutes()
  • Date.prototype.setUTCMonth()
  • Date.prototype.setUTCSeconds()
  • Date.prototype.setYear()
  • Date.prototype.toDateString() Issue 5
  • Date.prototype.toGMTString() Issue 5
  • Date.prototype.toISOString() Issue 5
  • Date.prototype.toJSON() Issue 5
  • Date.prototype.toLocaleDateString() Issue 5 and 6
  • Date.prototype.toLocaleString() Issue 5 and 6
  • Date.prototype.toLocaleTimeString() Issue 5 and 6
  • Date.prototype.toString() Issue 5
  • Date.prototype.toTimeString() Issue 5
  • Date.prototype.toUTCString() Issue 5
  • Date.UTC()
  • Date.prototype.valueOf()

Issues

  1. get_*() and some other methods - They take &self as input internally, and internal struct shouldn't be used in a wrapper API. Therefore, these would require input to be this: &JsValue, args: &[JsValue], context: &mut Context like others and use this_time_value()? Fixed using this_time_value()
  2. to_string()- how can I use Date::to_string() rather than alloc::string::ToString. My bad it compiles, just rust-analyzer was showing it as an issue.
  3. set_hours() and set_minutes() - they subtract local timezones when setting the value, e.g.
    • On further look:
       // both function call `builtins::date::mod.rs#L1038
      this.set_data(ObjectData::date(t));
      // `ObjectData::date` creates a new `Date` object `object::mods.rs#L423
      //                 | this date is chrono::Date<Tz(TimezoneOffset)> and Tz default is being used here which is GMT+0
      pub fn date(date: Date) -> Self {
        Self {
            kind: ObjectKind::Date(date),
            internal_methods: &ORDINARY_INTERNAL_METHODS,
        }
    }
    • BTW, in object::mod.rs's enum ObjectKind there is Date(chrono::Date) and it requires
      the generic argument, how is it being bypassed here?
    • Also in set_minutes() step 6, LocalTime should be used.
// reference date = 2000-01-01T06:26:53.984
date.set_hours(&[23.into(), 23.into(), 23.into(), 23.into()], context)?;
// would add tiemzone(+5:30) to it
// Is                 2000-01-01T17:53:23.023
// Should be    2000-01-01T23:23:23.023
  1. parse() - it uses chrono::parse_from_rfc3339 internally, while es6 spec recommends ISO8601. And it can also parse other formats like from MDN 04 Dec 1995 00:12:00 GMT which fails. So what should be done about it.
  2. to_*() - This is more general, as the internal date object uses chrono::NaiveDateTime which doesn't have timezone. It doesn't account for +4:00 in example below.
// Creates new `Date` object from given rfc3339 string.
let date = JsDate::new_from_parse(&JsValue::new("2018-01-26T18:30:09.453+04:00"), context);
println!("to_string: {:?}", date2.to_string(context)?); 
// IS:          Sat Jan 27 2018 00:00:09 GMT+0530
// Should:  Fri Jan 26 2018 20:00:09 GMT+0530
  1. to_locale_*() - requires ToDateTimeOptions and localization would require chrono's unstable-locales feature, which is available for DateTime and not for NaiveDateTime.
    • I should have looked properly, to_date_time_options is already implemented in builtins::intl. Anyway, I would still need some tips on how to use it. What would function signature be like in wrapper API, how would options be passed to the said API.
    • So to_date_time_options() takes options: &JsValue as an argument and build an object from it and fetch properties through Object.get(). If I want options to be { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' } what would JsValue look like to make it all work.
    date.to_locale_date_string(&[JsValue::new("en_EN"), OPTIONS], context)?;
    // OPTIONS need to be a JsValue which when converted into an object 
    // have these properties { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };

Possible improvements

  1. Right now, object::jsdate::set_full_year() and alike (input is a slice) are like below, into() doesn't feel ergonomic.
    #[inline]
    pub fn set_full_year(&self, values: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
        Date::set_full_year(&self.inner.clone().into(), values, context)
    }
    // Usage 
    date.set_full_year(&[2000.into(), 0.into(), 1.into()], context)?;
    // How can something like this be made to work
    #[inline]
    pub fn set_full_year<T>(&self, values: &[T], context: &mut Context) -> JsResult<JsValue>
    where
        T: Into<JsValue>,
    {
                                                          | expected reference `&[value::JsValue]` 
                                                          | found reference `&[T]`        
        Date::set_full_year(&self.inner.clone().into(), values, context)
    }
  2. Any other suggestion?

@codecov
Copy link

codecov bot commented Jul 16, 2022

Codecov Report

Merging #2181 (0a0c7d5) into main (2ffae5b) will increase coverage by 0.11%.
The diff coverage is 49.11%.

@@            Coverage Diff             @@
##             main    #2181      +/-   ##
==========================================
+ Coverage   38.69%   38.80%   +0.11%     
==========================================
  Files         314      315       +1     
  Lines       23887    24085     +198     
==========================================
+ Hits         9242     9347     +105     
- Misses      14645    14738      +93     
Impacted Files Coverage Δ
boa_engine/src/object/builtins/jsdate.rs 0.00% <0.00%> (ø)
boa_engine/src/builtins/date/mod.rs 86.27% <87.40%> (+3.14%) ⬆️
boa_engine/src/value/serde_json.rs 84.44% <0.00%> (-3.93%) ⬇️
boa_ast/src/property.rs 18.96% <0.00%> (-1.73%) ⬇️
boa_engine/src/vm/mod.rs 42.74% <0.00%> (-0.35%) ⬇️
boa_engine/src/context/mod.rs 32.25% <0.00%> (-0.21%) ⬇️
boa_engine/src/builtins/error/uri.rs 100.00% <0.00%> (ø)
boa_engine/src/builtins/error/eval.rs 100.00% <0.00%> (ø)
boa_engine/src/builtins/error/range.rs 100.00% <0.00%> (ø)
... and 10 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@lameferret lameferret force-pushed the WrapperJsDate branch 2 times, most recently from 59b3533 to f4c6508 Compare July 17, 2022 23:44
Copy link
Member

@raskad raskad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a first look at the changes in the builts themselves and left some suggestions.

The issues that you mention all sound out of scope of this PR to me. I think we should convert all of them to issues so they can get fixed. For the purpose of this PR we just want to expose the builtins, so we can use the existing funtionalities even if they are not 100% spec compliant.

boa_engine/src/builtins/date/mod.rs Outdated Show resolved Hide resolved
boa_engine/src/builtins/date/mod.rs Outdated Show resolved Hide resolved
boa_engine/src/builtins/date/mod.rs Outdated Show resolved Hide resolved
boa_engine/src/builtins/date/mod.rs Show resolved Hide resolved
boa_engine/src/builtins/date/mod.rs Show resolved Hide resolved
boa_engine/src/builtins/date/mod.rs Outdated Show resolved Hide resolved
@lameferret lameferret requested a review from raskad July 28, 2022 22:04
@lameferret lameferret force-pushed the WrapperJsDate branch 2 times, most recently from c53b2fe to 4a7fa76 Compare July 28, 2022 22:09
Copy link
Member

@Razican Razican left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great to me :) I just recommend a bit of documentation in the JsDate structure itself, but it can be merged. Thank you!!

Comment on lines 11 to 14
#[derive(Debug, Clone, Trace, Finalize)]
pub struct JsDate {
inner: JsObject,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add some documentation to the structure itself, maybe with a small example. We should probably also add some documentation at the module level, even if it's just a one-liner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review, I have added a doc-example for JsDate but didn't rebase it as the code structure has changed from what I remember.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm it shouldn't have changed a lot. The main difference might be that creating errors now uses JsNativeError::...() instead of context.construct...(). But checking at the merge conflicts, it seems it's just some import ordering and a different derive for Date.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey I tried the changes, it had some fixable cascading effects, but I'm stuck on "lazzy error types" without it /boa_examples/jsdate,rs it spits "? couldn't convert the error to JsString", or should I implement lazzy error too?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jedel1043 can you help here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just the signature of main. It should return JsResult<()> instead of Result<(), JsValue>.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jedel1043 thank you so much, I was really confused trying to figure out the issue.

@Razican Razican added enhancement New feature or request builtins PRs and Issues related to builtins/intrinsics API labels Oct 21, 2022
@Razican Razican added this to the v0.17.0 milestone Oct 21, 2022
@lameferret lameferret force-pushed the WrapperJsDate branch 2 times, most recently from 2fc1d97 to 8d8fa67 Compare October 27, 2022 21:27
Copy link
Member

@raskad raskad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! Sorry for the long wait on the review.

@boa-dev boa-dev deleted a comment from bors bot Nov 8, 2022
@bors
Copy link

bors bot commented Nov 8, 2022

🕐 Waiting for PR status (GitHub check) to be set, probably by CI. Bors will automatically try to run when all required PR statuses are set.

@raskad
Copy link
Member

raskad commented Nov 8, 2022

bors seems to have some problems with the github check status so I rebased the branch to hopefully fix that.

@raskad
Copy link
Member

raskad commented Nov 8, 2022

bors r+

bors bot pushed a commit that referenced this pull request Nov 8, 2022
This PR adds a safe wrapper around JavaScript `JsDate` from `builtins::date`, and is being tracked at #2098.

#### Implements following methods 
- [x] `new Date()`
- [x] `Date.prototype.getDate()`
- [x] `Date.prototype.getDay()`
- [x] `Date.prototype.getFullYear()`
- [x] `Date.prototype.getHours()`
- [x] `Date.prototype.getMilliseconds()`
- [x] `Date.prototype.getMinutes()`
- [x] `Date.prototype.getMonth()`
- [x] `Date.prototype.getSeconds()`
- [x] `Date.prototype.getTime()`
- [x] `Date.prototype.getTimezoneOffset()`
- [x] `Date.prototype.getUTCDate()`
- [x] `Date.prototype.getUTCDay()`
- [x] `Date.prototype.getUTCFullYear()`
- [x] `Date.prototype.getUTCHours()`
- [x] `Date.prototype.getUTCMilliseconds()`
- [x] `Date.prototype.getUTCMinutes()`
- [x] `Date.prototype.getUTCMonth()`
- [x] `Date.prototype.getUTCSeconds()`
- [x] `Date.prototype.getYear()`
- [x] `Date.now()`
- [ ] `Date.parse()` Issue 4
- [x] `Date.prototype.setDate()`
- [x] `Date.prototype.setFullYear()`
- [ ] `Date.prototype.setHours()` Issue 3
- [x] `Date.prototype.setMilliseconds()`
- [ ] `Date.prototype.setMinutes()` Issue 3
- [x] `Date.prototype.setMonth()`
- [x] `Date.prototype.setSeconds()`
- [x] `Date.prototype.setTime()`
- [x] `Date.prototype.setUTCDate()`
- [x] `Date.prototype.setUTCFullYear()`
- [x] `Date.prototype.setUTCHours()`
- [x] `Date.prototype.setUTCMilliseconds()`
- [x] `Date.prototype.setUTCMinutes()`
- [x] `Date.prototype.setUTCMonth()`
- [x] `Date.prototype.setUTCSeconds()`
- [x] `Date.prototype.setYear()`
- [ ] `Date.prototype.toDateString()` Issue 5
- [ ] `Date.prototype.toGMTString()` Issue 5
- [ ] `Date.prototype.toISOString()` Issue 5
- [ ] `Date.prototype.toJSON()` Issue 5
- [ ] `Date.prototype.toLocaleDateString()` Issue 5 and 6
- [ ] `Date.prototype.toLocaleString()` Issue 5 and 6
- [ ] `Date.prototype.toLocaleTimeString()` Issue 5 and 6
- [ ] `Date.prototype.toString()` Issue 5
- [ ] `Date.prototype.toTimeString()` Issue 5
- [ ] `Date.prototype.toUTCString()` Issue 5
- [x] `Date.UTC()`
- [x] `Date.prototype.valueOf()`

### Issues
1. ~~`get_*()` and some other methods - They take `&self` as input internally, and internal struct shouldn't be used in a wrapper API. Therefore, these would require input to be `this: &JsValue, args: &[JsValue], context: &mut Context` like others and use `this_time_value()`?~~ Fixed using `this_time_value()`
2. ~~`to_string()`- how can I use `Date::to_string()` rather than `alloc::string::ToString`.~~ My bad it compiles, just `rust-analyzer` was showing it as an issue.
3. `set_hours()` and `set_minutes()` - they subtract local timezones when setting the value, e.g.
    - On further look:
    ```rust
       // both function call `builtins::date::mod.rs#L1038
      this.set_data(ObjectData::date(t));
      // `ObjectData::date` creates a new `Date` object `object::mods.rs#L423
      //                 | this date is chrono::Date<Tz(TimezoneOffset)> and Tz default is being used here which is GMT+0
      pub fn date(date: Date) -> Self {
        Self {
            kind: ObjectKind::Date(date),
            internal_methods: &ORDINARY_INTERNAL_METHODS,
        }
    }
     ```
     - BTW, in `object::mod.rs`'s `enum ObjectKind` there is `Date(chrono::Date)` and it requires 
     the generic argument, how is it being bypassed here?
     - Also in `set_minutes()` step 6, `LocalTime` should be used.
```rust
// reference date = 2000-01-01T06:26:53.984
date.set_hours(&[23.into(), 23.into(), 23.into(), 23.into()], context)?;
// would add tiemzone(+5:30) to it
// Is                 2000-01-01T17:53:23.023
// Should be    2000-01-01T23:23:23.023
```
4. `parse()` - it uses `chrono::parse_from_rfc3339` internally, while es6 spec recommends ISO8601. And it can also parse other formats like from [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) `04 Dec 1995 00:12:00 GMT` which fails. So what should be done about it.
5. `to_*()` - This is more general, as the internal date object uses `chrono::NaiveDateTime` which doesn't have timezone. It doesn't account for `+4:00` in example below.
```rust
// Creates new `Date` object from given rfc3339 string.
let date = JsDate::new_from_parse(&JsValue::new("2018-01-26T18:30:09.453+04:00"), context);
println!("to_string: {:?}", date2.to_string(context)?); 
// IS:          Sat Jan 27 2018 00:00:09 GMT+0530
// Should:  Fri Jan 26 2018 20:00:09 GMT+0530
```
6. `to_locale_*()` - requires [`ToDateTimeOptions`](https://402.ecma-international.org/9.0/#sec-todatetimeoptions) and localization would require chrono's `unstable-locales` feature, which is available for `DateTime` and not for `NaiveDateTime`. 
    - I should have looked properly, `to_date_time_options` is already implemented in `builtins::intl`. Anyway, I would still need some tips on how to use it. What would function signature be like in wrapper API, how would `options` be passed to the said API. 
    - So `to_date_time_options()` takes `options: &JsValue` as an argument and build an object from it and fetch properties through `Object.get()`. If I want `options` to be `{ weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }` what would `JsValue` look like to make it all work. 
    ```rust 
    date.to_locale_date_string(&[JsValue::new("en_EN"), OPTIONS], context)?;
    // OPTIONS need to be a JsValue which when converted into an object 
    // have these properties { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
    ```


### Possible improvements 
1. Right now, `object::jsdate::set_full_year()` and alike (input is a slice) are like below, `into()` doesn't feel ergonomic.
    ```rust
    #[inline]
    pub fn set_full_year(&self, values: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
        Date::set_full_year(&self.inner.clone().into(), values, context)
    }
    // Usage 
    date.set_full_year(&[2000.into(), 0.into(), 1.into()], context)?;
    // How can something like this be made to work
    #[inline]
    pub fn set_full_year<T>(&self, values: &[T], context: &mut Context) -> JsResult<JsValue>
    where
        T: Into<JsValue>,
    {
                                                          | expected reference `&[value::JsValue]` 
                                                          | found reference `&[T]`        
        Date::set_full_year(&self.inner.clone().into(), values, context)
    }
    ```
2. Any other suggestion?
@bors
Copy link

bors bot commented Nov 8, 2022

Pull request successfully merged into main.

Build succeeded:

@bors bors bot changed the title Safe wrapper for JsDate [Merged by Bors] - Safe wrapper for JsDate Nov 8, 2022
@bors bors bot closed this Nov 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API builtins PRs and Issues related to builtins/intrinsics enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants