Skip to content
This repository has been archived by the owner on Mar 20, 2018. It is now read-only.

Commit

Permalink
Add dedicated formatter for instants
Browse files Browse the repository at this point in the history
This approach handles the instant case without exposing the conversion to local date-time. The current implementation is temporary and a placeholder for something better.
See #32
  • Loading branch information
jodastephen committed Sep 4, 2012
1 parent eeedbe2 commit 133c229
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 27 deletions.
34 changes: 10 additions & 24 deletions src/main/java/javax/time/Instant.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import javax.time.calendrical.DateTimeField;
import javax.time.calendrical.LocalDateTimeField;
import javax.time.format.CalendricalParseException;
import javax.time.format.DateTimeFormatters;

/**
* An instantaneous point on the time-line.
Expand Down Expand Up @@ -327,31 +328,18 @@ public static Instant from(DateTime calendrical) {

//-----------------------------------------------------------------------
/**
* Obtains an instance of {@code Instant} by parsing a string.
* Obtains an instance of {@code Instant} from a text string such as
* {@code 2007-12-03T10:15:30:00}.
* <p>
* This will parse the string produced by {@link #toString()} which is
* the ISO-8601 format {@code yyyy-MM-ddTHH:mm:ss.SSSSSSSSSZ}.
* The numbers must be ASCII numerals.
* The seconds are mandatory, but the fractional seconds are optional.
* There must be no more than 9 digits after the decimal point.
* The letters (T and Z) will be accepted in upper or lower case.
* The string must represent a valid instant in UTC and is parsed using
* {@link DateTimeFormatters#isoInstant()}.
*
* @param text the text to parse, not null
* @return an instant, not null
* @throws CalendricalParseException if the text cannot be parsed to an {@code Instant}
* @return the parsed instant, not null
* @throws CalendricalParseException if the text cannot be parsed
*/
// TODO: optimize and handle big instants
public static Instant parse(final CharSequence text) {
DateTimes.checkNotNull(text, "Text to parse must not be null");
int length = text.length();
if (length < 2) {
throw new CalendricalParseException("Instant could not be parsed: " + text, text, 0);
}
if (text.charAt(length - 1) != 'Z' && text.charAt(length - 1) != 'z') {
throw new CalendricalParseException("Instant could not be parsed: " + text, text, length - 1);
}
String str = text.toString().replace(',', '.'); //TODO properly, the decimal point may be either a dot or a comma
return OffsetDateTime.of(LocalDateTime.parse(str.substring(0, length - 1)), ZoneOffset.UTC).toInstant();
return DateTimeFormatters.isoInstant().parse(text, Instant.class);
}

//-----------------------------------------------------------------------
Expand Down Expand Up @@ -771,15 +759,13 @@ public int hashCode() {
/**
* A string representation of this instant using ISO-8601 representation.
* <p>
* The format of the returned string will be {@code yyyy-MM-ddTHH:mm:ss.SSSSSSSSSZ}.
* The format used is the same as {@link DateTimeFormatters#isoInstant()}.
*
* @return an ISO-8601 representation of this instant, not null
*/
@Override
public String toString() {
// TODO: optimize and handle big instants
// TODO: Consider epoch plus offset format instead
return OffsetDateTime.ofInstantUTC(this).toLocalDateTime().toString() + 'Z';
return DateTimeFormatters.isoInstant().print(this);
}

}
49 changes: 49 additions & 0 deletions src/main/java/javax/time/format/DateTimeFormatterBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
*/
package javax.time.format;

import static javax.time.calendrical.LocalDateTimeField.INSTANT_SECONDS;
import static javax.time.calendrical.LocalDateTimeField.NANO_OF_SECOND;
import static javax.time.calendrical.LocalDateTimeField.OFFSET_SECONDS;

import java.math.BigDecimal;
Expand All @@ -51,6 +53,8 @@

import javax.time.CalendricalException;
import javax.time.DateTimes;
import javax.time.Instant;
import javax.time.OffsetDateTime;
import javax.time.ZoneId;
import javax.time.ZoneOffset;
import javax.time.calendrical.DateTimeBuilder;
Expand Down Expand Up @@ -536,6 +540,23 @@ public Locale[] getAvailableLocales() {
}

