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

Fix DateFormat time zone is not restored and add Test. #2549

Merged
merged 17 commits into from Dec 5, 2023

Conversation

Carpe-Wang
Copy link
Contributor

@Carpe-Wang Carpe-Wang commented Nov 20, 2023

Purpose

Fix DateFormat time zone is not restored after parsing. Fixes #2547

Description

Before attempting to parse the date string, save the current timezone for each DateFormat object. Use a finally block to ensure that the original timezone is restored for each DateFormat object.

Checklist

  • New code follows the Google Java Style Guide
    This is automatically checked by mvn verify, but can also be checked on its own using mvn spotless:check.
    Style violations can be fixed using mvn spotless:apply; this can be done in a separate commit to verify that it did not cause undesired changes.
  • If necessary, new public API validates arguments, for example rejects null
  • New public API has Javadoc
    • Javadoc uses @since $next-version$
      ($next-version$ is a special placeholder which is automatically replaced during release)
  • If necessary, new unit tests have been added
    • Assertions in unit tests use Truth, see existing tests
    • No JUnit 3 features are used (such as extending class TestCase)
    • If this pull request fixes a bug, a new test was added for a situation which failed previously and is now fixed
  • mvn clean verify javadoc:jar passes without errors

@Carpe-Wang Carpe-Wang marked this pull request as draft November 20, 2023 13:03
@Carpe-Wang
Copy link
Contributor Author

Carpe-Wang commented Nov 20, 2023

I just fetch and pull the new code, and I use mvn clean test -X. there are some detailed error information as followed.

[ERROR] /Users/carpewang/googleDeveloper/gson/gson/src/main/java/module-info.java:[29,40] module not found: com.google.errorprone.annotations
[ERROR] Cannot access com.google.gson.module-info
[ERROR] Unable to resolve module
[ERROR] -> [Help 1]
org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (default-compile) on project gson: Compilation failure

And i git clone the gson' master branch code and use mvn clean test -X command, there a same error.
I try my best to solve the error, but I can't. could you help me in your spare time?

@Marcono1234
Copy link
Collaborator

Which JDK and Maven version are you using? Could you please provide the output of mvn --version?

@Carpe-Wang
Copy link
Contributor Author

Which JDK and Maven version are you using? Could you please provide the output of mvn --version?

Thank you for your reply. there is output of mvn --version

carpewang@bogon gson % mvn --version
Apache Maven 3.9.5 (57804ffe001d7215b5e7bcb531cf83df38f93546)
Maven home: /Users/carpewang/Desktop/apache-maven-3.9.5
Java version: 21.0.1, vendor: Oracle Corporation, runtime: /Users/carpewang/Library/Java/JavaVirtualMachines/openjdk-21.0.1/Contents/Home
Default locale: zh_CN_#Hans, platform encoding: UTF-8
OS name: "mac os x", version: "11.7.4", arch: "x86_64", family: "mac"

@Marcono1234
Copy link
Collaborator

It looks like you are using JDK 21; could you please try JDK 17 instead? Gson currently does not support building with JDK 21, see #2501.

@Carpe-Wang
Copy link
Contributor Author

It looks like you are using JDK 21; could you please try JDK 17 instead? Gson currently does not support building with JDK 21, see #2501.

ok, thank you for your help, I solve the error, and I am trying to do the picture which you mentioned before
image

and add the test.

@Carpe-Wang Carpe-Wang marked this pull request as ready for review November 22, 2023 14:56
@Carpe-Wang
Copy link
Contributor Author

It looks like you are using JDK 21; could you please try JDK 17 instead? Gson currently does not support building with JDK 21, see #2501.

ok, thank you for your help, I solve the error, and I am trying to do the picture which you mentioned before image

and add the test.

Hello, I had finished the SqlTimeTypeAdapter and SqlDateTypeAdapter. Please review the code.

Copy link
Collaborator

@Marcono1234 Marcono1234 left a comment

Choose a reason for hiding this comment

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

Thanks a lot! I have added a few small review comments but generally at least to me this PR looks good.

Comment on lines 66 to 68
TimeZone originalTimeZone = format.getTimeZone(); // Save the original time zone
utilDate = format.parse(s);
format.setTimeZone(originalTimeZone); // Restore the original time zone after parsing
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use try-finally here as well, to be safe (in case time zone was already changed despite an exception being thrown):

Suggested change
TimeZone originalTimeZone = format.getTimeZone(); // Save the original time zone
utilDate = format.parse(s);
format.setTimeZone(originalTimeZone); // Restore the original time zone after parsing
TimeZone originalTimeZone = format.getTimeZone(); // Save the original time zone
try {
utilDate = format.parse(s);
} finally {
format.setTimeZone(originalTimeZone); // Restore the original time zone after parsing
}

