Issue with DateTimeParseException when using STRICT resolver style - java-8

I am trying to parse a date string using the following pattern: yyMMdd and the STRICT resolver as follows:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat).withResolverStyle(ResolverStyle.STRICT);
LocalDate.parse(expiryDate, formatter);
I get the following DateTimeParseException:
java.time.format.DateTimeParseException: Text '160501' could not be
parsed: Unable to obtain LocalDate from TemporalAccessor:
{YearOfEra=2016, MonthOfYear=5, DayOfMonth=1},ISO of type
java.time.format.Parsed
When I swith to the default resolve style, i.e. ResolverStyle.SMART it allows such dates as 30th of February.
Can someone please help?

The strict resolver requires an era to go with YearOfEra. Change your pattern to use "u" instead of "y" and it will work, ie. "uuMMdd".

While JodaStephen has nicely explained the reason for the exception and given one good solution (use uu rather than yy), I am offering a couple of other possible solutions:
The obvious one that you probably don’t want: leave the resolver style at SMART (the default). In other words either leave out .withResolverStyle(ResolverStyle.STRICT) completely or change it to .withResolverStyle(ResolverStyle.SMART).
Provide a default era.
For the second option here is a code example:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("yyMMdd")
.parseDefaulting(ChronoField.ERA, 1)
.toFormatter()
.withResolverStyle(ResolverStyle.STRICT);
String expiryDate = "160501";
LocalDate result = LocalDate.parse(expiryDate, formatter);
System.out.println(result);
Output is:
2016-05-01
Where the last solution may make a difference compared to using uu in the format pattern:
It allows us to use a format pattern that is given to us where we cannot control whether pattern letter u or y is used.
With pattern letter y it will fail with an exception if the string contains a negative year. Depending on your situation and requirements this may be desirable or unacceptable.
Edit: The second argument to parseDefaulting() may also be written IsoEra.CE.getValue() rather than just 1 to make it clear that we are specifying the current era (CE; also often called Anno Domini or AD).

Related

Freemarker: How to write a BigDecimal's value that can be used in a BigDecimal constructor