//-----------------------------------------------------------------------
/**
* Appends an instant using ISO-8601 to the formatter.
* <p>
* Instants have a fixed output format.
* They are converted to a date-time with a zone-offset of UTC and printed
* using the standard ISO-8601 format.
* <p>
* An alternative to this method is to print/parse the instant as a single
* epoch-seconds value. That is achieved using {@code appendValue(INSTANT_SECONDS)}.
*
* @return this, for chaining, not null
*/
public DateTimeFormatterBuilder appendInstant() {
appendInternal(new InstantPrinterParser());
return this;
}

/**
* Appends the zone offset, such as '+01:00', to the formatter.
* <p>
Expand Down Expand Up @@ -2204,6 +2225,34 @@ public String toString() {
}
}

//-----------------------------------------------------------------------
/**
* Prints or parses an ISO-8601 instant.
*/
static final class InstantPrinterParser implements DateTimePrinterParser {

InstantPrinterParser() {
}

@Override
public boolean print(DateTimePrintContext context, StringBuilder buf) {
// TODO: implement this from INSTANT_SECONDS, handling big numbers
Instant instant = Instant.from(context.getCalendrical());
OffsetDateTime odt = OffsetDateTime.ofInstantUTC(instant);
buf.append(odt);
return true;
}

@Override
public int parse(DateTimeParseContext context, CharSequence text, int position) {
// TODO: implement this from INSTANT_SECONDS, handling big numbers
OffsetDateTime odt = OffsetDateTime.parse(text.subSequence(position, text.length()));
context.setParsedField(INSTANT_SECONDS, odt.get(INSTANT_SECONDS));
context.setParsedField(NANO_OF_SECOND, odt.get(NANO_OF_SECOND));
return text.length();
}
}

//-----------------------------------------------------------------------
/**
* Prints or parses a zone offset.
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/javax/time/format/DateTimeFormatters.java
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,31 @@ public static DateTimeFormatter isoOrdinalDate() {
.toFormatter();
}

//-----------------------------------------------------------------------
/**
* Returns the ISO instant formatter that prints/parses an instant in UTC.
* <p>
* This is the ISO-8601 extended format:<br />
* {@code yyyy-MM-dd'T'HH:mm:ssfnnnnnnnnnZZZ}
* <p>
* The year will print 4 digits, unless this is insufficient, in which
* case the full year will be printed together with a positive/negative sign.
*
* @return the ISO instant formatter, not null
*/
public static DateTimeFormatter isoInstant() {
return ISO_INSTANT;
}

/** Singleton formatter. */
private static final DateTimeFormatter ISO_INSTANT;
static {
ISO_INSTANT = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendInstant()
.toFormatter();
}

//-----------------------------------------------------------------------
// /**
// * Returns the ISO date formatter that prints/parses a date without an offset.
Expand Down
15 changes: 12 additions & 3 deletions src/test/java/javax/time/TestInstant.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

import javax.time.format.CalendricalParseException;
Expand Down Expand Up @@ -384,13 +385,21 @@ public void factory_parse(String text, long expectedEpochSeconds, int expectedNa
}

@Test(dataProvider="Parse")
public void factory_parse_comma(String text, long expectedEpochSeconds, int expectedNanoOfSecond) {
text = text.replace('.', ',');
Instant t = Instant.parse(text);
public void factory_parseLowercase(String text, long expectedEpochSeconds, int expectedNanoOfSecond) {
Instant t = Instant.parse(text.toLowerCase(Locale.ENGLISH));
assertEquals(t.getEpochSecond(), expectedEpochSeconds);
assertEquals(t.getNano(), expectedNanoOfSecond);
}

// TODO: should comma be accepted?
// @Test(dataProvider="Parse")
// public void factory_parse_comma(String text, long expectedEpochSeconds, int expectedNanoOfSecond) {
// text = text.replace('.', ',');
// Instant t = Instant.parse(text);
// assertEquals(t.getEpochSecond(), expectedEpochSeconds);
// assertEquals(t.getNano(), expectedNanoOfSecond);
// }

@DataProvider(name="ParseFailures")
Object[][] provider_factory_parseFailures() {
return new Object[][] {
Expand Down

0 comments on commit 133c229

Please sign in to comment.