(you might to have to reformat the code above)

Comment on lines 66 to 68
TimeZone originalTimeZone = format.getTimeZone(); // Save the original time zone
Date date = format.parse(s);
format.setTimeZone(originalTimeZone); // Restore the original time zone
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use try-finally here as well:

Suggested change
TimeZone originalTimeZone = format.getTimeZone(); // Save the original time zone
Date date = format.parse(s);
format.setTimeZone(originalTimeZone); // Restore the original time zone
TimeZone originalTimeZone = format.getTimeZone(); // Save the original time zone
try {
date = format.parse(s);
} finally {
format.setTimeZone(originalTimeZone); // Restore the original time zone after parsing
}

(you might to have to reformat the code above)

Date deserializedDate = gson.fromJson("\"1970-01-01 00:00 PST\"", Date.class);

// Serialize the deserialized date object again
String jsonAfterDeserialization = gson.toJson(deserializedDate);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe this should rather call toJson(originalDate); otherwise if deserializedDate is incorrect due to a bug we would get confusing failures here.

Copy link

Choose a reason for hiding this comment

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

Hyt

assertEquals("\"1970-01-01 00:00 UTC\"", json);

// Deserialize a date string with the PST timezone
Date deserializedDate = gson.fromJson("\"1970-01-01 00:00 PST\"", Date.class);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe add an assertion here that the date was parsed correctly, I assume it would be this:

Suggested change
Date deserializedDate = gson.fromJson("\"1970-01-01 00:00 PST\"", Date.class);
Date deserializedDate = gson.fromJson("\"1970-01-01 00:00 PST\"", Date.class);
assertThat(deserializedDate).isEqualTo(new Date(28800000));

String jsonAfterDeserialization = gson.toJson(deserializedDate);
// The expectation is that the date, after deserialization, when serialized again should still
// be in the UTC timezone
assertEquals("\"1970-01-01 08:00 UTC\"", jsonAfterDeserialization);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use Truth instead of the JUnit methods here; that will leads to better failure messages and makes the assertion easier to read:

Suggested change
assertEquals("\"1970-01-01 08:00 UTC\"", jsonAfterDeserialization);
assertThat(jsonAfterDeserialization).isEqualTo("\"1970-01-01 08:00 UTC\"");

@Marcono1234
Copy link
Collaborator

One more thing: Could you please edit the description to say "Fixes #2547" instead of "About issue is #2547".
GitHub will then link this PR to the issue (shown on the right under "Successfully merging this pull request may close these issues"), and once this PR is merged will automatically close the issue as well.

Just in case you didn't know this, you can edit the description of the PR by clicking the "..." at the top right:
GitHub PR description button screenshot

@Carpe-Wang
Copy link
Contributor Author

Thanks a lot! I have added a few small review comments but generally at least to me this PR looks good.

thanks I had change the pr description, and add try finally in DefaultDateTypeAdapterTest, SqlTimeTypeAdapter, and SqlDateTypeAdapter. But I meet some troubles when I want change testGsonDateFormat in DefaultDateTypeAdapterTest. Gson BUILD FAILURE because of

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:testCompile (default-testCompile) on project gson: Compilation failure
[ERROR] Warnings found, but -Werror was specified
[ERROR] 
[ERROR] -> [Help 1]
org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:testCompile (default-testCompile) on project gson: Compilation failure
Warnings found, but -Werror was specified

@@ -60,17 +60,18 @@ public java.sql.Date read(JsonReader in) throws IOException {
return null;
}
String s = in.nextString();
TimeZone originalTimeZone = format.getTimeZone(); // Save the original time zone
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please move this and the setTimeZone call inside the synchronized (this) { ... } block. Type adapters must be thread-safe and with the current implementation there could be a race condition.

