lazee / freemarker-java-8 Goto Github PK
View Code? Open in Web Editor NEWLibrary that adds java.time support to FreeMarker templates.
License: Apache License 2.0
Library that adds java.time support to FreeMarker templates.
License: Apache License 2.0
Hello, I created mcve example https://github.com/kortov/freemarker-java-8-mcve
You can run it as any SpringBoot app or with ./mvnw spring-boot:run then open localhost:8080 and it throws an exception
I'm just using an orinary LocalDateTime.now() and ${myDate.format()} in the template
My environment: Windows 7 SP 1, java 1.8.0_211, locale ru-RU
spring-boot 2.1.4.RELEASE
My date is printed in the logger as 2019-05-13T15:41:23.288
Maybe I'm using your library the wrong way, but readme says that I can use syntax ${mylocaldatetime.format()}
2019-05-13 15:48:07.713 ERROR 4436 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds] with root cause
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds
at java.time.LocalDate.get0(LocalDate.java:680) ~[na:1.8.0_181]
at java.time.LocalDate.getLong(LocalDate.java:659) ~[na:1.8.0_181]
at java.time.LocalDateTime.getLong(LocalDateTime.java:720) ~[na:1.8.0_181]
at java.time.format.DateTimePrintContext.getValue(DateTimePrintContext.java:298) ~[na:1.8.0_181]
at java.time.format.DateTimeFormatterBuilder$OffsetIdPrinterParser.format(DateTimeFormatterBuilder.java:3335) ~[na:1.8.0_181]
at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2179) ~[na:1.8.0_181]
at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2179) ~[na:1.8.0_181]
at java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1746) ~[na:1.8.0_181]
at java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1720) ~[na:1.8.0_181]
at java.time.LocalDateTime.format(LocalDateTime.java:1752) ~[na:1.8.0_181]
at no.api.freemarker.java8.time.LocalDateTimeAdapter$LocalDateTimeFormatter.exec(LocalDateTimeAdapter.java:61) ~[freemarker-java8-1.1.5.jar:na]
at freemarker.core.MethodCall._eval(MethodCall.java:65) ~[freemarker-2.3.28.jar:2.3.28]
at freemarker.core.Expression.eval(Expression.java:83) ~[freemarker-2.3.28.jar:2.3.28]
at freemarker.core.DollarVariable.calculateInterpolatedStringOrMarkup(DollarVariable.java:100) ~[freemarker-2.3.28.jar:2.3.28]
at freemarker.core.DollarVariable.accept(DollarVariable.java:63) ~[freemarker-2.3.28.jar:2.3.28]
at freemarker.core.Environment.visit(Environment.java:330) ~[freemarker-2.3.28.jar:2.3.28]
at freemarker.core.Environment.visit(Environment.java:336) ~[freemarker-2.3.28.jar:2.3.28]
at freemarker.core.Environment.process(Environment.java:309) ~[freemarker-2.3.28.jar:2.3.28]
at freemarker.template.Template.process(Template.java:384) ~[freemarker-2.3.28.jar:2.3.28]
at org.springframework.web.servlet.view.freemarker.FreeMarkerView.processTemplate(FreeMarkerView.java:389) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.servlet.view.freemarker.FreeMarkerView.doRender(FreeMarkerView.java:302) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.servlet.view.freemarker.FreeMarkerView.renderMergedTemplateModel(FreeMarkerView.java:253) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.servlet.view.AbstractTemplateView.renderMergedOutputModel(AbstractTemplateView.java:178) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:316) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1370) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1116) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1055) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.17.jar:9.0.17]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) ~[spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) ~[spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.6.RELEASE.jar:5.1.6.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200) ~[tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834) [tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415) [tomcat-embed-core-9.0.17.jar:9.0.17]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.17.jar:9.0.17]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_181]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_181]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.17.jar:9.0.17]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_181]
Hi, sorry to use this communication channel a bit inappropriately. But I'm not 100% sure you got my recent email. Maybe it ended up in the SPAM folder. (This has been happening to me a fair bit recently with gmail.)
I wrote you an email to bring your attention to my recent efforts on the FreeMarker codebase. (Look at https://github.com/freemarker/freemarker3/wiki). We had some email correspondence almost 4 years ago and I had stated my intention to work on this and you seemed interested in it. But 4 years is a lot of water under the bridge. So, if you're not really very interested in FreeMarker at this point, I won't be offended at all. It would be nice to have some clarity on this one way or the other, I guess.
I hope all is going well for you.
ZoneOffset is subclass of ZoneId so it is means that ZoneOffsetAdapter is never used (ZoneIdAdapter is always used for objects of ZoneId and ZoneOffset type)
So the following two if
statements should be in reverted order. (ZoneOffset first)
} else if (obj instanceof ZoneId) {
return new ZoneIdAdapter((ZoneId) obj);
} else if (obj instanceof ZoneOffset) {
return new ZoneOffsetAdapter((ZoneOffset) obj);
}
Follow up on #8 and get it released.
LocalDateTimeAdapter
uses ISO_ZONED_DATE_TIME
and I guess it should be using ISO_LOCAL_DATE_TIME
because I'm getting the following exception:
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds
as reference mentioned, java instant type can be formatted as following:
https://github.com/lazee/freemarker-java-8#ballot_box_with_check-javatimeinstant
${myinstant.format()}
but It doesnt support format as coded like this:
@Override
public TemplateModel getForType(String s) throws TemplateModelException {
if (METHOD_FORMAT.equals(s)) {
return new InstantFormatter(getObject());
}
throw new TemplateModelException(METHOD_UNKNOWN_MSG + s);
}
public class InstantFormatter extends AbstractFormatter<Instant> implements TemplateMethodModelEx {
public InstantFormatter(Instant obj) {
super(obj);
}
@Override
public Object exec(List list) throws TemplateModelException {
return getObject().toString(); // format ignored
}
}
generated value is always
getObject().toString()
so formatting wont work
I've searched other types of type, and find out this:
public class LocalDateFormatter extends AbstractFormatter<LocalDate> implements TemplateMethodModelEx {
public LocalDateFormatter(LocalDate obj) {
super(obj);
}
@Override
public Object exec(List list) throws TemplateModelException {
return getObject().format(createDateTimeFormatter(list, 0, DateTimeFormatter.ISO_LOCAL_DATE));
}
}
in your code-context, instant type also can be formatted.
public class FormattableInstantAdapter extends AbstractAdapter<Instant> implements AdapterTemplateModel,
TemplateScalarModel, TemplateHashModel {
public FormattableInstantAdapter(Instant obj, BeansWrapper wrapper) {
super(obj, wrapper);
}
@Override
public TemplateModel getForType(String s) throws TemplateModelException {
if (METHOD_FORMAT.equals(s)) {
return new InstantFormatter(getObject());
}
throw new TemplateModelException(METHOD_UNKNOWN_MSG + s);
}
public class InstantFormatter extends AbstractFormatter<Instant> implements TemplateMethodModelEx {
public InstantFormatter(Instant obj) {
super(obj);
}
@Override
public Object exec(List list) throws TemplateModelException {
return createDateTimeFormatter(list, 0, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.withZone(ZoneId.systemDefault()) // instant use UTC as default
.format(getObject());
}
}
}
Hello, I created mcve example https://github.com/kortov/freemarker-java-8-mcve
and in folder config you can check my config that I adapted a bit for a current Spring Boot.
In your readme you write
public class FreemarkerConfig extends FreeMarkerAutoConfiguration.FreeMarkerWebConfiguration {
When in Spring Boot 2.1.4.RELEASE I wrote
public class FreemarkerConfig extends FreeMarkerAutoConfiguration {
And I don't see any inner classes in FreeMarkerAutoConfiguration
plus I created a constructor, as Intellij Idea suggested it to me
public FreemarkerConfig(ApplicationContext applicationContext, FreeMarkerProperties properties) {
super(applicationContext, properties);
}
This issue just FYI and I don't see it as a big problem or maybe I'm used your config the wrong way, anyway I guess it could be interesting for you
Just to add the requested issue to see this getting deployed to maven central. Thanks for your contribution.
cfg = new Configuration(Configuration.VERSION_2_3_31);
cfg.setObjectWrapper(new Java8ObjectWrapper(Configuration.VERSION_2_3_31));
Instant arriveTime = Instant.now();
${arriveTime.format('yyyy-MM-dd HH : mm : ss')}
the output is not printed as excepted: 2021-03-16T16:17:45Z
My use case is a small tool that generates XMLs.
All times should have the format HH:mm:ss
However, by default seconds are only output if non-zero.
So, currently I have to to explicitly include the format everyhwere, so instead of
${startDate} I have to write ${startDate.format("yyyy-MM-dd'T'HH:mm:ss")}
Since I have a lot of LocalDateTime and LocalTime instances, this makes the templates unnecessary long and error prone.
It would be very nice to configure the time adapters to use a specific default formatter!
Another way to setup the Java8ObjectWrapper is via the following property:
spring.freemarker.settings.object_wrapper=no.api.freemarker.java8.Java8ObjectWrapper(Configuration.VERSION_2_3_31)
This takes advantage of Freemarker Configuration object builder expressions
Hi, Jakob. I'm trying to reactivate the FreeMarker project (outside of ASF, long story which I'll tell you if you're interested) and get a FreeMarker 3 out. I am, by the way, to all intents and purposes, the original author.
I ran across this and was wondering if you wanted to just add this newer time/date functionality straight to the FreeMarker core. (And possibly some other things if they occur to you.)
Please feel free to write me at [email protected]
Calling zoned_date_object.format(fmt_string)
without the second argument for zone_id will use the system's timezone - which really defeats the purpose of a ZonedDateTime.
In it's current form, to correctly format the zoned date time the ZoneId must also be passed as a string into the template - zoned_date_object.format(fmt_string, zoned_id_string)
.
If the user wanted to format the date object in the system timezone, they should use LocalDateTime.
I could probably submit a PR, but I first wanted to see if this line of thinking was valid.
I have a LocalDateTime I want to format as a date for specific locales.
Currently I have to set a specific pattern, but what I really want is to use the formatter from DateTimeFormatter.ofLocalizedDate(FormatStyle)
.
So in effect, a DateTimeFormatter created like the following would be great:
DateTimeFormatter.ofLocalizedDate(myFormatStyle).withLocale(myLocale);
Please fix in documentation
<dependency>
<groupId>no.api.freemarker</groupId>
<artifactId>freemarker-java-8</artifactId>
<version>1.1.2</version>
</dependency>
to
<dependency>
<groupId>no.api.freemarker</groupId>
<artifactId>freemarker-java8</artifactId>
<version>1.1.2</version>
</dependency>
I introduced this module when I to provide handier support for java time. Sadly a few existing FTL's got broken which called methods like Instant#toEpochMilli(), and I was actually a bit surprized that those methods disappeared now.
This might be useful for people using Spring in e.g. in the readme.md
: -
import no.api.freemarker.java8.Java8ObjectWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
public class FreemarkerConfig extends FreeMarkerAutoConfiguration.FreeMarkerWebConfiguration {
@Autowired
private freemarker.template.Configuration configuration;
@PostConstruct
public void postConstruct() {
configuration.setObjectWrapper(
new Java8ObjectWrapper(freemarker.template.Configuration.getVersion())); // VERSION_2_3_26
}
}
I get this trying to install locally:
$ mvn clean install
[INFO] Scanning for projects...
Downloading: https://repo.maven.apache.org/maven2/no/api/maven-projects/v3-super-pom/3.51/v3-super-pom-3.51.pom
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[FATAL] Non-resolvable parent POM for no.api.freemarker:freemarker-java8:1.0.1-SNAPSHOT: Could not find artifact no.api.maven-projects:v3-super-pom:pom:3.51 in central (https://repo.maven.apache.org/maven2) and 'parent.relativePath' points at wrong local POM @ line 21, column 13
@
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]
[ERROR] The project no.api.freemarker:freemarker-java8:1.0.1-SNAPSHOT (/Users/lovatog/Workspaces/github/amedia/freemarker-java-8/pom.xml) has 1 error
[ERROR] Non-resolvable parent POM for no.api.freemarker:freemarker-java8:1.0.1-SNAPSHOT: Could not find artifact no.api.maven-projects:v3-super-pom:pom:3.51 in central (https://repo.maven.apache.org/maven2) and 'parent.relativePath' points at wrong local POM @ line 21, column 13 -> [Help 2]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException
[ERROR] [Help 2] http://cwiki.apache.org/confluence/display/MAVEN/UnresolvableModelException
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.