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

to-offset-date-time #27087

Closed
wants to merge 7 commits into from
Closed

to-offset-date-time #27087

wants to merge 7 commits into from

Conversation

qiaozha
Copy link
Member

@qiaozha qiaozha commented Sep 12, 2023

Packages impacted by this PR

@azure/core-util

Issues associated with this PR

Some background microsoft/typespec#1899

Describe the problem that is addressed by this PR

In TypeSpec, we have a offsetDateTime type, which is suppose to provide the timezone difference when the format is rfc3339, like
2023-09-11T18:45:20.988+08:00

but typescript date.toISOString() doesn't have such info. as there's no easy way to convert it, I think it might be appropriate to add the convert function into core-util.

What are the possible designs available to address the problem? If there are more than one possible design, why was the one in this PR chosen?

Are there test cases added in this PR? (If not, why?)

Provide a list of related PRs (if any)

Command used to generate this PR:**(Applicable only to SDK release request PRs)

Checklists

  • Added impacted package name to the issue description
  • Does this PR needs any fixes in the SDK Generator?** (If so, create an Issue in the Autorest/typescript repository and link it here)
  • Added a changelog (if necessary)

describe("work in +08:00 timezone", function () {
it("converts a base64 string to bytes", function () {
const date = new Date("2022-01-05T19:08:10Z");
assert.strictEqual(toOffsetDateTime(date), "2022-01-05T19:08:10.000+08:00");
Copy link
Member Author

Choose a reason for hiding this comment

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

@xirzec I don't think the test case is correct for all timezones, but have no idea how to set a specific timezone, so that it works

@azure-sdk
Copy link
Collaborator

API change check

APIView has identified API level changes in this PR and created following API reviews.

azure-core-util

const date = new Date("2022-01-05T19:08:10.000Z");
assert.strictEqual(
Date.parse(toOffsetDateTime(date)) - Date.parse(date.toISOString()),
60 * date.getTimezoneOffset() * 1000
Copy link
Member Author

Choose a reason for hiding this comment

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

A little unsure whether the difference should be 0 ?
if it's 2022-01-05T19:08:10.000Z In UTC time, then it should be 2022-01-06T03:08:10.000+08:00 in the +08:00 timezone. so maybe purely use something like the below in toOffsetDateTime is incorrect?
date.toISOString().replace(/Z$/, "") + diff + pad(tzOffset / 60) + ":" + pad(tzOffset % 60)

Copy link
Member

@MaryGao MaryGao Sep 12, 2023

Choose a reason for hiding this comment

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

I think the difference should be 0 here, no matter the ISO datetime or offset datetime they are just format differences and the underlying number should be the one.

Not sure it's easy to convert them. If not, we may take the offsetDateTime as utcDatetime in typespec.

I notice that we didn't import any library to handle date and time. Not sure this is mainly for performance consideration.

I know with momentjs we could easily format any date (docs).

> moment.defaultFormat
'YYYY-MM-DDTHH:mm:ssZ'
> moment.defaultFormatUtc
'YYYY-MM-DDTHH:mm:ss[Z]'

image

Copy link
Member

Choose a reason for hiding this comment

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

My understanding is that moment is an extremely heavy dependency and something that will eventually be replaced by an ecmascript standard /cc @bterlson

import { toOffsetDateTime } from "../../src/datetime";
import { assert } from "chai";

describe("utcDateTime with timezone offset", function () {
Copy link
Member

@MaryGao MaryGao Sep 12, 2023

Choose a reason for hiding this comment

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

Could we add more cases to cover corner situations e.g +0, -11?

Copy link
Member Author

Choose a reason for hiding this comment

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

As stated here #27087 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

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

Let me know if you can figure out a way.

Copy link
Member Author

@qiaozha qiaozha left a comment

Choose a reason for hiding this comment

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

do some self reviews.

});
it("the to offset datetime should be correct in 0 milliseconds precision", function () {
const date = new Date();
assert.strictEqual(toOffsetDateTime(date, 0), moment(date).format("YYYY-MM-DDTHH:mm:ssZ"));
Copy link
Member Author

Choose a reason for hiding this comment

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

As I am not sure how to set the timezone, I realize we can think of the result of moment format as the source of truth to test the converter helper. if this tests can pass, then at least, the converter should be able to work in my local timezone and the one that the CI runs.

});
});

describe("work in 2022-01-05T19:08:10.000Z", function () {
Copy link
Member Author

Choose a reason for hiding this comment

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

add this one because of the milliseconds is 0, and we might miss to pad 0s to the end.

// Licensed under the MIT license.
export type OffsetMillisecondsPrecision = 0 | 1 | 2 | 3;

export function toOffsetDateTime(date: Date, precision: OffsetMillisecondsPrecision = 0): string {
Copy link
Member Author

Choose a reason for hiding this comment

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

add an option as the precision of milliseconds we want to keep. by default it's 0 now, but let me know if there;s other opppinion.

pad(date.getMinutes()) +
":" +
pad(date.getSeconds()) +
(precision !== 0 ? `.${precisedMilliseconds}`.padEnd(precision + 1, "0") : "") +
Copy link
Member Author

@qiaozha qiaozha Sep 14, 2023

Choose a reason for hiding this comment

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

padEnd first parameter is precison + 1 because there's a dot in the string.

const date = new Date();
assert.strictEqual(
Math.floor(
Math.abs(Date.parse(toOffsetDateTime(date)) - Date.parse(date.toISOString())) / 1000
Copy link
Member Author

Choose a reason for hiding this comment

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

Here as the toOffsetDateTime will lost the miliiseconds of three digit, and we have no way to assert the real diff, so I think we can assume the quotient of the diff divided by 1000 is zero.

const date = new Date("2022-01-05T19:08:10.000Z");
// assert.strictEqual(moment(date).format(), "2022-01-06T03:08:10+08:00");
assert.strictEqual(toOffsetDateTime(date), moment(date).format());
assert.strictEqual(Date.parse(toOffsetDateTime(date)) - Date.parse(date.toISOString()), 0);
Copy link
Member Author

Choose a reason for hiding this comment

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

As the milliseconds is zero, we don't have unixtime diff loss. so the diff should be zero for all precisions.

@@ -105,7 +105,8 @@
"sinon": "^15.0.0",
"typescript": "~5.0.0",
"util": "^0.12.1",
"ts-node": "^10.0.0"
"ts-node": "^10.0.0",
"moment": "^2.29.4"
Copy link
Member Author

Choose a reason for hiding this comment

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

this is the dev dependency, so I guess it's okay ?

const pad = (n: number) => `${Math.floor(Math.abs(n))}`.padStart(2, "0");
const precisedMilliseconds = getPrecisedMilliseconds(date, precision);
return (
date.getFullYear() +
Copy link
Member

Choose a reason for hiding this comment

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

I was wondering if this implementation would work if timezone is crossed year for example utc is in 2023, but with offset timezone it may 2022. The same question for crossed month and day.

Copy link
Member

Choose a reason for hiding this comment

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

@xirzec
Copy link
Member

xirzec commented Sep 14, 2023

Found this for mocking the timezone in Node-only tests at least: https://www.npmjs.com/package/timezone-mock

To step back for a bit, I'm not sure I understand what this datatype means from a TypeSpec perspective, especially for roundtripping. Like when a client retrieves a value from the service, is the offset based on the service's timezone? Or based on the last client that updated that field? If the timezone in the local client is different, do we change the value when doing a PUT even if the developer didn't touch that field?

This seems like an extremely error-prone feature of the language. @bterlson can you explain why TypeSpec has this type?

@xirzec
Copy link
Member

xirzec commented Sep 14, 2023

Talked to Brian offline, for now we can be lossy here - clients won't know the original offset that came from the service (we convert to Date) and we'll always serialize back to the local TZ. Eventually with Temporal we can do better, but not holding my breath for runtime support.

@xirzec
Copy link
Member

xirzec commented Sep 14, 2023

Second update: After more discussion @bterlson 's preference is to leave this type as string and not serialize/deserialize it to Date. Can we change the codegen to do that instead @qiaozha ?

@qiaozha
Copy link
Member Author

qiaozha commented Sep 14, 2023

@xirzec yeah, we can totally do that, and it makes much more sense to me. Because I also thougtht of that when client side get the response with a date time which is a offsetDateTime, customers will probably have to convert it by themselves, then I thought maybe they could leverage this helper function or other util whatever they like. Should I just close this PR ?

but I want to confirm with @bterlson, do you think we should make this cross language consistent ?

@qiaozha
Copy link
Member Author

qiaozha commented Sep 15, 2023

close it as offline confirmed with Brian, the current behavior of other language treating offsetDateTime exactly the same as utcDateTime is incorrect, but they can handle this as appropriate.

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

Successfully merging this pull request may close these issues.

4 participants