I have three date formats: YYYY-MM-DD, DDMMYYYY, MMDDYYYY this is how I pass date format in Spark to parse.
scala> val formatter = DateTimeFormatter.ofPattern("[MMddyyyy][yyyy-MM-dd][yyyyMMdd]")
formatter: java.time.format.DateTimeFormatter = [Value(MonthOfYear,2)Value(DayOfMonth,2)Value(YearOfEra,4,19,EXCEEDS_PAD)][Value(YearOfEra,4,19,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2)][Value(YearOfEra,4,19,EXCEEDS_PAD)Value(MonthOfYear,2)Value(DayOfMonth,2)]
For format MMddyyyy it's working
scala> LocalDate.parse("10062019",formatter)
res2: java.time.LocalDate = 2019-10-06
For format yyyyMMdd it's working
scala> LocalDate.parse("2019-06-20",formatter)
res3: java.time.LocalDate = 2019-06-20
For format yyyyMMdd, it's giving me an error
scala> LocalDate.parse("20190529",formatter)
java.time.format.DateTimeParseException: Text '20190529' could not be parsed: Invalid value for MonthOfYear (valid values 1 - 12): 20
at java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1920)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1855)
at java.time.LocalDate.parse(LocalDate.java:400)
... 66 elided
Caused by: java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 20
at java.time.temporal.ValueRange.checkValidIntValue(ValueRange.java:330)
at java.time.temporal.ChronoField.checkValidIntValue(ChronoField.java:722)
at java.time.chrono.IsoChronology.resolveYMD(IsoChronology.java:550)
at java.time.chrono.IsoChronology.resolveYMD(IsoChronology.java:123)
at java.time.chrono.AbstractChronology.resolveDate(AbstractChronology.java:472)
at java.time.chrono.IsoChronology.resolveDate(IsoChronology.java:492)
at java.time.chrono.IsoChronology.resolveDate(IsoChronology.java:123)
at java.time.format.Parsed.resolveDateFields(Parsed.java:351)
at java.time.format.Parsed.resolveFields(Parsed.java:257)
at java.time.format.Parsed.resolve(Parsed.java:244)
at java.time.format.DateTimeParseContext.toResolved(DateTimeParseContext.java:331)
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1955)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
... 67 more
If I pass 2 format yyyyMMdd, yyyy-MM-dd it's working fine
scala> val formatter = DateTimeFormatter.ofPattern("[yyyy-MM-dd][yyyyMMdd]")
scala> LocalDate.parse("20190529",formatter)
res5: java.time.LocalDate = 2019-05-29
scala> LocalDate.parse("2019-06-20",formatter)
res6: java.time.LocalDate = 2019-06-20
Same as yyyy-MM-dd, mmddyyy date format
scala> val formatter = DateTimeFormatter.ofPattern("[yyyy-MM-dd][MMddyyyy]")
scala> LocalDate.parse("10062019",formatter)
res7: java.time.LocalDate = 2019-10-06
scala> LocalDate.parse("2019-06-20",formatter)
res8: java.time.LocalDate = 2019-06-20
Is there any way that I can pass three different formats?
Java's own DateTimeFormatterBuilder#appendOptional can handle it without without relying on exception handling. From the documentation:
The formatter will format if data is available for all the fields contained within it. The formatter will parse if the string matches, otherwise no error is returned.
Which means you will not have to handle the multiple cases with exceptions.
Here is an example from a real code-base where I had to do this handling:
def dtf: DateTimeFormatter =
new DateTimeFormatterBuilder()
.appendOptional(DateTimeFormatter.ofPattern("dd/MM/yy"))
.appendOptional(DateTimeFormatter.ofPattern("dd/MM/yyyy"))
.toFormatter()
Here are two unit tests (in ScalaTest) demonstrating the usage:
"Short form is parsed" in {
assert(LocalDate.parse("28/08/20", dtf) == LocalDate.of(2020, 8, 28))
}
"Long form is parsed" in {
assert(LocalDate.parse("02/10/2020", dtf) == LocalDate.of(2020, 10, 2))
}
You can´t have in the formatter [yyyyMMdd] and [MMddyyyy] at the same time.
My idea is to normalize so you have [yyyy-MM-dd] and [MM-dd-yyyy], instead of 3 formats .
Hope this helped
Edited:
If you don´t have a chance you can do something like this, but it´s not very pretty.
val formatter1 = DateTimeFormatter.ofPattern("[yyyy-MM-dd][MMddyyyy]")
val formatter2 = DateTimeFormatter.ofPattern("[yyyy-MM-dd][yyyyMMdd]")
val time = "20190529"
if (time.matches("2+\\d*")) LocalDate.parse(time,formatter2) else
LocalDate.parse(time,formatter1)
With only the information in the question this is not possible. The string 10111213 could denote December 13, 1011 or October 11, 1213. However, assuming that your dates are always after year 1300, you’re lucky because then the YYYY part of the string cannot be parsed as MMDD becuase the month will be 13 or greater, that is, not valid. You can use this for deciding which of the formats is the correct one to use.
I would use three formatters and try them in turn:
private static final DateTimeFormatter[] DATE_FORMATTERS = {
DateTimeFormatter.ofPattern("uuuuMMdd"),
DateTimeFormatter.ofPattern("MMdduuuu"),
DateTimeFormatter.ofPattern("uuuu-MM-dd")
};
With these just do:
String dateString = "20190529";
LocalDate result = null;
for (DateTimeFormatter df : DATE_FORMATTERS) {
try {
result = LocalDate.parse(dateString, df);
break;
} catch (DateTimeParseException dtpe) {
// Ignore; try next formatter
}
}
System.out.println("" + dateString + " was parsed to " + result);
Output is:
20190529 was parsed to 2019-05-29
Let’s try the two other formats too:
10062019 was parsed to 2019-10-06
2019-06-20 was parsed to 2019-06-20
I recommend you add a null check to catch any unparseable date string and a range check on the parsed date so that 10111213 doesn’t slip through as valid. For example:
if (result == null) {
System.out.println(dateString + " could not be parsed");
}
else if (result.isBefore(LocalDate.now(ZoneId.of("Asia/Aden")))) {
System.out.println("Date should be in the future, was " + result);
}
PS I assume a typo in the first sentence of your question:
I have three date formats: YYYY-MM-DD, DDMMYYYY, MMDDYYYY this is how
I pass date format in Spark to parse.
The middle format should have been YYYYMMDD (otherwise you’ve got no chance).
Related
I'm facing a problem converting from string to LocalDateTime, and adding 3 months to the current month.
This is my code:
String str = "13/11/2020";
LocalDateTime dateTime = LocalDateTime.parse(str, DateTimeFormatter.ofPattern("dd-MM-yyyy"));
dateTime.plusMonths(3); // => output: 13/02/2021
System.out.println(dateTime);
But when I run it I get the following exception:
java.time.format.DateTimeParseException: Text '13/11/2020' could not be parsed at index 2
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949) ~[?:1.8.0_144]
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851) ~[?:1.8.0_144]
at java.time.LocalDateTime.parse(LocalDateTime.java:492) ~[?:1.8.0_144]
How can I fix this problem ? Many thanks
Multiple issues:
Your String is look like a LocalDate and LocalDateTime, it doesn't contain the time part
The pattern you are using is not the same format as your String it should be dd/MM/yyyy or dd/MM/uuuu and not dd-MM-yyyy
To parse your String you need:
LocalDate date = LocalDate.parse(str, DateTimeFormatter.ofPattern("dd/MM/uuuu"));
dateTime = dateTime.plusMonths(3);
Or if you want LocalDateTime, then you can use DateTimeFormatterBuilder with 0 for hours, minutes and seconds:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("dd/MM/uuuu")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
.toFormatter();
LocalDateTime dateTime = LocalDateTime.parse(str, formatter);
dateTime = dateTime.plusMonths(3);
and the result will be like this: 2021-02-13T00:00
Note: dateTime.plusMonths(3) is immutable so to change the value you have to assign the new value to the variable.
There are several issues in your code:
You are using the wrong format pattern. If the date string contains slashes, so should the pattern string as well: DateTimeFormatter.ofPattern("dd/MM/yyyy").
If the date does not include time, then use LocalDate instead of LocalDateTime.
The plusMonths result needs to be assigned to a new variable to get the updated instance.
The DateTimeFormatter needs to be used when printing the expected result 13/02/2021, so you can share the same formatter instance:
String str = "13/11/2020";
var dateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
var dateTime = LocalDate.parse(str, dateTimeFormatter);
var newDateTime = dateTime.plusMonths(3); // => output: 13/02/2021
System.out.println(newDateTime.format(dateTimeFormatter));
Date time strings like "2018-04-01 10:00:00" can be used to create Date objects with this Kotlin code:
val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val d : Date = format.parse("2018-04-01 10:00:00") // works fine
But how to parse date time strings with offsets like these:
2018-04-01 10:00:00+02 // GMT + 2 hours
2018-04-01 10:00:00+02:30 // GMT + 2 hours, 30 minutes
2018-04-01 10:00:00+0230 // GMT + 2 hours, 30 minutes
Java 8: Instant is not an option.
edit:
I've tried the suggestion and used 'x', 'X', 'z' and 'Z', 'XXX' with and without leading space. Compiles fine. The x-versions crashes when SimpleDateFormat is instantiated - seems unsupported in the used android api level:
// java.lang.IllegalArgumentException: Unknown pattern character 'X'
var formatter3 = SimpleDateFormat("yyyy-MM-dd HH:mm:ssX")
// java.lang.IllegalArgumentException: Unknown pattern character 'x'
var formatter4 = SimpleDateFormat("yyyy-MM-dd HH:mm:ssx")
// java.lang.IllegalArgumentException: Unknown pattern character 'x'
var formatter5 = SimpleDateFormat("yyyy-MM-dd HH:mm:ss x")
The z-versions:
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ")
or
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ssz")
val date = formatter.parse("2018-04-24 17:33:02+02")
seems to work.
#Andreas: Date Time parsing is a pain in the ass. Instead of downvoting the question, a simple line of code would have been more helpful
Time zones can be parsed with Z (RFC 822) or X (ISO 8601), see https://developer.android.com/reference/java/text/SimpleDateFormat
I'm trying to use Java 8 to re-format today's date but I'm getting the following error:
java.time.format.DateTimeParseException: Text '09-OCT-2017' could not be parsed:
Unable to obtain LocalDate from TemporalAccessor:
{WeekBasedYear[WeekFields[SUNDAY,1]]=2017, MonthOfYear=10, DayOfYear=9},ISO of type java.time.format.Parsed
Code:
public static String formatDate(String inputDate, String inputDateFormat, String returnDateFormat){
try {
DateTimeFormatter inputFormatter = new DateTimeFormatterBuilder().parseCaseInsensitive().appendPattern(inputDateFormat).toFormatter(Locale.ENGLISH);
LocalDate localDate = LocalDate.parse(inputDate, inputFormatter);
DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern(returnDateFormat);
String formattedString = localDate.format(outputFormatter);
return formattedString;
} catch (DateTimeParseException dtpe) {
log.error("A DateTimeParseException exception occured parsing the inputDate : " + inputDate + " and converting it to a " + returnDateFormat + " format. Exception is : " + dtpe);
}
return null;
}
I previously tried using SimpleDateFormat, but the problem is my inputDateFormat format is always in uppercase DD-MMM-YYYY, which was giving me incorrect results, so I tried using parseCaseInsensitive() to ignore the case sensitivity.
In the comments you told that the input format is DD-MMM-YYYY. According to javadoc, uppercase DD is the day of year field, and YYYY is the week based year field (which might be different from the year field).
You need to change them to lowercase dd (day of month) and yyyy (year of era). The parseCaseInsensitive() only takes care of the text fields - in this case, the month name (numbers are not affected by the case sensitivity - just because the month is in uppercase, it doesn't mean that the numbers patterns should also be).
The rest of the code is correct. Example (changing the format to yyyyMMdd):
String inputDate = "09-OCT-2017";
DateTimeFormatter inputFormatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
// use "dd" for day of month and "yyyy" for year
.appendPattern("dd-MMM-yyyy")
.toFormatter(Locale.ENGLISH);
LocalDate localDate = LocalDate.parse(inputDate, inputFormatter);
// use "dd" for day of month and "yyyy" for year
DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
String formattedString = localDate.format(outputFormatter);
System.out.println(formattedString); // 20171009
The output of the code above is:
20171009
Regarding your other comment about not having control over the input pattern, one alternative is to manually replace the letters to their lowercase version:
String pattern = "DD-MMM-YYYY";
DateTimeFormatter inputFormatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
// replace DD and YYYY with the lowercase versions
.appendPattern(pattern.replace("DD", "dd").replaceAll("YYYY", "yyyy"))
.toFormatter(Locale.ENGLISH);
// do the same for output format if needed
I don't think it needs a complex-replace-everything-in-one-step regex. Just calling the replace method multiple times can do the trick (unless you have really complex patterns that would require lots of different and complex calls to replace, but with only the cases you provided, that'll be enough).
I hope I got you right.
Formatting a String to LocalDate is acutally pretty simple. Your date format is that here right 09-Oct-2017?
Now you just need use the split command to divide that into a day, month and year:
String[] tempStr = inputDate.split("-");
int year = Integer.parseInt(tempStr[2]);
int month = Integer.parseInt(tempStr[1]);
int day = Integer.parseInt(tempStr[0]);
After that it´s pretty easy to get that to LocalDate:
LocalDate ld = LocalDate.of(year, month, day);
I hope that helps.
I just wrote this unit tests :
#Test
public void testGetDateFromString() throws ParseException{
String date = "52/29/2500";
Date dateFromString = DateHelper.getDateFromString(date, DateHelper.DD_MM_YYYY_FORMAT);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DateHelper.DD_MM_YYYY_FORMAT);
Date dateWithSimpleFormat = simpleDateFormat.parse(date);
Assert.assertNotNull(dateFromString);
Assert.assertNotNull(dateWithSimpleFormat);
Assert.assertTrue(dateFromString.equals(dateWithSimpleFormat));
System.out.println("dateFromString " + dateFromString);
System.out.println("dateWithSimpleFormat " + dateWithSimpleFormat);
}
And the output is :
dateFromString Wed Jun 21 00:00:00 CEST 2502
dateWithSimpleFormat Wed Jun 21 00:00:00 CEST 2502
The DateHelper.DD_MM_YYYY_FORMAT pattern is dd/MM/yyyy and getDateFromString is a method that parses a String date to a Date object using commons-lang library.
Why des the java.util.Date object verifies the date validity?
You need to set simpleDateFormat.setLenient(false); to make the SimpleDateFormat to validate your input strictly.
You can refer the setLenient documentation for further understanding. By the definition,
Specify whether or not date/time parsing is to be lenient. 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.
Use simpleDateFormat.setLenient(false); to enable strict parsing.
java.time
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd/MM/uuuu");
String date = "52/29/2500";
try {
LocalDate dateWithJavaTime = LocalDate.parse(date, dateFormatter);
System.out.println("dateWithJavaTime " + dateWithJavaTime);
} catch (DateTimeParseException dtpe) {
System.out.println("Invalid date. " + dtpe);
}
The output from this code is:
Invalid date. java.time.format.DateTimeParseException: Text
'52/29/2500' could not be parsed: Invalid value for MonthOfYear (valid
values 1 - 12): 29
Please enjoy not only that the validation works, but also the precision of the error message.
Other results:
For the string 52/11/2500 the result is “Invalid date. java.time.format.DateTimeParseException: Text '52/11/2500' could not be parsed: Invalid value for DayOfMonth (valid values 1 - 28/31): 52”.
For the string 29/02/2019 we get “dateWithJavaTime 2019-02-28”, which may surprise. To have this string rejected use
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd/MM/uuuu")
.withResolverStyle(ResolverStyle.STRICT);
Now we get
Invalid date. java.time.format.DateTimeParseException: Text
'29/02/2019' could not be parsed: Invalid date 'February 29' as '2019'
is not a leap year
Again enjoy how precise the message is.
What will be the regular expression for following Timestamp format
YYYY-MM-DD HH:mm:ss.S
YYYY-MM-DD HH:mm:ss.S AM/PM
YYYY-MM-DD HH:mm:ss.S AM/PM Z
YYYY-MM-DD HH:mm:ss.S Z
Where
Y: year,
M: Month,
D: Date,
H: hour,
m: minute,
s: second,
S: Milisecond 3 digit only,
Z: Time zone.
I am getting timestamp format in string format so want to validate it.
How to check above regular expression in GWT?
Just something simple as only describing the pattern like this:
^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}(?: [AP]M)?(?: [+-]\d{4})?$
as any tentative of real date validation with a regex sounds inherently wrong.
I got the uppercase Z as RFC822 timezone, regex needs changes to comply to TZDs or general textual time zones.
Apart from Datejs which relays on js to check a date-string, Gwt comes with DateTimeFormat to parse date-string and format dates with support for locales. It raises a IllegalArgumentException in the case the parsed string doesn't match the expected format .
String dateStr = "2011-04-21 20:37:36.999 -0800";
String fmt = "yyyy-MM-dd HH:mm:ss.S Z"; // your 4th case: YYYY-MM-DD HH:mm:ss.S Z
DateTimeFormat format = DateTimeFormat.getFormat(fmt);
try {
Date date = format.parse(dateStr);
System.out.println("Validated: " + date);
} catch (IllegalArgumentException e) {
System.out.println("Validation error: " + e.getMessage());
}
dateStr = "2011-04-21 08:37:36.999 PM -0800"; // your 3rd case YYYY-MM-DD HH:mm:ss.S AM/PM Z
fmt = "yyyy-MM-dd hh:mm:ss.S a Z";
format = DateTimeFormat.getFormat(fmt);
try {
Date date = format.parse(dateStr);
System.out.println("Validated: " + date);
} catch (IllegalArgumentException e) {
System.out.println("Validation error: " + e.getMessage());
}
You dont say whether the format string is fixed or it can be provided in runtime before performing the validation. So in the second case you need to use replace to change 'Y' by 'y', and 'AM/PM' to 'a' which are the symbols used in DateTimeFormat
I would say use Datejs
Otherwise you will need to do a lot of coding, and regex is not the best for verifying timestamps and if it is valid.
Datejs will check the date validity, and from that you will receive a Date object or null (if it is invalid!).
Date.parse("2013-02-02 12:01:01.000", "yyyy-MM-dd HH:mm:ss.u");
For more information see:
Datejs API documentation
Datejs Format spec