How to parse datetime like this - java

I understand that it sounds weird but I have datettime 2018-04-04 12:59:575Z.
Let's assume it is real, not a mistake and I can't find any standard for parsing this.
Is there a way to parse it in Java? What 3 numbers 575 at the end could mean?
edit:
There is strong doubt, that it is correct date time in my samples. I'm going to report a bug to creator. Thanks everyone for good advices.

It probably means there's a bug in wherever this string came from. I would investigate there if I had access to that code or report a bug to its owner.
There's no point parsing buggy data and guessing what the numbers mean. Bad data is worse than no data.

What 3 numbers 575 at the end could mean?
My guesses include:
It’s 12:59:57.5 (the .5 signifying half a second; assuming that the decimal point has been left out from the format).
575 are millisecond of the second, and seconds have been forgot. So it’s 12:59:ss.575 where we don’t know what ss should have been.
It’s 59,575 minutes past 12 o’clock (the same as 12:59:34.5). In defense of this option, ISO 8601 does allow decimals on the minutes, but then the decimal “point” should be either a comma or a period, not a colon.
I can't find any standard for parsing this.
I am pretty convinced that there isn’t any.
Is there a way to parse it in Java?
No, sorry, not as long as we don’t know what the string means.

You can use joda time api to parse the input String like below:-
String strDate="2018-04-04 12:59:575Z";
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:SSSZ");
DateTime dt = formatter.parseDateTime(strDate);
System.out.println(dt); //2018-04-04T08:59:00.575-04:00
575 in your input string is milliseconds.
But you need to find out whats the point of precision till
milliseconds if you are not including seconds.

There could be two possible options for 59 at the end. It could be minutes or seconds. I think, that minutes is more likely, because seconds could be not valuable. 575 is definitely millisecond.
DateTimeFormatter dfMinutes = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:SSS");
DateTimeFormatter dfSeconds = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:ss:SSS");
P.S. It could be yyyy-dd-MM instead of yyyy-MM-dd. But as I can see, we're in same locale.

Related

Create a DateTimeFormater with an Optional Section at Beginning

