[ 
https://issues.apache.org/jira/browse/AVRO-4269?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18090025#comment-18090025
 ] 

ASF subversion and git services commented on AVRO-4269:
-------------------------------------------------------

Commit 912ab25169ab829d5c523fd231b5abb286c4dbe1 in avro's branch 
refs/heads/main from Emanuele Russo
[ https://gitbox.apache.org/repos/asf?p=avro.git;h=912ab25169 ]

AVRO-4269: Fix timestamp-nanos conversion before epoch (#3813)

> TimestampNanosConversion.toLong(...) encodes pre-epoch instants with the 
> wrong nanosecond offset
> ------------------------------------------------------------------------------------------------
>
>                 Key: AVRO-4269
>                 URL: https://issues.apache.org/jira/browse/AVRO-4269
>             Project: Apache Avro
>          Issue Type: Bug
>          Components: java
>            Reporter: Ruiqi Dong
>            Priority: Major
>              Labels: pull-request-available
>          Time Spent: 40m
>  Remaining Estimate: 0h
>
> *Summary*
> `TimestampNanosConversion.toLong(...)` has a special path for negative epoch 
> seconds with positive nanoseconds. That path subtracts `1_000_000` instead of 
> `1_000_000_000`. As a result, an instant such as `1969-12-31T23:59:59.500Z` 
> is encoded as `499000000` instead of `-500000000`.
>  
> *Affected code*
> File: `lang/java/avro/src/main/java/org/apache/avro/data/TimeConversions.java`
> {code:java}
> public static class TimestampNanosConversion extends Conversion<Instant> {
>   ...
>   @Override
>   public Long toLong(Instant instant, Schema schema, LogicalType type) {
>     long seconds = instant.getEpochSecond();
>     int nanos = instant.getNano();
>     if (seconds < 0 && nanos > 0) {
>       long micros = Math.multiplyExact(seconds + 1, 1_000_000_000L);
>       long adjustment = nanos - 1_000_000;
>       return Math.addExact(micros, adjustment);
>     } else {
>       long micros = Math.multiplyExact(seconds, 1_000_000_000L);
>       return Math.addExact(micros, nanos);
>     }
>   }
> } {code}
> *Reproducer* 
> Add this test to 
> `lang/java/avro/src/test/java/org/apache/avro/data/TestTimeConversions.java`
> {code:java}
> @Test
> void timestampNanosConversionBeforeEpoch() {
>   TimestampNanosConversion conversion = new TimestampNanosConversion();
>   Instant beforeEpoch = Instant.ofEpochSecond(-1, 500_000_000);
>   assertEquals(-500_000_000L,
>       (long) conversion.toLong(beforeEpoch, TIMESTAMP_NANOS_SCHEMA, 
> LogicalTypes.timestampNanos()));
>   assertEquals(beforeEpoch,
>       conversion.fromLong(-500_000_000L, TIMESTAMP_NANOS_SCHEMA, 
> LogicalTypes.timestampNanos()));
> } {code}
> Also initialize:
> {code:java}
> TIMESTAMP_NANOS_SCHEMA = 
> LogicalTypes.timestampNanos().addToSchema(Schema.create(Schema.Type.LONG)); 
> {code}
> Run:
> {code:java}
> MAVEN_SKIP_RC=true 
> JAVA_HOME=/opt/homebrew/Cellar/openjdk@21/21.0.6/libexec/openjdk.jdk/Contents/Home
>  \
> PATH=/opt/homebrew/Cellar/openjdk@21/21.0.6/libexec/openjdk.jdk/Contents/Home/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin
>  \
> /opt/homebrew/bin/mvn -q -t toolchains-local.xml -pl lang/java/avro \
>   
> -Dtest=org.apache.avro.data.TestTimeConversions#timestampNanosConversionBeforeEpoch
>  test{code}
> Observed behavior:
> The test fails
> {code:java}
> expected: <-500000000> but was: <499000000> {code}
> Expected behavior:
> `Instant.ofEpochSecond(-1, 500_000_000)` should encode to `-500_000_000` 
> nanoseconds from the Unix epoch.
> Avro logical type `timestamp-nanos` represents an instant as a long count of 
> nanoseconds from the epoch. The current implementation corrupts pre-epoch 
> instants with a fractional nanosecond component, which can reorder timestamps 
> and break round-trip encoding. The fix direction is to subtract 
> `1_000_000_000` in the negative branch, matching the nanosecond unit used by 
> the rest of the method.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to