Wildcard in DateTimeFormatter - java

I need to parse a string into a LocalDate. The string looks like 31.* 03 2016 in regex terms (i.e. .* means that there may be 0 or more unknown characters after the day number).
Example input/output: 31xy 03 2016 ==> 2016-03-31
I was hoping to find a wildcard syntax in the DateTimeFormatter documentation to allow a pattern such as:
LocalDate.parse("31xy 03 2016", DateTimeFormatter.ofPattern("dd[.*] MM yyyy"));
but I could not find anything.
Is there a simple way to express optional unknown characters with a DateTimeFormatter?
ps: I can obviously modify the string before parsing it but that's not what I'm asking for.

There is no direct support for this in java.time.
The closest would be to use parse(CharSequence,ParsePosition) using two different formatters.
// create the formatter for the first half
DateTimeFormatter a = DateTimeFormatter.ofPattern("dd")
// setup a ParsePosition to keep track of where we are in the parse
ParsePosition pp = new ParsePosition();
// parse the date, which will update the index in the ParsePosition
String str = "31xy 03 2016";
int dom = a.parse(str, pp).get(DAY_OF_MONTH);
// some logic to skip the messy 'xy' part
// logic must update the ParsePosition to the start of the month section
pp.setIndex(???)
// use the parsed day-of-month in the formatter for the month and year
DateTimeFormatter b = DateTimeFormatter.ofPattern("MM yyyy")
.parseDefaulting(DAY_OF_MONTH, dom);
// parse the date, using the *same* ParsePosition
LocalDate date = b.parse(str, pp).query(LocalDate::from);
While the above is untested it should basically work. However, it would be far easier parse it manually.

I’d do it in two steps, use a regexp to get the original string into something that LocalDate can parse, for example:
String dateSource = "31xy 03 2016";
String normalizedDate = dateSource.replaceFirst("^(\\d+).*? (\\d+ \\d+)", "$1 $2");
LocalDate date = LocalDate.parse(normalizedDate, DateTimeFormatter.ofPattern("dd MM yyyy"));
System.out.println(date);
I know it’s not what you asked for.

Related

Handling various timestamp with one formatter in java/scala?

