How to parse military time (including truncated partials) into a Time? - java

Official airline departure and arrival times are often provided in hour and minutes. The following are typical examples:
1830 - 6:30 pm
730 - 7:30 am
30 - 30 minutes after midnight (ie 12:30 am)
The first two can be parsed using DateTimeFormatter with HHmm and Hmm. The third results in a parsing error, and attempting to parse it with only minutes (mm) results in a different error: Unable to obtain LocalTime from TemporalAccessor: {MinuteOfHour=30}
Constraints:
I would like to provide a general solution to handle this using formatters if possible, as i don't want to break parsing for all other time variants that work.
Obviously I could pre-process the incoming data to prepend missing zeros, but i have many GB of data and would like to avoid an additional pass.
Thanks for your help.
Update: An obvious solution is to prepend zeros in the same pass. For example, using Guava:
stringValue = Strings.padStart(stringValue, 4, '0');
LocalTime.parse(stringValue, TypeUtils.timeFormatter);
Still curious if there a way to do this only with standard formatting codes like hh and mm.

Well, you can create a default using DateTimeFormatterBuilder:
String timeStr = "30";
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ofPattern("mm"))
.parseDefaulting(ChronoField.HOUR_OF_DAY,0)
.toFormatter();
LocalTime parsedTime = LocalTime.parse(timeStr, formatter);

Related

Converting Joda DateTime to JavaTime

