Safe SimpleDateFormat parsing - java

I have a small block of code which parses response generation time from the response itself and turns it into a date for future purposes. It goes like this:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
Date responseTime = sdf.parse(RStime);
And it almost works like a charm. To be precise, it works 99.9% of the time, with the exception of one case: When the millisecond part is 000 then the Server doesn't append the .000 milliseconds at all, hence we have a problem.
Now, according to SimpleDateFormat docs if parsing fails, the function returns null. However, I probably misinterpreted it as it just throws an exception.
I am very new to Java and try-catch mechanisms, so could anyone please provide an elegant good-practice solution for handling such cases?
Thanks!

java.time
String rsTime = "2018-04-09T10:47:16.999-02:00";
OffsetDateTime responseTime = OffsetDateTime.parse(rsTime);
System.out.println("Parsed date and time: " + responseTime);
Output from this snippet is:
Parsed date and time: 2018-04-09T10:47:16.999-02:00
It works just as well for the version with the 000 milliseconds omitted:
String rsTime = "2018-04-09T10:47:16-02:00";
Parsed date and time: 2018-04-09T10:47:16-02:00
The classes you used, SimpleDateFormat and Date, are poorly designed and long outdated (the former in particular notoriously troublesome). So it is not only in this particular case I recommend using java.time, the modern Java date and time API, instead. However, the strings from your server are in ISO 8601 format, and OffsetDateTime and the other classes of java.time parse this format as their default, that is, without any explicit formatter, which already makes the task remarkably easier. Furthermore, in the standard the fractional seconds are optional, which is why both the variants of the string are parsed without any problems. OffsetDateTime also prints ISO 8601 back from it’s toString method, which is why in both cases a string identical to the parsed one is printed.
Only in case you indispensably need an old-fashioned Date object for a legacy API that you cannot change just now, convert like this:
Instant responseInstant = responseTime.toInstant();
Date oldfashionedDateObject = Date.from(responseInstant);
System.out.println("Converted to old-fashioned Date: " + oldfashionedDateObject);
Output on my computer in Europe/Copenhagen time zone is:
Converted to old-fashioned Date: Mon Apr 09 14:47:16 CEST 2018
Link: Oracle tutorial: Date Time explaining how to use java.time.

According to the SimpleDateFormat doc that you mentioned the parse method:
public Date parse(String text, ParsePosition pos)
Throws:
NullPointerException - if text or pos is null.
So one option is to catch that exception and do what you need inside the catch, for example:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
try {
Date responseTime = sdf.parse(RStime, position);
} catch (NullPointerException e) {
e.printStackTrace();
//... Do extra stuff if needed
}
Or the inherited method from DateFormat:
public Date parse(String source)
Throws:
ParseException - if the beginning of the specified string cannot be
parsed.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
try {
Date responseTime = sdf.parse(RStime);
} catch (ParseException e) {
e.printStackTrace();
//... Do extra stuff if needed
}

Is it actually an exceptional situation? If it is not then you probably shouldn't use exceptions in that case. In my opinion it is normal that time can end with .000ms. In this case you can check if the string contains . (dot) and if not append .000 to the end.
if(!RStime.contains(".")){
RStime+=".000";
}
Edit: I've forgot about time zone in the time String. You probably need something a little bit more complicated for that. Something like this should do it:
if(!RStime.contains(".")){
String firstPart = RStime.substring(0, 21);
String secondPart = RStime.substring(21);
RStime = firstPart + ".000" + secondPart;
}

You can check for a dot and then use the first or second format:
String timeString = "2018-04-09T10:47:16.999-02:00";
//String timeString = "2018-04-09T10:47:16-02:00";
String format = timeString.contains(".") ? "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" : "yyyy-MM-dd'T'HH:mm:ssXXX";
Date responseTime = new SimpleDateFormat(format).parse(timeString);
System.out.println("responseTime: " + responseTime);
If you comment-out the first line and comment-in the second and run it again, it will both print out:
responseTime: Mon Apr 09 14:47:16 CEST 2018
By the way:
Java 7 (the version you use obviously) returns a java.text.ParseException: Unparseable date: "2018-04-09T10:47:16-02:00"
Optionals are supported since Java 8.

Related

java.text.ParseException: Unparseable date Error on using Clock.systemUTC() [duplicate]

