Reputation: 53
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},
Upvotes: 2
Views: 756
Reputation: 86324
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 stringIn 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
.
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.
Duration
Upvotes: 5
Reputation: 79395
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
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
.
Upvotes: 3
Reputation: 44071
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
}
Upvotes: 1
Reputation: 730
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
Upvotes: -1
Reputation: 103244
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).
Upvotes: 2