I have timecodes with this structure hh:mm:ss.SSS for which i have a own Class, implementing the Temporal Interface.
It has the custom Field TimecodeHour Field allowing values greater than 23 for hour.
I want to parse with DateTimeFormatter. The hour value is optional (can be omitted, and hours can be greater than 24); as RegEx (\d*\d\d:)?\d\d:\d\d.\d\d\d
For the purpose of this Question my custom Field can be replaced with the normal HOUR_OF_DAY Field.
My current Formatter
DateTimeFormatter UNLIMITED_HOURS = new DateTimeFormatterBuilder()
.appendValue(ChronoField.HOUR_OF_DAY, 2, 2,SignStyle.NEVER)
.appendLiteral(':')
.parseDefaulting(TimecodeHour.HOUR, 0)
.toFormatter(Locale.ENGLISH);
DateTimeFormatter TIMECODE = new DateTimeFormatterBuilder()
.appendOptional(UNLIMITED_HOURS)
.appendValue(MINUTE_OF_HOUR, 2)
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.appendFraction(MILLI_OF_SECOND, 3, 3, true)
.toFormatter(Locale.ENGLISH);
Timecodes with a hour value parse as expected, but values with hours omittet throw an Exception
java.time.format.DateTimeParseException: Text '20:33.123' could not be parsed at index 5
I assume, as hour and minute have the same pattern, the parser starts at front and captures the minute value for the optional section.
Is this right, and how can solve this?
I started to suspect that 20:33.123 wasn’t meant to indicate a time of day between 20 and 21 minutes past midnight. Maybe rather an amount of time, a little longer than 20 minutes. If this is correct, use a Duration for it.
Unfortunately java.time does not include means for parsing and formatting a Duration in other than ISO 8601 format. This leaves us with at least three options:
Use a third-party library. Time4J offers an elegant solution, see below. Joda-Time has its PeriodFormatter class. Apache may also offer facilities for parsing and formatting of durations.
Convert your string to ISO 8601 format before parsing with Duration.parse().
Write your own parser.
I was thinking that we’re too lazy for 3. and that Joda-Time is getting dated, so I want to pursue options 1. and 2. here, option 1. in the Time4J variant.
A regex for adapting to ISO 8601
ISO 8601 format for a duration feels unusual at first, but is straightforward. PT20M33.123S means 20 minutes 33.123 seconds.
public static Duration parse(String timeCodeString) {
String iso8601 = timeCodeString
.replaceFirst("^(\\d{2,}):(\\d{2}):(\\d{2}\\.\\d{3})$", "PT$1H$2M$3S")
.replaceFirst("^(\\d{2}):(\\d{2}\\.\\d{3})$", "PT$1M$2S");
return Duration.parse(iso8601);
}
Let’s try it out:
System.out.println(parse("20:33.123"));
System.out.println(parse("123:20:33.123"));
Output is:
PT20M33.123S
PT123H20M33.123S
My two calls to replaceFirst first handle the case with hours, then the case without hours. So either will convert a string that matches your regex to ISO 8601 format. Which the Duration class then parses. And as you can see, Duration also prints ISO 8601 format back. Formatting it differently is not bad, though, search for how.
Time4J
The Time4J library offers the really elegant solution very much along the same line of thought as yours. All we really need is this formatter:
private static final Formatter<ClockUnit> TIME_CODE_PARSER
= Duration.formatter(ClockUnit.class, "[###hh:mm:ss.fff][mm:ss.fff]");
Simply use like this:
System.out.println(TIME_CODE_PARSER.parse("20:33.123"));
System.out.println(TIME_CODE_PARSER.parse("123:20:33.123"));
PT20M33,123000000S
PT123H20M33,123000000S
The Time4J Duration class too prints ISO 8601 format. It appears that it uses comma as decimal separator as is preferred in ISO 8601, and that it prints 9 decimals on the seconds also when some of them are 0.
In the format pattern string ###hh means 2 to 5 digit hours, and fff means three digits of decimal fraction of second.
Anything wrong with your approach?
Was there anything wrong with your approach? ChronoField.HOUR_OF_DAY means that: hour of day. 0 is midnight, 12 is noon and 23 is near the end of the day. This is not what you want, so yes, you are using the wrong means. While you can probably get it to work, anyone maintaining your code after you will find it confusing and will probably have a hard time making modification in line with your intentions.
Links
Wikipedia article: ISO 8601
Joda-Time PeriodFormatter
Time4J TimeSpanFormatter
Try with two optional parts (one with hours, other without) like in:
var formatter = new DateTimeFormatterBuilder()
.optionalStart()
.appendValue(HOUR_OF_DAY, 2, 4, SignStyle.NEVER).appendLiteral(":")
.appendValue(MINUTE_OF_HOUR, 2).appendLiteral(":")
.appendValue(SECOND_OF_MINUTE, 2)
.optionalEnd()
.optionalStart()
.parseDefaulting(HOUR_OF_DAY, 0)
.appendValue(MINUTE_OF_HOUR, 2).appendLiteral(":")
.appendValue(SECOND_OF_MINUTE, 2)
.optionalEnd()
.toFormatter(Locale.ENGLISH);
I do not know about TimecodeHour, so I used HOUR_OF_DAY to test(also too lazy to include fractions)
I think fundamentally the problem is that it gets stuck going down the wrong path. It sees a field of length 2, which we know is the minutes but it believes is the hours. Once it believes the optional section is present, when we know it's not, the whole thing is destined to fail.
This is provable by changing the minimum hour length to 3.
.appendValue(TimecodeHour.HOUR, 3, 4, SignStyle.NEVER)
It now knows that the "20" cannot be hours, since hours requires at least 3 digits. With this small change, it now parses correctly, whether the optional section is present or not.
So presuming that the hours field really does need to be between 2 and 4 digits, I think you're stuck with having to implement a workaround. For example, count the number of colons in the string and use a different formatter depending on which one you run into. Using a different delimiter besides a colon for the hours would also work.
The parser logic has undergone quite a few bug fixes over the various Java versions since it was introduced - as you can imagine, there are so many potential edge cases - so I was hopeful using a recent version of Java would make this problem disappear. Unfortunately, it seems even in Java 16, the behaviour is still the same.

SimpleDateFormat [0] issue