This question already has answers here:
how to parse OffsetTime for format HHmmssZ
(2 answers)
Closed 1 year ago.
I am getting an parse error while parsing a date
java.text.ParseException: Unparseable date: "2021-06-17T05:49:41.174Z"
Unparseable date: "2021-06-17T05:49:41.174Z"
my code looks like this
private static String generateAndValidate(int count) {
Clock clock = Clock.systemUTC();
String clockTime=clock.instant().toString();
String result=clockTime;
SimpleDateFormat output = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ",Locale.ENGLISH);
try {
output.parse(clockTime);
} catch (ParseException e) {
System.out.println("process date parse error. Going for retry.");
}
return result;
}
Also tried hard coding the value here
SimpleDateFormat output = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ",Locale.ENGLISH);
try {
output.parse("2021-06-17T05:49:41.174Z");
} catch (ParseException e) {
System.out.println("process date parse error. Going for retry.");
}
What could be the problem?
EDIT: The reason for the failing of your code is in the answer given by #GS3!
My answer provides alternatives that are generally considered mroe up-to-date:
I would not recommend to use a java.text.SimpleDateFormat here because you are involving a very old and practically outdated API while you are receiving the time by the modern API utilizing a java.time.Clock.
A good move would be to use java.time.format.DateTimeFormatters for parsing, but I think you could even skip the clock and use OffsetDateTime.now(ZoneOffset.UTC).
However, this code definitely parses the String produced by your first lines:
public static void main(String[] args) {
// your first two lines
Clock clock = Clock.systemUTC();
String clockTime = clock.instant().toString();
// a hint to the problem
System.out.println(clockTime + " <--- 6 fractions of second");
// how to parse a String like that in plain java.time
OffsetDateTime odt = OffsetDateTime.parse(clockTime);
System.out.println(odt.format(
DateTimeFormatter.ISO_OFFSET_DATE_TIME
)
);
}
The output of that will look like the following (obviously having different values):
2021-06-17T06:34:55.490370Z <--- 6 fractions of second
2021-06-17T06:34:55.49037Z
The output that uses a DateTimeFormatter.ISO_OFFSET_DATE_TIME is just one option, you can still define your own pattern using a DateTimeFormatter.ofPattern(yourPatternString), a DateTimeFormatterBuilder in order to handle optional parts or one of the other built-in formatters.
If you just want to get the current moment and store it in a some datetime class, you can use the now() method the datetime classes in java.time have:
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
looks suitable here, but there's a ZonedDateTime, too.
Just have a look at java.time...
In SimpleDateFormat, Z represents a restricted subset of the RFC-822 time zone syntax. Instant::toString() provides a timestamp in the ISO-8601 format. You can fix this by using X instead of Z.
SimpleDateFormat output = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX",Locale.ENGLISH);

Java Date .after() method not comparing 12:00 correctly [duplicate]

This question already has answers here:
12:xx shown as 00:xx in SimpleDateFormat.format("hh:mm:ss")
(1 answer)
Comparing two times in android
(4 answers)
Closed 2 years ago.
After some debugging, I found that it was because 12:00 was being set to 0:00, but surely if this was a 24-hour clock only 24:00 would be set to 0:00
SimpleDateFormat time_format = new SimpleDateFormat ("hh:mm");
String start_time = "11:00"
String end_time = "12:00"
try { //parses both times to the date data type
start_time_format = time_format.parse(start_time);
end_time_format = time_format.parse(end_time);
} catch (ParseException e) {
e.printStackTrace();
}
if (end_time_format.after(start_time_format)){
}else{
//how come this is always true
}
This is because those old Date and SimpleDateFormat are troublesome.
What happens here is that, because hh is used, the number 12 'overflows' to 0 here. The code which actually does this is found in the SimpleDateFormat source code, in the subParse method.
You should use HH. And while you're at it – you should use modern Java Date and Time API from the java.time package:
String startTimeStr = "11:00";
String endTimeStr = "12:00";
LocalTime startTime = LocalTime.parse(startTimeStr);
LocalTime endTime = LocalTime.parse(endTimeStr);
// No need for a explicit formatting string, because the default is used here,
// which is HH:mm[:ss[.nnnnnnnnn]]
if (endTime.isAfter(startTime)) {
System.out.println("If");
}
else {
System.out.println("Else");
}
But even if you used an explicit DateTimeFormatter with the pattern string hh:mm, then a DateTimeException would have been thrown because the value to be parsed were ambiguous. 11:00 and 12:00 in a twelve-hour clock could mean two things: either AM or PM. The parser by default doesn't like ambiguous values, so instead of just choosing one and moving on, causing confusion to everyone, the authors decided to immediately stop the parsing process and throw an exception. The following code shows this:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm");
String startTimeStr = "11:00";
LocalTime startTime = LocalTime.parse(startTimeStr, formatter);
Date class of java set 12:00 to 00:00 of the day.therefore new date with 12:00 convert to Jan 01 11:00:00 1970.To avoid this effect you can use "HH:mm" format string.

