Freemarker Error Expected hash? - java

I want to transform a Instant time to Date but I'm getting this error:
freemarker.template.TemplateException: Expected hash. newDate evaluated instead to freemarker.template.SimpleDate
I'm doing this on Java:
Date newDate = new Date();
Instant instant = Instant.now();
webContext.put("newDate",new Date());
webContext.put("instant",instant);
And I'm doing this on Freemarker:
[#assign dateFormated = newDate.getAsDate().from(instant.ofEpochSecond(data.time.seconds))/]
Thank you

FreeMarker templates don't in general expose Java API-s, or allow you to access Java classes by name. I mean, in some cases it does, but not in general like newDate has no subvariables (like getAsDate) in FreeMarker. There are utilities with which you can expose the static methods of classes, like:
TemplateHashModel staticModels
= ((BeansWrapper) configuration.getObjectWrapper())
.getStaticModels();
webContext.put("Date", staticModels.get("java.util.Date"));
webContext.put("Instant", staticModels.get("java.time.Instant"));
where configuration is your freemarker.template.Configuration singleton. Actually, you can add Date and Instant to that singleton with Configuration.setSharedVariable, once where you configure FreeMarker.
And then, you can write Date.from(Instant.now()) into a template, because now there's a Date and and Instant variable, and you have specifically told FreeMarker to expose thier static methods.

Related

Date.plus not working in 2.5.4 Groovy Runtime, what is the alternative?

We want to add days to the current date and format it in a specific way. This was solved in Groovy 2.4.13 and the following date manipulation works fine:
​today = new Date()+90;today.format('yyyy-MM-dd HH:mm:ss.S');
Result: 2019-12-02 08:07:15.294
In Groovy 2.5.4 the same expression throws this exception:
groovy.lang.MissingMethodException: No signature of method:
java.util.Date.plus() is applicable for argument types: (Integer)
values: [90] Possible solutions: parse(java.lang.String),
split(groovy.lang.Closure), use([Ljava.lang.Object;),
is(java.lang.Object), wait(), clone() at
Script1.run(Script1.groovy:3)
I was able to reproduce this behaviour in "Groovy sandboxes" online:
Working fine here: groovy-playground (Version 2.4.1.5)
Failing here: groovyconsole (Version 2.5.7)
What is the working alternative in this case? I have read about a new Date API, but couldn't find the details about how to use it, with date manipulation (+ 90 days for example).
Take a look at TimeCategory
import groovy.time.TimeCategory
def theDate = use(TimeCategory){new Date() + 90.days}.format('yyyy-MM-dd HH:mm:ss.S')
I agree with Ole V.V.'s recommendations to use the new Date/Time API. Here is how you would write his Java sample in a more Groovy style.
// you can assemble aggregate types by left shifting the aggregates
// I'm not endorsing this approach, necessarily, just pointing it out as an alternative
ZonedDateTime now = LocalDate.now() << LocalTime.now() << ZoneId.of('Africa/Bamako')
// the plus operator is overloaded
ZonedDateTime in90Days = now + 90
// you can pass a String to format without needed a full DateTimeFormatter instance
println in90Days.format('uuuu-MM-dd HH:mm:ss.S')
While Groovy adds some further support for the old Java Date class, I still believe that you should not use it. It was always poorly designed and is now long outdated. Instead use java.time, the modern Java date and time API. I am sorry that I will have to trust you to translate from Java code.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.S");
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Africa/Bamako"));
ZonedDateTime in90Days = now.plusDays(90);
System.out.println(in90Days.format(formatter));
Output when running just now was:
2020-01-01 08:37:13.3
Please substitute your desired time zone if it didn’t happen to be Africa/Bamako.
Link: Oracle tutorial: Date Time explaining how to use java.time.
You can use Calendar to achieve that
Calendar cal = new GregorianCalendar();
cal.add(Calendar.DATE, 90);
Date date = cal.getTime();
All steps must be separate and not in a single line.

How to get the day (ex: Monday) from a time Instant in FreeMarker

So I got this Instant date in Java: "2018-05-19T22:00:00Z".
How can I get the day of the week? like monday..
I was trying this but it doesn't work:
${date?string["EEEE"]}
Thanks
Freemarker does not work with Instant, so you would need to convert that to string in Java (something like "2018-05-19T22:00:00Z") and then convert the string doing the following:
<#setting locale="en_US">
${"2018-05-19T22:00:00Z"?datetime.iso?string["EEEE"]}
First convert the string to datetime in iso format, and then back again to string with the pattern of your choosing.
By changing the locale setting, you may get the day of the week in different languages.
I think that the best way to work in Freemarker is to always have strings or integers variables.
At this point FreeMarker does not support Java 8 time, see the contribute page:
What should I contribute?
Usually, contributors come because they want to fix/improve a certain thing. But if you just want to help in general, here are some topics ...
Support for Java 8 date/time API-s (this is actually certainly a difficult one). Note that there's a 3rd party solution for this, https://github.com/amedia/freemarker-java-8, but it's not as seamless as a native solution could be.
...
So you could check out the third party solution.
See also:
Java.time (Java 8) support in Freemarker
${date?string["EEEE"]} works fine as long as date is a java.util.Date object.
You can test it in this way:
<#assign date = .now>
${date?string["EEEE"]}
I guess that it doesn't work because your date is a String, in this case you should parse it in Java (server-side) and expose a java.util.Date variable to the template.
I would advise you to use new Date Time API defined in Java 8.Using Date Time API you can fetch all data related to Date and Time easily.
Java 8 has defined a separate Enum for handling days of the week named – DayOfWeek (java.time.DayOfWeek)
java.time.DayOfWeek is an Enum which defines 7 constants representing the seven days of the week – MONDAY(int value=1), TUESDAY(2), WEDNESDAY(3)… till SUNDAY(7).
import java.time.*;
public class Java8Tester {
public static void main(String args[]) {
Java8Tester java8tester = new Java8Tester();
java8tester.testLocalDateTime();
}
public void testLocalDateTime() {
// Get the current date and time
LocalDateTime currentTime = LocalDateTime.parse("2018-05-19T22:00:00");
System.out.println("Current DateTime: " + currentTime);
DayOfWeek dayOfWeek = currentTime.getDayOfWeek();
System.out.println(currentTime + " was a " + dayOfWeek);
}
}

Get current date in Thymeleaf

How can I print current date (and time, eventually) from Thymeleaf?
I'm trying these functions:
http://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html#dates
but I can't get them work.
Try with this:
${#dates.format(#dates.createNow(), 'dd MMM yyyy HH:mm')}
will be created a java.util.Date() object then formatted as you prefer.
Using the #calendars utility object
This is an alternative method:
${#calendars.format(#calendars.createNow(), 'dd MMM yyyy HH:mm')}
the result will be the same.
This one works fine for me:
${#dates.format(#dates.createNow(),'YYYY/MM/dd HH:mm')}
The Date is going to be processed by the #dates utility.
The new LocalDateTime, LocalDate classes are going to be processed by the #temporals utility.
Set the format:
<p th:text="${#dates.format(standardDate, 'dd-MM-yyyy HH:mm')}"></p>
<p th:text="${#temporals.format(localDateTime, 'dd-MM-yyyy HH:mm')}"></p>
<p th:text="${#temporals.format(localDate, 'MM-yyyy')}"></p>
Where you have to sent it to a view as following:
model.addAttribute("standardDate", new Date());
model.addAttribute("localDateTime", LocalDateTime.now());
model.addAttribute("localDate", LocalDate.now());
Formatting the LocalDate is only possible if we will specify only the particular date fields, skipping the time fields.
Output result:
24-05-2019 21:57
24-05-2019 21:57
24-05-2019
Just another way to get the current date and time in thymeleaf is using,
${execInfo.now}
The current date and time (${execInfo.now}), a Calendar object
corresponding to the moment the template engine started its execution
for this template.
You can create an WebContext to modify the context variables,
WebContext ctx = new WebContext(request, servletContext, request.getLocale());
When the context is created, it creates a object which holds the two values for the template engine. The object name is execInfo. The two variables are templateName and now. These variables can be accessed across anywhere in the templates.
If you need to format the date format you can do like this,
WebContext ctx = new WebContext(request, servletContext, request.getLocale());
ctx.setVariable("today", dateFormat.format(cal.getTime()));
Example:
Current time : <div th:text="${execInfo.now.time}">Wed Feb 10 13:55:58 IST 2016</div>
I hope this works :
<b th:text="${#execInfo.now.time}"></b>

Spring + Thymeleaf: time in user's timezone

How can I print date and time is specified timezone with Thymeleaf? Something like:
<span th:text="${#dates.format(myDate, 'yyyy-MM-dd HH:mm', 'PST')}">2010-01-01 16:30</span>
Another approach to the same problem may be to use your own static methods:
${T(xx.xxx.utils.DateUtils).format(myDate, 'yyyy-MM-dd HH:mm', 'CET')}
public static String format(Date date, String pattern, String timeZone) {
TimeZone tz = TimeZone.getTimeZone(timeZone);
return DateFormatUtils.format(date, pattern, tz);
}
or even directly from lang3 (does not work on GAE because of some class access restrictions in sun.util.calendar package):
<div th:with="timeZone=${T(java.util.TimeZone).getTimeZone('CET')}">
<span th:text="${T(org.apache.commons.lang3.time.DateFormatUtils).format(myDate, 'yyyy-MM-dd HH:mm', timeZone)}"></span>
</div>
As I was puzzled by this question, I searched extensively for possible solutions.
These are my findings:
I did not found any clean function for changing timezone and displaying it like it is in jsp:
<fmt:timeZone value="US">
<fmt:formatDate value="${today}" type="both" />
</fmt:timeZone>
Possible solution, that works would be to create calendar instance using createForTimeZone and format it, since it returns a raw calendar value, so from this:
#calendars.createForTimeZone(year, month, day, hour, minute, second, milisecond, Object timezone)
you would get something like this:
java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="PST",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=PST,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2014,MONTH=1,WEEK_OF_YEAR=14,WEEK_OF_MONTH=1,DAY_OF_MONTH=24,DAY_OF_YEAR=91,DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=6,HOUR_OF_DAY=7,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]
As you can see (you have to look carefully) it did converted time to the timezone provided.
Now, I still haven't gotten to the point where I can get it all to work fine, but if you add calendars.format in front of this, you would get it to properly show time in the given timezone. ${#calendars.format(#calendars.createForTimeZone(year, month, day, hour, minute, second, milisecond, Object timezone), 'dd-MMM-yyyy HH:mm')}
Adding "zzz" to the end of the string, always return my locale timezone. I guess there are way to work this out so it looks better, but main point for me was to find out if it was possible at all.
Examples that work:
${#dates.format(#calendars.createForTimeZone(#calendars.year(ticket.ticketDate), #calendars.month(ticket.ticketDate), #calendars.day(ticket.ticketDate), #calendars.hour(ticket.ticketDate), #calendars.minute(ticket.ticketDate),'PST'), 'yyyy-MMM-dd HH:mm')}
${#calendars.format(#calendars.createForTimeZone(#calendars.year(ticket.ticketDate), #calendars.month(ticket.ticketDate), #calendars.day(ticket.ticketDate), #calendars.hour(ticket.ticketDate), #calendars.minute(ticket.ticketDate),'CET'), 'yyyy-MMM-dd HH:mm')}
and either one would return identical results.
Here are the results when comparing same format, using PST and CET:
2014-Feb-24 16:00
2014-Feb-24 07:00
or:
2014-Mar-01 03:00
2014-Feb-28 18:00
Regards,
I found this answer when I wanted to format LocalDateTime to some time zone in the templates. It turned out that the purpose of LocalDateTime is to do not work with time zones at all.
However, there is also a class called ZonedDateTime which purpose is obvious. You can also use LocalDateTime#atZone which creates a new instance of local converted to the new zone.
Note that usual DateTimeFormatter ignores any time zone settings in the case of local date time but not in the case of zoned date time. So you can use usual formatters as well in the templates.
The server renders the page based on the server time, in order to get the timezone of the user from the request, the user when submitting the request should attach the timezone information in the request headers or parameters, so that the server knows the appropriate time zone to render. To do that, use javascript to get the browser's time zone.
Using Thymeleaf's #temporals doesn't provide the ability to use the constructor new Temporals(locale, zoneId).
Create your own Temporals temporals = new Temporals(LocaleContextHolder.getLocale(), zone) (somewhere, session bean, or so - zone should probably come from your user's preferences), and put it available in the controller (eg. as "temporalsZone").
Then use it in the UI: th:text="${temporalsZone.format(value, pattern, #locale)}"> and enjoy the full support of #temporals.
If you add org.thymeleaf.extras:thymeleaf-extras-java8time as a dependency, you get the #temporals object to help format types like:
Instant
LocalDateTime
ZonedDateTime
etc
If you want to format java.util.Date you can use #dates instead.
The methods you're looking for are #dates.format or #temporals.format. You can specify a Locale as the third argument. The general syntax is #temporals.format(<temporal object>, <pattern>, <optional locale>)
Examples:
th:text="${#temporals.format(myDate, 'dd-MM-yyyy', new java.util.Locale('en'))}"
th:text="${#temporals.format(myDate, 'dd-MM-yyyy', #java.util.Locale#ENGLISH)}"
Note that this is true even if you're working with Kotlin Spring Boot. The syntax in the Thymeleaf template isn't Java, it's an OGNL Expression.
https://commons.apache.org/proper/commons-ognl/language-guide.html
I'll quote the useful syntax used here:
#variable
Context variable reference
#class#method(args)
Static method reference
#class#field
Static field reference
new class(args)
Constructor call
One other option is to specify the Locale in the Thymeleaf context, if you just want to override the default system Locale. I've included a Kotlin snippet of how that might work:
val context = Context() // org.thymeleaf.Context
context.locale = Locale.ENGLISH
context.setVariable("x", 0)
templateEngine.process("classpath:template.html", context)
Then you can simply use th:text="${#temporals.format(datum.timestamp, 'E MMM dd yyyy')} without the explicit Locale argument.

Date Formatting Issue

The following Java test passes on our US hosted build server. It also passes on non-US servers, e.g. in Germany. It fails on my local server, which is running in Ireland. The following code illustrates a failing test.
org.junit.ComparisonFailure: expected:<[4/6/09 11:30 AM]> but was:<[06/04/09 11:30]>
Is there a system setting I can provide to get these tests passing locally?
public void testFormattedDate() {
// Set the default time zone in case this unit test is executed in a different country
TimeZone.setDefault(TimeZone.getTimeZone(DateUtil.DEFAULT_TIMEZONE));
final Date utilDate = new Date();
utilDate.setDate(6);
utilDate.setHours(11);
utilDate.setMinutes(30);
utilDate.setMonth(3);
utilDate.setSeconds(45);
utilDate.setYear(109);
SimpleDateFormat dateFormatter = new SimpleDateFormat();
final String formattedOutput = dateFormatter.format(utilDate);
Assert.assertEquals("4/6/09 11:30 AM", formattedOutput);
}
Have to tried to provide a pattern to the SimpleDateFormat ?
SimpleDateFormat dateFormatter = new SimpleDateFormat("d/M/yy HH:mm a");
The time is correct but the SimpleDateFormat() constructor internally calls a package private construtor using Locale.getDefault(). Thus you either can provide a format of your own or provide another locale, which seems to only be possible with a custom format, i.e. using SimpleDateFormat(String pattern, Locale locale).
The problem is that SimpleDateFormat() uses a locale dependent pattern, thus the system's default locale might result in a different pattern than what you get in the USA (I assume the German server doesn't use the German locale as its default since then you should get a date like 06.04.09 11:30).

Categories