freemarker-java8 is a Java library that extends FreeMarker by adding support for the java.time
API.
It’s straightforward to integrate into your codebase and easy to use.
Despite the name, the library is compatible with Java 8 and all newer versions.
While the library is rarely updated, it’s still maintained and supported. If you have any questions or issues, please don’t hesitate to open an issue. We also test on new versions and distributions of Java, regularly.
This library enables you to format and display values from java.time
classes directly within FreeMarker
templates. Additionally, it offers useful comparison and conversion methods to enhance your template logic.
While it provides valuable functionality, it isn't a perfect solution due to FreeMarker's lack of support for custom built-ins. Hopefully, future versions of FreeMarker will include native support, but according to their contribution guidelines, this seems unlikely.
- Installation
- Setup
- Upgrade guides
- Usage
- Formatting
- About pattern
- About zone
- java.time.Clock
- java.time.Duration
- java.time.Instant
- java.time.LocalDate
- java.time.LocalDateTime
- java.time.LocalTime
- java.time.MonthDay
- java.time.OffsetDateTime
- java.time.OffsetTime
- java.time.Period
- java.time.Year
- java.time.YearMonth
- java.time.ZonedDateTime
- java.time.ZonedId
- java.time.ZonedOffset
- Comparison
- Formatting
You need Java 8 or higher. Tested on Freemarker 2.3.33, and should at least work fine for all 2.3.x versions.
<dependency>
<groupId>no.api.freemarker</groupId>
<artifactId>freemarker-java8</artifactId>
<version>3.0.0</version>
</dependency>
implementation 'no.api.freemarker:freemarker-java8:3.0.0'
freemarker-java8 extends the DefaultObjectWrapper to add support for the java.time classes. All you need to do is to replace the default object wrapper with the freemarker-java8 implementation in your FreeMarker Configuration object.
this.configuration = new Configuration(); // Or get the configuration from your framework like DropWizard or Spring Boot.
this.configuration.setObjectWrapper(new Java8ObjectWrapper(Configuration.VERSION_2_3_33));
This is how you can add freemarker-java8 to your FreeMarker configuration in Spring / Spring Boot.
package com.example.demo;
import no.api.freemarker.java8.Java8ObjectWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
@Configuration
public class FreemarkerConfig implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof FreeMarkerConfigurer) {
FreeMarkerConfigurer configurer = (FreeMarkerConfigurer) bean;
configurer.getConfiguration().setObjectWrapper(new Java8ObjectWrapper(freemarker.template.Configuration.getVersion()));
}
return bean;
}
}
You can also configure it via Spring boot properties like this:
spring.freemarker.settings.object_wrapper=no.api.freemarker.java8.Java8ObjectWrapper(Configuration.VERSION_2_3_33)
This takes advantage of Freemarker Configuration object builder expressions
Upgrade information has moved to UPGRADE.md. Also look at CHANGELOG.md for changes in new versions.
All format methods uses the java.time.format.DateTimeFormatter for formatting.
Instead of customizing your own pattern in the format methods, you can use one of the predefined styles from:
java.text.format.DateFormat
(DEFAULT, SHORT, MEDIUM, LONG, FULL)java.time.format.DateTimeFormatter
(Eg: ISO_OFFSET_DATE_TIME, follow link for more)- Custom styles shipped with this library
- LONG_DATE
- LONG_DATETIME
- LONG_TIME
- MEDIUM_DATE
- MEDIUM_DATETIME
- MEDIUM_TIME
- SHORT_DATE
- SHORT_DATETIME
- SHORT_TIME
${mydate.format('LONG_DATE')}
${mydate.format('ISO_OFFSET_DATE_TIME')}
${mydate.format('yyyy-MM-dd Z')}
When no pattern are given, each java.time class has a default pattern. This default can be overridden in the static
DefaultFormatters
class, where each class has its own setter method:
no.api.freemarker.java8.time.DefaultFormatters.setClockFormatter(DateTimeFormatter.ofPattern('yyyy MM dd HH:mm:ss'))
When a method takes zone
as argument, it must be a valid Java ZoneId
, like Europe/Oslo
or -07:00
.
The zone
argument is only supported for the java.time
classes where it makes sense.
When a zone is not explicitly given as argument, the formatter will by default use the zone found in the object itself, if it exists. If not, it will pick one based on the active zone strategy.
Th default behaviour can be changed if you like. Sometimes you might want all dates and times to be converted into your local timezone.
Java8ObjectMapper
now a second argument where you can choose one of four strategies for the time zone used when formatting a ZonedDateTime:
- KeepingZoneStrategy - (DEFAULT) Will use the zone from the objects themself. For objects without zone information, ZoneId.systemDefault() is used.
- EnviromentZoneStrategy - Will use the same time zone as Freemarker itself.
- SystemZoneStrategy - Will convert the time zone into ZoneId.systemDefault().
- StaticZoneStrategy - Allows you to explicitly set a ZoneId.
this.objectWrapper = new Java8ObjectWrapper(VERSION_2_3_33, new KeepingZoneStrategy());
this.objectWrapper = new Java8ObjectWrapper(VERSION_2_3_33, new StaticZoneStrategy(ZoneId.of("Europe/Oslo")));
Methods for formatting java.time.Clock
.
- format()
- format(pattern)
- format(pattern, zone)
When no pattern is specified, ISO_LOCAL_DATE_TIME
is used.
${myclock.format()}
${myclock.format('yyyy-MM-dd')}
${myclock.format('yyyy-MM-dd Z', 'Asia/Seoul')}
Provided access to a java.time.Duration
object values.
- nano()
- seconds()
${myduration.seconds}
${myduration.nano}
Methods for formatting java.time.Instant
.
- format()
- format(pattern)
- format(pattern, zone)
When no pattern is specified, ISO_LOCAL_DATE_TIME
is used.
${myinstant.format()}
${myzoneddatetime.format('yyyy-MM-dd')}
${myzoneddatetime.format('yyyy-MM-dd Z', 'Asia/Seoul')}
Methods for formatting java.time.LocalDate
.
- format()
- format(pattern)
${mylocaldate.format()}
${mylocaldate.format('yyyy MM dd')}
${mylocaldate.format('FULL_DATE')}
Methods for formatting java.time.LocalDateTime
. We also have methods for converting
a LocalDateTime
into a ZoneDateTime
.
- format()
- format(pattern)
- format(pattern)
- toZonedDateTime()
- toZonedDateTime(zone)
${mylocaldatetime.format()}
${mylocaldatetime.format('yyyy-MM-dd HH : mm : ss')}
${mylocaldatetime.format('MEDIUM_DATETIME')}
${mylocaldatetime.toZonedDateTime()}
${mylocaldatetime.toZonedDateTime('Asia/Seoul')}
${mylocaldatetime.toZonedDateTime('Asia/Seoul')}.format('yyyy-MM-dd HH : mm : ss Z')}
When no pattern is specified, ISO_LOCAL_DATE_TIME
is used.
When no zone
are given to toZoneDateTime
, the ZoneStrategy are used to pick one for you.
Methods for formatting java.time.LocalTime
.
- format()
- format(pattern)
${mylocaltime.format()}
${mylocaltime.format('HH : mm : ss')}
${mylocaltime.format('SHORT_TIME')}
When no pattern is specified, ISO_LOCAL_TIME
is used.
Methods for formatting java.time.MonthDay
.
- format()
- format(pattern)
${mymonthday.format()}
${mymonthday.format('MM dd')}
When no pattern is specified, MM:dd
is used.
Methods for formatting java.time.OffsetDateTime
.
- format()
- format(pattern)
- format(pattern, zone)
When no pattern is specified, ISO_OFFSET_DATE_TIME
is used.
Uses the normalized ZoneId from the Offset myOffsetDateTime.getOffset().normalized()
${myoffsetdatetime.format()}
${myoffsetdatetime.format('yyyy MM dd HH mm ss')}
${myoffsetdatetime.format('FULL_DATETIME')}
${myoffsetdatetime.format('yyyy MM dd HH mm ss Z', 'Asia/Seoul')}
Methods for formatting java.time.OffsetTime
.
- format()
- format(pattern)
${myoffsettime.format()}
${myoffsettime.format('HH mm ss')}
${myoffsettime.format('MEDIUM_TIME')}
When no pattern is specified, ISO_OFFSET_TIME
is used.
Provides access to the values of the a Period object within your template.
- days()
- months()
- years()
${myperiod.days}
${myperiod.months}
${myperiod.years}
Methods for formatting java.time.Year
.
- format()
- format(pattern)
${myyear.format()}
${myyear.format('yyyy')}
When no pattern is specified, yyyy
is used.
Methods for formatting java.time.YearMonth
.
- format()
- format(pattern)
${myyear.format()}
${myyear.format('yyyy MM')}
When no pattern is specified, yyyy-MM
is used.
Methods for formatting java.time.ZonedDateTime
.
- format()
- format(pattern)
- format(pattern, zone)
${myzoneddatetime.format()}
${myzoneddatetime.format('yyyy-MM-dd HH mm s Z')}
${myzoneddatetime.format('yyyy-MM-dd HH mm s Z', 'Asia/Seoul')}
Example:
new Java8ObjectWrapper(VERSION_2_3_33, new EnvironmentZoneStrategy());
// or
new Java8ObjectWrapper(VERSION_2_3_33, new StaticZoneStrategy(ZoneId.of("Europe/Oslo")));
Methods for formatting ZoneId display name.
You can override the textstyle with one of these values:
- FULL
- FULL_STANDALONE
- SHORT
- SHORT_STANDALONE
- NARROW
- NARROW_STANDALONE
You can also override the locale, but Java only seems to have locale support for a few languages.
- format()
- format(textStyle)
- format(textstyle, locale)
${myzoneid.format()}
${myzoneid.format('short')}
${myzoneid.format('short', 'no-NO')}
Methods for formatting the ZoneOffset display name. You can override the textstyle with one of these values:
- FULL
- FULL_STANDALONE
- SHORT
- SHORT_STANDALONE
- NARROW
- NARROW_STANDALONE
You can also override the locale, but Java only seems to have locale support for a few languages.
- format()
- format(textStyle)
${myzoneoffset.format()}
${myzoneoffset.format('short')}
Can compare two LocalDate objects for equality.
- isEqual(localDate)
- isAfter(localDate)
- isBefore(localDate)
${localDate.isEqual(anotherlocalDate)}
${localDate.isAfter(anotherlocalDate)}
${localDate.isBefore(anotherlocalDate)}
Can compare two LocalDateTime objects for equality.
- isEqual(localDateTime)
- isAfter(localDateTime)
- isBefore(localDateTime)
${localDateTime.isEqual(anotherlocalDateTime)}
${localDateTime.isAfter(anotherlocalDateTime)}
${localDateTime.isBefore(anotherlocalDateTime)}
Can compare two LocalTime objects for equality.
- isEqual(localDateTime)
- isAfter(localDateTime)
- isBefore(localDateTime)
${localTime.isEqual(anotherlocalTime)}
${localTime.isAfter(anotherlocalTime)}
${localTime.isBefore(anotherlocalTime)}
Can create a new Temporal object with specified time difference from the original object, supporting
java.time.Instant,
java.time.LocalDate,
java.time.LocalDateTime,
java.time.LocalTime,
java.time.OffsetDateTime,
java.time.OffsetTime,
java.time.Year,
java.time.YearMonth,
java.time.ZonedDateTime
- plusSeconds(long)
- plusMinutes(long)
- plusDays(long)
- plusWeeks(long)
- plusMonths(long)
- plusYears(lone)
${localDateTime.plusMonths(1).plus.Hours(-2).plusMinutes(5).plusSeconds(30).format()}