ISO 8601 Time Interval Parsing in Java - java

ISO 8601 defines a syntax for representing a time interval.
There are four ways to express a time interval:
Start and end, such as "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z"
Start and duration, such as "2007-03-01T13:00:00Z/P1Y2M10DT2H30M"
Duration and end, such as "P1Y2M10DT2H30M/2008-05-11T15:30:00Z"
Duration only, such as "P1Y2M10DT2H30M", with additional context information
If any elements are missing from the end value, they are assumed to be the same as for the start value including the time zone. This feature of the standard allows for concise representations of time intervals. For example, the date of a two-hour meeting including the start and finish times could be simply shown as "2007-12-14T13:30/15:30", where "/15:30" implies "/2007-12-14T15:30" (the same date as the start), or the beginning and end dates of a monthly billing period as "2008-02-15/03-14", where "/03-14" implies "/2008-03-14" (the same year as the start).
In addition, repeating intervals are formed by adding "R[n]/" to the beginning of an interval expression, where R is used as the letter itself and [n] is replaced by the number of repetitions. Leaving out the value for [n] means an unbounded number of repetitions. So, to repeat the interval of "P1Y2M10DT2H30M" five times starting at "2008-03-01T13:00:00Z", use "R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M".
I am looking for a good Java parser (if possible compatible with the Joda-Time library) to parse this syntax. Any pointers to a good library ?

java.time
The java.time framework built into Java 8 and later has a Duration.parse method for parsing an ISO 8601 formatted duration:
java.time.Duration d = java.time.Duration.parse("PT1H2M34S");
System.out.println("Duration in seconds: " + d.get(java.time.temporal.ChronoUnit.SECONDS));
Prints Duration in seconds: 3754

For anyone on a project that might be restricted from using 3rd party libraries (licensing reasons, or whatever), Java itself provides at least a portion of this capability, since Java 1.6 (or earlier?), using the javax.xml.datatype.DatatypeFactory.newDuration(String) method and Duration class. The DatatypeFactory.newDuration(String) method will parse a string in "PnYnMnDTnHnMnS" format. These classes are intended for XML manipulation, but since XML uses ISO 8601 time notation, they also serve as convenient duration parsing utilities.
Example:
import javax.xml.datatype.*;
Duration dur = DatatypeFactory.newInstance().newDuration("PT5H12M36S");
int hours = dur.getHours(); // Should return 5
I haven't personally used any duration format except the 4th one you list, so I can't vouch for whether it successfully parses them or not.

I take it you have already tried Joda-Time? Feeding the example strings from your question through Interval.parse(Object) reveals that it can handle "start and end", "start and duration" and "duration and end", but not implied fields nor repetition.
2007-03-01T13:00:00Z/2008-05-11T15:30:00Z => from 2007-03-01T13:00:00.000Z to 2008-05-11T15:30:00.000Z
2007-03-01T13:00:00Z/P1Y2M10DT2H30M => from 2007-03-01T13:00:00.000Z to 2008-05-11T15:30:00.000Z
P1Y2M10DT2H30M/2008-05-11T15:30:00Z => from 2007-03-01T13:00:00.000Z to 2008-05-11T15:30:00.000Z
2007-12-14T13:30/15:30 => java.lang.IllegalArgumentException: Invalid format: "15:30" is malformed at ":30"
R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M => java.lang.IllegalArgumentException: Invalid format: "R5"
The only other comprehensive date/time library that I know of is JSR-310, which does not appear to handle intervals like these.
At this point, building your own improvements on top of Joda-Time is probably your best choice, sorry. Are there any specific ISO interval formats that you need to handle beyond those already supported by Joda-Time?

