From 4215c5926325d6fe21d1d8df0d9097cfdd5703d1 Mon Sep 17 00:00:00 2001 From: Matt Coley Date: Thu, 11 Oct 2018 13:25:58 -0400 Subject: [PATCH 1/4] Support timezone conversions in datetimeformat filter. --- .../lib/filter/DateTimeFormatFilter.java | 8 ++++-- .../com/hubspot/jinjava/lib/fn/Functions.java | 27 ++++++++++++++----- .../lib/filter/DateTimeFormatFilterTest.java | 17 ++++++++++++ 3 files changed, 44 insertions(+), 8 deletions(-) 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..c89bc3b28 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/DateTimeFormatFilter.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/DateTimeFormatFilter.java @@ -1,5 +1,6 @@ package com.hubspot.jinjava.lib.filter; +import com.google.common.base.Joiner; import com.hubspot.jinjava.doc.annotations.JinjavaDoc; import com.hubspot.jinjava.doc.annotations.JinjavaParam; import com.hubspot.jinjava.doc.annotations.JinjavaSnippet; @@ -11,7 +12,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') %}"), @@ -19,6 +21,8 @@ }) public class DateTimeFormatFilter implements Filter { + private static final Joiner ERROR_MESSAGE_JOINER = Joiner.on(", "); + @Override public String getName() { return "datetimeformat"; @@ -29,7 +33,7 @@ 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..0b7894a6c 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,22 @@ 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); + } + } + + ZonedDateTime d = getDateTimeArg(var, zoneOffset); if (d == null) { return ""; @@ -78,14 +93,14 @@ 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(); } else if (var instanceof ZonedDateTime) { @@ -101,7 +116,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"); + } } From d42c40edbd8b6577252ba0263ffafd53baed5f90 Mon Sep 17 00:00:00 2001 From: Matt Coley Date: Thu, 11 Oct 2018 13:27:17 -0400 Subject: [PATCH 2/4] slight fix. --- .../com/hubspot/jinjava/lib/filter/DateTimeFormatFilter.java | 4 ---- 1 file changed, 4 deletions(-) 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 c89bc3b28..5bbb9d03e 100644 --- a/src/main/java/com/hubspot/jinjava/lib/filter/DateTimeFormatFilter.java +++ b/src/main/java/com/hubspot/jinjava/lib/filter/DateTimeFormatFilter.java @@ -1,6 +1,5 @@ package com.hubspot.jinjava.lib.filter; -import com.google.common.base.Joiner; import com.hubspot.jinjava.doc.annotations.JinjavaDoc; import com.hubspot.jinjava.doc.annotations.JinjavaParam; import com.hubspot.jinjava.doc.annotations.JinjavaSnippet; @@ -21,8 +20,6 @@ }) public class DateTimeFormatFilter implements Filter { - private static final Joiner ERROR_MESSAGE_JOINER = Joiner.on(", "); - @Override public String getName() { return "datetimeformat"; @@ -38,7 +35,6 @@ public Object filter(Object var, JinjavaInterpreter interpreter, else { return Functions.dateTimeFormat(var); } - } } From f5e27a6c5f02e06c90e5d1e22b76268ba01beef4 Mon Sep 17 00:00:00 2001 From: Matt Coley Date: Thu, 11 Oct 2018 13:50:01 -0400 Subject: [PATCH 3/4] better zones. --- src/main/java/com/hubspot/jinjava/lib/fn/Functions.java | 4 ++++ 1 file changed, 4 insertions(+) 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 0b7894a6c..c473e7695 100644 --- a/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java +++ b/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java @@ -66,6 +66,7 @@ public static List immutableListOf(Object... items) { public static String dateTimeFormat(Object var, String... format) { ZoneId zoneOffset = ZoneOffset.UTC; + if (format.length > 1) { String timezone = format[1]; try { @@ -73,6 +74,8 @@ public static String dateTimeFormat(Object var, String... format) { } catch (DateTimeException e) { throw new InvalidDateFormatException(timezone, e); } + } else if (var instanceof ZonedDateTime) { + zoneOffset = ((ZonedDateTime) var).getZone(); } ZonedDateTime d = getDateTimeArg(var, zoneOffset); @@ -105,6 +108,7 @@ private static ZonedDateTime getDateTimeArg(Object var, ZoneId zoneOffset) { d = ((PyishDate) var).toDateTime(); } 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()); } From dc28adbbe4e3abc651cc7370f6a87a9f7bfab87c Mon Sep 17 00:00:00 2001 From: Matt Coley Date: Thu, 11 Oct 2018 13:53:45 -0400 Subject: [PATCH 4/4] Final conversions. --- src/main/java/com/hubspot/jinjava/lib/fn/Functions.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 c473e7695..7d6b9e55c 100644 --- a/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java +++ b/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java @@ -76,6 +76,8 @@ public static String dateTimeFormat(Object var, String... format) { } } else if (var instanceof ZonedDateTime) { zoneOffset = ((ZonedDateTime) var).getZone(); + } else if (var instanceof PyishDate) { + zoneOffset = ((PyishDate) var).toDateTime().getZone(); } ZonedDateTime d = getDateTimeArg(var, zoneOffset); @@ -105,7 +107,9 @@ private static ZonedDateTime getDateTimeArg(Object var, ZoneId zoneOffset) { } else if (var instanceof Number) { 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);