I am trying to update my existing elasticsearch springboot project and as the source code is fairly old it still uses joda time. Now I have to upgrade all the functions of Joda time to java time. Now in the project We use Date Time of Joda Time
Code Sample for Joda Time
DateTime target = new DateTime(String targetDate, UTC);
We use this function currently in our code to convert a String to Date.
Using this function the String
2022-10-01T00:00:00.000
gets converted to
2022-10-01T00:00:00.000Z
I am trying to replicate the same in java time.
I tried to parse the targetDate using OffsetDateTime and ZonedDateTime but both gave me errors.
Text '2022-10-01T00:00:00.0000' could not be parsed at index 24
After some attempts I was able to move forward by using LocalDateTime
LocalDateTime target = LocalDateTime.parse(String targetDate);
Which was able to parse the String but the format was not correct the format I got was
2022-10-01T00:00Z
I also tried using the formatter with LocalDateTime
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSS'Z'");
LocalDateTime target= LocalDateTime.parse(targetDate,formatter);
But This Still gave me the Error
Text '2022-10-01T00:00:00.0000' could not be parsed at index 24
Now I am a bit confused regarding this.
Any help is appreciated.
And Please correct me if my way of asking question or formatting is wrong at any point still new to this.
Regards.
EDIT: Sorry for the confusion but as pointed out I should have mentioned that I want the returned value as the java.time datetime object and not a String so that I can further perform some logic on it. Sorry for this.
Thanks and Regards
The String "2022-10-01T00:00:00.000" can be parsed to a LocalDateTime because it only consists of year, month of year, day of month, hour of day, minute of hour, second of minute and fractions of second.
Your desired output String "2022-10-01T00:00:00.000Z" represents the same values plus an offset, the Z for Zulu time, which basically means UTC.
If you want to add an offset to the input String with java.time, you can parse it to a LocalDateTime and then append the desired offset, which results in an OffsetDateTime. You can print that in a desired format using a DateTimeFormatter, either use a prebuilt one or define one yourself.
Here's a small example:
public static void main(String[] args) {
// input value
String dateTime = "2022-10-01T00:00:00.000";
// parse it and append an offset
OffsetDateTime odt = LocalDateTime.parse(dateTime).atOffset(ZoneOffset.UTC);
// define a formatter that formats as desired
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX");
// and print the OffsetDateTime using that formatter
System.out.println(odt.format(dtf));
}
Output:
2022-10-01T00:00:00.000Z
Clarification update:
There is just a single instance OffsetDateTime in my example with the following values:
year (2022)
month of year (10)
day of month (1)
hour of day (0)
minute of hour (0)
second of minute (0)
franctions of second (0)
offset (UTC / +00:00)
This instance of OffsetDateTime can be used for calculations (e.g. add/subtract days, months or other units) and it can be formatted as String. It also has a toString() method we don't have under control, but is used if you don't explicitly format it.
The following lines (first one is the last of my example above) show some different usages:
// print formatted by the DateTimeFormatter from the above example
System.out.println(odt.format(dtf));
// print the object directly, implicitly using its toString()
System.out.println(odt);
// print formatted by a prebuilt DateTimeFormatter (several are available)
System.out.println(odt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
// print with a formatter that uses locale dependant expressions like month names
System.out.println(odt.format(
DateTimeFormatter.ofPattern("EEEE, MMM dd HH:mm:ss xxx",
Locale.ENGLISH)));
the last one also uses a different representation for UTC: instead of Z it shows the offset in hours and minutes.
Output:
2022-10-01T00:00:00.000Z
2022-10-01T00:00Z
2022-10-01T00:00:00Z
Saturday, Oct 01 00:00:00 +00:00

Unable to parse optional microseconds in localTime

I am receiving timestamp in format : HHmmss followed by milleseconds and microseconds.Microseconds after the '.' are optional
For example: "timestamp ":"152656375.489991" is 15:26:56:375.489991.
Below code is throwing exceptions:
final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
.appendPattern("HHmmssSSS")
.appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true)
.toFormatter();
LocalTime.parse(dateTime,FORMATTER);
Can someone please help me with DateTimeformatter to get LocalTime in java.
Here is the stacktrace from the exception from the code above:
java.time.format.DateTimeParseException: Text '152656375.489991' could not be parsed: Conflict found: NanoOfSecond 375000000 differs from NanoOfSecond 489991000 while resolving MicroOfSecond
at java.base/java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1959)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1894)
at java.base/java.time.LocalTime.parse(LocalTime.java:463)
at com.ajax.so.Test.main(Test.java:31)
Caused by: java.time.DateTimeException: Conflict found: NanoOfSecond 375000000 differs from NanoOfSecond 489991000 while resolving MicroOfSecond
at java.base/java.time.format.Parsed.updateCheckConflict(Parsed.java:329)
at java.base/java.time.format.Parsed.resolveTimeFields(Parsed.java:462)
at java.base/java.time.format.Parsed.resolveFields(Parsed.java:267)
at java.base/java.time.format.Parsed.resolve(Parsed.java:253)
at java.base/java.time.format.DateTimeParseContext.toResolved(DateTimeParseContext.java:331)
at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1994)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1890)
... 3 more
There are many options, depending on the possible variations in the strings you need to parse.
1. Modify the string so you need no formatter
String timestampString = "152656375.489991";
timestampString = timestampString.replaceFirst(
"^(\\d{2})(\\d{2})(\\d{2})(\\d{3})(?:\\.(\\d*))?$", "$1:$2:$3.$4$5");
System.out.println(timestampString);
LocalTime time = LocalTime.parse(timestampString);
System.out.println(time);
The output from this snippet is:
15:26:56.375489991
The replaceFirst() call modifies your string into 15:26:56.375489991, the default format for LocalTime (ISO 8601) so it can be parsed without any explicit formatter. For this I am using a regular expression that may not be too readable. (…) enclose groups that I use as $1, $2, etc., in the replacement string. (?:…) denotes a non-capturing group, that is, cannot be used in the replacement string. I put a ? after it to specify that this group is optional in the original string.
This solution accepts from 1 through 6 decimals after the point and also no fractional part at all.
2. Use a simpler string modification and a formatter
I want to modify the string so I can use this formatter:
private static DateTimeFormatter fullParser
= DateTimeFormatter.ofPattern("HHmmss.[SSSSSSSSS][SSS]");
This requires the point to be after the seconds rather than after the milliseoncds. So move it three places to the left:
timestampString = timestampString.replaceFirst("(\\d{3})(?:\\.|$)", ".$1");
LocalTime time = LocalTime.parse(timestampString, fullParser);
15:26:56.375489991
Again I am using a non-capturing group, this time to say that after the (captured) group of three digits must come either a dot or the end of the string.
3. The same with a more flexible parser
The formatter above specifies that there must be either 9 or 3 digits after the decimal point, which may be too rigid. If you want to accept something in between too, a builder can build a more flexible formatter:
private static DateTimeFormatter fullParser = new DateTimeFormatterBuilder()
.appendPattern("HHmmss")
.appendFraction(ChronoField.NANO_OF_SECOND, 3, 9, true)
.toFormatter();
I think that this would be my favourite approach, again depending on the exact requirements.
4. Parse only a part of the string
There is no problem so big and awful that it cannot simply be run away
from (Linus in Peanuts, from memory)
If you can live without the microseconds, ignore them:
private static DateTimeFormatter partialParser
= DateTimeFormatter.ofPattern("HHmmssSSS");
To parse only a the part of the string up to the point using this formatter:
TemporalAccessor parsed
= partialParser.parse(timestampString, new ParsePosition(0));
LocalTime time = LocalTime.from(parsed);
15:26:56.375
As you can see it has ignored the part from the decimal point, which I wouldn’t find too satisfactory.
What went wrong in your code?
Your 6 digits after the decimal point denote nanoseconds. Microseconds would have been only 3 decimals after the milliseconds. To use appendFraction() to parse these you would have needed a TemporalUnit of nano of millisecond. The ChronoUnit enum offers nano of day and nano of second, but not nano of milli. TemporalUnit is an interface, so in theory we could develop our own nano of milli class for the purpose. I tried to develop a class implementing TemporalUnit once, but gave up, I couldn’t get it to work.
Links
Wikipedia article: ISO 8601
Regular expressions in Java - Tutorial

