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);
Related
This question already has answers here:
Convert date string (EST) to Java Date (UTC)
(2 answers)
Convert Calender to Date according to timezone [duplicate]
(1 answer)
Closed 3 days ago.
I have a string with value "25/12/2021" and I want to convert this string into a date format which has a New York timezone.
I am new to Java dates
I tried the below:
SimpleDateFormat zdate = new SimpleDateFormat("dd/MM/yyyy");
zdate.setTimeZone(TimeZone.getTimeZone("America/New_York"));
Date zdate2 = zdate.parse("25/12/2021")
but the zdate2 object has the date in my local timezone rather than the New_York timezone.
What am I doing wrong here?
If you are not forced to use java.util.Date, which itself does not have any zone, you could have your requirement met by implementing with java.time:
You just have to
parse it to a LocalDate, a class only having year, month of year and day of month
append a time of day
append the zone
Here's an example…
public static void main(String[] args) throws ParseException {
// example input
String input = "25/12/2021";
// create a pattern handler for parsing
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd/MM/uuuu");
// create the zone
ZoneId americaNewYork = ZoneId.of("America/New_York");
// parse the String to a LocalDate, then add time of day and zone
ZonedDateTime result = LocalDate.parse(input, dtf)
.atStartOfDay(americaNewYork);
System.out.println(result);
}
Output:
2021-12-25T00:00-05:00[America/New_York]
Small protest about parse() on time objects, the following is what it really takes to assure creation from String information that parse() methods continually reject !!!
bash-5.1$ javac ZoneParse.java
Note: ZoneParse.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
bash-5.1$ java ZoneParse
24 Dec 2021 13:00:00 GMT
bash-5.1$
It will be something alike the following if safe...
import java.time.*;
public class ZoneParse{
public ZoneParse(){
String[] dt = ("25/12/2021").split("/");
ZonedDateTime ztm = (LocalDate.of(new Integer(dt[2]).intValue(),new Integer(dt[1]).intValue(),new Integer(dt[0]).intValue())).atStartOfDay(ZoneId.systemDefault());
System.out.println((java.util.Date.from( (ztm.withZoneSameInstant((ZoneId)ZoneId.of("America/New_York")).toInstant()))).toGMTString());
}//enconstr
public static void main(String[] args){
new ZoneParse();
}//enmain
}//enclss
NOTE withZoneSameInstant() changes both the time and the zone.
withZoneSameLocal() only retags the time as belonging as another time zone
The updated time library since around java 8 is quicker
This following link is a library with some time assistance under construction avoids parse() as much as possible including a pair of methods of String fill in arguments to create a timedate.
https://drive.google.com/file/d/1gjHmdC-BW0Q2vXiQYmp1rzPU497sybNy/view?usp=share_link
The below erroneous code is because parse() is probably one of the WORST explained methods in the docs, it generally requires a few different pages each of different sections to get any explanation of how to use it.
ZonedDateTime ztm = ZonedDateTime.parse("25/12/2021");
Instant it = (ztm.withZoneSameInstant((ZoneId)ZoneId.of("America/New_York"))).toInstant();
java.util.Date date = java.util.Date.from(it);
SimpleDateFormat zdate = new SimpleDateFormat("dd/MM/yyyy");
zdate.setTimeZone(TimeZone.getTimeZone("America/New_York"));
Date zdate2 = zdate.parse("25/12/2021")
Remember the four 'y' letters in format
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.
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.
I am working on a REST API which supports Date as a query param. Since it is Query param it will be String. Now the Date can be sent in the following formats in the QueryParams:
yyyy-mm-dd[(T| )HH:MM:SS[.fff]][(+|-)NNNN]
It means following are valid dates:
2017-05-05 00:00:00.000+0000
2017-05-05 00:00:00.000
2017-05-05T00:00:00
2017-05-05+0000
2017-05-05
Now to parse all these different date-times i am using Java8 datetime api. The code is as shown below:
DateTimeFormatter formatter = new DateTimeFormatterBuilder().parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd[[ ][['T'][ ]HH:mm:ss[.SSS]][Z]"))
.toFormatter();
LocalDateTime localDateTime = null;
LocalDate localDate = null;
ZoneId zoneId = ZoneId.of(ZoneOffset.UTC.getId());
Date date = null;
try {
localDateTime = LocalDateTime.parse(datetime, formatter);
date = Date.from(localDateTime.atZone(zoneId).toInstant());
} catch (Exception exception) {
System.out.println("Inside Excpetion");
localDate = LocalDate.parse(datetime, formatter);
date = Date.from(localDate.atStartOfDay(zoneId).toInstant());
}
As can be seens from the code I am using DateTimeFormatter and appending a pattern. Now I am first trying to parse date as LocalDateTime in the try-block and if it throws an exception for cases like 2017-05-05 as no time is passed, I am using a LocalDate in the catch block.
The above approach is giving me the solution I am looking for but my questions are that is this the standard way to deal with date sent as String and is my approach is in line with those standards?
Also, If possible what is the other way I can parse the different kinds of date (shown as the Valid dates above) except some other straightforward solutions like using an Array list and putting all the possible formats and then using for-loop trying to parse the date?
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
// time is optional
.optionalStart()
.parseCaseInsensitive()
.appendPattern("[ ]['T']")
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.optionalEnd()
// offset is optional
.appendPattern("[xx]")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
.toFormatter();
for (String queryParam : new String[] {
"2017-05-05 00:00:00.000+0000",
"2017-05-05 00:00:00.000",
"2017-05-05T00:00:00",
"2017-05-05+0000",
"2017-05-05",
"2017-05-05T11:20:30.643+0000",
"2017-05-05 16:25:09.897+0000",
"2017-05-05 22:13:55.996",
"2017-05-05t02:24:01"
}) {
Instant inst = OffsetDateTime.parse(queryParam, formatter).toInstant();
System.out.println(inst);
}
The output from this snippet is:
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T00:00:00Z
2017-05-05T11:20:30.643Z
2017-05-05T16:25:09.897Z
2017-05-05T22:13:55.996Z
2017-05-05T02:24:01Z
The tricks I am using include:
Optional parts may be included in either optionalStart/optionalEnd or in [] in a pattern. I use both, each where I find it easier to read, and you may prefer differently.
There are already predefined formatters for date and time of day, so I reuse those. In particular I take advantage of the fact that DateTimeFormatter.ISO_LOCAL_TIME already handles optional seconds and fraction of second.
For parsing into an OffsetDateTime to work we need to supply default values for the parts that may be missing in the query parameter. parseDefaulting does this.
In your code you are converting to a Date. The java.util.Date class is long outdated and has a number of design problems, so avoid it if you can. Instant will do fine. If you do need a Date for a legacy API that you cannot change or don’t want to change just now, convert in the same way as you do in the question.
EDIT: Now defaulting HOUR_OF_DAY, not MILLI_OF_DAY. The latter caused a conflict when only the millis were missing, but it seems the formatter is happy with just default hour of day when the time is missing.
I usually use the DateUtils.parseDate which belongs to commons-lang.
This method looks like this:
public static Date parseDate(String str,
String... parsePatterns)
throws ParseException
Here is the description:
Parses a string representing a date by trying a variety of different parsers.
The parse will try each parse pattern in turn. A parse is only deemed successful if it parses the whole of the input string. If no parse patterns match, a ParseException is thrown.
The parser will be lenient toward the parsed date.
#Configuration
public class DateTimeConfig extends WebMvcConfigurationSupport {
/**
* https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#format-configuring-formatting-globaldatetimeformat
* #return
*/
#Bean
#Override
public FormattingConversionService mvcConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
// Register JSR-310 date conversion with a specific global format
DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"));
dateTimeRegistrar.registerFormatters(conversionService);
// Register date conversion with a specific global format
DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
dateRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd"));
dateRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
dateRegistrar.setFormatter(new DateFormatter("yyyy-MM-dd'T'HH:mm:ss'Z'"));
dateRegistrar.registerFormatters(conversionService);
return conversionService;
}
}
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.