Parse timestamp string in Java with variable number of nanoseconds

I have a CSV that contains timestamps in the following formats:
yyyy-MM-dd HH:mm:ssX
yyyy-MM-dd HH:mm:ss.SX
yyyy-MM-dd HH:mm:ss.SSX
yyyy-MM-dd HH:mm:ss.SSSX
yyyy-MM-dd HH:mm:ss.SSSSX
yyyy-MM-dd HH:mm:ss.SSSSSX
yyyy-MM-dd HH:mm:ss.SSSSSSX
How can I parse a string that could contain any one of the above formats?
The following code can parse the timestamp when 3-6 nanoseconds are present, but fails when the nano seconds aren't present or are less than 3:
String time = "2018-11-02 11:39:03.0438-04";
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSX");
Date date = sdf.parse(time);
System.out.println("Date and Time: " + date.getTime());
I currently have a method that iterates from 0-6 and generates a string with a number of "S" equal to the value of the iterated variable. The method attempts to parse the string within a try/catch until the string is successfully parsed. For example, the string 2018-11-02 11:39:03.0438-04 will attempt to be parsed five times before being successful.
The CSV is an export of a PostgreSQL table that has columns with type TIMESTAMP WITH TIME ZONE and appears to cut off trailing "0" nanosecond places.
I'm using Java 8 and am open to any external libraries (Joda?).
You'd better use Java Time API1, from the package java.time.
Date, SimpleDateFormatter and Calendar classes are flawed and obsolete.
The DateTimeFormatter class provides numerous options, so you can configure all you need. Note that by using the method appendFraction, the nanos are right-padded.
String[] dateStrs = {
"2018-11-02 11:39:03.4-04",
"2018-11-02 11:45:22.71-04",
"2018-11-03 14:59:17.503-04"
};
DateTimeFormatter f = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd HH:mm:ss.")
.appendFraction(ChronoField.NANO_OF_SECOND, 1, 9, false)
.appendPattern("X")
.toFormatter();
// Single item:
LocalDateTime date = LocalDateTime.parse("2018-11-02 11:39:03.7356562-04", f);
// Multiple items:
List<LocalDateTime> dates = Arrays.asList(dateStrs).stream()
.map(t -> LocalDateTime.parse(t, f))
.collect(Collectors.toList());
1 Java 8 new Date and Time API is heavily influenced by Joda Time. In fact the main author is Stephen Colebourne, the author of Joda Time.
The first 19 characters are identical.
Also, you have different lengths in the different cases. You can use a switch to test the length of the String and handle the separate cases for the different possible values.
I'm not sure but something like this seems to work for me:
String time = "2018-11-02 11:39:03.0438-04";
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSX");
Date date = sdf.parse(time);
System.out.println("Date and Time: " + date.getTime());
In general, you want to you the longest format possible, with 6x S in this case.

java.lang.IllegalArgumentException: Invalid format

