Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 86 additions & 8 deletions src/main/java/com/amazon/ion/Timestamp.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.amazon.ion.util.IonTextUtils.printCodePointAsString;

import com.amazon.ion.impl._Private_Utils;
import com.amazon.ion.system.IonTextWriterBuilder;
import com.amazon.ion.util.IonTextUtils;
import java.io.IOException;
import java.math.BigDecimal;
Expand Down Expand Up @@ -133,6 +134,18 @@ public final class Timestamp
private static final int FLAG_MINUTE = 0x08;
private static final int FLAG_SECOND = 0x10;

/**
* The default maximum number of digits in the fractional component that will be written when a text representation
* of a timestamp is requested. If more precision is needed, timestamps can be serialized using a text IonWriter
* configured with {@link IonTextWriterBuilder#withMaximumTimestampPrecisionDigits(int)}.
*
* @see #toString()
* @see #toZString()
* @see #print(Appendable)
* @see #printZ(Appendable)
*/
public static final int DEFAULT_MAXIMUM_DIGITS_TEXT = 10000;

/**
* The precision of the Timestamp.
*/
Expand Down Expand Up @@ -2035,16 +2048,34 @@ public Timestamp withLocalOffset(Integer offset)
* Returns the string representation (in Ion format) of this Timestamp in
* its local time.
*
* @throws IonException if the text representation of the timestamp requires more than
* {@link Timestamp#DEFAULT_MAXIMUM_DIGITS_TEXT}.
*
* @see #toZString()
* @see #print(Appendable)
*/
@Override
public String toString()
{
return toString(DEFAULT_MAXIMUM_DIGITS_TEXT);
}

/**
* Returns the string representation (in Ion format) of this Timestamp in
* its local time.
*
* @throws IonException if the text representation of the timestamp requires more than
* the given maximum.
*
* @see #toZString()
* @see #print(Appendable)
*/
String toString(int maximumDigits)
{
StringBuilder buffer = new StringBuilder(32);
try
{
print(buffer);
print(buffer, maximumDigits);
}
catch (IOException e)
{
Expand All @@ -2059,6 +2090,9 @@ public String toString()
* Returns the string representation (in Ion format) of this Timestamp
* in UTC.
*
* @throws IonException if the text representation of the timestamp requires more than
* {@link Timestamp#DEFAULT_MAXIMUM_DIGITS_TEXT}.
*
* @see #toString()
* @see #printZ(Appendable)
*/
Expand Down Expand Up @@ -2087,11 +2121,33 @@ public String toZString()
* @param out not {@code null}
*
* @throws IOException propagated when the {@link Appendable} throws it
* @throws IonException if the text representation of the timestamp requires more than
* {@link Timestamp#DEFAULT_MAXIMUM_DIGITS_TEXT}.
*
* @see #printZ(Appendable)
*/
public void print(Appendable out)
throws IOException
{
print(out, DEFAULT_MAXIMUM_DIGITS_TEXT);
}

/**
* Prints to an {@code Appendable} the string representation (in Ion format)
* of this Timestamp in its local time.
* <p>
* This method produces the same output as {@link #toString()}.
*
* @param out not {@code null}
*
* @throws IOException propagated when the {@link Appendable} throws it
* @throws IonException if the text representation of the timestamp requires more than
* the given maximum.
*
* @see #printZ(Appendable)
*/
private void print(Appendable out, int maximumDigits)
throws IOException
{
// we have to make a copy to preserve the "immutable" contract
// on Timestamp and we don't want someone reading the calendar
Expand All @@ -2103,7 +2159,7 @@ public void print(Appendable out)
adjusted = make_localtime();
}

print(out, adjusted);
print(out, adjusted, maximumDigits);
}


Expand All @@ -2116,6 +2172,8 @@ public void print(Appendable out)
* @param out not {@code null}
*
* @throws IOException propagated when the {@code Appendable} throws it.
* @throws IonException if the text representation of the timestamp requires more than
* {@link Timestamp#DEFAULT_MAXIMUM_DIGITS_TEXT}.
*
* @see #print(Appendable)
*/
Expand Down Expand Up @@ -2146,16 +2204,36 @@ public void printZ(Appendable out)
}


/**
* Throws if the text representation of the timestamp would require more than the given number of digits in its
* fractional component.
* @param value a Timestamp with a non-null fraction.
* @param maximumDigits the maximum number of digits allowed.
*/
private static void requirePrecisionWithinLimit(Timestamp value, int maximumDigits) {
if (value._fraction.scale() > maximumDigits) {
throw new IonException(String.format(
"Timestamp with %d digits of precision cannot be serialized because it exceeds the " +
"configurable maximum timestamp precision of %d digits. Timestamps that require more digits " +
"may be written using a text writer configured with " +
"IonTextWriterBuilder.withMaximumTimestampPrecisionDigits.",
value._fraction.scale(),
maximumDigits
));
}
}

/**
* helper for print(out) and printZ(out) so that printZ can create
* a zulu time and pass it directly and print can apply the local
* offset and adjust the various fields (without breaking the
* contract to be immutable).
* @param out destination for the text image of the value
* @param adjusted the time value with the fields adjusted to match the desired text output
* @param maximumDigits the maximum number of digits allowed in the fractional seconds
* @throws IOException
*/
private static void print(Appendable out, Timestamp adjusted)
private static void print(Appendable out, Timestamp adjusted, int maximumDigits)
throws IOException
{
// null is our first "guess" to get it out of the way
Expand All @@ -2164,28 +2242,28 @@ private static void print(Appendable out, Timestamp adjusted)
return;
}

if (adjusted._precision == Precision.SECOND && adjusted._fraction != null) {
requirePrecisionWithinLimit(adjusted, maximumDigits);
}

// so we have a real value - we'll start with the date portion
// which we always have
print_digits(out, adjusted._year, 4);
if (adjusted._precision == Precision.YEAR) {
assert adjusted._offset == UNKNOWN_OFFSET;
out.append("T");
return;
}

out.append("-");
print_digits(out, adjusted._month, 2); // convert calendar months to a base 1 value
if (adjusted._precision == Precision.MONTH) {
assert adjusted._offset == UNKNOWN_OFFSET;
out.append("T");
return;
}

out.append("-");
print_digits(out, adjusted._day, 2);
if (adjusted._precision == Precision.DAY) {
assert adjusted._offset == UNKNOWN_OFFSET;
// out.append("T");
return;
}

Expand All @@ -2202,7 +2280,7 @@ private static void print(Appendable out, Timestamp adjusted)
}
}

if (adjusted._offset != UNKNOWN_OFFSET) {
if (adjusted._offset != null) {
int min, hour;
min = adjusted._offset;
if (min == 0) {
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/amazon/ion/_Private_Trampoline.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.amazon.ion

/**
* **NOT FOR APPLICATION USE. This method may be removed at any time.**
* Trampoline to the non-public `Timestamp.toString(Int)` method.
*/
internal fun printTimestamp(timestamp: Timestamp, maximumDigits: Int): String {
return timestamp.toString(maximumDigits)
}
6 changes: 4 additions & 2 deletions src/main/java/com/amazon/ion/impl/IonWriterSystemText.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.amazon.ion.impl;

import static com.amazon.ion.SystemSymbols.SYMBOLS;
import static com.amazon.ion._Private_TrampolineKt.printTimestamp;
import static com.amazon.ion.impl._Private_IonConstants.tidList;
import static com.amazon.ion.impl._Private_IonConstants.tidSexp;
import static com.amazon.ion.impl._Private_IonConstants.tidStruct;
Expand Down Expand Up @@ -641,13 +642,14 @@ public void writeTimestamp(Timestamp value) throws IOException
else if (_options._timestamp_as_string)
{
// Timestamp is ASCII-safe so this is easy
String valueText = printTimestamp(value, _options.getMaximumTimestampPrecisionDigits());
_output.appendAscii('"');
_output.appendAscii(value.toString());
_output.appendAscii(valueText);
_output.appendAscii('"');
}
else
{
_output.appendAscii(value.toString());
_output.appendAscii(printTimestamp(value, _options.getMaximumTimestampPrecisionDigits()));
}

closeValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,13 @@ private _Private_IonTextWriterBuilder fillDefaults()
b.setNewLineType(NewLineType.PLATFORM_DEPENDENT);
}

if (b.getMaximumTimestampPrecisionDigits() < 1) {
throw new IllegalArgumentException(String.format(
"Configured maximum timestamp precision must be positive, not %d.",
b.getMaximumTimestampPrecisionDigits()
));
}

return (_Private_IonTextWriterBuilder) b.immutable();
}

Expand Down
44 changes: 44 additions & 0 deletions src/main/java/com/amazon/ion/system/IonTextWriterBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.amazon.ion.IonCatalog;
import com.amazon.ion.IonWriter;
import com.amazon.ion.SymbolTable;
import com.amazon.ion.Timestamp;
import com.amazon.ion.impl._Private_IonTextWriterBuilder;
import com.amazon.ion.impl._Private_Utils;
import java.io.OutputStream;
Expand Down Expand Up @@ -221,6 +222,7 @@ public static IonTextWriterBuilder json()
private int myLongStringThreshold;
private NewLineType myNewLineType;
private boolean myTopLevelValuesOnNewLines;
private int myMaximumTimestampPrecisionDigits = Timestamp.DEFAULT_MAXIMUM_DIGITS_TEXT;


/** NOT FOR APPLICATION USE! */
Expand All @@ -240,6 +242,7 @@ protected IonTextWriterBuilder(IonTextWriterBuilder that)
this.myLongStringThreshold = that.myLongStringThreshold;
this.myNewLineType = that.myNewLineType;
this.myTopLevelValuesOnNewLines = that.myTopLevelValuesOnNewLines;
this.myMaximumTimestampPrecisionDigits = that.myMaximumTimestampPrecisionDigits;
}


Expand Down Expand Up @@ -764,6 +767,47 @@ public final IonTextWriterBuilder withWriteTopLevelValuesOnNewLines(boolean writ

//=========================================================================

/**
* Gets the maximum number of digits of fractional second precision allowed to be written for timestamp values.
*
* @return the currently configured maximum.
*
* @see #setMaximumTimestampPrecisionDigits(int)
* @see #withMaximumTimestampPrecisionDigits(int)
*/
public final int getMaximumTimestampPrecisionDigits() {
return myMaximumTimestampPrecisionDigits;
}

/**
* Sets the maximum number of digits of fractional second precision allowed to be written for timestamp values.
* Default: {@link Timestamp#DEFAULT_MAXIMUM_DIGITS_TEXT}.
*
* @see #getMaximumTimestampPrecisionDigits()
* @see #withMaximumTimestampPrecisionDigits(int)
*/
public void setMaximumTimestampPrecisionDigits(int maximumTimestampPrecisionDigits) {
mutationCheck();
myMaximumTimestampPrecisionDigits = maximumTimestampPrecisionDigits;
}

/**
* Sets the maximum number of digits of fractional second precision allowed to be written for timestamp values.
* Default: {@link Timestamp#DEFAULT_MAXIMUM_DIGITS_TEXT}.
*
* @return this instance, if mutable; otherwise a mutable copy of this instance.
*
* @see #getMaximumTimestampPrecisionDigits()
* @see #setMaximumTimestampPrecisionDigits(int)
*/
public final IonTextWriterBuilder withMaximumTimestampPrecisionDigits(int maximumTimestampPrecisionDigits) {
IonTextWriterBuilder b = mutable();
b.setMaximumTimestampPrecisionDigits(maximumTimestampPrecisionDigits);
return b;
}

//=========================================================================

/**
* Creates a new writer that will write text to the given output
* stream.
Expand Down
Loading