I've below SimpleDateFormat Code
Date date = new Date();
DateFormat inpuDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SS'Z'");
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
String dateStr = inpuDateFormat.format(cal.getTime());
It works perfectly on my dev servers but it fails on sandbox instances with following error.
org.junit.ComparisonFailure: expected:<...20-08-12T19:06:02.85[0]Z> but was:<...20-08-12T19:06:02.85[]Z>
I've handled it as
dateStr = dateStr.replace("[0]","");
dateStr = dateStr.replace("[]","");
But, I still didn't get the logic why my date is different on different server instances and is there any better way to handle it
java.time
There certainly is a much better way to handle it. Use java.time, the modern Java date and time API, for your date and time work, not Date, DateFormat, SimpleDateFormat nor Calendar.
Instant now = Instant.now();
String dateStr1 = now.toString();
System.out.println(dateStr1);
Output in one run was:
2020-07-24T18:06:07.988093Z
You notice that six decimals on the seconds were output, not two. In other runs you may have three decimals or no fraction at all. Don’t worry, for the majority of purposes you’ll be just fine. The format printed is ISO 8601, and according to ISO 8601 the count of decimals on the seconds, even the presence of seconds at all, is optional. So whatever you need the string for, as long as ISO 8601 format is expected, the string from the above code snippet should be accepted.
I am exploiting the fact that Instant.toString() produces ISO 8601 format, so we don’t need any formatter.
If for some strange reason you do need exactly two decimals on the seconds, use a formatter for specifying so (edit: now outputting Z):
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSX")
.withZone(ZoneOffset.UTC);
String dateStr2 = formatter2.format(now);
System.out.println(dateStr2);
2020-07-24T18:06:07.98Z
To a DateTimeFormatter (opposite a SimpleDateFormat) uppercase S in the format pattern string means fraction of second, and you are free to place from one through nine of them to get from one to nine decimals.
What went wrong in your code?
First, the message that you got from your JUnit test was:
org.junit.ComparisonFailure: expected:<...20-08-12T19:06:02.85[0]Z> but was:<...20-08-12T19:06:02.85[]Z>
The square brackets is JUnit’s way of drawing our attention to the difference between the expected and the actual value. So they are not part of those values. What JUnit tells us is that the value was expected to end in .850Z but instead ended in just .85Z. So a zero was missing. Your test is probably too strict since as I said, it shouldn’t matter whether there are two or three decimals. And 02.85 and 02.850 are just different ways of denoting the exact same value.
This role of the square brackets also explains why replacing [0] and [] in the string didn’t help: the square brackets were never in the strings, so the replacements never made any change to the strings.
Second, to SimpleDateFormat (opposite DateTimeFormatter) format pattern letter uppercase S means millisecond. So putting any other number than three of them makes no sense and gives you incorrect results. In your code you put two. In nine of ten cases the millisecond value is in the interval 100 through 999, and in this case SimpleDateFormat prints all three digits in spite of the only two pattern letters S. This probably explains why your unit test passed in your development environment. On your sandbox incidentally the time ended in 2.085 seconds. The correct ways to render this include 02.08 and 02.085. Your SimpleDateFormat chose neither. To it the millisecond value of 85 was to be rendered in two positions, so it produces 02.85, which is the wrong value, 765 milliseconds later. And your unit test objected while this once there were only two decimals, not three.
Third, not what you asked, but no matter if using the troublesome SimpleDateFormat or the modern DateTimeFormatter you must never hardcode Z as a literal in the format pattern string. The trailing Z means UTC or offset zero from UTC. It needs to be printed (and parsed if that were the case) as an offset, or you get wrong results. The way to make sure you get a Z and not for example an offset of +02:00 is to make sure that an offset of 0 is specified. This was why I put .withZone(ZoneOffset.UTC) on my formatter.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601
Try to remove the quotes around the 'Z', as 'Z' is a constant whilst without quotes it means 'time zone':
DateFormat inpuDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
(By the way, in most cases you want to use three decimal places for milliseconds: "SSS".)

How can I parse RFC 3339 date string into ZondeDateTime?

