Skip to content

Conversation

m1a2st
Copy link
Collaborator

@m1a2st m1a2st commented Jan 18, 2025

Jira: https://issues.apache.org/jira/browse/KAFKA-18585

The test scenario involves inputting dates in ISO8601 format yyyy-MM-dd. At this point, the time information is already lost. Then in the Values#convertToDate method, the date conversion is performed using string manipulation, without any timezone information being provided. Therefore, it's expected and normal behavior that the time gets converted to 00:00:00.

The solution is to explicitly convert Java Date objects, which internally store UTC timestamps, to UTC+0 timezone

before:
CleanShot 2025-01-19 at 00 13 28@2x
after:
CleanShot 2025-01-19 at 00 14 17@2x

Committer Checklist (excluded from commit message)

  • Verify design and implementation
  • Verify test coverage and CI build status
  • Verify documentation (including upgrade notes)

@github-actions github-actions bot added triage PRs from the community connect tests Test fixes (including flaky tests) small Small PRs labels Jan 18, 2025
@github-actions github-actions bot removed the triage PRs from the community label Jan 20, 2025
@m1a2st
Copy link
Collaborator Author

m1a2st commented Jan 20, 2025

Hello @AndrewJSchofield, if you have free cycle, PTAL

TimeZone tz = TimeZone.getDefault();
java.util.Date currentDate3 = new java.util.Date(current.getTime() - currentMillis - tz.getOffset(current.getTime()));
java.util.Date d3 = Values.convertToDate(Date.SCHEMA, LocalDate.ofEpochDay(days).format(DateTimeFormatter.ISO_LOCAL_DATE));
assertEquals(currentDate, d3);
Copy link
Member

Choose a reason for hiding this comment

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

I must be missing something here so please help me understand the following:

Java's util.Date is timezone agnostic (it's always epochs in UTC timezone) until you want to print it (where you can format it). Hence, the currentDate here should already be in UTC timezone. And the actual value from LocalDate.ofEpochDay(days).format(DateTimeFormatter.ISO_LOCAL_DATE) should also be in UTC. So, where is the disconnect here?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I'm not sure either. But I can believe it's possible to write a test that is timezone-dependent.

@AndrewJSchofield
Copy link
Member

Digging into this further, I would say that the behaviour for some of these tests with respect to timezones is incorrect, or at least unwise. I am not convinced by this patch, but I will dig into it further and give a more concrete answer shortly.

TimeZone tz = TimeZone.getDefault();
java.util.Date currentDate3 = new java.util.Date(current.getTime() - currentMillis - tz.getOffset(current.getTime()));
java.util.Date d3 = Values.convertToDate(Date.SCHEMA, LocalDate.ofEpochDay(days).format(DateTimeFormatter.ISO_LOCAL_DATE));
assertEquals(currentDate, d3);
Copy link
Member

Choose a reason for hiding this comment

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

@divijvaidya is correct that java.util.Date is timezone-agnostic. However, the LocalDate here is timezone-ignorant and the timezone offset is not accounted for. The methods available for manipulating dates and times with timezones are a bit limited, and I think that it's best to stop using LocalDate.ofEpochDay(long) and moving to LocalDateTime.ofInstant(Instant, ZoneId).

I suggest replacing the first line of the method with:

        LocalDateTime localTime = LocalDateTime.now();
        LocalDateTime localTimeTruncated = localTime.truncatedTo(ChronoUnit.DAYS);
        ZoneOffset zoneOffset = ZoneId.systemDefault().getRules().getOffset(localTime);
        java.util.Date current = new java.util.Date(localTime.toEpochSecond(zoneOffset) * 1000);

And then the check becomes:

        // ISO8601 strings - accept a string matching pattern "yyyy-MM-dd"
        java.util.Date d3 = Values.convertToDate(Date.SCHEMA, LocalDate.ofEpochDay(days).format(DateTimeFormatter.ISO_LOCAL_DATE));
        LocalDateTime date3 = LocalDateTime.ofInstant(Instant.ofEpochMilli(d3.getTime()), ZoneId.systemDefault());
        assertEquals(localTimeTruncated, date3);