The only library which is capable to model all the features of interval parsing you want is actually my library Time4J (range-module). Examples:
// case 1 (start/end)
System.out.println(MomentInterval.parseISO("2012-01-01T14:15Z/2014-06-20T16:00Z"));
// output: [2012-01-01T14:15:00Z/2014-06-20T16:00:00Z)
// case 1 (with some elements missing at end component and different offset)
System.out.println(MomentInterval.parseISO("2012-01-01T14:15Z/08-11T16:00+00:01"));
// output: [2012-01-01T14:15:00Z/2012-08-11T15:59:00Z)
// case 1 (with missing date and offset at end component)
System.out.println(MomentInterval.parseISO("2012-01-01T14:15Z/16:00"));
// output: [2012-01-01T14:15:00Z/2012-01-01T16:00:00Z)
// case 2 (start/duration)
System.out.println(MomentInterval.parseISO("2012-01-01T14:15Z/P2DT1H45M"));
// output: [2012-01-01T14:15:00Z/2012-01-03T16:00:00Z)
// case 3 (duration/end)
System.out.println(MomentInterval.parseISO("P2DT1H45M/2012-01-01T14:15Z"));
// output: [2011-12-30T12:30:00Z/2012-01-01T14:15:00Z)
// case 4 (duration only, in standard ISO-format)
Duration<IsoUnit> isoDuration = Duration.parsePeriod("P2DT1H45M");
// case 4 (duration only, in alternative representation)
Duration<IsoUnit> isoDuration = Duration.parsePeriod("P0000-01-01T15:00");
System.out.println(isoDuration); // output: P1M1DT15H
Some remarks:
Other interval classes exist with similar parsing capabilities, for example DateInterval or TimestampInterval in the package net.time4j.range.
For handling durations only (which can span both calendar and clock units as well), see also the javadoc. There are also formatting features, see nested class Duration.Formatter or the localized version net.time4j.PrettyTime (actually in 86 languages).
Interoperability is offered with Java-8 (java.time-package) but not with Joda-Time. For example: The start or end component of a MomentInterval can easily be queried by getStartAsInstant() or getEndAsInstant().
Repeating intervals are supported by the class IsoRecurrence. Example:
IsoRecurrence<MomentInterval> ir =
IsoRecurrence.parseMomentIntervals("R5/2008-03-01T13:00:00Z/P1Y2M10DT2H30M");
ir.intervalStream().forEach(System.out::println);
Output:
[2008-03-01T13:00:00Z/2009-05-11T15:30:00Z)
[2009-05-11T15:30:00Z/2010-07-21T18:00:00Z)
[2010-07-21T18:00:00Z/2011-10-01T20:30:00Z)
[2011-10-01T20:30:00Z/2012-12-11T23:00:00Z)
[2012-12-11T23:00:00Z/2014-02-22T01:30:00Z)

Related

Parse to LocalTime pattern mm:ss.S

