Skip to content

Floor pre-epoch Timestamp seconds in DateTimeConverter.convertToType - #433

Open
rootvector2 wants to merge 1 commit into
apache:masterfrom
rootvector2:datetimeconverter-preepoch-timestamp
Open

Floor pre-epoch Timestamp seconds in DateTimeConverter.convertToType#433
rootvector2 wants to merge 1 commit into
apache:masterfrom
rootvector2:datetimeconverter-preepoch-timestamp

Conversation

@rootvector2

Copy link
Copy Markdown
Contributor

DateTimeConverter.convertToType decomposes a java.sql.Timestamp into whole seconds with timestamp.getTime() / 1000 * 1000 and adds the sub-second part back via timestamp.getNanos() / 1000000, but integer division truncates toward zero while getNanos() is always in [0, 999999999] (the JDK constructor borrows a second to normalize a negative fraction), so a pre-epoch timestamp with a non-zero fraction rounds the whole-second term up toward zero and then adds a non-negative nanos term, gaining a full second: new java.sql.Timestamp(-500L) (1969-12-31T23:59:59.500Z) converts to epoch millis 500 instead of -500. Math.floorDiv makes the whole-second term floor so it agrees with the nanos term; every date/time converter inherits this one path and post-epoch values are unchanged. Found auditing the date/time converters; the existing tests only feed positive System.currentTimeMillis(), so they never hit it.

  • Read the contribution guidelines for this project.
  • Read the ASF Generative Tooling Guidance if you use Artificial Intelligence (AI).
  • I used AI to create any part of, or all of, this pull request. Which AI tool was used to create this pull request, and to what extent did it contribute?
  • Run a successful build using the default Maven goal with mvn; that's mvn on the command line by itself.
  • Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied. This may not always be possible, but it is a best practice.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Each commit in the pull request should have a meaningful subject line and body. Note that a maintainer may squash commits during the merge process.

getTime() / 1000 truncates toward zero, so a pre-epoch java.sql.Timestamp with a sub-second part gained a whole second; use Math.floorDiv so the whole-second term agrees with the non-negative getNanos() term.
@garydgregory garydgregory changed the title floor pre-epoch Timestamp seconds in DateTimeConverter.convertToType Floor pre-epoch Timestamp seconds in DateTimeConverter.convertToType Aug 8, 2026
@garydgregory
garydgregory requested a lite review from Copilot August 8, 2026 12:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes DateTimeConverter.convertToType for pre-epoch java.sql.Timestamp values with a fractional component by using floor semantics when deriving the whole-second millisecond portion, and adds a regression test to cover the negative timestamp case.

Changes:

  • Use Math.floorDiv when deriving whole-second milliseconds for java.sql.Timestamp conversions to avoid rounding toward zero for negative timestamps.
  • Add a unit test that verifies converting new Timestamp(-500L) preserves -500 epoch millis.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java Adjusts timestamp whole-second computation to floor for negative values.
src/test/java/org/apache/commons/beanutils2/converters/DateConverterTest.java Adds a regression test for pre-epoch Timestamp conversion correctness.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +203 to 205
long timeInMillis = Math.floorDiv(timestamp.getTime(), 1000) * 1000;
timeInMillis += timestamp.getNanos() / 1000000;
return toDate(targetType, timeInMillis);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hello @rootvector2
Please review Copiolot's comment. The comment suggests we are also missing a unit tests for the path Copilot found. Please verify and provide an additional test.
Thank you!

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.

3 participants