Skip to content

feat: Support encoding and decoding of non-moment date objects #78

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
merged 8 commits into from
Feb 22, 2023

Conversation

jimmycallin
Copy link
Contributor

@jimmycallin jimmycallin commented Feb 15, 2023

Resolves FT-bc5b3fba-b1ca-11ed-ba1e-a2da51ff8a88

  • I have added automatic tests where applicable
  • The PR title is suitable as a release note
  • The PR contains a description of what has been changed
  • The description contains manual test instructions

Changes

As part of easing migration from moment, as described in #71 , we start by supporting encoding of non-moment date objects. This includes all date libraries that have toISOString as a method on their date objects, as well as native Date library, and also an ISO6801 compatible string.

Test

Make sure encoding all these date formats work as expected.

@jimmycallin jimmycallin requested a review from a team as a code owner February 15, 2023 22:37
@jimmycallin jimmycallin force-pushed the support-non-moment-encoding branch 4 times, most recently from 706b3ac to 69981a7 Compare February 15, 2023 22:45
Copy link
Contributor

@lucaas lucaas left a comment

Choose a reason for hiding this comment

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

Did a first pass on the code

@@ -307,7 +306,8 @@ export class Session {
return out;
}

if (data && data._isAMomentObject) {
const date = convertToISOString(data);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we try to convert all strings and numbers, or should we separate checking for date-like objects and encoding to iso string?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what you mean by this comment. currently we support converting all date objects, including moment and dayjs that have toISOString method - as well as converting ISO 8601 compatible strings to UTC. Do you want to split strings and number cases from object cases?

Copy link
Contributor

Choose a reason for hiding this comment

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

Basically, the question was if we should do:

if (isDateLike(data)) {
    data = convertToISOString(data)
}

Rather than:

    date = convertToISOString(data)
    if (date) {
        data = date;
    }

I am not convinced that we should. I just thought it looked odd to try converting all types (except arrays and pojo:s already handled).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't mind either way, so let me know if you want me to change it

@@ -0,0 +1,20 @@
function isIsoDate(str: string) {
if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(str)) return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we also encode strings in a non-UTC timezone?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we're encoding strings in a non-utc timezone, e.g. 2023-01-01T01:00:00+01:00, and converting to UTC. see tests.

Copy link
Contributor

Choose a reason for hiding this comment

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

The previous regex wasn't matching them - now it looks better.

// if this is a date object of type moment or dayjs, or regular date object (all of them has toISOString)
((typeof data !== "string" && typeof data.toISOString === "function") ||
// if it's a ISO string already
(typeof data == "string" && isIsoDate(data)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we encode already formatted strings?

Copy link
Contributor Author

@jimmycallin jimmycallin Feb 16, 2023

Choose a reason for hiding this comment

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

we support non-UTC timezones, while the server doesn't. this is to make sure they are in UTC format, since toISOString always return UTC format.

Copy link
Contributor

Choose a reason for hiding this comment

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

Just checking, do we have a use case to supporting these? It seems like you have to jump through a few hoops to format dates like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nothing that I'm aware of, I thought it was better to support encoding all ISO8601 compatible strings than just a subset of them.

};
}

// Ensure that the moment object is in local time zone and format
// to timezone naive string.
return {
__type__: "datetime",
value: data.local().format(ENCODE_DATETIME_FORMAT),
value: moment(date).local().format(ENCODE_DATETIME_FORMAT),
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we target the v2 for these changes as well? I am not really sure how to test this for regressions, so feels safer to do this while also removing this branch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i have intentionally separated code for this PR to be backwards compatible by making sure all moment objects are passed in the same manner as before, so I hope we can target this for a minor release. we can add additional tests for this if there are certain use cases you are unsure about.

Copy link
Contributor

Choose a reason for hiding this comment

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

I want to ensure that this path hasn't changed, without having to disable timezone support locally.

Can we add a test for:

const date = moment()
const convertedDate = convertToISOString(date)
const encodedDate = moment(date).local().format(ENCODE_DATETIME_FORMAT)

assert date.local().format(ENCODE_DATETIME_FORMAT) === encodedDate

Copy link
Contributor Author

Choose a reason for hiding this comment

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

please see new tests!

// need to strip from query.
value = moment(value).utc().format(ENCODE_DATETIME_FORMAT);
} else if (convertToISOString(value)) {
value = convertToISOString(value);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the comment not true?

// Server does not store microsecond or timezone currently so
// need to strip from query.

These will be translated to a SQL comparison, so I think we need to strip to match how the dates are stored in the database.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's true that the server currently does not store microseconds, but I believe it's better to allow sending in all ISO8691 validated strings and convert them to whatever is supported in the server (as it currently works), than removing microseconds in the client.

server does store timezones in a way by only supporting UTC timezone, and we are thus converting to UTC dates always.

Copy link
Contributor

Choose a reason for hiding this comment

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

I tried testing this manually, and it does look like it works, though I am not sure why.. both of these return the expected Note.

select id from Note where thread_activity is "2023-02-20 09:58:01"
select id from Note where thread_activity is "2023-02-20T09:58:01.102Z"

Having a date-times as identifyingKey doesn't make much sense, but I assume we should continue to support it. As long as it does, I am happy.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the server encode all dates to a precision the database handles, which means that it strips the milliseconds (and timezone) from the string, which is why it is backwards compatible.

if the server API one day start supporting milliseconds granularity, this query will stop working yes, but that would be a breaking change from the server API anyway.

@jimmycallin jimmycallin requested a review from lucaas February 19, 2023 23:08
// need to strip from query.
value = moment(value).utc().format(ENCODE_DATETIME_FORMAT);
} else if (convertToISOString(value)) {
value = convertToISOString(value);
Copy link
Contributor

Choose a reason for hiding this comment

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

I tried testing this manually, and it does look like it works, though I am not sure why.. both of these return the expected Note.

select id from Note where thread_activity is "2023-02-20 09:58:01"
select id from Note where thread_activity is "2023-02-20T09:58:01.102Z"

Having a date-times as identifyingKey doesn't make much sense, but I assume we should continue to support it. As long as it does, I am happy.

@@ -307,7 +306,8 @@ export class Session {
return out;
}

if (data && data._isAMomentObject) {
const date = convertToISOString(data);
Copy link
Contributor

Choose a reason for hiding this comment

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

Basically, the question was if we should do:

if (isDateLike(data)) {
    data = convertToISOString(data)
}

Rather than:

    date = convertToISOString(data)
    if (date) {
        data = date;
    }

I am not convinced that we should. I just thought it looked odd to try converting all types (except arrays and pojo:s already handled).

};
}

// Ensure that the moment object is in local time zone and format
// to timezone naive string.
return {
__type__: "datetime",
value: data.local().format(ENCODE_DATETIME_FORMAT),
value: moment(date).local().format(ENCODE_DATETIME_FORMAT),
Copy link
Contributor

Choose a reason for hiding this comment

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

I want to ensure that this path hasn't changed, without having to disable timezone support locally.

Can we add a test for:

const date = moment()
const convertedDate = convertToISOString(date)
const encodedDate = moment(date).local().format(ENCODE_DATETIME_FORMAT)

assert date.local().format(ENCODE_DATETIME_FORMAT) === encodedDate

@jimmycallin jimmycallin force-pushed the support-non-moment-encoding branch from 069e987 to e401e39 Compare February 20, 2023 11:11
@jimmycallin jimmycallin requested a review from lucaas February 20, 2023 11:23
@jimmycallin jimmycallin changed the title feat: support encoding non-moment date objects feat: support encoding and decoding of non-moment date objects Feb 20, 2023
@jimmycallin jimmycallin changed the title feat: support encoding and decoding of non-moment date objects feat: Support encoding and decoding of non-moment date objects Feb 21, 2023
Copy link
Contributor

@lucaas lucaas left a comment

Choose a reason for hiding this comment

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

Looks good and working as intended. Consider if we should rename ISO to Iso for consistency.

@@ -96,18 +96,20 @@ export interface ResponseError {
error?: Data;
}

export interface MutatationOptions {
export interface MutationOptions {
Copy link
Contributor

Choose a reason for hiding this comment

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

🤓 🏅

private decode(
data: any,
identityMap: Data = {},
decodeDatesAsISO: boolean = false
Copy link
Contributor

Choose a reason for hiding this comment

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

I was bout to comment that we could make the third argument a decodeOptions to future-proof if we have a need for more options, but seeing as this is assumed to be private - I don't think it matters.

@jimmycallin jimmycallin merged commit bc82086 into main Feb 22, 2023
@jimmycallin jimmycallin deleted the support-non-moment-encoding branch February 22, 2023 10:55
gismya pushed a commit that referenced this pull request May 15, 2023
* feat: support encoding non-moment date objects

* add tests for convert_to_iso_string

* added docstring

* add additional encoding tests

* support non-moment decoding

* non-UTC timezone for tests in CI

* consistent camel casing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants