I convert two types of Strings to an ISO format using SimpleDateFormat for parsing and org.apache.commons.lang.time.DateFormatUtils for formatting (since they provide a ISO formatter out-of-the-box).The pattern Strings for parsing are M/d/y H:m and d.M.y H:m. A typical String to convert may look either like 4/14/2009 11:22 or 4.14.2009 11:22. I initialize the parsers as follows:
SimpleDateFormat SLASH = new SimpleDateFormat(PATTERN_S, Locale.getDefault());
SimpleDateFormat DOT = new SimpleDateFormat(PATTERN_D, Locale.getDefault());
I get the the formatter:
FastDateFormat isoFormatter = DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT
After creating a Date from the parsed String:
Date date = FORMAT_SLASH.parse(old);
it is formatted for output:
isoFormatter.format(date)
The strange thing is : when a String with slashes was converted, the output looks like 2009-04-14T11:42:00+01:00 (which is correct) but when a String with dots was converted, the output looks like 2010-02-14T11:42:00+02:00, shifting my timezone to somewhere between Finland and South Africa, the year to 2010 and the month to february
What is going wrong here and why?
EDIT : changed the output strings to match real output (damn you, cut-n-paste). The reason was the interchanged M and d in the pattern strings that I failed to notice. 14 seems to be a perfecty valid month - its next year's february and even non-lenient settings can't force the formatter to reject it. The timeshift issue is resolved and the reason for the TimeZone change is provided by Jim Garrison. Thanks Ahmad and Jim
Your dot pattern is d.M.y H:m while your example shows that you meant M.d.y H:m, I supposed this would throw a ParseException, but it doesn't and it causes timezone issues.
Related
Using java I try to format the current date with the timezone using SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSSZ");
sdf.format(new Date());
This give me as results :
2021-04-28T13:45:52:308+0300
I want to get the timezone format with the "Z" instead of "+"
wanted results : "2021-04-28T13:45:52:308Z03:00"
I writed the date output in a file log that will be parsed by telegraf plugin writed in Go language that expect date with time zone with the following format : json_time_format = "2006-01-02T15:04:05Z07:00"
Is there a pattern allows that ?
You misunderstood. 2006-01-02T15:04:05Z07:00 does not mean that you should have a Z instead of a plus (what would you put instead of a minus, then?) This way of specifying a date and time format approximates how the fixed example date and time of Mon Jan 2 15:04:05 MST 2006 would be formatted, but it’s only an approximation. Specifically when it comes to the offset from UTC, the format requires Z when the offset is zero and +hh:mm or -hh:mm when it is non-zero. In accordance with ISO 8601 and RFC-3339. You see immediately that just giving the correct formatting of the example date and time, 2006-01-02T15:04:05-07:00, would not tell the reader that offset 0 should be given as Z. Therefore this particular requirement is specified as Z07:00 in the format. According to Format a time or date [complete guide] (link at the bottom), your particular format, 2006-01-02T15:04:05-0700, denotes ISO 8601 or RFC-3339.
So all you need to do is use DateTimeFormat.ISO_OFFSET_DATE_TIME or OffsetDateTime.toString().
A couple of examples follow.
String result = OffsetDateTime.now().toString();
System.out.println(result);
Output when running on Java 8 in my time zone just now:
2021-04-29T17:00:55.716+02:00
If the fraction of second is not allowed — well, according to ISO 8601 it is optional, so it should be, but if not:
String result = OffsetDateTime.now().truncatedTo(ChronoUnit.SECONDS)
.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
2021-04-29T17:00:55+02:00
If you have got an old-fashioned Date object from legacy code, convert it before formatting:
Date oldfashionedDate = new Date();
String result = oldfashionedDate.toInstant()
.atZone(ZoneId.systemDefault())
.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
2021-04-29T17:00:55.739+02:00
Links
Oracle tutorial: Date Time explaining how to use java.time.
Wikipedia article: ISO 8601
Format a time or date [complete guide]
Z is for "Zulu time" or zero hour offset, i.e. UTC +0:00
It's not correct to use it if you're not in that timezone. How would you know whether you're before or after the meridian if you replace it with Z? Given Z03:00 do you parse it as +03:00 or -03:00?
Since Z means Zulu time offset you can't use it as part of the format string but you can of course add Z as a hardcoded character
ZonedDateTime now = ZonedDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'Z");
System.out.println(formatter.format(now));
2021-04-29T15:34:17.661Z+0200
Then if you don't want the '+' you can remove it afterwards but it is not clear on what to do whit a '-' so I left it out of the answer.
Semantically as mentioned above its quite wrong.But you can achieve this with some custom parsing logic.
I will assume 2 things:
The date will not contain timezones with negative differences
The date format will not change
In any other cases, this is not safe!!
But here you go:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSSZ");
String originalDateString = sdf.format(new Date());
String[] parts = originalDateString.split("\\+");
StringBuilder sb = new StringBuilder(parts[1]);
sb.insert(2, ':');
parts[1] = sb.toString();
String result = String.join("Z",parts);
System.out.println(result);
This will create from this:
2021-04-29T14:12:21:376+0000
This:
2021-04-29T14:12:21:376Z00:00
I have found that SimpleDateFormat::parse(String source)'s behavior is (unfortunatelly) defaultly set as lenient: setLenient(true).
By default, parsing is lenient: If the input is not in the form used by this object's format method but can still be parsed as a date, then the parse succeeds.
If I set the leniency to false, the documentation said that with strict parsing, inputs must match this object's format. I have used paring with SimpleDateFormat without the lenient mode and by mistake, I had a typo in the date (letter o instead of number 0). (Here is the brief working code:)
// PASSED (year 199)
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("03.12.199o"));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("03.12.199o")); //WTF?
In my surprise, this has passed and no ParseException has been thrown. I'd go further:
// PASSED (year 1990)
String string = "just a String to mess with SimpleDateFormat";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("03.12.1990" + string));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("03.12.1990" + string));
Let's go on:
// FAILED on the 2nd line
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy");
System.out.println(simpleDateFormat.parse("o3.12.1990"));
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("o3.12.1990"));
Finally, the exception is thrown: Unparseable date: "o3.12.1990". I wonder where is the difference in the leniency and why the last line of my first code snippet has not thrown an exception? The documentation says:
With strict parsing, inputs must match this object's format.
My input clearly doesn't strictly match the format - I expect this parsing to be really strict. Why does this (not) happen?
Why does this (not) happen?
It’s not very well explained in the documentation.
With lenient parsing, the parser may use heuristics to interpret
inputs that do not precisely match this object's format. With strict
parsing, inputs must match this object's format.
The documentation does help a bit, though, by mentioning that it is the Calendar object that the DateFormat uses that is lenient. That Calendar object is not used for the parsing itself, but for interpreting the parsed values into a date and time (I am quoting DateFormat documentation since SimpleDateFormat is a subclass of DateFormat).
SimpleDateFormat, no matter if lenient or not, will accept 3-digit year, for example 199, even though you have specified yyyy in the format pattern string. The documentation says about year:
For parsing, if the number of pattern letters is more than 2, the year
is interpreted literally, regardless of the number of digits. So using
the pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
DateFormat, no matter if lenient or not, accepts and ignores text after the parsed text, like the small letter o in your first example. It objects to unexpected text before or inside the text, as when in your last example you put the letter o in front. The documentation of DateFormat.parse says:
The method may not use the entire text of the given string.
As I indirectly said, leniency makes a difference when interpreting the parsed values into a date and time. So a lenient SimpleDateFormat will interpret 29.02.2019 as 01.03.2019 because there are only 28 days in February 2019. A strict SimpleDateFormat will refuse to do that and will throw an exception. The default lenient behaviour can lead to very surprising and downright inexplicable results. As a simple example, giving the day, month and year in the wrong order: 1990.03.12 will result in August 11 year 17 AD (2001 years ago).
The solution
VGR already in a comment mentioned LocalDate from java.time, the modern Java date and time API. In my experience java.time is so much nicer to work with than the old date and time classes, so let’s give it a shot. Try a correct date string first:
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.mm.yyyy");
System.out.println(LocalDate.parse("03.12.1990", dateFormatter));
We get:
java.time.format.DateTimeParseException: Text '03.12.1990' could not
be parsed: Unable to obtain LocalDate from TemporalAccessor:
{Year=1990, DayOfMonth=3, MinuteOfHour=12},ISO of type
java.time.format.Parsed
This is because I used your format pattern string of dd.mm.yyyy, where lowercase mm means minute. When we read the error message closely enough, it does state that the DateTimeFormatter interpreted 12 as minute of hour, which was not what we intended. While SimpleDateFormat tacitly accepted this (even when strict), java.time is more helpful in pointing out our mistake. What the message only indirectly says is that it is missing a month value. We need to use uppercase MM for month. At the same time I am trying your date string with the typo:
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
System.out.println(LocalDate.parse("03.12.199o", dateFormatter));
We get:
java.time.format.DateTimeParseException: Text '03.12.199o' could not
be parsed at index 6
Index 6 is where is says 199. It objects because we had specified 4 digits and are only supplying 3. The docs say:
The count of letters determines the minimum field width …
It would also object to unparsed text after the date. In short it seems to me that it gives you everything that you had expected.
Links
DateFormat.setLenient documentation
Oracle tutorial: Date Time explaining how to use java.time.
Leniency is not about whether the entire input matches but whether the format matches. Your input can still be 3.12.1990somecrap and it would work.
The actual parsing is done in parse(String, ParsePosition) which you could use as well. Basically parse(String) will pass a ParsePosition that is set up to start at index 0 and when the parsing is done the current index of that position is checked.
If it's still 0 the start of the input didn't match the format, not even in lenient mode.
However, to the parser 03.12.199 is a valid date and hence it stops at index 8 - which isn't 0 and thus the parsing succeeded. If you want to check whether everything was parsed you'd have to pass your own ParsePosition and check whether the index is matches to the length of the input.
If you use setLenient(false) it will still parse the date till the desired pattern is meet. However, it will check the output date is a valid date or not. In your case, 03.12.199 is a valid date, so it will not throw an exception. Lets take an example to understand where the setLenient(false) different from setLenient(true)/default.
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy");
System.out.println(simpleDateFormat.parse("31.02.2018"));
The above will give me output: Sat Mar 03 00:00:00 IST 2018
But the below code throw ParseException as 31.02.2018 is not a valid/possible date:
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy");
simpleDateFormat.setLenient(false);
System.out.println(simpleDateFormat.parse("31.02.2018"));
I want format a date given in the following format 1st March 1990. The date should be formatted to YYYY-MM-DD. I have the following code. It gives me an unparsable date. From this i can understand, this is not the correct way to format this date as its not a valid pattern.
public class DateFormattingTest {
public static void main(String[] args) throws ParseException {
String dateString = "1st March 1984";
dateString = dateString.replaceFirst("[a-zA-Z]{2}","") ;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("d MMMM yyyy");
Date rightNow = simpleDateFormat.parse(dateString);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String formattedDate = dateFormat.format(rightNow);
System.out.println(formattedDate);
}
}
I have revised and looked for date formatting patterns as well. I cannot find something related to this pattern "1st March 1990". I don't want sample code for this issue. I want to find out what am i doing wrong in here? Can someone suggest an approach to parse such a date?
Thanks.
You have three problems.
First, you're trying to parse the date using the format YYYY-MM-DD. That's not the format of your data, which is why parsing is failing.
Second, you're expecting a Date object to retain information about a particular format. It doesn't. Instead, you would parse from one text format to a Date, and then use another DateFormat (with the desired output format) to format the Date into a String. Date.toString() will always use the same format, regardless of how you arrived at the Date.
Third, your format of YYYY-MM-DD isn't really what you want - you want yyyy-MM-dd. YYYY is the "weekyear", and DD is the "day of year".
I don't know of any SimpleDateFormat approach which would handle the ordinal part of your input string ("1st", "2nd" etc) - you'll probably need to put a bit of work into stripping that out. Once you've got a value such as "1 March 1990" you can parse with a SimpleDateFormat using the pattern d MMMM yyyy. Make sure you set the time zone and the locale appropriately.
Your date 1st was not incorporated into the Java DateFormat. If you can switch to 1 and use the appropriate DateFormat, the parsing of Date will work or else you would need to convert the ordinal number to number by stripping the suffix.
This might be a good related post to peek at.
The problem is that your dateString does not match the pattern specified in your simpleDateFormat format. It is expecting a date in the format YYYY-MM-DD, the meaning of these symbols can be found here.
I am having problems parsing time strings in Java that are in the format of 2013-01-09 09:15:03.000000. In my data, the last three digits are always 0 (meaning the input strings have only millisecond precision), so I passed this format to SimpleDateFormat:
formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS'000'");
but formatter.parse("2013-01-09 09:15:02.500000"); throws an exception:
Unparseable date: "2013-01-09 09:15:02.500000"
at java.text.DateFormat.parse(DateFormat.java:357)
Anyone knows how to do it correctly? I can work around by using format yyyy-MM-dd HH:mm:ss.SSS and using substring to get rid of last three digits but that's really hacky.
EDIT: can anyone explain why the format string yyyy-MM-dd HH:mm:ss.SSS'000' can't be used to parse time "2013-01-09 09:15:02.500000"
try java.sql.Timestamp
Timestamp ts = Timestamp.valueOf("2013-01-09 09:15:03.500000");
Date date = new Date(ts.getTime())
it's also thread-safe and fast as opposed to SimpleDateFormat
java.time
I should like to contribute the modern answer. Use java.time, the modern Java date and time API. One option, you may use a formatter:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSSSSS");
LocalDateTime dateTime = LocalDateTime.parse(timeString, formatter);
System.out.println(dateTime);
When using the string from your question, "2013-01-09 09:15:02.500000", this printed:
2013-01-09T09:15:02.500
If you want the value printed with six decimals on the seconds even when the last three decimals are 0, use the same formatter to format the time back into a string:
System.out.println(dateTime.format(formatter));
The other option, you may exploit the fact that your string resembles the ISO 8601 format, the format that the modern classes parse as their default, that is, without any explicit formatter. Only ISO 8601 has a T to denote the start of the time part, but we can fix that easily:
LocalDateTime dateTime = LocalDateTime.parse(timeString.replace(' ', 'T'));
It gives the same result, 2013-01-09T09:15:02.500. It’s shorter, but also more tricky.
Why bother?
The classes Date and Timestamp are long outdated, and SimpleDateFormat in particular has proven troublesome. Its surprising behaviour in your situation is just one little story out of very many. The modern API is generally so much nicer to work with.
Why didn’t your formatter work?
While the format pattern strings used by SimpleDateFormat and DateTimeFormatter are similar, there are differences. One is that SimpleDateFormat understands uppercase S as milliseconds no matter of there are one or nine of them, whereas to DateTimeFormatter they mean fraction of second. Your SimpleDateFormat furthermore grabbed all six digits after the decimal point, ignoring the fact that you had typed only three S, so there were no zeroes left to match the '000' (by the way, the apostrophes are not necessary, only letters need them).
Link
Oracle Tutorial
I've figured out myself. Just FYI, Apache commons' FastDateFormat seems accepting the SSS000 format and parses the time correctly.
I have this code:
DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
dateFormat.setLenient(false);
Date date = dateFormat.parse("10/20/20128");
and I would expect the dateFormat.parse call to throw ParseException since the year I'm providing is 5 characters long instead of 4 like in the format I defined. But for some reason even with the lenient set to false this call returns a Date object of 10/20/20128.
Why is that? It doesn't make much sense to me. Is there another setting to make it even more strict?
20128 is a valid year and Java hopes the world to live that long I guess.
if the number of pattern letters is more than 2, the year is
interpreted literally, regardless of the number of digits.
Reference.
If you want to validate if a date is in limit, you can define one and check-
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
Date maxDate = sdf.parse("01/01/2099"); // This is the limit
if(someDate.after(maxDate)){
System.out.println("Invalid date");
}
See the javadoc
Year: If the formatter's Calendar is the Gregorian calendar, the
following rules are applied.
For formatting, if the number of pattern letters is 2, the year is truncated to 2 digits; otherwise it is interpreted as a number.
For parsing, if the number of pattern letters is more than 2, the year is interpreted literally, regardless of the number of digits.
So using the pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12
A.D.
For parsing with the abbreviated year pattern ("y" or "yy"), SimpleDateFormat must interpret the abbreviated year relative to some
century. It does this by adjusting dates to be within 80 years before
and 20 years after the time the SimpleDateFormat instance is created.
For example, using a pattern of "MM/dd/yy" and a SimpleDateFormat
instance created on Jan 1, 1997, the string "01/11/12" would be
interpreted as Jan 11, 2012 while the string "05/04/64" would be
interpreted as May 4, 1964. During parsing, only strings consisting
of exactly two digits, as defined by Character.isDigit(char), will be
parsed into the default century. Any other numeric string, such as a
one digit string, a three or more digit string, or a two digit string
that isn't all digits (for example, "-1"), is interpreted literally.
So "01/02/3" or "01/02/003" are parsed, using the same pattern, as
Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
Otherwise, calendar system specific forms are applied. For both formatting and parsing, if the number of pattern letters is 4 or
more, a calendar specific long form is used. Otherwise, a calendar
specific short or abbreviated form is used.
Therefore, it will read all the characters that come after the last / as the year.
See java.text.SimpleDateFormat API, pattern letter y: For parsing, if the number of pattern letters is more than 2, the year is interpreted literally, regardless of the number of digits.
Using SimpleDateFormat in java, you can achieve a number of human readable formats. Consider this code snippet:
Date curDate = new Date();
SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
String DateToStr = format.format(curDate);
System.out.println(DateToStr);
format = new SimpleDateFormat("dd-M-yyyy hh:mm:ss");
DateToStr = format.format(curDate);
System.out.println(DateToStr);
format = new SimpleDateFormat("dd MMMM yyyy zzzz", Locale.ENGLISH);
DateToStr = format.format(curDate);
System.out.println(DateToStr);
format = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z");
DateToStr = format.format(curDate);
System.out.println(DateToStr);
Look at the various outputs generated by the various formats:
2014/05/11
11-5-2014 11:11:51
11 May 2014 Eastern European Summer Time
Sun, 11 May 2014 23:11:51 EEST
Feel free to modify the format string and you might get the desired result.
java.time
I always recommend that you use java.time, the modern Java date and time API, for your date work. In addition it so happens that java.time will give you the exception you asked for.
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MM/dd/uuuu")
.withResolverStyle(ResolverStyle.LENIENT);
LocalDate date = LocalDate.parse("10/20/20128", dateFormatter);
Result:
Exception in thread "main" java.time.format.DateTimeParseException:
Text '10/20/20128' could not be parsed at index 6
The mentioned index 6 is where the 5-digit year is in your string.
This said, the range check suggested by others could still be a good idea. With java.time this is easy. Say for example that we don’t want to accept any future date:
LocalDate date = LocalDate.parse("10/20/8012", dateFormatter);
if (date.isAfter(LocalDate.now(ZoneId.systemDefault()))) {
System.err.println("Date must not be in the future, was " + date);
}
Date must not be in the future, was 8012-10-20
Tutorial link: Trail: Date Time (The Java™ Tutorials) explaining how to use java.time.