diff --git a/src/main/java/com/hubspot/jinjava/lib/filter/DateTimeFormatFilter.java b/src/main/java/com/hubspot/jinjava/lib/filter/DateTimeFormatFilter.java index 53b959936..5bbb9d03e 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/DateTimeFormatFilter.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/DateTimeFormatFilter.java @@ -11,7 +11,8 @@ value = "Formats a date object", params = { @JinjavaParam(value = "value", defaultValue = "current time", desc = "The date variable or UNIX timestamp to format"), - @JinjavaParam(value = "format", defaultValue = StrftimeFormatter.DEFAULT_DATE_FORMAT, desc = "The format of the date determined by the directives added to this parameter") + @JinjavaParam(value = "format", defaultValue = StrftimeFormatter.DEFAULT_DATE_FORMAT, desc = "The format of the date determined by the directives added to this parameter"), + @JinjavaParam(value = "timezone", defaultValue = "utc", desc = "Time zone of output date") }, snippets = { @JinjavaSnippet(code = "{% content.updated|datetimeformat('%B %e, %Y') %}"), @@ -29,12 +30,11 @@ public Object filter(Object var, JinjavaInterpreter interpreter, String... args) { if (args.length > 0) { - return Functions.dateTimeFormat(var, args[0]); + return Functions.dateTimeFormat(var, args); } else { return Functions.dateTimeFormat(var); } - } } diff --git a/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java b/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java index dba63730a..7d6b9e55c 100644 --- a/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java +++ b/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java @@ -2,7 +2,9 @@ import static com.hubspot.jinjava.util.Logging.ENGINE_LOG; +import java.time.DateTimeException; import java.time.Instant; +import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -21,6 +23,7 @@ import com.hubspot.jinjava.doc.annotations.JinjavaSnippet; import com.hubspot.jinjava.interpret.InterpretException; import com.hubspot.jinjava.interpret.JinjavaInterpreter; +import com.hubspot.jinjava.objects.date.InvalidDateFormatException; import com.hubspot.jinjava.objects.date.PyishDate; import com.hubspot.jinjava.objects.date.StrftimeFormatter; import com.hubspot.jinjava.tree.Node; @@ -57,10 +60,27 @@ public static List immutableListOf(Object... items) { @JinjavaDoc(value = "formats a date to a string", params = { @JinjavaParam(value = "var", type = "date", defaultValue = "current time"), - @JinjavaParam(value = "format", defaultValue = StrftimeFormatter.DEFAULT_DATE_FORMAT) + @JinjavaParam(value = "format", defaultValue = StrftimeFormatter.DEFAULT_DATE_FORMAT), + @JinjavaParam(value = "timezone", defaultValue = "utc", desc = "Time zone of output date") }) public static String dateTimeFormat(Object var, String... format) { - ZonedDateTime d = getDateTimeArg(var); + + ZoneId zoneOffset = ZoneOffset.UTC; + + if (format.length > 1) { + String timezone = format[1]; + try { + zoneOffset = ZoneId.of(timezone); + } catch (DateTimeException e) { + throw new InvalidDateFormatException(timezone, e); + } + } else if (var instanceof ZonedDateTime) { + zoneOffset = ((ZonedDateTime) var).getZone(); + } else if (var instanceof PyishDate) { + zoneOffset = ((PyishDate) var).toDateTime().getZone(); + } + + ZonedDateTime d = getDateTimeArg(var, zoneOffset); if (d == null) { return ""; @@ -78,18 +98,21 @@ public static String dateTimeFormat(Object var, String... format) { } } - private static ZonedDateTime getDateTimeArg(Object var) { + private static ZonedDateTime getDateTimeArg(Object var, ZoneId zoneOffset) { ZonedDateTime d = null; if (var == null) { - d = ZonedDateTime.now(ZoneOffset.UTC); + d = ZonedDateTime.now(zoneOffset); } else if (var instanceof Number) { - d = ZonedDateTime.ofInstant(Instant.ofEpochMilli(((Number) var).longValue()), ZoneOffset.UTC); + d = ZonedDateTime.ofInstant(Instant.ofEpochMilli(((Number) var).longValue()), zoneOffset); } else if (var instanceof PyishDate) { - d = ((PyishDate) var).toDateTime(); + PyishDate pyishDate = ((PyishDate) var); + d = pyishDate.toDateTime(); + d = d.withZoneSameInstant(zoneOffset); } else if (var instanceof ZonedDateTime) { d = (ZonedDateTime) var; + d = d.withZoneSameInstant(zoneOffset); } else if (!ZonedDateTime.class.isAssignableFrom(var.getClass())) { throw new InterpretException("Input to function must be a date object, was: " + var.getClass()); } @@ -101,7 +124,7 @@ private static ZonedDateTime getDateTimeArg(Object var) { @JinjavaParam(value = "var", type = "date", defaultValue = "current time"), }) public static long unixtimestamp(Object var) { - ZonedDateTime d = getDateTimeArg(var); + ZonedDateTime d = getDateTimeArg(var, ZoneOffset.UTC); if (d == null) { return 0; diff --git a/src/test/java/com/hubspot/jinjava/lib/filter/DateTimeFormatFilterTest.java b/src/test/java/com/hubspot/jinjava/lib/filter/DateTimeFormatFilterTest.java index cd74e55a3..16287c472 100644 --- a/src/test/java/com/hubspot/jinjava/lib/filter/DateTimeFormatFilterTest.java +++ b/src/test/java/com/hubspot/jinjava/lib/filter/DateTimeFormatFilterTest.java @@ -11,6 +11,7 @@ import com.hubspot.jinjava.Jinjava; import com.hubspot.jinjava.interpret.JinjavaInterpreter; +import com.hubspot.jinjava.objects.date.InvalidDateFormatException; import com.hubspot.jinjava.objects.date.StrftimeFormatter; public class DateTimeFormatFilterTest { @@ -58,4 +59,20 @@ public void itHandlesVarsAndLiterals() throws Exception { assertThat(interpreter.getErrorsCopy()).isEmpty(); } + @Test + public void itSupportsTimezones() throws Exception { + assertThat(filter.filter(1539277785000L, interpreter, "%B %d, %Y, at %I:%M %p")).isEqualTo("October 11, 2018, at 05:09 PM"); + assertThat(filter.filter(1539277785000L, interpreter, "%B %d, %Y, at %I:%M %p", "America/New_York")).isEqualTo("October 11, 2018, at 01:09 PM"); + assertThat(filter.filter(1539277785000L, interpreter, "%B %d, %Y, at %I:%M %p", "UTC+8")).isEqualTo("October 12, 2018, at 01:09 AM"); + } + + @Test(expected = InvalidDateFormatException.class) + public void itThrowsExceptionOnInvalidTimezone() throws Exception { + filter.filter(1539277785000L, interpreter, "%B %d, %Y, at %I:%M %p", "Not a timezone"); + } + + @Test(expected = InvalidDateFormatException.class) + public void itThrowsExceptionOnInvalidDateformat() throws Exception { + filter.filter(1539277785000L, interpreter, "Not a format"); + } }