adding minutes using a formatted string in java or joda time

I'm trying to add a string representation of time in minutes (4:30) to another string (10:00:00) like you would say 'ten o'clock plus 4 minutes, 30 seconds.
If I sound a bit verbose, it's because I've spent 7 hrs searching the web for an answer and keep getting how to convert a fixed string of even minutes to date/time.
I tried to use joda time but can't figure out how to make 4:30 into an integer (I can make it work with'04'). These times are strings in code from variables, not something the user enters at the command line.
I'm using JDK 1.7 and netbeans 8.
Java 8 Time API
You could take advantage of Java 8's new Time API, which is similar to JodaTime
The first thing you need is a Duration, something like...
Duration d = Duration.parse("PT0H4M30S");
or
Duration d = Duration.ofMinutes(4).plusSeconds(30);
Next, you need to generate a LocalTime value
LocalTime time = LocalTime.parse("10:00:00", DateTimeFormatter.ofPattern("HH:mm:ss"));
and then you can simply add the Duration to it
time = time.plus(d);
which will result in a value of
10:04:30
The difficult part is getting the values of the duration from the String, but if you can guarantee the format, you could simply use String#split
JodaTime
JodaTime would make it slightly easier, for example, you can parse 4:30 into a Period using something like...
PeriodFormatter formatter = new PeriodFormatterBuilder()
.appendMinutes().appendSuffix(":")
.appendSeconds()
.toFormatter();
Period p = formatter.parsePeriod("4:30");
Then you could simply parse 10:00:00 into a LocalTime and add the Period
LocalTime lt = LocalTime.parse("10:00:00", DateTimeFormat.forPattern("HH:mm:ss"));
lt = lt.plus(p);
System.out.println(lt);
which outputs...
10:04:30.000

java.text.ParseException: Unparseable date "yyyy-MM-dd'T'HH:mm:ss.SSSZ" - SimpleDateFormat