I would like to use freemarker to generate java code that instantiates a BigDecimal.
The value of the BigDecimal is present at generation time.
the BigDecimal API would work like this:
BigDecimal copy = new BigDecimal(original.toString());
Alas, the freemarker magic uses numeric conversion on my value of original, so this does not work (in a freemarkter template):
BigDecimal copy = new BigDecimal("${original?c}");
None of the numeric conversions (percent, number, computer, ...) works - c/computer most interestingly, because it outputs 0 if the value becomes too big.
With considerable pain, I might be able to wrap the BigDecimal in another Object that just gives me a toString and is not a number, so freemarker might leave its value untouched and my generated BigDecimal is correct, but that's only a last resort.
Maybe there is a way to invoke the toString() method and print the result into the target document?
ERRATUM:
because it outputs 0 if the value becomes too big should read because it outputs 0 if the value becomes too small (see #ddkany's answer)
Update: FreeMarker 2.3.32 now supports lossless formatting with ?c, which is not based on DecimalFormat, but mostly on toString. To use that, you have to set the c_format configuration setting to any other value than its backward compatible default, "legacy". Like setting it to "JavaScript or JSON" is fine for most projects (that's also the default if you just set the incompatible_improvements configuration setting to 2.3.32). See the fine details of how numers are formatted in the documentation of the c built-in: https://freemarker.apache.org/docs/ref_builtins_number.html#ref_builtin_c
Old answer, for 2.3.31 and before:
What FreeMarke does by default is whatever Java's DecimalFormat does (for the localized medium format by default, again defined by the Java platform).
?c uses new DecimalFormat("0.################") with fixed US locale (and some more symbol adjustments for INF and NaN). So I don't know how that gives 0 for a huge BigDecimal number. Are you sure about that? I guess it was actually a very small number, so it was rounded down. Well, switching to "scientific" format would make more sense then, though.
To have whatever formatting logic you need, you can register your own number formatter like, in this case, configuration.setCustomNumberFormats(Map.of("toString", new MyToStringTemplateNumberFormatFactory()), and then you can use ${original?string.#toString}. Or rather, you can set the number_format to #toString, and then just use ${original}.

ZEN : Allow multiple date formats in a dateText control and converting them to the YYYY-MM-DD

There is a finite list of date formats that users want to use to enter a date in a form. These formats include single digits for month and day and double digits for year. The field is represented by a dateText control.
How would one get to allow a dateText control to accept multiple date formats ? I see only 3 listed (https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GZCP_forms_dateText), do those include using single digits for month and day ?
I tried to set the value of format = "#(myPageProperty.myValue)# " but I got a compilation error in Studio so that went nowhere. Has anyone ever been able to set the format value depending on the user input value?
I am guessing that the control input value must be converted to the YYYY-MM-DD before validation. I am open to calling a javascript function to do that but where would be the best place to put it?
for details see Class %ZEN.Component.dateText
setting format:
Property format As %ZEN.Datatype.string(MAXLEN = 3, **VALUELIST = ",MDY,DMY,YMD",** ZENEXPRESSION = 1)
you have exactly 3 formats or ""
Your guess on values is correct and documented:
/// The value of this control is always in the canonical form: YYYY-MM-DD
As this is one of the oldest components of ZEN your only chance to achieve
your way of operation is to create your own version inheriting from
Class %ZEN.Component.dateText and overloading the parts you want to change

In Freemarker Change output date format irrespective of input format

${(.vars["OCRResponse"].Date)?datetime("ANY RANDOM FORMAT")?string("mm-dd-yy").
Can we use If Else within ?datetime, or can we resolve this by using switch case?
If that date format is quite "random", and you need to do this a lot, then you are probably better of writing a freemarker.core.TemplateDateFormat+TemplateDateFormatFactory implementation, do the complex date parsing logic in Java, then register the factory as a "custom date format" (that's a FreeMarker configuration setting), let's say with name "random". Then you can do ${OCRResponse.Date?date.#random?string('MM-dd-yy')}. If you set the date_format configuration setting to MM-dd-yy, then you can even just write ${OCRResponse.Date?date.#random}.
You can find concrete examples of defining a custom formats here: https://freemarker.apache.org/docs/pgui_config_custom_formats.html
Another possibility is to use #if/#elseif/#else of course. If you need to do that on multiple places, then put your parser logic into a #function, where you #return the parsed date. So where you insert a date you just have something like ${parseRandom(OCRResponse.Date)} (here I have assumed that date_format is MM-dd-yy, otherwise add ?string('MM-dd-yy')).

How to format CRM "Date Only" field to string with Freemarker?

Im trying to build a email template with Freemarker/Clickdimensions plugin in CRM 2013. I have a "Date only" field on an entity which for example contains the date 2017-04-17. I want this date to show as the following: Monday 17 april.
This is done with Freemarker and I have tried the following:
<#assign x = Recipient.field_booking.field_scheduleddate?time>
${x?string.full}
This doesnt seem to work. Im not getting any result at all, just an empty line.
Does anyone know what could be wrong?
I will assume that field_scheduleddate is a string (not a java.util.Date).
At ?time FreeMarker should throw and exception saying something like that the string doesn't follow the expected pattern. I suspect the framework you are using catches and suppresses that exception (which makes using FreeMarker much much harder). Check the logs, maybe it's there.
You want to deal with a date-only value there, hence you should use ?date, as ?time is for time-only values. Also, field_scheduleddate apparently uses ISO 8601 format, so unless the date_format configuration setting is set to ISO, you will have to use ?date.iso (supported since FreeMarker 2.3.21).
As of printing the date, ?string.full should work, but usually you should set date_format globally to the format you prefer, and then you can simply write ${x}.
(Also note that #assign is unnecessary above, as you can put arbitrarily complex expression inside ${}.)

How to change DateFormatSymbols in JDK8 to solve incompatibility with JDK7 (genitive month names)?

As Xaerxess found in this topic: Month name in genitive (Polish locale) with Joda-Time DateTimeFormatter
in JDK8 DateFormatSymbols.getInstance(new Locale("pl", "PL")).getMonths() returns month names in genitive by default. Previous Java version returns month names in nominative case.
With this, for example, SimpleDateFormat format with "dd-MMMM-yyyy" pattern gives different result in JDK8 than in JDK6 or 7.
It's a big change and some of my old application doesn't work properly with a new month names. I'm looking for a solution to change globally default month names for Locale PL.
I tried with DateFormatSymbols.getInstance().setMonths(new String[] {..}), but it doesn't work globally.
If I'll find a solution for changing default month names with Java code, I could add this code at application initialization, without correcting the whole app. In my case I'll just simply add an servlet to my web app with load-on-startup option.
Or maybe you have a different idea how to make Java 8 compatible in this case? Maybe there is parameter / option which I could pass to jvm on start?
I had the same issue. Of all the billion standards my client decided to use all capital letters for the month breaking the default parsers. So my date looked like:
02-DEC-15
Now Java time can Parse "Dec" but not "DEC". Annoying .. I searched for a while and found the java8 replacement for this problem.
This is how you can add custom anything to your formatter:
public static void main(String[] args) {
String test = "02-DEC-15";
Map<Long, String> lookup = new HashMap<>();
lookup.put(1L, "JAN");
lookup.put(2L, "FEB");
lookup.put(3L, "MAR");
lookup.put(4L, "APR");
lookup.put(5L, "MAY");
lookup.put(6L, "JUN");
lookup.put(7L, "JUL");
lookup.put(8L, "AUF");
lookup.put(9L, "SEP");
lookup.put(10L, "OCT");
lookup.put(11L, "NOV");
lookup.put(12L, "DEC");
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("dd-")
.appendText(ChronoField.MONTH_OF_YEAR, lookup)
.appendPattern("-yy")
.toFormatter();
LocalDate parse = LocalDate.parse(test,formatter);
System.out.println(parse);
}
This is a bit tricky because it did not behave the way I expected it. Here's a few points to consider:
Each append call will add a new parser instance that will be called IN ORDER strictly failing if it does not work. So for example, if you take the above example and thought you could just add months, saying:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("dd-MMM-yy")
.appendText(ChronoField.MONTH_OF_YEAR, lookup)
.toFormatter();
This will fail, because the pattern adds multiple parsers for your formatter:
dd
-
MMM
-
yy
Adding the lookup at the end won't work, because MMM will already fail your parsing context.
Each step can therefore be added separately. Look at:
MonthDay#PARSER
This is what finally helped me to find the correct solution. In any case, using the Builder you can construct whatever freaky non-standard parsers you want. Please however do remember to yell at everyone who thinks they can come up with yet another way of representing a date that needs to be unleashed into this world.
I hope this saves someone some heartache.
Artur
Edit: So once again I half ignored the question. I don't think there is a default way, unless you register a provider for yourself. However that seems a bit wrong to me. I think the way the parsing is meant to work is to have a static instance of your parser that you use. This is with regards to looking at how for example the default parsers in java time are implemented.
So just create your custom parser at startup and reference it throughout your application for parsing.

Resources