How can parse LocalTime from String e.g. "10:38.0" in mm:ss.S format? I struggle to change the format.
public static LocalTime parseTime(String time) {
return localTime = LocalTime.parse(time, DateTimeFormatter.ofPattern("mm:ss.S"));
}
Getting error
ISO of type java.time.format.Parsed
java.time.format.DateTimeParseException: Text '10:38.2' could not be parsed: Unable to obtain LocalTime from TemporalAccessor: {MinuteOfHour=10, MicroOfSecond=200000, MilliOfSecond=200, NanoOfSecond=200000000, SecondOfMinute=38},
java.time.Duration.parse()
As several others have correctly and wisely stated, your example string of 10:38.0 looks more like an amount of time, a duration. Not like a time of day a little more than 10 minutes after midnight. So LocalTime is not the correct class to use here. Use Duration. And parse the string into a Duration object.
The Duration class only supports parsing of ISO 8601 format, though. ISO 8601 format goes like PT10M38.0S for a period of time of 10 minutes 38.0 seconds (or for example PT10M38S or PT10M38.00000S, they work too). There are more ways to overcome this limitation. Arvind Kumar Avinash already shows one in his answer. My way would be to convert the string before parsing it:
public static Duration parseTime(String time) {
String iso = time.replaceFirst("^(\\d+):(\\d+(?:\\.\\d*)?)$", "PT$1M$2S");
return Duration.parse(iso);
}
Let’s try it out:
Duration dur = parseTime("10:38.0");
System.out.println(dur);
Output is:
PT10M38S
You see that the Duration prints back in ISO 8601 format too.
Depending on what further processing you want your duration for you are likely to find many useful methods in the documentation of that class; link below.
How time.replaceFirst("^(\\d+):(\\d+(?:\\.\\d*)?)$", "PT$1M$2S") works: I am using a regular expression to match your string:
^: Match the beginning of your string.
(\\d+): A capturing group matching one or more digits. Round brackets denote capturing groups. I will need this feature in the replacement below.
:: A colon (indeed).
(\\d+(?:\\.\\d*)?): A capturing group of digits optionally followed by a dot and zero or more further digits. (?: denotes the beginning of a non-capturing group that I use since I don’t need it separately in the replacement. ? after the non-capturing group denotes that it is optional (so 38 with no fraction would work for the seconds too).
$: match the end of your string
In my replacement string, PT$1M$2S, $1 and $2 denotes whatever was marched by the first and second capturing groups, which is what inserts 10 and 38.0 into the resulting string to obtain PT10M38.0S.
Nicer solution with an external library: Time4J
Using the non-trivial regular expression above to make your string and Duration.parse() meet isn’t the perfectly beautiful solution. Pattern-based parsing of a duration is supported by the Time4J library. So if you can tolerate an external dependency, consider using it. See the details in the answer by Meno Hochshield, the author of Time4J.
Links
Wikipedia article: ISO 8601
Documentation of Java regular expressions
Documentation of Duration
Answer by Meno Hochschild to this question
DateTimeFormatterBuilder#parseDefaulting
You can use DateTimeFormatterBuilder#parseDefaulting to default the hour of the day to zero.
However, in common sense, 10:38.0 represents a duration. You can obtain a Duration object by finding the duration between the parsed LocalTime and LocalTime.MIN.
Demo:
import java.time.Duration;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
String str = "10:38.0";
DateTimeFormatter dtf = new DateTimeFormatterBuilder()
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.appendPattern("mm:ss.S")
.toFormatter(Locale.ENGLISH);
LocalTime time = LocalTime.parse(str, dtf);
System.out.println(time);
Duration duration = Duration.between(LocalTime.MIN, time);
System.out.println(duration);
}
}
Output:
00:10:38
PT10M38S
ONLINE DEMO
Learn more about the modern Date-Time API* from Trail: Date Time.
* If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring. Note that Android 8.0 Oreo already provides support for java.time.
The problem is, mm:ss isn't really a local time. It's more like a sprint race time. The error occurs because the translation demands a value for # of hours passed, and none are available ('no hours' is interpreted here as: They weren't in the pattern, not: "They were missing, therefore lets assume there are 0 hours").
One hacky way to fix that is to change your pattern to [HH:]mm:ss - the [] indicates optional input. Now the meaning changes: just 10:20 is interpreted as (via the optional input aspect) a shorthand for 00:10:20 and that is parsable into a LocalTime.
But, perhaps LocalTime isn't quite what you're looking for here; if indeed this describes the time passed to measure an event, you're looking for Duration, not LocalTime. Unfortunately, parsing 10:20 into a duration of 10 minutes and 20 seconds is non-trivial, the API just doesn't support it (only way to get there from a DTFormatter object is via LocalTime, crazily enough).
a) Parsing an expression like mm:ss.S without hours is not possible with the class DateTimeFormatter because such a parser tries to interprete it as point in time, not as duration. The missing hour is a fixed requirement for resolving the result to an instance of LocalTime.
b) You probably want a duration, not a LocalTime. Well, java.time has indeed a class named java.time.Duration but it can only format and parse a subset of ISO-8601-like expressions, for example: PT10M38.2S The pattern you want is not supported. Sorry.
c) Some people suggest a compromise by saying: Interprete LocalTime as kind of duration (not really true!) then parse the expression with a default hour value and finally evaluate the minute-of-hour and second-of-minute and so on. However, such a hacky workaround will only work if you never get time component values greater than 59 minutes or 59 seconds.
d) My external library Time4J supports pattern-based printing and parsing of durations. Example using the class net.time4j.Duration.Formatter:
#Test
public void example() throws ParseException {
TemporalAmount ta =
Duration.formatter(ClockUnit.class, "mm:ss.f")
.parse("10:38.2")
.toTemporalAmount();
System.out.println(LocalTime.of(5, 0).plus(ta)); // 05:10:38.200
}
The example also demonstrates a bridge to Java-8-classes like LocalTime via the conversion method toTemporalAmount(). If you use net.time4j.PlainTime instead then the bridge is of course not necessary.
Furthermore, one of many features of the time4j-duration-class is controlled normalizing when an expression contains a time component which does not fit into a standard clock scheme like 10 minutes and 68 seconds (= 11min + 8 sec).
#Test
public void example2() throws ParseException {
net.time4j.Duration dur =
Duration.formatter(ClockUnit.class, "mm:ss.f")
.parse("10:68.2")
.with(Duration.STD_CLOCK_PERIOD); // normalizing
System.out.println(PlainTime.of(5, 0).plus(dur)); // 05:11:08.200
}
I believe you want: .ofPattern("H:mm.s")
public static LocalTime parseTime(String time) {
return LocalTime.parse(time, DateTimeFormatter.ofPattern("H:mm.s"));
}
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html