Or rewrite this so that the synchronized block covers the try; I guess that should be fine as well, e.g.:

    synchronized (this) {
      TimeZone originalTimeZone = format.getTimeZone(); // Save the original time zone
      try {
        Date utilDate = format.parse(s);
        return new java.sql.Date(utilDate.getTime());
      } catch (ParseException e) {
        throw new JsonSyntaxException("Failed parsing '" + s + "' as SQL Date; at path " + in.getPreviousPath(), e);
      } finally {
        format.setTimeZone(originalTimeZone); // Restore the original time zone after parsing
      }
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is my mistake; I did not carefully read the code review you provided at the beginning.

@@ -61,16 +61,17 @@ public Time read(JsonReader in) throws IOException {
return null;
}
String s = in.nextString();
TimeZone originalTimeZone = format.getTimeZone(); // Save the original time zone
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as #2549 (comment)

@Marcono1234
Copy link
Collaborator

But I meet some troubles when I want change testGsonDateFormat in DefaultDateTypeAdapterTest. Gson BUILD FAILURE because of

Warnings found, but -Werror was specified

You have to look further up in the Maven log output then; there should be one or more "[WARNING] ..." lines for the maven-compiler-plugin. Alternatively, you can also push the non-working code if you want so that we can see the error message here in the GitHub workflow log, and suggest how to solve it.

@Carpe-Wang
Copy link
Contributor Author

Date deserializedDate = gson.

I solved it. Thank you for your patient replys. I believe that the suggestion in the image to use toJson(originalDate) is unnecessary because I have already performed toJson on both originalDate and deserializedDate, with corresponding assertions for each.
image

Copy link
Collaborator

@Marcono1234 Marcono1234 left a comment

Choose a reason for hiding this comment

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

I believe that the suggestion in the image to use toJson(originalDate) is unnecessary because I have already performed toJson on both originalDate and deserializedDate, with corresponding assertions for each.

Ok, that is fine for me now because you added assertThat(deserializedDate.getTime()).isEqualTo(...).

Just two more small things, see comments.

Comment on lines 250 to 252
Date expectedDate = new Date(28800000); // This represents the original date in UTC
String expectedJson = gson.toJson(expectedDate);
assertThat(jsonAfterDeserialization).isEqualTo(expectedJson);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please hardcode the expected JSON string here:

Suggested change
Date expectedDate = new Date(28800000); // This represents the original date in UTC
String expectedJson = gson.toJson(expectedDate);
assertThat(jsonAfterDeserialization).isEqualTo(expectedJson);
assertThat(jsonAfterDeserialization).isEqualTo("\"1970-01-01 08:00 UTC\"");

Otherwise your current code would fail to detect the bug (if you reverted your setTimeZone fix) because both gson.toJson(deserializedDate) and gson.toJson(expectedDate) would use PST as time zone, and would therefore both be wrong. But because both are wrong in the same way, the test would erroneously pass as successful.


// Serialize the date object
String json = gson.toJson(originalDate);
assertEquals("\"1970-01-01 00:00 UTC\"", json);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use Truth for assertions here:

Suggested change
assertEquals("\"1970-01-01 00:00 UTC\"", json);
assertThat(json).isEqualTo("\"1970-01-01 00:00 UTC\"");

@Carpe-Wang
Copy link
Contributor Author

I believe that the suggestion in the image to use toJson(originalDate) is unnecessary because I have already performed toJson on both originalDate and deserializedDate, with corresponding assertions for each.

Ok, that is fine for me now because you added assertThat(deserializedDate.getTime()).isEqualTo(...).

Just two more small things, see comments.

Thank you for your suggestions; they are helpful for improving my skills. I have made the changes according to your comments.

Copy link
Collaborator

@Marcono1234 Marcono1234 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 your work on this! The changes look good to me.
@eamonnmcmanus, could you please have a look?


Just one small note, but you don't have to change the code for that:
I thought a bit more about the gson.toJson(deserializedDate) in the test. This bothers me a bit because the point is that the time zone of the DateFormat is not restored. The current test code makes it look a bit as if Date internally stored a time zone as well (which it doesn't), and as if the DateFormat 'forces' usage of UTC even though the Date has PST as time zone (again, which is not actually the case).
That is why I think it would have been better to call gson.toJson(originalDate) a second time instead of gson.toJson(deserializedDate), because that makes it clearer that the issue here is with the DateFormat, and not with the deserialized Date.

@Carpe-Wang
Copy link
Contributor Author

Carpe-Wang commented Nov 29, 2023

Thanks for your work on this! The changes look good to me. @eamonnmcmanus, could you please have a look?

Just one small note, but you don't have to change the code for that: I thought a bit more about the gson.toJson(deserializedDate) in the test. This bothers me a bit because the point is that the time zone of the DateFormat is not restored. The current test code makes it look a bit as if Date internally stored a time zone as well (which it doesn't), and as if the DateFormat 'forces' usage of UTC even though the Date has PST as time zone (again, which is not actually the case). That is why I think it would have been better to call gson.toJson(originalDate) a second time instead of gson.toJson(deserializedDate), because that makes it clearer that the issue here is with the DateFormat, and not with the deserialized Date.

Thank you for your patient guidance, which is meaningful for developing my skills and valuable to me.

@Carpe-Wang
Copy link
Contributor Author

Hello @eamonnmcmanus, if there are any changes that you are still dissatisfied with, or if you think there are areas that can be improved, please let me know.

Copy link
Member

@eamonnmcmanus eamonnmcmanus 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 doing this! And sorry it took me a while to get to it.

@eamonnmcmanus eamonnmcmanus merged commit 58d1a9f into google:main Dec 5, 2023
11 checks passed
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.

DateFormat time zone is not restored after parsing, affecting subsequent serialization
4 participants