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

Adds support for writing binary Ion 1.1 timestamps #618

Merged
merged 4 commits into from
Nov 8, 2023

Conversation

popematt
Copy link
Contributor

@popematt popematt commented Oct 19, 2023

Issue #, if available:

None

Description of changes:

Adds support for writing binary Ion 1.1 timestamps.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@popematt popematt requested a review from zslayton October 19, 2023 20:50
@codecov
Copy link

codecov bot commented Oct 19, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

📢 Thoughts on this report? Let us know!

@popematt popematt requested a review from tgregg October 20, 2023 20:48
@popematt popematt marked this pull request as draft October 23, 2023 18:31
@popematt popematt changed the base branch from master to ion-11-encoding October 25, 2023 18:43
Comment on lines -3 to -6
import com.amazon.ion.Decimal;
import com.amazon.ion.IonType;
import com.amazon.ion.Timestamp;
import com.amazon.ion.impl.bin.utf8.Utf8StringEncoder;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These were unused imports.

@@ -72,7 +86,7 @@ public void testWriteNullValueForDatagram() {
"true, 5E",
"false, 5F",
})
public void testWriteBooleanValue(Boolean value, String expectedBytes) {
public void testWriteBooleanValue(boolean value, String expectedBytes) {
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 an incidental change. Then writeBooleanValue method accepts boolean, so I made the test do the same.

Comment on lines +161 to +162
public void testWriteIntegerValueForBigInteger(BigInteger value, String expectedBytes) {
assertWritingValue(expectedBytes, value, IonEncoder_1_1::writeIntValue);
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 also an incidental change since I realized that JUnit already knows how to convert to BigInteger.

@@ -1453,17 +1453,40 @@ public static int fixedUIntLength(final long value) {
*/
public int writeFixedUInt(final long value) {
if (value < 0) {
throw new IllegalArgumentException("Attempted to write a FlexUInt for " + value);
throw new IllegalArgumentException("Attempted to write a FixedUInt for " + value);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just fixing a typo that I noticed while working on Timestamps.

@popematt popematt marked this pull request as ready for review October 25, 2023 19:31
Copy link
Contributor

@tgregg tgregg left a comment

Choose a reason for hiding this comment

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

I still need to double-check the tests for you, but leaving these comments here for now.

}

// Condition 2: The fractional seconds are a common precision.
int secondsScale = value.getDecimalSecond().scale();
Copy link
Contributor

Choose a reason for hiding this comment

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

getDecimalSecond() creates a new BigDecimal on each call. Let's use getZFractionalSecond() instead. It's deprecated, but if we ever get around to removing it we'll replace it with some internal accessor. Note: this also requires a null check.


// Condition 2: The fractional seconds are a common precision.
int secondsScale = value.getDecimalSecond().scale();
if (secondsScale != 0 && secondsScale != 3 && secondsScale != 6 && secondsScale != 9) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Note: in my initial prototype I used secondsScale % 3 != 0 || secondsScale > 9. Comparing these alternatives, I think it's pretty much a wash. In the common case (that the scale matches one of the common precisions) my suggestion always requires two comparisons, but it also has the modulo. Yours varies depending on the actual precision. One comparison if the scale is 0, two if it's 3, and so on. Leaving this comment in case you feel like forming a strong opinion on which one is preferable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't have a preference, and I suspect that it will not have very significant difference. I'll create an issue to revisit this later.

Comment on lines 29 to 30
static final int L_TIMESTAMP_OFFSET_BIT_OFFSET = 34;
static final int L_TIMESTAMP_SECOND_BIT_OFFSET = 44;
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks off. The offset is 12 bits, but here the second bit is only 10 after the offset bits.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch.


bits |= ((long) value.getSecond()) << S_U_TIMESTAMP_SECOND_BIT_OFFSET;

int secondsScale = value.getDecimalSecond().scale();
Copy link
Contributor

Choose a reason for hiding this comment

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

getZFractionalSecond() here too, and below


int secondsScale = value.getDecimalSecond().scale();
if (secondsScale != 0) {
long fractionalSeconds = value.getDecimalSecond().remainder(BigDecimal.ONE).movePointRight(secondsScale).longValue();
Copy link
Contributor

Choose a reason for hiding this comment

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

I see value.unscaledValue().longValue() in my prototype. Maybe you can get that to work. Remember that any BigDecimal method that returns BigDecimal allocates a new one, so we want to minimize that.

@ParameterizedTest
@CsvSource({
// OpCode MYYYYYYY DDDDDMMM mmmHHHHH ooooommm ssssssoo ffffffff ffffffff ffffffff ..ffffff
"2023-10-15T01:00-14:00, 01111000 00110101 01111101 00000001 01000000 00000010",
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the offset bits all be zero for this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why yes, it should. 😅
I've gone and manually double checked several more test cases with offsets both in the short form and the long form timestamps. I forgot about adjusting the offset for the short form, but the long form looks okay.

@popematt popematt merged commit 9b59da2 into amazon-ion:ion-11-encoding Nov 8, 2023
8 of 27 checks passed
popematt added a commit to popematt/ion-java that referenced this pull request Nov 16, 2023
@popematt popematt deleted the timestamp branch November 21, 2023 00:53
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.

2 participants