java parse date with picosecond

I have a string date with picoseconds (between 9 to 12 digits after the period), and when I try to parse the date with the DateTimeFormatter :
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSSSSS");
it throws an exception which says that i cant create a format with more than 9 digits after the period.
Is there a another class which can parse the date, or a different way to parse.
Any solution will help me, but prefer classes / ways which work with LocalDateTime
No solution
I have heard of no class, library, or database that represents picosecond resolution of time.
As commented on the Question, and as famously taught by Dr. Grace Hopper, light travels only about a foot in a nanosecond.
Light travels but a mere fraction of a millimeter in a picosecond. Think of the width of an individual grain of ground pepper, as Hopper showed in lectures. This is smaller than the distance taken by computer technology to move electrical signals around. So such a fine measurement of time is not likely to be practical or relevant in our lifetime.
Truncate
If the picos are not really necessary to your application, I suggest truncating your string, lopping off any digits after the first nine of the fractional second. Then parse as a Instant or LocalDateTime etc.
Roll-your-own class
If you must represent the picoseconds, I suggest splitting your input string in two, based on the decimal fraction delimiter (which under ISO 8601 standard can be either a comma or a period FULL STOP, with comma preferred).
Parse the first part as a LocalDateTime or Instant or a count of whole seconds since an epoch such as 1970-01-01T00:00:00Z.
Then parse the second part as 64-bit long integer.
You could write a little class named something like PicoMoment, with a parse method, to represent these two parts internally. Look to the OpenJDK source code to see similar representations made internally in the java.time classes.
If you go this route, I suggest you follow the java.time API as a model.