This code:
DateTimeParser[] parsers = { DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss zzz").getParser(),
DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss").getParser(), DateTimeFormat.forPattern("dd/MM/yyyy HH:mm").getParser(),
DateTimeFormat.forPattern("HH:mm").getParser() };
DateTimeFormatter formatter = new DateTimeFormatterBuilder().append(null, parsers).toFormatter();
Session session;
DateTime dTime = null;
Calendar calendar;
try{
if (completedTime != null && !completedTime.equalsIgnoreCase("")){
LocalDateTime jt = LocalDateTime.parse(completedTime, formatter);
LocalDateTime dt;
LocalDateTime retDate;
produces the error:
java.lang.IllegalArgumentException: Invalid format: "09/05/2015 04:00:00 GDT" is malformed at " GDT"
at the LocalDateTime jt = LocalDateTime.parse(completedTime, formatter); line
I can't for the life of me work out why it is failing. I am pretty sure it is something simple, but I haven't spotted it.
You may want to refer to this thread (or one of the many others like it). My best advice would be to try cutting to only one "z" in your parser.
You need to manually specify a mapping from timezone abbreviation to timezone. For example:
return new DateTimeFormatterBuilder()
.appendPattern("dd/MM/yyyy HH:mm:ss ")
.appendTimeZoneShortName(UK_TIMEZONE_SYMBOLS)
.toFormatter();
Here UK_TIMEZONE_SYMBOLS is a Map<String,DateTimeZone> which contains our view of timezone names (so BST is British summer time, not Bangladesh standard time)
Here's how we build ours:
public static Map<String, String> buildTimeZoneSymbolMap(Locale locale) {
Map<String, String> timeZoneSymbols = Maps.newLinkedHashMap();
for (String[] zoneInfo : DateFormatSymbols.getInstance(locale).getZoneStrings()) {
String timeZone = zoneInfo[0];
if (!timeZoneSymbols.containsKey(zoneInfo[2])) {
timeZoneSymbols.put(zoneInfo[2], timeZone);
}
if (zoneInfo[4] != null && !timeZoneSymbols.containsKey(zoneInfo[4])) {
timeZoneSymbols.put(zoneInfo[4], timeZone);
}
}
timeZoneSymbols.put("UTC", "GMT");
return timeZoneSymbols;
}
public static Map<String, DateTimeZone> buildDateTimeZoneSymbolMap(Locale locale) {
return Maps.transformValues(buildTimeZoneSymbolMap(locale), input -> DateTimeZone.forTimeZone(TimeZone.getTimeZone(input)));
}
public static final Map<String, DateTimeZone> UK_TIMEZONE_SYMBOLS = ImmutableMap.copyOf(buildDateTimeZoneSymbolMap(Locale.UK));
First thing to note:
What is "GDT"? The website http://www.timeanddate.com/time/zones/ does not yield an answer. So if it really exists and is not a typo then what is your locale? Remember that time zone names and abbreviations are highly localized.
Second: The count of pattern symbols "z" is okay - for classes like SimpleDateFormat etc. - see its documentation. Either four letters for the full name or less than four letters for the abbreviation:
General time zone: Time zones are interpreted as text if they have
names. Text: For formatting, if the number of pattern letters is 4 or
more, the full form is used; otherwise a short or abbreviated form is
used if available. For parsing, both forms are accepted, independent
of the number of pattern letters.
But you use Joda-Time. Its documentation clearly states:
Zone names: Time zone names ('z') cannot be parsed.
I have verified this non-support using the newest Joda-Time version 2.7 by following code:
DateTimeFormatter formatter = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss z").withLocale(Locale.GERMANY);
DateTime dt = formatter.parseDateTime("09/05/2015 04:00:00 MESZ");
System.out.println("Joda-Time: " + dt);
// Exception in thread "main" java.lang.IllegalArgumentException: Invalid format: "09/05/2015 04:00:00 MESZ" is malformed at "MESZ"
Of course, "MESZ" is correct and must be interpreted as Europe/Berlin in context of given german locale.
However, since version update (2.2) the same code set to Locale.US works for some timezones names like "EDT", "PST" etc., see also this commit. So we can finally say, the parsing support of Joda-Time for timezone names and abbreviations is best to say very limited. Once again, what is your Locale? If it is not US then I can understand why you get the exception. And you will also get an exception for the input "GDT" even if we consider it as valid due to the limited capabilities of Joda-Time-parser.

Having trouble format a string into a date object

I have a string that looks like this
Aug 16, 2013,11:30:10
The comma can be replaced by a different separator and the date & time position can be switched.
I am trying to use this format for my SimpleDateFormat(dtString is the String above):
Date d = null;
try {
d = new SimpleDateFormat("MMMM dd, yyyy,hh:mm:ss", Locale.ENGLISH).parse(dtString);
} catch (ParseException ex) {
Logger.getLogger(MonKaiClientImpl.class.getName()).log(Level.SEVERE, null, ex);
}
return d;
but when I run d.getYear() the result is 113.
All of the other Date methods return the correct result except .getYear(). Am I missing something? Is my SimpleDateFormatter wrong?
You should not use Date#getYear. It's deprecated.
As for the result you get, it's as specified in the Javadoc:
Returns a value that is the result of subtracting 1900 from the year that contains or begins with the instant in time represented by this Date object, as interpreted in the local time zone.
Use Calendar API instead. Or even better, if you can use 3rd party library, then I would really suggest you to try Joda Time. Or wait for Java 8 to come next year, that has a new Date Time API.
Simple way to check the result of SimpleDateFormat parsing is this
System.out.println(d);
and it shows correct result
Fri Aug 16 11:30:10 EEST 2013
the problems is that Date methods interpreting year, month, day, hour, minute, and second are deprecated. See Date API

Categories

Resources