Problem:
I should parse an RFC3339 date string. It works fine with ISO_ZONED_DATE_TIME:
ZonedDateTime.parse("1985-04-12T23:20:50.52Z", ISO_ZONED_DATE_TIME);
ZonedDateTime.parse("1996-12-19T16:39:57-08:00", ISO_ZONED_DATE_TIME);
Let's say I'll fix a problem of Unknown Local Offset Convention just to not accept these dates.
But I still have a problem with some corner cases like this:
1990-12-31T23:59:60Z
This represents the leap second inserted at the end of 1990.
1990-12-31T15:59:60-08:00
This represents the same leap second in Pacific Standard Time, 8
hours behind UTC."1990-12-31T15:59:60-08:00"
Question:
How can I parse it avoiding to lose any seconds?
Update:
Does it exist any alternative to ZonedDateTime that suits well
RFC3339?
java.time doesn’t offer any direct support for what you want. Just earlier today I wrote this answer that also has a section on parsing a leap second. But what is said there is all there is.
So there’s hand parsing left. I’d try something along these lines: Use a regular expression for detecting whether the second is 60. If so: Substitute it with 59. Parse. Convert to UTC. If the time of day in UTC is 23:59:59, assume there was a valid leap second in the original string; otherwise the string didn’t denote a valid time.
I suggest that in case of a leap second second values up to 60.999999999 are valid. So to detect whether there is a 60 you need to look at what comes after the colon (if any) after the minutes, and not depend on whether there is a fractional part too.

Android: Format milliseconds into >60 seconds when no minute format is given

I got a millisecond value. I want to use a format string, to format those milliseconds into a time format. e.g. 45000ms = 45s or 0m 45s or whatever. So this is no special thing. I used SimpleDateFormat for this, but now comes my problem:
61000ms ends up in 1m 1s. This is okay for me, IF there is a minute given in the format string. But when there is no minute given in the format string, it should print out 61s instead of just 1s.
Is there any easy way to achieve this? Currently I dont see it without doing any ugly string formatting code.
I hope you can help me :)
Thanks in advanced!
slain
Edit:
For better understanding:
you got 65400 milliseconds.
format string has minutes, seconds, milliseconds: 1m 5s 400ms
format string has minutes and seconds: 1m 5s
format string has seconds: 65s
format string has seconds and milliseconds: 65s 4ms
format string has minutes and milliseconds: 1m 5400ms
I'm not sure what exactly you are trying to convert but have a look at time units.
If I understand correctly, you are given a number of milliseconds, and a time format string, and need to produce a correctly formatted time? If not, ignore the rest of this...
Maybe not the BEST way, but kind of a nice waterfall: Convert the milliseconds to a set of integers for days, hours, minutes, seconds (or more, or less, depending on the expected range), and then iterate forward through the format string. If day is present, great, stick the number of days in there. If not, skip it, multiply by 24 and add to hours. Do the same for hours->minutes, minutes->seconds, and you should end up with it correctly formatted, even if with weird formats (like days and seconds but not minutes).
Not exactly sure what you're looking for, but you can parse milliseconds like so;
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd MMM yyyy");
public static void main(String[] args) {
final long millis = System.currentTimeMillis();
System.out.println(DATE_FORMAT.format(new Date(millis))); // Prints 05 Aug 2011
}
Obviously, you can tweak the date format as necessary to display seconds, milliseconds, etc.

Best Duration type in Java or .Net

I have seen way to many places where a method takes a long or an int to represent durations in either nanoseconds, milliseconds (most common), seconds and even days. This is a good place to look for errors, too.
The problem is also quite complex once you realize that you can mean for the duration to be a certain number of seconds, or an interval that fits the human perception of time better, so that a duration of 24 hours is always going to be the next day at the same "wall-clock" time. Or that a year is either 365 or 366 days depending on the date, so that a year from 28th of February is always going to be the 28th of February.
Why is there no distinct type to represent this? I have found none in either Java or .net
In .Net you can use the TimeSpan structure to represent a length of time.
For Java, take a look at Joda (an intuitive and consistent date/time library) and its Duration and Period classes. DateTime objects can handle addition and manipulation via these objects.
(answer changed to reflect the comments below re. the Period class)
It's not an easy problem. Maybe Joda-Time would be a useful library for you. It has a Duration class that can do what you are asking for.

Categories

Resources