How to parse LDAP Generalized Time in Java 8?

Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching Rules defines a Generalized Time which has a seconds component that may be a leap-second.
There are more complications with the definition since the fractional component could refer to either be fractional hours, minutes or seconds depending on what is present.
I have tried various approaches using the DateTimeFormatterBuilder such as a appendInstant, parseUnresolved, and even a custom TemporalField.
The API for DateTimeFormatter takes/returns types written against the date/time abstractions but yet it doesn't seem to actually work with anything other than the standard implementations which is pretty disappointing.
Surely others have hit similar custom formats and I'd hope that these use cases were considered as part of JSR-310.
What are the options?
Is it possible to create a custom formatter which can reuse most of the existing ISO8601 parsing logic?
Discussion of Java-8-approach:
Handling of leap seconds
I don't know if parsing leap seconds is really important for you (because it is rather an exotic feature which hardly happens in standard business applications), but I would not recommend to use the standard Java-8-API for doing it, see also the officially documented limitations:
the handling of leap-seconds is limited to
DateTimeFormatterBuilder.appendInstant()
So following intuitive approach fails (and does so for any other pattern):
DateTimeFormatter dtf =
DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ssXXX").withResolverStyle(ResolverStyle.SMART);
TemporalAccessor raw = dtf.parse("2016-12-31T23:59:60Z");
Instant instant = Instant.from(raw);
System.out.println(
instant
+ " (leap-second-parsed=" + raw.query(DateTimeFormatter.parsedLeapSecond()) + ")");
Rather you have to do this:
DateTimeFormatter dtf =
DateTimeFormatter.ISO_INSTANT;
TemporalAccessor raw = dtf.parse("2016-12-31T23:59:60Z");
Instant instant = Instant.from(raw);
System.out.println(
instant
+ " (leap-second-parsed=" + raw.query(DateTimeFormatter.parsedLeapSecond()) + ")");
// 2016-12-31T23:59:59Z (leap-second-parsed=true)
However, it fails for an input with timezone offset not equal to zero, and the code does not validate due to internal lack of leap second data if the input is really a true leap second, for example it says that "2015-05-01T23:59:60Z" is a leap second (but we know better it is not such one).
Handling of decimal hours and minutes
The suggested solution given by S. Colebourne (the author of java.time-API) is flawed. Using appendFraction() suffers from handling only ONE element but handling of decimal parts requires to handle the specified element AND all other elements with higher precision. See first the printing example (based on the code of the proposal):
DateTimeFormatter f = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd HH")
.appendFraction(ChronoField.MINUTE_OF_HOUR, 0, 6, true)
.appendOffsetId()
.toFormatter();
OffsetDateTime dt = OffsetDateTime.of(2017, 3, 21, 5, 28, 59, 0, ZoneOffset.UTC);
System.out.println(dt); // 2017-03-21T05:28:59Z
System.out.println(dt.format(f)); // 2017-03-21 05.466666Z
OffsetDateTime dt2 = OffsetDateTime.of(2017, 3, 21, 5, 28, 0, 0, ZoneOffset.UTC);
System.out.println(dt2); // 2017-03-21T05:28Z
System.out.println(dt2.format(f)); // 2017-03-21 05.466666Z
We see that two different OffsetDateTime-values result in the same decimal hour which is obviously wrong. The difference was just a delta in the field SECOND_OF_MINUTE (not taken into account by appendFraction()).
What about parsing? We can observe the same effect in reverse which makes the whole approach unusable.
Let the input to be parsed "2017-03-01 13.52Z" as given in the proposal as example. The observed parsed value is: 2017-03-01T13:31Z But this result is NOT correct. It should be: 2017-03-01T13:31.2Z or 2017-03-01T13:31:12Z (explanation: 0.52 x 60 = 31.2 => minute component and 0.2 * 60 = 12 => second component).
Conclusion: Don't use the standard API for handling decimal values of time-related elements. There is no support by design. I say even "by design" because all fields finally work with a long-primitive (as value type) which is not suitable to incorporate decimal values based on several fields.
What to do else? I have set up my own library to fill gaps in the java.time-API like described above.
Time4J-solution (v4.25 or later):
I suggest you to use following code in order to model the LDAP-specification. It is rather complex but this is necessary due to the complexity of the specification itself.
ChronoFormatter<PlainDate> df =
ChronoFormatter.setUp(PlainDate.axis(), Locale.ROOT)
.addFixedInteger(PlainDate.YEAR, 4)
.addFixedInteger(PlainDate.MONTH_AS_NUMBER, 2)
.addFixedInteger(PlainDate.DAY_OF_MONTH, 2)
.build();
ChronoFormatter<Moment> mf =
ChronoFormatter.setUp(Moment.axis(), Locale.US) // US for preference of dot in decimal elements
.addCustomized(PlainDate.COMPONENT, df)
.addFixedInteger(PlainTime.DIGITAL_HOUR_OF_DAY, 2)
.startOptionalSection()
.addFixedInteger(PlainTime.MINUTE_OF_HOUR, 2)
.startOptionalSection()
.addFixedInteger(PlainTime.SECOND_OF_MINUTE, 2)
.startOptionalSection()
.addLiteral('.', ',')
.addFraction(PlainTime.NANO_OF_SECOND, 1, 9, false)
.endSection()
.endSection()
.endSection()
.addTimezoneOffset(DisplayMode.SHORT, false, Collections.singletonList("Z"))
.or()
.addCustomized(PlainDate.COMPONENT, df)
.addFixedInteger(PlainTime.DIGITAL_HOUR_OF_DAY, 2)
.addFixedDecimal(PlainTime.DECIMAL_MINUTE)
.addTimezoneOffset(DisplayMode.SHORT, false, Collections.singletonList("Z"))
.or()
.addCustomized(PlainDate.COMPONENT, df)
.addFixedDecimal(PlainTime.DECIMAL_HOUR)
.addTimezoneOffset(DisplayMode.SHORT, false, Collections.singletonList("Z"))
.build();
assertThat(
mf.parse("199412160532-0500").toString(),
is("1994-12-16T10:32:00Z"));
assertThat(
mf.parse("199412160532Z").toString(),
is("1994-12-16T05:32:00Z"));
assertThat(
mf.parse("20161231185960.123456789-0500").toString(),
is("2016-12-31T23:59:60,123456789Z"));
assertThat(
mf.parse("201612311859.25-0500").toString(),
is("2016-12-31T23:59:15Z"));
assertThat(
mf.parse("2016123118.25-0500").toString(),
is("2016-12-31T23:15:00Z"));
As you can see, the code does handle leap seconds (even with non-zero offsets). Time4J also validates leap seconds because it manages its independent leap second data (for example extracted from IANA-TZDB). And a leap second is stored within an object of type Moment. This type is the counterpart to java.time.Instant. A conversion between both types is trivial (or directly via the method moment.toTemporalAccessor()). Just to note: The leap second itself will be lost during such a conversion. If you just want to ignore the leap second i.e. handle it like the last second before then either just go with the conversion to Instant or work with standard POSIX-related methods in Moment (and the conversion to any "local" types like PlainTimestamp/LocalDateTime etc. also looses the leap second).
Decimal values are also supported because the interface ChronoElement (as counterpart to TemporalField) is generified and based on an object value type, not a long-primitive, see for example the element for the decimal minute which uses the value type BigDecimal.
Finally parsing dots or commas is possible (as required by the LDAP-specification). This is a detail which is also not supported by Java-8, for comparison see the JDK-issue 8163932.
This code parses fractional hours. It can be adjusted for fractional days:
DateTimeFormatter f = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd HH")
.appendFraction(MINUTE_OF_HOUR, 0, 6, true)
.appendOffsetId()
.toFormatter();
OffsetDateTime dt = OffsetDateTime.now();
System.out.println(dt.format(f));
System.out.println(OffsetDateTime.parse("2017-03-01 13.52Z", f));
This code can be used to find a leap second:
DateTimeFormatter fmt = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
String text = "2017-03-01T23:59:60";
ParsePosition pp = new ParsePosition(0);
TemporalAccessor accessor = fmt.parseUnresolved(text, pp);
if (pp.getErrorIndex() >= 0) {
throw new DateTimeParseException("Parse error", text, pp.getErrorIndex());
}
if (accessor.getLong(SECOND_OF_MINUTE) == 60) {
System.out.println("Leap second");
} else {
System.out.println("Not a leap second");
}
The time library is very extensible. There are implementations at ThreeTen-Extra which show what can be done in terms of alternate date-time classes like YearQuarter and alternative fields like PackedFields.
Update:
It is worth noting for posterity that the complexity here comes from the need to support fractional minutes, hours and days. When parsing a standard Instant, leap seconds are just ignored by default.