I tried this with various timezones locally and it seems to work properly. Essentially, the time information is discarded in a way that a person in the timezone would expect looking at a clock.

Copy link
Collaborator Author

@m1a2st m1a2st Jan 21, 2025

Choose a reason for hiding this comment

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

We can observe that when java.util.Date performs equals comparison, it takes timezone into account. Therefore, the equals comparison will yield different results under different timezones. In my case, being in UTC+8, it results in an 8-hour difference.

https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/Date.java#L1210

Copy link
Member

Choose a reason for hiding this comment

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

@m1a2st Are you happy that this works now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure, using LocalDateTime makes sense to me.

@AndrewJSchofield AndrewJSchofield merged commit b4674c0 into apache:trunk Jan 21, 2025
9 checks passed
pranavt84 pushed a commit to pranavt84/kafka that referenced this pull request Jan 27, 2025
airlock-confluentinc bot pushed a commit to confluentinc/kafka that referenced this pull request Jan 27, 2025
manoj-mathivanan pushed a commit to manoj-mathivanan/kafka that referenced this pull request Feb 19, 2025

// ISO8601 strings - accept a string matching pattern "yyyy-MM-dd"
LocalDateTime localTimeTruncated = localTime.truncatedTo(ChronoUnit.DAYS);
java.util.Date d3 = Values.convertToDate(Date.SCHEMA, LocalDate.ofEpochDay(days).format(DateTimeFormatter.ISO_LOCAL_DATE));
Copy link
Member

@chia7712 chia7712 Sep 3, 2025

Choose a reason for hiding this comment

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

Sorry for raising this question on the merged PR. This test case is intended to ensure the parser works for ISO8601. That said, is it really necessary to use days here? It seems to me that relying on days could introduce a risk: since the granularity is at the "day" level, it my case an off-by-one issue if the local time falls on a different "day" than UTC. For example, when the local time is 9/1 01:00 (UTC+8)

Perhaps we could instead use localTime.format(DateTimeFormatter.ISO_LOCAL_DATE) to avoid the conversion risk

@AndrewJSchofield WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

@chia7712 Yes, I think you are totally right. There will be an off-by-one issue if the local time falls on a different day than UTC.

Copy link
Member

Choose a reason for hiding this comment

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

Were you thinking something like this?

        java.util.Date d3 = Values.convertToDate(Date.SCHEMA, localTime.format(DateTimeFormatter.ISO_LOCAL_DATE));
        LocalDateTime date3 = LocalDateTime.ofInstant(Instant.ofEpochMilli(d3.getTime()), ZoneId.systemDefault());
        LocalDateTime localTimeTruncated = localTime.truncatedTo(ChronoUnit.DAYS);
        assertEquals(localTimeTruncated, date3);

Copy link
Member

Choose a reason for hiding this comment

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

yes! we are on the same page!

Copy link
Member

Choose a reason for hiding this comment

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

Do you have a free cycle? If not, my mentee can file a minor patch based on your approach.

Copy link
Member

Choose a reason for hiding this comment

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

I suggest the mentee does it and then I'll review. Thanks for following up on this.

Copy link
Member

Choose a reason for hiding this comment

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

AndrewJSchofield pushed a commit that referenced this pull request Sep 15, 2025
This test case ensures that the parser can convert ISO8601 correctly.
However, when the local time falls on a different day than the UTC time,
there will be an off-by-one issue.

I changed the test to convert the local time and then compare it with
the expected local time. This should fix the off-by-one issue.

[Reference
link](#18611 (comment))

Reviewers: Andrew Schofield <[email protected]>

---------

Signed-off-by: Alex <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
connect small Small PRs tests Test fixes (including flaky tests)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants