DateTimeFormatter single digit month - java

I'm trying to parse a date where the month goes from 1 to 12 (And not from 01 to 12).
I'm trying this:
System.out.println(DateTimeFormatter.ofPattern("[[MM][M]yyyy").parse("112019"));
System.out.println(DateTimeFormatter.ofPattern("[[MM][M]yyyy").parse("82019"));
The first line works, the second fails.
Even Myyyy fails to parse the second line. I have not been able to find any pattern able to parse 82019 :(
[M][MM] as well as [MM][M] fail. Reading the documentation it says:
M/L month-of-year number/text 7; 07; Jul; July; J
So M is supposed to parse both 8 and 11.
Anyone has been able to get something working?
This works:
System.out.println(DateTimeFormatter.ofPattern("M-yyyy").parse("8-2019"));
But the data I got don't have any separation between the month and the year.

Perhaps DateTimeFormatterBuilder is what you are looking for:
String s = "112019";
System.out.println(new DateTimeFormatterBuilder()
.appendPattern("M")
.appendValue(ChronoField.YEAR, 4)
.toFormatter()
.parse(s)
);

DateTimeFormatter yearMonthFormatter = new DateTimeFormatterBuilder()
.appendValue(ChronoField.MONTH_OF_YEAR, 1, 2, SignStyle.NEVER)
.appendValue(ChronoField.YEAR, 4)
.toFormatter();
System.out.println(YearMonth.parse("112019", yearMonthFormatter));
System.out.println(YearMonth.parse("82019", yearMonthFormatter));
Output from this snippet is:
2019-11
2019-08
Unintuitively (but very practical in many other situations) a single pattern letter for a numerical field does not mean 1 digit, but as many digits as it takes. So M is not what we need here. What we do need instead is a DateTimeFormatterBuilder and its appendValue method, which comes in a number of overloaded versions. What we use for parsing fields that haven’t got any delimiter between them is known as adjacent value parsing. java.time can do this when all fields except the first have fixed widths, which you fulfil nicely.
I prefer to parse into a YearMonth, it’s a perfect match for the information in your strings, but that’s not the point here.
Link: Documentation of DateTimeFormatterBuilder.appendValue(TemporalField, int) explaining adjacent value parsing.

If all of your dates are given in the format you provided, consider isolating the values like so:
String s = "112019";
int yearIndex = s.length() - 4;
String pretty = s.substring(0, yearIndex) + " " + s.substring(yearIndex);
System.out.println(DateTimeFormatter.ofPattern("[M] yyyy").parse(pretty));

Related

JSR310 How to internationalize a pattern for a month-day?

Im trying to format a date without a year (just day and month, e.g 12.10)
DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT) still yield year for me (12.10.20).
so I tried DateTimeFormatter.ofPattern("dd. MM") but that obviously hardcodes order and dot, which wont make american users happy. (who expect slashes and month first)
How can I internationalize a pattern? Is there some abstract syntax for separators etc?
Well, as Ole pointed out there is no 100% satisfying solution using java.time only. But my library Time4J has found a solution based on the data of the CLDR repository (ICU4J also gives support) using the type AnnualDate (as replacement for MonthDay):
LocalDate yourLocalDate = ...;
MonthDay md = MonthDay.from(yourLocalDate);
AnnualDate ad = AnnualDate.from(md);
ChronoFormatter<AnnualDate> usStyle =
ChronoFormatter.ofStyle(DisplayMode.SHORT, Locale.US, AnnualDate.chronology());
ChronoFormatter<AnnualDate> germanStyle =
ChronoFormatter.ofStyle(DisplayMode.SHORT, Locale.GERMANY, AnnualDate.chronology());
System.out.println("US-format: " + usStyle.format(ad)); // US-format: 12/31
System.out.println("German: " + germanStyle.format(ad)); // German: 31.12.
I don’t think that a solution can be made that gives 100 % satisfactory results for all locales. Let’s give it a shot anyway.
Locale formattingLocale = Locale.getDefault(Locale.Category.FORMAT);
String formatPattern = DateTimeFormatterBuilder.getLocalizedDateTimePattern(
FormatStyle.SHORT, null, IsoChronology.INSTANCE, formattingLocale);
// If year comes first, remove it and all punctuation and space before and after it
formatPattern = formatPattern.replaceFirst("^\\W*[yu]+\\W*", "")
// If year comes last and is preceded by a space somewhere, break at the space
// (preserve any punctuation before the space)
.replaceFirst("\\s\\W*[yu]+\\W*$", "")
// Otherwise if year comes last, remove it and all punctuation and space before and after it
.replaceFirst("\\W*[yu]+\\W*$", "");
DateTimeFormatter monthDayFormatter
= DateTimeFormatter.ofPattern(formatPattern, formattingLocale);
For comparison I am printing a date both using the normal formatter with year from your question and using my prepared formatter.
LocalDate exampleDate = LocalDate.of(2020, Month.DECEMBER, 31);
System.out.format(formattingLocale, "%-11s %s%n",
exampleDate.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)),
exampleDate.format(monthDayFormatter));
Output in French locale (Locale.FRENCH):
31/12/2020 31/12
In Locale.GERMAN:
31.12.20 31.12
Edit: My German girl friend informs me that this is wrong. We should always write a dot after each of the two numbers because both are ordinal numbers. Meno Hochschild, the German author of the other answer, also produces 31.12. with two dots for German.
In Locale.US:
12/31/20 12/31
It might make American users happy. In Swedish (Locale.forLanguageTag("sv")):
2020-12-31 12-31
In a comment I mentioned Bulgarian (bg):
31.12.20 г. 31.12
As far as I have understood, “г.” (Cyrillic g and a dot) is an abbreviation of a word that means year, so when leaving out the year, we should probably leave this abbreviation out too. I’m in doubt whether we ought to include the dot after 12.
Finally Hungarian (hr):
31. 12. 2020. 31. 12.
How the code works: We are first inquiring DateTimeFormatterBuilder about the short date format pattern for the locale. I assume that this is the pattern that your formatter from the question is also using behind the scenes (haven’t checked). I then use different regular expressions to remove the year from different variants, see the comments in the code. Year may be represented by y or u, so I take both into account (in practice y is used). Now it’s trivial to build a new formatter from the modified pattern. For the Bulgarian: from my point of view there is an error in Java regular expressions, they don’t recognize Cyrillic letters as word characters, which is why г was removed too (the error is in documentation too, it claims that a word character is [a-zA-Z_0-9]).
We were lucky, though, in our case it produces the result that I wanted.
If you’re happy with a 90 % solution, this would be my suggestion, and I hope you can modify it to any needs your users in some locale may have.
Link: Documentation of Java regular expressions (regex)

Unable to parse optional microseconds in localTime

I am receiving timestamp in format : HHmmss followed by milleseconds and microseconds.Microseconds after the '.' are optional
For example: "timestamp ":"152656375.489991" is 15:26:56:375.489991.
Below code is throwing exceptions:
final DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
.appendPattern("HHmmssSSS")
.appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true)
.toFormatter();
LocalTime.parse(dateTime,FORMATTER);
Can someone please help me with DateTimeformatter to get LocalTime in java.
Here is the stacktrace from the exception from the code above:
java.time.format.DateTimeParseException: Text '152656375.489991' could not be parsed: Conflict found: NanoOfSecond 375000000 differs from NanoOfSecond 489991000 while resolving MicroOfSecond
at java.base/java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1959)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1894)
at java.base/java.time.LocalTime.parse(LocalTime.java:463)
at com.ajax.so.Test.main(Test.java:31)
Caused by: java.time.DateTimeException: Conflict found: NanoOfSecond 375000000 differs from NanoOfSecond 489991000 while resolving MicroOfSecond
at java.base/java.time.format.Parsed.updateCheckConflict(Parsed.java:329)
at java.base/java.time.format.Parsed.resolveTimeFields(Parsed.java:462)
at java.base/java.time.format.Parsed.resolveFields(Parsed.java:267)
at java.base/java.time.format.Parsed.resolve(Parsed.java:253)
at java.base/java.time.format.DateTimeParseContext.toResolved(DateTimeParseContext.java:331)
at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1994)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1890)
... 3 more
There are many options, depending on the possible variations in the strings you need to parse.
1. Modify the string so you need no formatter
String timestampString = "152656375.489991";
timestampString = timestampString.replaceFirst(
"^(\\d{2})(\\d{2})(\\d{2})(\\d{3})(?:\\.(\\d*))?$", "$1:$2:$3.$4$5");
System.out.println(timestampString);
LocalTime time = LocalTime.parse(timestampString);
System.out.println(time);
The output from this snippet is:
15:26:56.375489991
The replaceFirst() call modifies your string into 15:26:56.375489991, the default format for LocalTime (ISO 8601) so it can be parsed without any explicit formatter. For this I am using a regular expression that may not be too readable. (…) enclose groups that I use as $1, $2, etc., in the replacement string. (?:…) denotes a non-capturing group, that is, cannot be used in the replacement string. I put a ? after it to specify that this group is optional in the original string.
This solution accepts from 1 through 6 decimals after the point and also no fractional part at all.
2. Use a simpler string modification and a formatter
I want to modify the string so I can use this formatter:
private static DateTimeFormatter fullParser
= DateTimeFormatter.ofPattern("HHmmss.[SSSSSSSSS][SSS]");
This requires the point to be after the seconds rather than after the milliseoncds. So move it three places to the left:
timestampString = timestampString.replaceFirst("(\\d{3})(?:\\.|$)", ".$1");
LocalTime time = LocalTime.parse(timestampString, fullParser);
15:26:56.375489991
Again I am using a non-capturing group, this time to say that after the (captured) group of three digits must come either a dot or the end of the string.
3. The same with a more flexible parser
The formatter above specifies that there must be either 9 or 3 digits after the decimal point, which may be too rigid. If you want to accept something in between too, a builder can build a more flexible formatter:
private static DateTimeFormatter fullParser = new DateTimeFormatterBuilder()
.appendPattern("HHmmss")
.appendFraction(ChronoField.NANO_OF_SECOND, 3, 9, true)
.toFormatter();
I think that this would be my favourite approach, again depending on the exact requirements.
4. Parse only a part of the string
There is no problem so big and awful that it cannot simply be run away
from (Linus in Peanuts, from memory)
If you can live without the microseconds, ignore them:
private static DateTimeFormatter partialParser
= DateTimeFormatter.ofPattern("HHmmssSSS");
To parse only a the part of the string up to the point using this formatter:
TemporalAccessor parsed
= partialParser.parse(timestampString, new ParsePosition(0));
LocalTime time = LocalTime.from(parsed);
15:26:56.375
As you can see it has ignored the part from the decimal point, which I wouldn’t find too satisfactory.
What went wrong in your code?
Your 6 digits after the decimal point denote nanoseconds. Microseconds would have been only 3 decimals after the milliseconds. To use appendFraction() to parse these you would have needed a TemporalUnit of nano of millisecond. The ChronoUnit enum offers nano of day and nano of second, but not nano of milli. TemporalUnit is an interface, so in theory we could develop our own nano of milli class for the purpose. I tried to develop a class implementing TemporalUnit once, but gave up, I couldn’t get it to work.
Links
Wikipedia article: ISO 8601
Regular expressions in Java - Tutorial

Unable to convert string to LocalDateTime object

I am trying to convert a string to LocaleDateTime object in Java8 as below :
DateTimeFormatter globalFormat = DateTimeFormatter.ofPattern("yyyyMMddhhmmssSS");
String input = "2019082905020425";
LocalDateTime currentDateTime = LocalDateTime.parse(input, globalFormat);
But I am getting below exception, if someone can help me with a solution on the same :
Exception in thread "main" java.time.format.DateTimeParseException:
Text '2019082905020425' could not be parsed at index 0 at
java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1947)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1849)
at java.time.LocalDateTime.parse(LocalDateTime.java:492)at test.main(Test.java:20)
It’s a bug in Java 8.
Workaround for Java 8
DateTimeFormatter globalFormat = new DateTimeFormatterBuilder()
.appendPattern("yyyyMMddHHmmss")
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.toFormatter();
String input = "2019082905020425";
String adaptedInput = input + "0";
LocalDateTime currentDateTime = LocalDateTime.parse(adaptedInput, globalFormat);
System.out.println("Parsed date and time: " + currentDateTime);
Output from this snippet is (tested on jdk-1.8.0_121):
Parsed date and time: 2019-08-29T05:02:04.250
Java 8 cannot separate an integer field like ss and a fractional fields like SS without any separator between them. The workaround is to parse the fraction as an integer too. Your string includes 100ths of seconds, and no integer field for those is built in. So I append an extra 0 (zero) so that we’ve got milliseconds, and then use ChronoField.MILLI_OF_SECOND for parsing.
Whether it was really a bug can maybe be debated. There never was any strict promise in the docs that it should work, but it seemed to be the expectation of many, and in any case they fixed it in Java 9.
I have made one more correction, and you will want to check whether this is the correction you want: Lowercase hh is for hour within AM or PM from 01 through 12. If you intended this, you need to specify whether you want AM or PM. Instead I assumed that 05 was an hour of day from 00 through 23. Use uppercase HH for parsing this.
Edit: use a regular expression? #josejuan advocates a regular expression over the above. It’s an option, and can save us of the explicit formatter completely:
String input = "2019082905020425";
String adaptedInput = input.replaceFirst(
"^(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})$",
"$1-$2-$3T$4:$5:$6.$7");
LocalDateTime currentDateTime = LocalDateTime.parse(adaptedInput);
The result is the same as before. For my part I find the latter code quite a lot harder to read and maintain. Also once you migrate to Java 9 or higher, I think that the first snippet above lends itself more directly to going back to the code from which you started, which is what you want in the end. Pick the solution that you prefer.
The code is working on Java 9 and later
On Java 9 and later the change from hh to HH is all we need for the code in the question to work fine.
Links
Java bug DateTimeFormatter won't parse dates with custom format "yyyyMMddHHmmssSSS" in the bug database
Question Is java.time failing to parse fraction-of-second? about the bug
Question Comparing two times in android about hh in a format pattern string

How can I make a leading zero in DateTime-Pattern optional

I have a user input field and would like to parse his date, whatever he puts in.
The user might provide his date with a leading zero or without one, so I wanna be able to parse an input like this
02.05.2019
and also this
2.5.2019
But as far as I can tell there is no way to make the leading zero optional, either always have 2 digits like 01, 03, 12 and so on, or only have the necessary digits like 1, 3, 12.
So apparently I have to decide whether to allow leading zeros or not, but is there seriously no way to make the leading zero optional ?
Well, I tested a pattern that included a leading zero dd.MM.uuuu and I tested a pattern that did not include a leading zero d.M.uuuu and when I parsed the wrong input with the wrong pattern exceptions were thrown.
Therefore my question is if there is a way to make the leading zero optional.
This is trivial when you know it. One pattern letter, for example d or M, will accept either one or two digits (or for year up to 9 digits).
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("d.M.u");
System.out.println(LocalDate.parse("02.05.2019", dateFormatter));
System.out.println(LocalDate.parse("3.5.2019", dateFormatter));
System.out.println(LocalDate.parse("4.05.2019", dateFormatter));
System.out.println(LocalDate.parse("06.5.2019", dateFormatter));
System.out.println(LocalDate.parse("15.12.2019", dateFormatter));
Output:
2019-05-02
2019-05-03
2019-05-04
2019-05-06
2019-12-15
I searched for this information in the documentation and didn’t find it readily. I don’t think it is well documented.
You can create a DateTimeFormatter with a custom format like this
DateTimeFormatter.ofPattern("d.M.yyyy")
Then you can parse dates if they provide 1 or 2 digits for the day and month.
String input = "02.5.2019";
LocalDate date = LocalDate.parse(input, DateTimeFormatter.ofPattern("d.M.yyyy"));
I've used LocalDate here from the new java.time package so I'm assuming that your java version is recent.
Your suggested date format should work - just as this test:
#Test
public void test() throws ParseException {
SimpleDateFormat f = new SimpleDateFormat("d.M.yyyy");
f.parse("7.8.2019");
f.parse("07.08.2019");
f.parse("007.008.002019");
}
The DateTimeFormatter will not accept leading zeros for year in comparison, but leading zeros for day and month are not an issue:
#Test
public void test2() throws ParseException {
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
DateTimeFormatter f = builder.appendPattern("d.M.yyyy").toFormatter();
f.parse("7.8.2019");
f.parse("07.08.2019");
f.parse("007.008.2019");
}

Format a date using the %f %s (I think Java 7 formatter) type notation

I am using Spring Batch to output my domain object to a CSV file. To do this I am making use of a FormatterLineAggregator. It uses the %s %f type formats.
See below.
FormatterLineAggregator<MasterList> lineAggregator = new FormatterLineAggregator<>();
lineAggregator.setFormat("%s,%.2f,%.2f,%s,%s,%s,%s,%s,%s,%s,%s");
This code is formatting my object into this line in my CSV.
A,100.00,100.00,Country Road Shirt,Promotion Component Name A,wasnow,On Sale,100,100 / 1000,2016-07-24,2016-07-24
I am really unfamiliar with the %s %f notation.
I want my dates on the end of that line to look like dd/mm/yyyy hh24:mi:ss instead they are yyyy-mm-dd.
How can I do that using this notation?
Also does anyone know where I can find a reference explaining the syntax more?
Formatter class exist since Java 1.5.0.
You can use the below format to format your date.
%te/%<tm/%<tY %<tT
'e' Day of month, formatted as two digits, i.e. 1 - 31.
'm' Month, formatted as two digits with leading zeros as necessary, i.e. 01 - 13
'Y' Year, formatted as at least four digits with leading zeros as necessary, e.g. 0092 equals 92 CE for the Gregorian calendar.
'T' Time formatted for the 24-hour clock as "%tH:%tM:%tS".
Here we reference arguments by position is to use the '<' ('\u003c') flag, which causes the argument for the previous format specifier to be re-used.
Example:
StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb, Locale.US);
formatter.format("%s,%.2f,%.2f,%s,%s,%s,%s,%d,%s,%te/%<tm/%<tY %<tT,%te/%<tm/%<tY %<tT","A",11.2,12.3,"Country Road Shirt","Promotion Component Name A","wasnow","On Sale",100,"100 / 1000",new Date(),new Date());
System.out.println(sb);
Output:
A,11.20,12.30,Country Road Shirt,Promotion Component Name A,wasnow,On Sale, 100,100 / 1000,25/07/2016 11:10:47,25/07/2016 11:10:47
Here is the code in ideone
For more information you can refer Java Doc Formatter

Categories