java.time ISO date format with fixed millis digits (in Java 8 and later)

By default, the toString method of Instant uses the DateTimeFormatter.ISO_INSTANT formatter. That formatter won’t print the digits for fraction-of-second if they happen to be 0.
java-time examples:
2015-10-08T17:13:07.589Z
2015-10-08T17:13:07Z
Joda-Time examples (and what I'd expect from java.time):
2015-10-08T17:13:07.589Z
2015-10-08T17:13:07.000Z
This is really frustrating to parse in some systems. Elasticsearch was the first problem I encountered, there's no pre-defined format that supports optional millis, but I can probably work around that with a custom format. The default just seems wrong.
It appears that you can’t really build your own format string for Instants anyway. Is the only option implementing my own java.time.format.DateTimeFormatterBuilder.InstantPrinterParser?
Just create a DateTimeFormatter that keeps three fractional digits.
DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendInstant(3).toFormatter();
Then use it. For example:
System.out.println(formatter.format(Instant.now()));
System.out.println(formatter.format(Instant.now().truncatedTo(ChronoUnit.SECONDS)));
…prints (at the time I run it):
2015-10-08T21:26:16.571Z
2015-10-08T21:26:16.000Z
Excerpt of the class doc:
… The fractionalDigits parameter allows the output of the fractional second to be controlled. Specifying zero will cause no fractional digits to be output. From 1 to 9 will output an increasing number of digits, using zero right-padding if necessary. The special value -1 is used to output as many digits as necessary to avoid any trailing zeroes. …

Java : Custom Timestamp Format : verify format to microsec precision

My Objective is
to create a java class that can handle the below two requirements
(A) 1. Verify if the format of a timestamp matches with expected format.
CCYY-MM-DD'T'hh:mm:ss'.0000000000+'uh:um"
Ex: the expected format is not static.
It may be either of these
"2013-09-10T18:30:20.123456+10:00" or
"2013-09-10T18:30:20.123+10:00".
I am not bothered about the
precision and value. Only the format matters.
(B) 2. Verify if the timestamp is in a certain range.
Ex: Verify if the timestamp is in
between "2013-09-10 18:27" and "2013-09-10 18:33". (verification is only upto minute level precision) (may be a delta of + or - 2min)
As suggested by one of the member, I have edited the post to target at
One specific question.
The QUESTION :
How to validate the custom timestamp upto microsec precision using JAVA class ?
The two arguments for this class will be
1) Expected FORMAT as a String
2) timestamp value as a String
Based on analysis from various search results, below is my understanding :
Java (by default) does not parse/format Timestamp at microsecond level( I used SimpleDateFormat)
If 6 digits are given in milliseconds place, it will re-calculate the value into seconds and the dateformat will be updated and the new dateformat will have 3 digits in milliseconds precision.
I have also seen a thread which suggests to use java.sql.Timestamp.
Tried this approach but not working.
I was not able to convert my strTimestamp 2013-09-10T18:30:20.123456+10:00 into Timestamp object.
Timestamp ts = Timestamp.valueOf(strTimestamp);
java.lang.IllegalArgumentException:
Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]
I was not able convert my input format into Timestamp object.
I have a workaround to validate using regular expression :
2013-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9]).[0-9][0-9][0-9][0-9][0-9][0-9]\+10:00
The problem with this reg ex is that, my expected timestamp format is not static. So i have to use a regex for every pattern.
So I am trying to figure out if there is any robust solution in java, which can be self sufficient even if the expected format changes.
java.time in Java 8
JSR 310 defined a new java.time package in Java 8. Its date-time class resolves to nanoseconds. That gives you 9 digits after the decimal point.
The java.time package is inspired by Joda-Time but entirely re-architected. Concepts are similar.
Like Joda-Time, the java.time package uses ISO 8601 formats as its defaults for parsing and formatting. So you can input or output strings such as 2013-09-10T18:30:20.123456789+10:00.
An early release of Java 8 is available now. Official release should be this month.
A project to backport this package to earlier versions of Java was underway. I do not know of its current status or success. The backport project is independent of Oracle and the OpenJDK project.
Milliseconds
The old bundled classes, java.util.Date & .Calendar, use a precision of milliseconds.
Ditto for the excellent Joda-Time library, milliseconds precision.
So not enough digits in the fractional seconds to meet your needs.
A java.sql.Timestamp is not going to help you, because that is a java.util.Date.
The code is fairly simple, if you use the right format String with SimpleDateFormat, which you let do the heavy lifting. Here's an entire working solution:
public static boolean isNear(String timestamp, int microPlaces, Date near, int minutes) {
if (!timestamp.matches(".*\\.\\d{" + microPlaces + "}\\D.*") {
return false;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSSSSZ");
try {
Date date = sdf.parse(timestamp.replaceAll(":(?=\\d\\d$)", ""));
return Math.abs(date.getTime() - near.getTime()) <= minutes * 60000;
} catch (ParseException ignore) {
return false; // string was not of correct format
}
}
This may not be exactly what you had in mind - if not, you should be able to use it as a basis for what you want. The key points are:
The S format string means "microseconds", and it doesn't require all the digits - so your timestamp can have any number
Java 6 needs the colon removed from the timezone. Java 7 doesn't need this - use the X format string instead of Z
A failure to parse a date from the input throws a ParseException - do what you want with this event
I chose to make the API give central date for the range and a +/- minute value. You may need to pass two dates - up to you. Use Date.before() and Date.after() to compare if you do that.
Here's some test code testing your examples and a couple of edge cases:
public static void main(String[] args) throws Exception {
Date near = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm").parse("2013-09-10T18:32");
System.out.println(isNear("2013-09-10T18:30:20.123456+10:00", near, 2));
System.out.println(isNear("2013-09-10T18:30:20.123+10:00", near, 2));
System.out.println(isNear("2013-09-10T18:10:20.123+10:00", near, 1));
System.out.println(isNear("XXXX-09-10T18:10:20.123+10:00", near, 1));
}
Output:
true
true
false
false
Really I`m also trying to find answer to this problem. As I have no ability to add comment to the Bohemian answer. I want to mention that 'S' pattern in SimpleDateFormat is used not for microseconds but for milliseconds. It means that for pattern "yyyy-MM-dd'T'hh:mm:ss.SSSSSSZ" provided microsecond digits in string would be parsed as milliseconds.
So the first three digits would be passed as XXX seconds and their value value would be added to date. So we can receive mistake about 16 minutes.

Categories