I would appreciate any help with finding bug for this exception:
java.text.ParseException: Unparseable date: "2007-09-25T15:40:51.0000000Z"
and following code:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
Date date = sdf.parse(timeValue);
long mills = date.getTime();
this.point.time = String.valueOf(mills);
It throws expcetion with Date date = sdf.parse(timeValue); .
timeValue = "2007-09-25T15:40:51.0000000Z"; , as in exception.
Thanks.
Z represents the timezone character. It needs to be quoted:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
(Answer now extensively revised, thanks for the corrections in the comments)
In Java 7 you can use the X pattern to match an ISO8601 timezone, which includes the special Z (UTC) value.
The X pattern also supports explicit timezones, e.g. +01:00
This approach respects the timezone indicator correctly, and avoids the problem of treating it merely as a string, and thus incorrectly parsing the timestamp in the local timezone rather than UTC or whatever.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
Date date = sdf.parse("2007-09-25T15:40:51Z");
Date date2 = sdf.parse("2007-09-25T15:40:51+01:00");
This can also be used with milliseconds:
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
Date date3 = sdf2.parse("2007-09-25T15:40:51.500Z");
However, as others have pointed out, your format has 7-digit fractional seconds, which are presumably tenth-microseconds. If so, SimpleDateFormat cannot handle this, and you will get incorrect results, because each 0.1 microsecond will be interpreted as a millisecond, giving a potential overall error of up to 10,000 seconds (several hours).
In the extreme case, if the fractional second value is 0.9999999 seconds, that will be incorrectly interpreted as 9999999 milliseconds, which is about 167 minutes, or 2.8 hours.
// Right answer, error masked for zero fractional seconds
Date date6 = sdf2.parse("2007-09-25T15:40:51.0000000Z");
// Tue Sep 25 15:40:51 GMT 2007
// Error - wrong hour
// Should just half a second different to the previous example
Date date5 = sdf2.parse("2007-09-25T15:40:51.5000000Z");
// Tue Sep 25 17:04:11 GMT 2007
This error is hidden when the fractional seconds are zero, as in your example, but will manifest whenever they are nonzero.
This error can be detected in many cases, and its impact reduced, by turning off "lenient" parsing which by default will accept a fractional part of more than one second and carry it over to the seconds/minutes/hours parts:
sdf2.setLenient(false);
sdf2.parse("2007-09-25T15:40:51.5000000Z");
// java.text.ParseException: Unparseable date: "2007-09-25T15:40:51.5000000Z"
This will catch cases where the millis value is more than 999, but does not check the number of digits, so it is only a partial and indirect safeguard against millis/microseconds mismatches. However, in many real-world datasets this will catch a large number of errors and thus indicate the root problem, even if some values slip through.
I recommend that lenient parsing is always disabled unless you have a specific need for it, as it catches a lot of errors that would otherwise be silently hidden and propagated into downstream data.
If your fractional seconds are always zero, then you could use one of the solutions here, but with the risk that they will NOT work if the code is later used on non-zero fractional seconds. You may wish to document this and/or assert that the value is zero, to avoid later bugs.
Otherwise, you probably need to convert your fractional seconds into milliseconds, so that SimpleDateFormat can interpret them correctly. Or use one of the newer datetime APIs.
java.time
I recommend that you use java.time, the modern Java date and time API, for your date and time work. Your string is in ISO 8601 format and can be directly parsed by the java.time.Instant class without us specifying any formatter:
String timeValue = "2007-09-25T15:40:51.0000000Z";
Instant i = Instant.parse(timeValue);
long mills = i.toEpochMilli();
String time = String.valueOf(mills);
System.out.println(time);
Output:
1190734851000
May use a formatter for output if desired
If we know for a fact that the millisecond value will never be negative, java.time can format it into a string for us. This saves the explicit conversion to milliseconds first.
private static final DateTimeFormatter EPOCH_MILLI_FORMATTER
= new DateTimeFormatterBuilder().appendValue(ChronoField.INSTANT_SECONDS)
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.toFormatter(Locale.ROOT);
Now formatting is trivial:
assert ! i.isBefore(Instant.EPOCH) : i;
String time = EPOCH_MILLI_FORMATTER.format(i);
And output is still the same:
1190734851000
In particular if you need to format Instant objects to strings in more places in your program, I recommend the latter approach.
What went wrong in your code?
First of all, there is no way that SimpleDateFormat can parse 7 decimals of fraction of second correctly. As long as the fraction is zero, the result will happen to come out correct anyway, but imagine a time that is just one tenth of a second after the full second, for example, 2007-09-25T15:40:51.1000000Z. In this case SimpleDateFormat would parse the fraction into a million milliseconds, and your result would be more than a quarter of an hour off. For greater fractions the error could be several hours.
Second as others have said format pattern letter Z does not match the offset of Z meaning UTC or offset zero from UTC. This caused the exception that you observed. Putting Z in quotes as suggested in the accepted answer is wrong too since it will cause you to miss this crucial information from the string, again leading to an error of several hours (in most time zones).
Link
Oracle tutorial: Date Time explaining how to use java.time.

Date Format Conversion in Java

I have a requirement in project
to Convert 24 hours format to 12 hours format
for example
if it is 17:12:01 it should be converted to 05:12:01
You can use a SimpleDateFormat http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html to format it.
Date date = new Date(yourdate);
// format however you see fit
SimpleDateFormat format = new SimpleDateFormat("hh:mm:ss");
String formatted = format.format(date);
As suggested by Software Monkey, also use a SimpleDateFormat to parse your 24hr date if you don't already have it in millis.
Use two SimpleDateFormat objects - one to parse and the other to format the time.
Note also that 05:12:01 is not technically a 12 hour time (rather it appears to be an AM 24 hour time, given the lead 0 on the hour and the lack of a meridian designation). You probably want 5:12:01 PM.
It is possible that a 12 hour SimpleDateFormat will correctly parse a 24 hour time, provided that it is configured for lenient parsing. Just saying, so perhaps you need only one SimpleDateFormat.

Categories