Skip to content
master
Go to file
Code

README.md

java.time support for FreeMarker

FJ8 (freemarker-java-8) is a Java library that adds java.time api support to FreeMarker. It is easy to add to your codebase, and very easy to use.

Basically this library allows you to format and print values from java.time classes within FreeMarker templates. As a bonus you also get some comparison functions.

It is not a perfect solution as FreeMarker doesn’t support custom built-ins. Hopefully future versions of FreeMarker will add native support, but it doesn't look promising (http://freemarker.org/contribute.html).

Basically this library allows you to format java.time types within your templates, using the new java.time.format.DateTimeFormatter.

Table of content

Installation

You need Java 8 or higher. FJ8 is tested on Freemarker 2.3.23, and should at least work fine for all 2.3.x versions.

Maven

<dependency>
    <groupId>no.api.freemarker</groupId>
    <artifactId>freemarker-java8</artifactId>
    <version>2.0.0</version>
</dependency>

Gradle

implementation 'no.api.freemarker:freemarker-java8:2.0.0'

Setup

FJ8 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 FJ8 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_23));

Spring setup

This is how you can add FJ8 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;
    }
}

Thanks to Desson Ariawan for the example

Upgrade from 1.3 to 2.0

The 2.0 release addresses two major issues reported by users (#18/#16). It also introduces a new feature for manipulating time (#28).

The upgrade itself is nothing else than changing the version in your build configuration (pom.xml or something else). However if you need to stick to the old behaviour on how time zones are treated when formatting ZonedDateTime objects, then you need to add a second argument to Java8ObjectWrapper upon initialization:

configuration.setObjectWrapper(
	new Java8ObjectWrapper(VERSION_2_3_23, new EnvironmentTimeStrategy()
);

Usage

Formatting java.time classes

All format methods uses the java.time.format.DateTimeFormatter for formatting.

☑️ java.time.Clock

This is a simple implementation where format just prints the toString() value of the object.

Methods

  • format()

Example

${myclock.format()}

java.time.Duration

Gives access to the Duration values.

Methods

  • nano()
  • seconds()

Example

${myduration.seconds}
${myduration.nano}

☑️ java.time.Instant

This is a simple implementation where format just prints the toString() value of the object.

Methods

  • format()
  • format(pattern)

Example

${myinstant.format()}

☑️ java.time.LocalDate

Allows you to print a LocalDate on a default pattern, by providing a custom pattern or a builtin format style.

Methods

  • format()

Example

${mylocaldate.format()}
${mylocaldate.format('yyyy MM dd')}
${mylocaldate.format('FULL_DATE')}

☑️ java.time.LocalDateTime

Allows you to print a LocalDateTime on a default pattern, by providing a custom pattern or a builtin format style.

Methods

  • format()
  • format(pattern)

Example

${mylocaldatetime.format()}
${mylocaldatetime.format('yyyy-MM-dd HH : mm : ss')}
${mylocaldatetime.format('MEDIUM_DATETIME')}

☑️ java.time.LocalTime

Allows you to print a LocalTime on a default pattern, by providing a custom pattern or a builtin format style.

Methods

  • format()
  • format(pattern)

Example

${mylocaltime.format()}
${mylocaltime.format('HH : mm : ss')}
${mylocaltime.format('SHORT_TIME')}

☑️ java.time.MonthDay

Allows you to print a MonthDay on a default pattern or by providing a custom pattern.

Methods

  • format()
  • format(pattern)

Example

${mymonthday.format()}
${mymonthday.format('MM dd')}

☑️ java.time.OffsetDateTime

Allows you to print a OffsetDateTime on a default pattern, by providing a custom pattern or a builtin format style.

Methods

  • format()
  • format(pattern)

Example

${myoffsetdatetime.format()}
${myoffsetdatetime.format('yyyy MM dd HH mm ss')}
${myoffsetdatetime.format('FULL_DATETIME')}

☑️ java.time.OffsetTime

Allows you to print a OffsetTime on a default pattern, by providing a custom pattern or a builtin format style.

Methods

  • format()
  • format(pattern)

Example

${myoffsettime.format()}
${myoffsettime.format('HH mm ss')}
${myoffsettime.format('MEDIUM_TIME')}

☑️ java.time.Period

Provides access to the values of the a Period object within your template.

Methods

  • days()
  • months()
  • years()

Example

${myperiod.days}
${myperiod.months}
${myperiod.years}

☑️ java.time.Year

Allows you to print a Year on a default pattern or by providing a custom pattern.

Methods

  • format()
  • format(pattern)

Example

${myyear.format()}
${myyear.format('yyyy')}

☑️ java.time.YearMonth

Allows you to print a YearMonth on a default pattern or by providing a custom pattern.

Methods

  • format()
  • format(pattern)

Example

${myyear.format()}
${myyear.format('yyyy MM')}

☑️ java.time.ZonedDateTime

Allows you to print a YearMonth on a default pattern/timezone or by providing a custom pattern.

Methods

  • format()
  • format(pattern)
  • format(pattern, zone)

Example

${myzoneddatetime.format()}
${myzoneddatetime.format('yyyy-MM-dd Z')}
${myzoneddatetime.format('yyyy-MM-dd Z', 'Asia/Seoul')}

Notice

When a zone is not set, the formatter will use the zone found in the ZonedDateTime object itself. This behaviour can be changed if you want to. Scenarious where that might come in handy could be when you always wants to convert the timezone into your local timezone.

Java8ObjectMappernow takes a second argument where you can choose one of four strategies for the time zone used when formatting a ZonedDateTime:

  • EnviromentZonedDateTimeStrategy - Will convert the time zone into the one currently set within Freemarker.
  • KeepingZonedDateTimeStrategy - Will use the zone from the ZonedDateTime object itself (DEFAULT)
  • SystemZonedDateTimeStrategy - Will convert the time zone into ZoneId.systemDefault().
  • StaticSystemZoneDateTimeStrategy - Will use the time zone set when creating this strategy.

Example:

new Java8ObjectWrapper(VERSION_2_3_23, new EnvironmentZonedDateTimeStrategy());
// or
new Java8ObjectWrapper(VERSION_2_3_23, new StaticZonedDateTimeStrategy(ZoneId.of("Europe/Oslo")));

☑️ java.time.ZonedId

Prints the ZoneId display name. You can override the textstyle with one of these values [FULL, FULL_STANDALONE, SHORT, SHORT_STANDALONE, NARROW and NARROW_STANDALONE]. You can also override the locale, but Java only seems to have locale support for a few languages.

Methods

  • format()
  • format(textStyle)
  • format(textstyle, locale)

Example

${myzoneid.format()}
${myzoneid.format('short')}
${myzoneid.format('short', 'no-NO')}

☑️ java.time.ZonedOffset

Prints the ZoneOffset display name. You can override the textstyle with one of these values [FULL, FULL_STANDALONE, SHORT, SHORT_STANDALONE, NARROW and NARROW_STANDALONE]. You can also override the locale, but Java only seems to have locale support for a few languages.

Methods

  • format()
  • format(textStyle)

Example

${myzoneoffset.format()}
${myzoneoffset.format('short')}

Comparison

☑️ java.time.LocalDate

Can compare two LocalDate objects for equality.

Methods

  • isEqual(localDate)
  • isAfter(localDate)
  • isBefore(localDate)

Example

${localDate.isEqual(anotherlocalDate)}
${localDate.isAfter(anotherlocalDate)}
${localDate.isBefore(anotherlocalDate)}

☑️ java.time.LocalDateTime

Can compare two LocalDateTime objects for equality.

Methods

  • isEqual(localDateTime)
  • isAfter(localDateTime)
  • isBefore(localDateTime)

Example

${localDateTime.isEqual(anotherlocalDateTime)}
${localDateTime.isAfter(anotherlocalDateTime)}
${localDateTime.isBefore(anotherlocalDateTime)}

☑️ java.time.LocalTime

Can compare two LocalTime objects for equality.

Methods

  • isEqual(localDateTime)
  • isAfter(localDateTime)
  • isBefore(localDateTime)

Example

${localTime.isEqual(anotherlocalTime)}
${localTime.isAfter(anotherlocalTime)}
${localTime.isBefore(anotherlocalTime)}

Manipulating time

☑️ java.time.temporal.Temporal

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

Methods

  • plusSeconds()
  • plusMinutes()
  • plusDays()
  • plusWeeks()
  • plusMonths()
  • plusYears()

Example

${localDateTime.plusMonths(1).plus.Hours(-2).plusMinutes(5).plusSeconds(30).format()}

Notice

Recently this repository was moved from the Amedia organisation to my private account on Github. The reason behind this is that I recently left Amedia after 13 years (!) and that they let me take this project with me. The package naming will however stay the same.

You can’t perform that action at this time.