I am currently getting two version of timestamp format eg '2017-04-17 20:33:45.223+05:30' and '2017-04-17 20:33:45+05:30'.My parsing is failing due to dynamic timestamp .Is it possible to handle both of these time stamp with one DateTimeFormatter Pattern .Below is the example code what i tried
val myDate=LocalDateTime.parse("2017-04-17 20:33:45.223+05:30", DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSZ")).toDateTime(DateTimeZone.UTC)//this will fail if time stamp comes with '2017-04-17 20:33:45+05:30
I had seen one way to achieve the same using optional part however I canot make it work
val pattern = "MM/dd/yyyy HH:mm:ss[.SSS]Z"
val fmt = DateTimeFormatter.ofPattern(pattern)
val temporalAccessor = fmt.parse("2017-04-17 20:33:45.223+05:30")
Ant help on this or any suggestion how to handle such cases will be helpful .Thanks in advance .
uuuu-MM-dd
Edit: This fixes it. I am using java.time, the modern Java date and time API, and Java syntax.
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss[.SSS]xxx", Locale.ROOT);
Trying it out:
String[] variants = {
"2017-04-17 20:33:45.223+05:30",
"2017-04-17 20:33:45+05:30",
// Variants we don’t want to accept
"2017-04-17 20:00+05:30",
"2017-04-17 20:00:00.000000+05:30" };
for (String inputString : variants) {
try {
OffsetDateTime dateTime = OffsetDateTime.parse(inputString, FORMATTER);
System.out.println("Parsed: " + dateTime);
} catch (DateTimeParseException dtpe) {
System.out.println("Invalid: " + inputString);
}
}
Output:
Parsed: 2017-04-17T20:33:45.223+05:30
Parsed: 2017-04-17T20:33:45+05:30
Invalid: 2017-04-17 20:00+05:30
Invalid: 2017-04-17 20:00:00.000000+05:30
What went wrong in your code?
You had the right idea for your purpose.
You attempted using the outmoded Joda-Time library. Joda-Time can support optional parts when parsing, but not through the square bracket syntax. Instead its DateTimeFormatterBuilder has got an appendOptional method.
In your java.time code this part of your format pattern string doesn’t match any of your inputs: MM/dd/yyyy. Java parsed 20 as a 2 digit month number (postponing validation of the number) and threw the exception because no slash was found after 20.
Edit 2: why xxx works but Z doesn't:
With Joda-Time’s DateTimeFormat one Z is for offset without colon, for example +0530. ZZ should have worked for +05:30 with colon.
With java.time both x and Z (and also upper case X) are for zone offset. Here too Z is for offset without colon. Either xxx or ZZZZZ works for +05:30.
Use the built-in formatters
Original answer, likely useful for others: This one does it (using Java syntax):
private static final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.appendOffsetId()
.toFormatter();
Let’s try it out:
String[] variants = {
"2017-04-17 20:33:45.223+05:30",
"2017-04-17 20:33:45+05:30",
"2017-04-17 20:00+05:30",
"2017-04-17 20:00:00.000000+05:30" };
for (String inputString : variants) {
OffsetDateTime dateTime = OffsetDateTime.parse(inputString, FORMATTER);
System.out.println(dateTime);
}
Output:
2017-04-17T20:33:45.223+05:30
2017-04-17T20:33:45+05:30
2017-04-17T20:00+05:30
2017-04-17T20:00+05:30
I am exploiting the fact that the built-in DateTimeFormatter.ISO_LOCAL_TIME accepts a time both with and without decimals on the seconds. We can reuse existing formatters in our own formatter through a DateTimeFormatterBuilder.
parseBest looks a good fit for this
public TemporalAccessor parseBest(CharSequence text,
TemporalQuery<?>... queries)
Fully parses the text producing an object of one of the specified
types.
This parse method is convenient for use when the parser can handle optional elements. For example, a pattern of 'uuuu-MM-dd HH.mm[ VV]'
can be fully parsed to a ZonedDateTime, or partially parsed to a
LocalDateTime. The queries must be specified in order, starting from
the best matching full-parse option and ending with the worst matching
minimal parse option. The query is typically a method reference to a
from(TemporalAccessor) method.
The result is associated with the first type that successfully parses

Generic date parsing in java

I have date strings in various formats like Oct 10 11:05:03 or 12/12/2016 4:30 etc
If I do
// some code...
getDate("Oct 10 11:05:03", "MMM d HH:mm:ss");
// some code ...
The date gets parsed, but I am getting the year as 1970 (since the year is not specified in the string.) But I want the year as current year if year is not specidied. Same applies for all fields.
here is my getDate function:
public Date getDate(dateStr, pattern) {
SimpleDateFormat parser = new SimpleDateFormat(pattern);
Date date = parser.parse(myDate);
return date;
}
can anybody tell me how to do that inside getDate function (because I want a generic solution)?
Thanks in advance!
If you do not know the format in advance, you should list the actual formats you are expecting and then try to parse them. If one fails, try the next one.
Here is an example of how to fill in the default.
You'll end up with something like this:
DateTimeFormatter f = new DateTimeFormatterBuilder()
.appendPattern("ddMM")
.parseDefaulting(YEAR, currentYear)
.toFormatter();
LocalDate date = LocalDate.parse("yourstring", f);
Or even better, the abovementioned formatter class supports optional elements. Wrap the year specifier in square brackets and the element will be optional. You can then supply a default with parseDefaulting.
Here is an example:
String s1 = "Oct 5 11:05:03";
String s2 = "Oct 5 1996 13:51:56"; // Year supplied
String format = "MMM d [uuuu ]HH:mm:ss";
DateTimeFormatter f = new DateTimeFormatterBuilder()
.appendPattern(format)
.parseDefaulting(ChronoField.YEAR, Year.now().getValue())
.toFormatter(Locale.US);
System.out.println(LocalDate.parse(s1, f));
System.out.println(LocalDate.parse(s2, f));
Note: Dates and times are not easy. You should take into consideration that date interpreting is often locale-dependant and this sometimes leads to ambiguity. For example, the date string "05/12/2018" means the 12th of May, 2018 when you are American, but in some European areas it means the 5th of December 2018. You need to be aware of that.
One option would be to concatenate the current year onto the incoming date string, and then parse:
String ts = "Oct 10 11:05:03";
int currYear = Calendar.getInstance().get(Calendar.YEAR);
ts = String.valueOf(currYear) + " " + ts;
Date date = getDate(ts, "yyyy MMM d HH:mm:ss");
System.out.println(date);
Wed Oct 10 11:05:03 CEST 2018
Demo
Note that we could have used StringBuilder above, but the purpose of brevity of code, I used raw string concatenations instead. I also fixed a few typos in your helper method getDate().

Date as longvalue to LocalDateTime

How to convert a long value like 2018051822111234L to yyyyMMdd HH:mm?
2018051822111234 -> 2018 05 18 22:11:12.
I tried with LocalDate.parse and DateFormatter(yyyy-MM-dd'T'HH:mm:ssZZZZZ). It doesn’t work for me.
String asString = Long.toString(2018051822111234L);
asString = asString.substring(0, asString.length() - 2);
String result = LocalDateTime.parse(asString, DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
.format(DateTimeFormatter.ofPattern("yyyy MM dd HH:mm:ss"));
Monsieur Nizet has already provided an excellent answer. It’s just me: I’d like to parse the full precision of the input long. It’s easier to throw away information later than it is to add it later if it wasn’t parsed at first.
Java 9 solution:
DateTimeFormatter longFormatter = DateTimeFormatter.ofPattern("uuuuMMddHHmmssSS");
DateTimeFormatter desiredFormatter = DateTimeFormatter.ofPattern("uuuu MM dd HH:mm:ss");
String asString = Long.toString(2018051822111234L);
String result = LocalDateTime.parse(asString, longFormatter)
.format(desiredFormatter);
This prints
2018 05 18 22:11:12
As you have already said yourself, this doesn’t work in Java 8 because of this bug in the JRE: DateTimeFormatter won't parse dates with custom format "yyyyMMddHHmmssSSS". The bug report mentions the following workaround:
DateTimeFormatter longFormatter = new DateTimeFormatterBuilder()
.appendPattern("uuuuMMddHHmmss")
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.toFormatter();
asString += '0';
The workaround formatter has three decimals on the seconds, corresponding to milliseconds, whereas your long has only two. So above I am appending an extra 0 to the string before parsing. It was what I could get to work in Java 8 (also tried appendFraction(), in vain). Now the result is the same as above.

How to convert Gregorian string to Gregorian Calendar?

I have to compute something based on the Calendar's date, but I am receiving the complete Gregorian Calendar's String value.
Eg i/p received {may be - "new GregorianCalendar().toString()"} as String :- java.util.GregorianCalendar[time=1410521241348,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/London",offset=0,dstSavings=3600000,useDaylight=true,transitions=242,lastRule=java.util.SimpleTimeZone[id=Europe/London,offset=0,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2014,MONTH=8,WEEK_OF_YEAR=37,WEEK_OF_MONTH=2,DAY_OF_MONTH=12,DAY_OF_YEAR=255,DAY_OF_WEEK=6,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=0,HOUR_OF_DAY=12,MINUTE=27,SECOND=21,MILLISECOND=348,ZONE_OFFSET=0,DST_OFFSET=3600000]
I want to extract the Calendar's date value to process further computation.
You could find the time in the input string and convert it to a Gregorian Calendar. Then you would have to set its timezone as specified in the ZoneInfo field. Something like this might work:
String calendarAsString="java.util.GregorianCalendar[time=1410521241348,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id=\"Europe/London\",offset=0,dstSavings=3600000,useDaylight=true,transitions=242,lastRule=java.util.SimpleTimeZone[id=Europe/London,offset=0,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2014,MONTH=8,WEEK_OF_YEAR=37,WEEK_OF_MONTH=2,DAY_OF_MONTH=12,DAY_OF_YEAR=255,DAY_OF_WEEK=6,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=0,HOUR_OF_DAY=12,MINUTE=27,SECOND=21,MILLISECOND=348,ZONE_OFFSET=0,DST_OFFSET=3600000]";
int timeStart=calendarAsString.indexOf("time=")+5;
int timeEnd=calendarAsString.indexOf(',');
String timeStr=calendarAsString.substring(timeStart, timeEnd);
long timeInMillis=Long.parseLong(timeStr);
int timezoneIdStart=calendarAsString.indexOf("\"")+1;
int timezoneIdEnd=calendarAsString.indexOf("\",");
String timeZoneStr=calendarAsString.substring(timezoneIdStart, timezoneIdEnd);
System.out.println("time="+timeInMillis+" zone="+timeZoneStr);
Calendar calendar=Calendar.getInstance(TimeZone.getTimeZone(timeZoneStr));
calendar.setTimeInMillis(timeInMillis);
System.out.println(calendarAsString);
System.out.println(calendar);
or you can use a regular expression to do it, instead
String regex="time=([0-9]*),.*ZoneInfo\\[id=\"([^\"]*)\"";
Pattern pattern=Pattern.compile(regex);
Matcher matcher=pattern.matcher(calendarAsString);
matcher.find();
timeStr=matcher.group(1);
timeInMillis=Long.parseLong(timeStr);
timeZoneStr=matcher.group(2);
System.out.println("time="+timeInMillis+" zone="+timeZoneStr);
calendar=Calendar.getInstance(TimeZone.getTimeZone(timeZoneStr));
calendar.setTimeInMillis(timeInMillis);
System.out.println(calendar);
Note: if you just want the calendar's Date value, you can construct it from the timeInMillis, without having to reconstruct the whole GregorianCalendar object (and without having to find the timezone if you don't want to).
Date date=new Date(timeInMillis);
Other answers are too complicated or wrong. The following will give you the milliseconds since the epoch, which is a universal timestamp that you can easily convert to most time representation classes, including Calendar or Date:
Pattern gregorianPattern = Pattern.compile("^java.util.GregorianCalendar\\[time=(\\d+).*");
Matcher matcher = gregorianPattern.matcher(param);
if(matcher.matches()) {
return Long.parseLong(matcher.group(1));
}
GregorianCalendar g=new GregorianCalendar(1975, 5, 7);
Date d=g.getTime();
System.out.println(g.toString());
SimpleDateFormat formatter=new SimpleDateFormat("yyyy MM dd");
System.out.println(formatter.format(d));
This is way to grab date from GregorianCalendar. i wish this will help to you
Adding more to this, you can retrieve any information you want using the format. It's just a matter of providing the correct format.
Ex:
Adding z or Z provides you with the timezone information
"yyyy MM dd z" - 2014 10 12 PDT
"yyyy MM dd Z" - 2014 10 12 -0700
Adding a 'T' would result in something like this:
"yyyy-MM-dd'T'HH:mm:ss.sssZ" - 2014-10-12T14:23:51.890+0530
In this HH represents hours in 24 hour format mm minutes ss seconds sss milliseconds.

Java Best way to parse quarterly dates

I have a date as APR-JUN10 or APR-JUN 2010 and i need output as 2010-06-30 I need the best way to parse above date in java and should be flexible in adding more such format of dates. note: APR-JUN10 will not parse by any java api, we have to break down APR & JUN 10 and get date as 2010-06-30.
You need to firm up your requirements.
Currently all you have told us is that APR-JUN 2010 should translate to the last day of June.
But what about FEB-JUN 2010? Should that also translate to the last day of June? Or should it throw a parse exception due to not being a full quarter? What about JUL-JUN 2010, where the second month is before the first? What about MAY-JUL 2010 -- three months but perhaps your definition of "quarter" requires starts of January, April, July, October.
Once you have your own requirements down, you can get to work on the conversion.
It's unlikely that an existing DateFormat implementation will do this exact task for you. You're likely to need to parse the string in your own code.
If the only legal options are JAN-MAR, APR-JUN, JUL-SEP, OCT-DEC, then you just have a five-way switch statement to set the month and day on a Calendar object (the fifth way being a default: case that throws an exception.
If your requirement is more complex, then your code will need to be more complex. Breaking the string into parts using a regex would be a good first step.
Pattern patt = Pattern.compile("(.{3})-(.{3}) (\d+)");
Matcher matcher = patt.matcher(qaurterString);
if(! matcher.find() || m.groupCount() != 3) {
throw new ParseException(...)
}
String fromMonth = matcher.group(1);
String toMonth = matcher.group(2);
int year = Integer.parseInt(matcher.group(3));
I think you'll have to write parsing code from scratch, whatever you do. The neatest end result would for you to create a class that implements DateFormat.
String s = "APR-JUN10";
// validation of quarter part
String quarter = s.substring(0, 7);
if (
!quarter.equals("JAN-MAR") && !quarter.equals("APR-JUN")
&& !quarter.equals("JUL-SEP") && !quarter.equals("OCT-DEC")
) {
throw new IllegalArgumentException("Input is not a quarter date: " + s);
}
// text processing with preprocessing hack (substring(4))
SimpleDateFormat inputFormat = new SimpleDateFormat("MMMyy", Locale.ENGLISH);
inputFormat.setLenient(false);
Date date = inputFormat.parse(s.substring(4));
System.out.println(date);
// Output: Tue Jun 01 00:00:00 CEST 2010 [format chooses 1 as default day-of-month]
// Go to end of month/quarter
GregorianCalendar gcal = new GregorianCalendar();
gcal.clear();
gcal.setTime(date);
gcal.set(Calendar.DAY_OF_MONTH, gcal.getActualMaximum(Calendar.DAY_OF_MONTH));
// format as ISO-date
SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM-dd");
String output = outputFormat.format(gcal.getTime());
System.out.println(output); // 2010-06-30
For the input "APR-JUN 2010" you need the input format pattern "MMM yyyy", else the solution is the same. Of course, the proposed solution assumes that every input starts with JAN-MAR, APR-JUN, JUL-SEP or OCT-DEC (you wrote about quarters). If you want you can validate it before processing phase by mean of s.substring(0, 7) etc.
UPDATE: I have now added the validation feature, see code.

Categories

Resources