diff --git a/src/main/java/com/hubspot/jinjava/JinjavaConfig.java b/src/main/java/com/hubspot/jinjava/JinjavaConfig.java index ad527458c..d854d21e7 100644 --- a/src/main/java/com/hubspot/jinjava/JinjavaConfig.java +++ b/src/main/java/com/hubspot/jinjava/JinjavaConfig.java @@ -25,6 +25,8 @@ import com.hubspot.jinjava.interpret.JinjavaInterpreterFactory; import com.hubspot.jinjava.mode.DefaultExecutionMode; import com.hubspot.jinjava.mode.ExecutionMode; +import com.hubspot.jinjava.objects.date.CurrentDateTimeProvider; +import com.hubspot.jinjava.objects.date.DateTimeProvider; import com.hubspot.jinjava.random.RandomNumberGeneratorStrategy; import com.hubspot.jinjava.tree.parse.DefaultTokenScannerSymbols; import com.hubspot.jinjava.tree.parse.TokenScannerSymbols; @@ -62,6 +64,7 @@ public class JinjavaConfig { private final int rangeLimit; private final int maxNumDeferredTokens; private final InterpreterFactory interpreterFactory; + private final DateTimeProvider dateTimeProvider; private TokenScannerSymbols tokenScannerSymbols; private final ELResolver elResolver; private final ExecutionMode executionMode; @@ -121,6 +124,7 @@ private JinjavaConfig(Builder builder) { elResolver = builder.elResolver; executionMode = builder.executionMode; legacyOverrides = builder.legacyOverrides; + dateTimeProvider = builder.dateTimeProvider; enablePreciseDivideFilter = builder.enablePreciseDivideFilter; objectMapper = builder.objectMapper; } @@ -241,6 +245,10 @@ public boolean getEnablePreciseDivideFilter() { return enablePreciseDivideFilter; } + public DateTimeProvider getDateTimeProvider() { + return dateTimeProvider; + } + public static class Builder { private Charset charset = StandardCharsets.UTF_8; private Locale locale = Locale.ENGLISH; @@ -258,6 +266,7 @@ public static class Builder { private boolean nestedInterpretationEnabled = true; private RandomNumberGeneratorStrategy randomNumberGeneratorStrategy = RandomNumberGeneratorStrategy.THREAD_LOCAL; + private DateTimeProvider dateTimeProvider = new CurrentDateTimeProvider(); private boolean validationMode = false; private long maxStringLength = 0; private int rangeLimit = DEFAULT_RANGE_LIMIT; @@ -306,6 +315,11 @@ public Builder withRandomNumberGeneratorStrategy( return this; } + public Builder withDateTimeProvider(DateTimeProvider dateTimeProvider) { + this.dateTimeProvider = dateTimeProvider; + return this; + } + public Builder withTrimBlocks(boolean trimBlocks) { this.trimBlocks = trimBlocks; return this; diff --git a/src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java b/src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java index a3a392019..7ede5f016 100644 --- a/src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java +++ b/src/main/java/com/hubspot/jinjava/interpret/JinjavaInterpreter.java @@ -156,9 +156,9 @@ public void addBlock(String name, BlockInfo blockInfo) { /** * Creates a new variable scope, extending from the current scope. Allows you to create a nested * contextual scope which can override variables from higher levels. - * + *
* Should be used in a try/finally context, similar to lock-use patterns: - * + *
*
* interpreter.enterScope();
* try (interpreter.enterScope()) {
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 94c2b2c07..e620d2988 100644
--- a/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java
+++ b/src/main/java/com/hubspot/jinjava/lib/fn/Functions.java
@@ -13,6 +13,7 @@
import com.hubspot.jinjava.interpret.TemplateError;
import com.hubspot.jinjava.mode.ExecutionMode;
import com.hubspot.jinjava.objects.Namespace;
+import com.hubspot.jinjava.objects.date.DateTimeProvider;
import com.hubspot.jinjava.objects.date.PyishDate;
import com.hubspot.jinjava.objects.date.StrftimeFormatter;
import com.hubspot.jinjava.tree.Node;
@@ -245,7 +246,14 @@ public static ZonedDateTime getDateTimeArg(Object var, ZoneId zoneOffset) {
JinjavaInterpreter.getCurrent().getPosition()
);
}
- d = ZonedDateTime.now(zoneOffset);
+ long currentMillis = JinjavaInterpreter
+ .getCurrentMaybe()
+ .map(JinjavaInterpreter::getConfig)
+ .map(JinjavaConfig::getDateTimeProvider)
+ .map(DateTimeProvider::getCurrentTimeMillis)
+ .orElse(System.currentTimeMillis());
+
+ d = ZonedDateTime.ofInstant(Instant.ofEpochMilli(currentMillis), zoneOffset);
} else if (var instanceof Number) {
d =
ZonedDateTime.ofInstant(
diff --git a/src/main/java/com/hubspot/jinjava/objects/date/CurrentDateTimeProvider.java b/src/main/java/com/hubspot/jinjava/objects/date/CurrentDateTimeProvider.java
new file mode 100644
index 000000000..937d4d00a
--- /dev/null
+++ b/src/main/java/com/hubspot/jinjava/objects/date/CurrentDateTimeProvider.java
@@ -0,0 +1,9 @@
+package com.hubspot.jinjava.objects.date;
+
+public class CurrentDateTimeProvider implements DateTimeProvider {
+
+ @Override
+ public long getCurrentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+}
diff --git a/src/main/java/com/hubspot/jinjava/objects/date/DateTimeProvider.java b/src/main/java/com/hubspot/jinjava/objects/date/DateTimeProvider.java
new file mode 100644
index 000000000..ca6c53c9e
--- /dev/null
+++ b/src/main/java/com/hubspot/jinjava/objects/date/DateTimeProvider.java
@@ -0,0 +1,5 @@
+package com.hubspot.jinjava.objects.date;
+
+public interface DateTimeProvider {
+ long getCurrentTimeMillis();
+}
diff --git a/src/main/java/com/hubspot/jinjava/objects/date/FixedDateTimeProvider.java b/src/main/java/com/hubspot/jinjava/objects/date/FixedDateTimeProvider.java
new file mode 100644
index 000000000..75bc71ce8
--- /dev/null
+++ b/src/main/java/com/hubspot/jinjava/objects/date/FixedDateTimeProvider.java
@@ -0,0 +1,14 @@
+package com.hubspot.jinjava.objects.date;
+
+public class FixedDateTimeProvider implements DateTimeProvider {
+ private long currentTimeMillis;
+
+ public FixedDateTimeProvider(long currentTimeMillis) {
+ this.currentTimeMillis = currentTimeMillis;
+ }
+
+ @Override
+ public long getCurrentTimeMillis() {
+ return currentTimeMillis;
+ }
+}
diff --git a/src/main/java/com/hubspot/jinjava/objects/date/PyishDate.java b/src/main/java/com/hubspot/jinjava/objects/date/PyishDate.java
index 9ffd07e5e..ee5722e45 100644
--- a/src/main/java/com/hubspot/jinjava/objects/date/PyishDate.java
+++ b/src/main/java/com/hubspot/jinjava/objects/date/PyishDate.java
@@ -1,5 +1,6 @@
package com.hubspot.jinjava.objects.date;
+import com.hubspot.jinjava.JinjavaConfig;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.objects.PyWrapper;
import com.hubspot.jinjava.objects.serialization.PyishObjectMapper;
@@ -50,7 +51,17 @@ public PyishDate(Long epochMillis) {
this(
ZonedDateTime.ofInstant(
Instant.ofEpochMilli(
- Optional.ofNullable(epochMillis).orElseGet(System::currentTimeMillis)
+ Optional
+ .ofNullable(epochMillis)
+ .orElseGet(
+ () ->
+ JinjavaInterpreter
+ .getCurrentMaybe()
+ .map(JinjavaInterpreter::getConfig)
+ .map(JinjavaConfig::getDateTimeProvider)
+ .map(DateTimeProvider::getCurrentTimeMillis)
+ .orElseGet(System::currentTimeMillis)
+ )
),
ZoneOffset.UTC
)
diff --git a/src/test/java/com/hubspot/jinjava/lib/filter/UnixTimestampFilterTest.java b/src/test/java/com/hubspot/jinjava/lib/filter/UnixTimestampFilterTest.java
index fc28df5e9..e52cea3de 100644
--- a/src/test/java/com/hubspot/jinjava/lib/filter/UnixTimestampFilterTest.java
+++ b/src/test/java/com/hubspot/jinjava/lib/filter/UnixTimestampFilterTest.java
@@ -20,12 +20,12 @@ public void setup() {
}
@After
- public void tearDown() throws Exception {
+ public void tearDown() {
assertThat(interpreter.getErrorsCopy()).isEmpty();
}
@Test
- public void itRendersFromDate() throws Exception {
+ public void itRendersFromDate() {
assertThat(interpreter.renderFlat("{{ d|unixtimestamp }}")).isEqualTo(timestamp);
}
}
diff --git a/src/test/java/com/hubspot/jinjava/lib/fn/TodayFunctionTest.java b/src/test/java/com/hubspot/jinjava/lib/fn/TodayFunctionTest.java
index abb3830c4..63cbd9fcb 100644
--- a/src/test/java/com/hubspot/jinjava/lib/fn/TodayFunctionTest.java
+++ b/src/test/java/com/hubspot/jinjava/lib/fn/TodayFunctionTest.java
@@ -10,12 +10,15 @@
import com.hubspot.jinjava.interpret.InvalidArgumentException;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.mode.EagerExecutionMode;
+import com.hubspot.jinjava.objects.date.FixedDateTimeProvider;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import org.junit.Test;
public class TodayFunctionTest extends BaseInterpretingTest {
+ private static final String ZONE_NAME = "America/New_York";
+ private static final ZoneId ZONE_ID = ZoneId.of(ZONE_NAME);
@Test
public void itDefaultsToUtcTimezone() {
@@ -23,10 +26,32 @@ public void itDefaultsToUtcTimezone() {
assertThat(zonedDateTime.getZone()).isEqualTo(ZoneOffset.UTC);
}
+ @Test
+ public void itUsesFixedDateTimeProvider() {
+ long ts = 1233333414223L;
+
+ JinjavaInterpreter.pushCurrent(
+ new JinjavaInterpreter(
+ new Jinjava(),
+ new Context(),
+ JinjavaConfig
+ .newBuilder()
+ .withDateTimeProvider(new FixedDateTimeProvider(ts))
+ .build()
+ )
+ );
+ try {
+ assertThat(Functions.today(ZONE_NAME))
+ .isEqualTo(ZonedDateTime.of(2009, 1, 30, 0, 0, 0, 0, ZONE_ID));
+ } finally {
+ JinjavaInterpreter.popCurrent();
+ }
+ }
+
@Test
public void itParsesTimezones() {
- ZonedDateTime zonedDateTime = Functions.today("America/New_York");
- assertThat(zonedDateTime.getZone()).isEqualTo(ZoneId.of("America/New_York"));
+ ZonedDateTime zonedDateTime = Functions.today(ZONE_NAME);
+ assertThat(zonedDateTime.getZone()).isEqualTo(ZONE_ID);
}
@Test(expected = InvalidArgumentException.class)
@@ -52,7 +77,7 @@ public void itDefersWhenExecutingEagerly() {
)
);
try {
- Functions.today("America/New_York");
+ Functions.today(ZONE_NAME);
} finally {
JinjavaInterpreter.popCurrent();
}
diff --git a/src/test/java/com/hubspot/jinjava/lib/fn/UnixTimestampFunctionTest.java b/src/test/java/com/hubspot/jinjava/lib/fn/UnixTimestampFunctionTest.java
index e7cb1e3b9..de494fd85 100644
--- a/src/test/java/com/hubspot/jinjava/lib/fn/UnixTimestampFunctionTest.java
+++ b/src/test/java/com/hubspot/jinjava/lib/fn/UnixTimestampFunctionTest.java
@@ -8,7 +8,9 @@
import com.hubspot.jinjava.interpret.DeferredValueException;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.mode.EagerExecutionMode;
+import com.hubspot.jinjava.objects.date.FixedDateTimeProvider;
import java.time.ZonedDateTime;
+import org.assertj.core.data.Offset;
import org.junit.Test;
public class UnixTimestampFunctionTest {
@@ -24,14 +26,29 @@ public void itGetsUnixTimestamps() {
.isLessThanOrEqualTo(System.currentTimeMillis());
assertThat(Functions.unixtimestamp(epochMilliseconds)).isEqualTo(epochMilliseconds);
assertThat(Functions.unixtimestamp(d)).isEqualTo(epochMilliseconds);
- assertThat(
- Math.abs(
- Functions.unixtimestamp((Object) null) -
- ZonedDateTime.now().toEpochSecond() *
- 1000
- )
+ assertThat(Functions.unixtimestamp((Object) null))
+ .isCloseTo(System.currentTimeMillis(), Offset.offset(1000L));
+ }
+
+ @Test
+ public void itUsesFixedDateTimeProvider() {
+ long ts = 1233333414223L;
+
+ JinjavaInterpreter.pushCurrent(
+ new JinjavaInterpreter(
+ new Jinjava(),
+ new Context(),
+ JinjavaConfig
+ .newBuilder()
+ .withDateTimeProvider(new FixedDateTimeProvider(ts))
+ .build()
)
- .isLessThan(1000);
+ );
+ try {
+ assertThat(Functions.unixtimestamp((Object) null)).isEqualTo(ts);
+ } finally {
+ JinjavaInterpreter.popCurrent();
+ }
}
@Test(expected = DeferredValueException.class)
diff --git a/src/test/java/com/hubspot/jinjava/objects/date/PyishDateTest.java b/src/test/java/com/hubspot/jinjava/objects/date/PyishDateTest.java
index 0c25fc12f..849c5636c 100644
--- a/src/test/java/com/hubspot/jinjava/objects/date/PyishDateTest.java
+++ b/src/test/java/com/hubspot/jinjava/objects/date/PyishDateTest.java
@@ -3,8 +3,11 @@
import static org.assertj.core.api.Assertions.assertThat;
import com.hubspot.jinjava.Jinjava;
+import com.hubspot.jinjava.JinjavaConfig;
+import com.hubspot.jinjava.interpret.Context;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.objects.serialization.PyishObjectMapper;
+import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Date;
@@ -18,6 +21,28 @@ public void itUsesCurrentTimeWhenNoneProvided() {
assertThat(d.toDate()).isCloseTo(new Date(), 10000);
}
+ @Test
+ public void itUsesDateTimeProviderWhenNoTimeProvided() {
+ long ts = 123345414223L;
+
+ JinjavaInterpreter.pushCurrent(
+ new JinjavaInterpreter(
+ new Jinjava(),
+ new Context(),
+ JinjavaConfig
+ .newBuilder()
+ .withDateTimeProvider(new FixedDateTimeProvider(ts))
+ .build()
+ )
+ );
+ try {
+ PyishDate d = new PyishDate((Long) null);
+ assertThat(d.toDate()).isEqualTo(Date.from(Instant.ofEpochMilli(ts)));
+ } finally {
+ JinjavaInterpreter.popCurrent();
+ }
+ }
+
@Test
public void testStrfmt() {
PyishDate d = new PyishDate(ZonedDateTime.parse("2013-11-12T14:15:00+00:00"));