You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by tp...@apache.org on 2022/05/23 17:20:07 UTC
[nifi] branch main updated: NIFI-7230: Created toInstant(), formatInstant(), toNanos() and toMicros() expression language functions.
This is an automated email from the ASF dual-hosted git repository.
tpalfy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new a4797327fc NIFI-7230: Created toInstant(), formatInstant(), toNanos() and toMicros() expression language functions.
a4797327fc is described below
commit a4797327fc36ce9a1bc151a1e3edfb366e21ce59
Author: Lehel Boér <Le...@hotmail.com>
AuthorDate: Fri Mar 11 15:07:11 2022 +0100
NIFI-7230: Created toInstant(), formatInstant(), toNanos() and toMicros() expression language functions.
This closes #5888.
Signed-off-by: Tamas Palfy <ta...@gmail.com>
---
.../nifi/expression/AttributeExpression.java | 5 +-
.../language/antlr/AttributeExpressionLexer.g | 4 +
.../language/antlr/AttributeExpressionParser.g | 7 +-
.../language/compile/ExpressionCompiler.java | 59 ++++++++
.../language/evaluation/InstantEvaluator.java | 46 ++++++
.../language/evaluation/InstantQueryResult.java | 45 ++++++
.../evaluation/cast/EpochTimeEvaluator.java | 59 ++++++++
.../evaluation/cast/InstantCastEvaluator.java | 93 ++++++++++++
.../evaluation/cast/NumberCastEvaluator.java | 7 +-
.../evaluation/cast/WholeNumberCastEvaluator.java | 3 +
.../functions/InstantFormatEvaluator.java | 71 +++++++++
.../functions/NumberToInstantEvaluator.java | 52 +++++++
.../functions/StringToInstantEvaluator.java | 68 +++++++++
.../attribute/expression/language/TestQuery.java | 158 +++++++++++++++++++++
.../main/asciidoc/expression-language-guide.adoc | 90 ++++++++++++
15 files changed, 759 insertions(+), 8 deletions(-)
diff --git a/nifi-api/src/main/java/org/apache/nifi/expression/AttributeExpression.java b/nifi-api/src/main/java/org/apache/nifi/expression/AttributeExpression.java
index d0568fff5c..ce733adbb1 100644
--- a/nifi-api/src/main/java/org/apache/nifi/expression/AttributeExpression.java
+++ b/nifi-api/src/main/java/org/apache/nifi/expression/AttributeExpression.java
@@ -75,8 +75,7 @@ public interface AttributeExpression {
*/
ResultType getResultType();
- public static enum ResultType {
-
- STRING, BOOLEAN, WHOLE_NUMBER, DATE, DECIMAL, NUMBER;
+ enum ResultType {
+ STRING, BOOLEAN, WHOLE_NUMBER, DATE, INSTANT, DECIMAL, NUMBER;
}
}
diff --git a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g
index b4b91bddde..e0cb4caf24 100644
--- a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g
+++ b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionLexer.g
@@ -124,6 +124,8 @@ IS_EMPTY : 'isEmpty';
NOT_NULL : 'notNull';
TO_NUMBER : 'toNumber';
TO_DECIMAL : 'toDecimal';
+TO_MICROS : 'toMicros';
+TO_NANOS : 'toNanos';
URL_ENCODE : 'urlEncode';
URL_DECODE : 'urlDecode';
NOT : 'not';
@@ -167,7 +169,9 @@ LESS_THAN : 'lt';
GREATER_THAN_OR_EQUAL : 'ge';
LESS_THAN_OR_EQUAL : 'le';
FORMAT : 'format'; // takes string date format; uses SimpleDateFormat
+FORMAT_INSTANT : 'formatInstant';
TO_DATE : 'toDate'; // takes string date format; converts the subject to a Long based on the date format
+TO_INSTANT : 'toInstant';
MOD : 'mod';
PLUS : 'plus';
MINUS : 'minus';
diff --git a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g
index de6f1847c1..997dbd834a 100644
--- a/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g
+++ b/nifi-commons/nifi-expression-language/src/main/antlr3/org/apache/nifi/attribute/expression/language/antlr/AttributeExpressionParser.g
@@ -80,7 +80,7 @@ oneArgString : ((SUBSTRING_BEFORE | SUBSTRING_BEFORE_LAST | SUBSTRING_AFTER | SU
PREPEND | APPEND | STARTS_WITH | ENDS_WITH | CONTAINS | JOIN | JSON_PATH | JSON_PATH_DELETE | FROM_RADIX | UUID3 | UUID5 | HASH) LPAREN! anyArg RPAREN!) |
(TO_RADIX LPAREN! anyArg (COMMA! anyArg)? RPAREN!);
twoArgString : ((REPLACE | REPLACE_FIRST | REPLACE_ALL | IF_ELSE | JSON_PATH_SET | JSON_PATH_ADD) LPAREN! anyArg COMMA! anyArg RPAREN!) |
- ((SUBSTRING | FORMAT | PAD_LEFT | PAD_RIGHT | REPEAT) LPAREN! anyArg (COMMA! anyArg)? RPAREN!);
+ ((SUBSTRING | FORMAT | FORMAT_INSTANT | PAD_LEFT | PAD_RIGHT | REPEAT) LPAREN! anyArg (COMMA! anyArg)? RPAREN!);
threeArgString: ((JSON_PATH_PUT) LPAREN! anyArg COMMA! anyArg COMMA! anyArg RPAREN!);
fiveArgString : GET_DELIMITED_FIELD LPAREN! anyArg (COMMA! anyArg (COMMA! anyArg (COMMA! anyArg (COMMA! anyArg)?)?)?)? RPAREN!;
@@ -94,15 +94,16 @@ multiArgBool : (IN) LPAREN! anyArg (COMMA! anyArg)* RPAREN!;
// functions that return Numbers (whole or decimal)
-zeroArgNum : (LENGTH | TO_NUMBER | TO_DECIMAL | COUNT) LPAREN! RPAREN!;
+zeroArgNum : (LENGTH | TO_NUMBER | TO_DECIMAL | TO_MICROS | TO_NANOS | COUNT) LPAREN! RPAREN!;
oneArgNum : ((INDEX_OF | LAST_INDEX_OF) LPAREN! anyArg RPAREN!) |
((MOD | PLUS | MINUS | MULTIPLY | DIVIDE) LPAREN! anyArg RPAREN!);
oneOrTwoArgNum : MATH LPAREN! anyArg (COMMA! anyArg)? RPAREN!;
zeroOrOneOrTwoArgNum : TO_DATE LPAREN! anyArg? (COMMA! anyArg)? RPAREN!;
+zeroOrTwoArgNum: TO_INSTANT LPAREN! (anyArg COMMA! anyArg)? RPAREN!;
stringFunctionRef : zeroArgString | oneArgString | twoArgString | threeArgString | fiveArgString;
booleanFunctionRef : zeroArgBool | oneArgBool | multiArgBool;
-numberFunctionRef : zeroArgNum | oneArgNum | oneOrTwoArgNum | zeroOrOneOrTwoArgNum;
+numberFunctionRef : zeroArgNum | oneArgNum | zeroOrTwoArgNum | oneOrTwoArgNum | zeroOrOneOrTwoArgNum;
anyArg : WHOLE_NUMBER | DECIMAL | numberFunctionRef | STRING_LITERAL | zeroArgString | oneArgString | twoArgString | fiveArgString | booleanLiteral | zeroArgBool | oneArgBool | multiArgBool
| expression | parameterReference;
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java
index 767928a757..17a0173ec5 100644
--- a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/compile/ExpressionCompiler.java
@@ -31,12 +31,15 @@ import org.apache.nifi.attribute.expression.language.evaluation.BooleanEvaluator
import org.apache.nifi.attribute.expression.language.evaluation.DateEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.DecimalEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.InstantEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.NumberEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.StringEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.WholeNumberEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.cast.BooleanCastEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.cast.DateCastEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.cast.DecimalCastEvaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.cast.EpochTimeEvaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.cast.InstantCastEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.cast.NumberCastEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.cast.StringCastEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.cast.WholeNumberCastEvaluator;
@@ -63,6 +66,7 @@ import org.apache.nifi.attribute.expression.language.evaluation.functions.IPEval
import org.apache.nifi.attribute.expression.language.evaluation.functions.IfElseEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.InEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.IndexOfEvaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.functions.InstantFormatEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.IsEmptyEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.IsNullEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.JsonPathAddEvaluator;
@@ -83,6 +87,7 @@ import org.apache.nifi.attribute.expression.language.evaluation.functions.NotEva
import org.apache.nifi.attribute.expression.language.evaluation.functions.NotNullEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.NowEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.NumberToDateEvaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.functions.NumberToInstantEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.OneUpSequenceEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.OrEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.PadLeftEvaluator;
@@ -98,6 +103,7 @@ import org.apache.nifi.attribute.expression.language.evaluation.functions.Replac
import org.apache.nifi.attribute.expression.language.evaluation.functions.ReplaceNullEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.StartsWithEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.StringToDateEvaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.functions.StringToInstantEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.SubstringAfterEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.SubstringAfterLastEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.functions.SubstringBeforeEvaluator;
@@ -139,12 +145,15 @@ import org.apache.nifi.expression.AttributeExpression.ResultType;
import org.apache.nifi.flowfile.FlowFile;
import java.net.UnknownHostException;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionLexer.TO_MICROS;
+import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionLexer.TO_NANOS;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ALL_ATTRIBUTES;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ALL_DELINEATED_VALUES;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.ALL_MATCHING_ATTRIBUTES;
@@ -173,6 +182,7 @@ import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpre
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FALSE;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FIND;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FORMAT;
+import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FORMAT_INSTANT;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.FROM_RADIX;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.GET_DELIMITED_FIELD;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.GET_STATE_VALUE;
@@ -229,6 +239,7 @@ import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpre
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.THREAD;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_DATE;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_DECIMAL;
+import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_INSTANT;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_LITERAL;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_LOWER;
import static org.apache.nifi.attribute.expression.language.antlr.AttributeExpressionParser.TO_NUMBER;
@@ -498,6 +509,7 @@ public class ExpressionCompiler {
return (Evaluator<Long>) evaluator;
case STRING:
case DATE:
+ case INSTANT:
case DECIMAL:
case NUMBER:
return addToken(new WholeNumberCastEvaluator(evaluator), evaluator.getToken());
@@ -538,6 +550,7 @@ public class ExpressionCompiler {
return (Evaluator<Number>) evaluator;
case STRING:
case DATE:
+ case INSTANT:
case DECIMAL:
case WHOLE_NUMBER:
return addToken(new NumberCastEvaluator(evaluator), evaluator.getToken());
@@ -559,6 +572,18 @@ public class ExpressionCompiler {
return new DateCastEvaluator(evaluator);
}
+ private InstantEvaluator toInstantEvaluator(final Evaluator<?> evaluator) {
+ return toInstantEvaluator(evaluator, null);
+ }
+
+ private InstantEvaluator toInstantEvaluator(final Evaluator<?> evaluator, final String location) {
+ if (evaluator.getResultType() == ResultType.INSTANT) {
+ return (InstantEvaluator) evaluator;
+ }
+
+ return new InstantCastEvaluator(evaluator);
+ }
+
private Evaluator<?> buildFunctionEvaluator(final Tree tree, final Evaluator<?> subjectEvaluator, final List<Evaluator<?>> argEvaluators) {
switch (tree.getType()) {
case TRIM: {
@@ -843,6 +868,15 @@ public class ExpressionCompiler {
return addToken(new NumberToDateEvaluator(toWholeNumberEvaluator(subjectEvaluator)), "toDate");
}
}
+ case TO_INSTANT: {
+ if (argEvaluators.isEmpty()) {
+ return addToken(new NumberToInstantEvaluator(toWholeNumberEvaluator(subjectEvaluator)), "toInstant");
+ } else if (subjectEvaluator.getResultType() == ResultType.STRING && argEvaluators.size() == 2) {
+ return addToken(new StringToInstantEvaluator(toStringEvaluator(subjectEvaluator), toStringEvaluator(argEvaluators.get(0)), toStringEvaluator(argEvaluators.get(1))), "toInstant");
+ } else {
+ return addToken(new NumberToInstantEvaluator(toWholeNumberEvaluator(subjectEvaluator)), "toInstant");
+ }
+ }
case TO_NUMBER: {
verifyArgCount(argEvaluators, 0, "toNumber");
switch (subjectEvaluator.getResultType()) {
@@ -851,6 +885,7 @@ public class ExpressionCompiler {
case DECIMAL:
case NUMBER:
case DATE:
+ case INSTANT:
return addToken(toWholeNumberEvaluator(subjectEvaluator), "toNumber");
default:
throw new AttributeExpressionLanguageParsingException(subjectEvaluator + " returns type " + subjectEvaluator.getResultType() + " but expected to get " + ResultType.STRING +
@@ -865,12 +900,29 @@ public class ExpressionCompiler {
case STRING:
case NUMBER:
case DATE:
+ case INSTANT:
return addToken(toDecimalEvaluator(subjectEvaluator), "toDecimal");
default:
throw new AttributeExpressionLanguageParsingException(subjectEvaluator + " returns type " + subjectEvaluator.getResultType() + " but expected to get " + ResultType.STRING +
", " + ResultType.WHOLE_NUMBER + ", or " + ResultType.DATE);
}
}
+ case TO_MICROS: {
+ verifyArgCount(argEvaluators, 0, "toMicros");
+ if (subjectEvaluator.getResultType() == ResultType.INSTANT) {
+ return addToken(new EpochTimeEvaluator(ChronoUnit.MICROS, subjectEvaluator), "toMicros");
+ } else {
+ throw new AttributeExpressionLanguageParsingException(subjectEvaluator + " returns type " + subjectEvaluator.getResultType() + " but expected to get " + ResultType.INSTANT);
+ }
+ }
+ case TO_NANOS: {
+ verifyArgCount(argEvaluators, 0, "toNanos");
+ if (subjectEvaluator.getResultType() == ResultType.INSTANT) {
+ return addToken(new EpochTimeEvaluator(ChronoUnit.NANOS, subjectEvaluator), "toNanos");
+ } else {
+ throw new AttributeExpressionLanguageParsingException(subjectEvaluator + " returns type " + subjectEvaluator.getResultType() + " but expected to get " + ResultType.INSTANT);
+ }
+ }
case TO_RADIX: {
if (argEvaluators.size() == 1) {
return addToken(new ToRadixEvaluator(toWholeNumberEvaluator(subjectEvaluator),
@@ -930,6 +982,13 @@ public class ExpressionCompiler {
throw new AttributeExpressionLanguageParsingException("format() function takes 1 or 2 arguments");
}
}
+ case FORMAT_INSTANT: {
+ if (argEvaluators.size() == 2) {
+ return addToken(new InstantFormatEvaluator(toInstantEvaluator(subjectEvaluator), toStringEvaluator(argEvaluators.get(0)), toStringEvaluator(argEvaluators.get(1))), "format");
+ } else {
+ throw new AttributeExpressionLanguageParsingException("format() function takes 2 arguments");
+ }
+ }
case OR: {
return addToken(new OrEvaluator(toBooleanEvaluator(subjectEvaluator), toBooleanEvaluator(argEvaluators.get(0))), "or");
}
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/InstantEvaluator.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/InstantEvaluator.java
new file mode 100644
index 0000000000..2bcad0658f
--- /dev/null
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/InstantEvaluator.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.attribute.expression.language.evaluation;
+
+import org.apache.nifi.attribute.expression.language.EvaluationContext;
+import org.apache.nifi.expression.AttributeExpression;
+
+import java.time.Instant;
+
+public abstract class InstantEvaluator implements Evaluator<Instant> {
+ private String token;
+
+ @Override
+ public AttributeExpression.ResultType getResultType() {
+ return AttributeExpression.ResultType.INSTANT;
+ }
+
+ @Override
+ public int getEvaluationsRemaining(final EvaluationContext context) {
+ return 0;
+ }
+
+ @Override
+ public String getToken() {
+ return token;
+ }
+
+ @Override
+ public void setToken(final String token) {
+ this.token = token;
+ }
+}
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/InstantQueryResult.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/InstantQueryResult.java
new file mode 100644
index 0000000000..96456d9b35
--- /dev/null
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/InstantQueryResult.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.attribute.expression.language.evaluation;
+
+import org.apache.nifi.expression.AttributeExpression;
+
+import java.time.Instant;
+
+public class InstantQueryResult implements QueryResult<Instant> {
+
+ private final Instant instant;
+
+ public InstantQueryResult(final Instant instant) {
+ this.instant = instant;
+ }
+
+ @Override
+ public Instant getValue() {
+ return instant;
+ }
+
+ @Override
+ public AttributeExpression.ResultType getResultType() {
+ return AttributeExpression.ResultType.INSTANT;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(getValue());
+ }
+}
\ No newline at end of file
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/EpochTimeEvaluator.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/EpochTimeEvaluator.java
new file mode 100644
index 0000000000..91807b8911
--- /dev/null
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/EpochTimeEvaluator.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.attribute.expression.language.evaluation.cast;
+
+import org.apache.nifi.attribute.expression.language.EvaluationContext;
+import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.InstantQueryResult;
+import org.apache.nifi.attribute.expression.language.evaluation.NumberEvaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.NumberQueryResult;
+import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
+import org.apache.nifi.expression.AttributeExpression;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+public class EpochTimeEvaluator extends NumberEvaluator {
+
+ private final ChronoUnit chronoUnit;
+ private final Evaluator<?> subjectEvaluator;
+
+ public EpochTimeEvaluator(final ChronoUnit chronoUnit, final Evaluator<?> subjectEvaluator) {
+ this.chronoUnit = chronoUnit;
+ this.subjectEvaluator = subjectEvaluator;
+ }
+
+ @Override
+ public QueryResult<Number> evaluate(EvaluationContext evaluationContext) {
+ final QueryResult<?> result = subjectEvaluator.evaluate(evaluationContext);
+ if (result.getValue() == null) {
+ return new NumberQueryResult(null);
+ }
+
+ if (result.getResultType() == AttributeExpression.ResultType.INSTANT) {
+ final Instant instant = ((InstantQueryResult) result).getValue();
+ long time = chronoUnit.between(Instant.EPOCH, instant);
+ return new NumberQueryResult(time);
+ }
+ return new NumberQueryResult(null);
+ }
+
+ @Override
+ public Evaluator<?> getSubjectEvaluator() {
+ return subjectEvaluator;
+ }
+}
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/InstantCastEvaluator.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/InstantCastEvaluator.java
new file mode 100644
index 0000000000..32372142f1
--- /dev/null
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/InstantCastEvaluator.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.attribute.expression.language.evaluation.cast;
+
+import org.apache.nifi.attribute.expression.language.EvaluationContext;
+import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.InstantEvaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.InstantQueryResult;
+import org.apache.nifi.attribute.expression.language.evaluation.NumberQueryResult;
+import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
+import org.apache.nifi.attribute.expression.language.evaluation.StringQueryResult;
+import org.apache.nifi.attribute.expression.language.exception.AttributeExpressionLanguageException;
+import org.apache.nifi.attribute.expression.language.exception.AttributeExpressionLanguageParsingException;
+import org.apache.nifi.expression.AttributeExpression;
+
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
+import java.util.regex.Pattern;
+
+public class InstantCastEvaluator extends InstantEvaluator {
+
+ public static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+");
+
+ private final Evaluator<?> subjectEvaluator;
+
+ public InstantCastEvaluator(final Evaluator<?> subjectEvaluator) {
+ if (subjectEvaluator.getResultType() == AttributeExpression.ResultType.BOOLEAN) {
+ throw new AttributeExpressionLanguageParsingException("Cannot implicitly convert Data Type " + subjectEvaluator.getResultType() + " to " + AttributeExpression.ResultType.INSTANT);
+ }
+
+ this.subjectEvaluator = subjectEvaluator;
+ }
+
+ @Override
+ public QueryResult<Instant> evaluate(final EvaluationContext evaluationContext) {
+ final QueryResult<?> result = subjectEvaluator.evaluate(evaluationContext);
+ if (result.getValue() == null) {
+ return new InstantQueryResult(null);
+ }
+
+ switch (result.getResultType()) {
+ case INSTANT:
+ return (InstantQueryResult) result;
+ case STRING:
+ final String value = ((StringQueryResult) result).getValue().trim();
+ if (NUMBER_PATTERN.matcher(value).matches()) {
+ return new InstantQueryResult(Instant.ofEpochMilli(Long.parseLong(value)));
+ }
+ final DateTimeFormatter dtf = new DateTimeFormatterBuilder()
+ .appendOptional(DateTimeFormatter.ISO_INSTANT)
+ .appendOptional(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
+ .appendOptional(DateTimeFormatter.RFC_1123_DATE_TIME)
+ .toFormatter();
+ try {
+ Instant instant = dtf.parse(value, Instant::from);
+ return new InstantQueryResult(instant);
+ } catch (DateTimeParseException e) {
+ throw new AttributeExpressionLanguageException("Could not implicitly convert input to INSTANT: " + value);
+ }
+ case WHOLE_NUMBER:
+ return new InstantQueryResult(Instant.ofEpochMilli((Long) result.getValue()));
+ case DECIMAL:
+ Double resultDouble = (Double) result.getValue();
+ return new InstantQueryResult(Instant.ofEpochMilli(resultDouble.longValue()));
+ case NUMBER:
+ final Number numberValue = ((NumberQueryResult) result).getValue();
+ return new InstantQueryResult(Instant.ofEpochMilli(numberValue.longValue()));
+ default:
+ return new InstantQueryResult(null);
+ }
+ }
+
+ @Override
+ public Evaluator<?> getSubjectEvaluator() {
+ return subjectEvaluator;
+ }
+}
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/NumberCastEvaluator.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/NumberCastEvaluator.java
index 5de82a1bc3..45af533fc1 100644
--- a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/NumberCastEvaluator.java
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/NumberCastEvaluator.java
@@ -20,6 +20,7 @@ import org.apache.nifi.attribute.expression.language.EvaluationContext;
import org.apache.nifi.attribute.expression.language.evaluation.DateQueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.DecimalQueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.InstantQueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.NumberEvaluator;
import org.apache.nifi.attribute.expression.language.evaluation.NumberQueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
@@ -59,14 +60,14 @@ public class NumberCastEvaluator extends NumberEvaluator {
case STRING:
final String trimmed = ((StringQueryResult) result).getValue().trim();
NumberParsing.ParseResultType parseType = NumberParsing.parse(trimmed);
- switch (parseType){
+ switch (parseType) {
case DECIMAL:
return new NumberQueryResult(Double.valueOf(trimmed));
case WHOLE_NUMBER:
Long resultValue;
try {
resultValue = Long.valueOf(trimmed);
- } catch (NumberFormatException e){
+ } catch (NumberFormatException e) {
// Will only occur if trimmed is a hex number
resultValue = Long.decode(trimmed);
}
@@ -77,6 +78,8 @@ public class NumberCastEvaluator extends NumberEvaluator {
}
case DATE:
return new NumberQueryResult(((DateQueryResult) result).getValue().getTime());
+ case INSTANT:
+ return new NumberQueryResult(((InstantQueryResult) result).getValue().toEpochMilli());
default:
return new NumberQueryResult(null);
}
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/WholeNumberCastEvaluator.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/WholeNumberCastEvaluator.java
index d74ff18072..3a26c38e80 100644
--- a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/WholeNumberCastEvaluator.java
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/cast/WholeNumberCastEvaluator.java
@@ -20,6 +20,7 @@ import org.apache.nifi.attribute.expression.language.EvaluationContext;
import org.apache.nifi.attribute.expression.language.evaluation.DateQueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.DecimalQueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.InstantQueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.NumberQueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.StringQueryResult;
@@ -72,6 +73,8 @@ public class WholeNumberCastEvaluator extends WholeNumberEvaluator {
}
case DATE:
return new WholeNumberQueryResult(((DateQueryResult) result).getValue().getTime());
+ case INSTANT:
+ return new WholeNumberQueryResult(((InstantQueryResult) result).getValue().toEpochMilli());
case DECIMAL:
final Double resultValue = ((DecimalQueryResult) result).getValue();
return new WholeNumberQueryResult(resultValue.longValue());
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/InstantFormatEvaluator.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/InstantFormatEvaluator.java
new file mode 100644
index 0000000000..a8453ae9be
--- /dev/null
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/InstantFormatEvaluator.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.attribute.expression.language.evaluation.functions;
+
+import org.apache.nifi.attribute.expression.language.EvaluationContext;
+import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.InstantEvaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
+import org.apache.nifi.attribute.expression.language.evaluation.StringEvaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.StringQueryResult;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+public class InstantFormatEvaluator extends StringEvaluator {
+
+ private final InstantEvaluator subject;
+ private final Evaluator<String> format;
+ private final Evaluator<String> timeZone;
+
+ public InstantFormatEvaluator(final InstantEvaluator subject, final Evaluator<String> format, final Evaluator<String> timeZone) {
+ this.subject = subject;
+ this.format = format;
+ this.timeZone = timeZone;
+ }
+
+ @Override
+ public QueryResult<String> evaluate(final EvaluationContext evaluationContext) {
+ final Instant subjectValue = subject.evaluate(evaluationContext).getValue();
+ if (subjectValue == null) {
+ return new StringQueryResult(null);
+ }
+
+ final QueryResult<String> formatResult = format.evaluate(evaluationContext);
+ final String format = formatResult.getValue();
+ if (format == null) {
+ return null;
+ }
+
+
+ final QueryResult<String> tzResult = timeZone.evaluate(evaluationContext);
+ final String tz = tzResult.getValue();
+
+ DateTimeFormatter dtf = DateTimeFormatter.ofPattern(format, Locale.US)
+ .withZone(ZoneId.of(tz));
+
+ return new StringQueryResult(dtf.format(subjectValue));
+ }
+
+ @Override
+ public Evaluator<?> getSubjectEvaluator() {
+ return subject;
+ }
+
+}
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/NumberToInstantEvaluator.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/NumberToInstantEvaluator.java
new file mode 100644
index 0000000000..683c07567b
--- /dev/null
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/NumberToInstantEvaluator.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.attribute.expression.language.evaluation.functions;
+
+import org.apache.nifi.attribute.expression.language.EvaluationContext;
+import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.InstantEvaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.InstantQueryResult;
+import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
+
+import java.time.Instant;
+
+public class NumberToInstantEvaluator extends InstantEvaluator {
+
+ private final Evaluator<Long> subject;
+
+ public NumberToInstantEvaluator(final Evaluator<Long> subject) {
+ this.subject = subject;
+ }
+
+ @Override
+ public QueryResult<Instant> evaluate(final EvaluationContext evaluationContext) {
+ final QueryResult<Long> result = subject.evaluate(evaluationContext);
+ final Long value = result.getValue();
+ if (value == null) {
+ return new InstantQueryResult(null);
+ }
+
+ return new InstantQueryResult(Instant.ofEpochMilli(value));
+ }
+
+ @Override
+ public Evaluator<?> getSubjectEvaluator() {
+ return subject;
+ }
+
+
+}
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/StringToInstantEvaluator.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/StringToInstantEvaluator.java
new file mode 100644
index 0000000000..8147f4f51f
--- /dev/null
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/evaluation/functions/StringToInstantEvaluator.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.attribute.expression.language.evaluation.functions;
+
+import org.apache.nifi.attribute.expression.language.EvaluationContext;
+import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.InstantEvaluator;
+import org.apache.nifi.attribute.expression.language.evaluation.InstantQueryResult;
+import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
+import org.apache.nifi.attribute.expression.language.exception.IllegalAttributeException;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+public class StringToInstantEvaluator extends InstantEvaluator {
+
+ private final Evaluator<String> subject;
+ private final Evaluator<String> format;
+ private final Evaluator<String> timeZone;
+
+ public StringToInstantEvaluator(final Evaluator<String> subject, final Evaluator<String> format, final Evaluator<String> timeZone) {
+ this.subject = subject;
+ this.format = format;
+ this.timeZone = timeZone;
+ }
+
+ @Override
+ public QueryResult<Instant> evaluate(final EvaluationContext evaluationContext) {
+ final String subjectValue = subject.evaluate(evaluationContext).getValue();
+ final String formatValue = format.evaluate(evaluationContext).getValue();
+ if (subjectValue == null || formatValue == null) {
+ return new InstantQueryResult(null);
+ }
+
+ final QueryResult<String> tzResult = timeZone.evaluate(evaluationContext);
+ final String tz = tzResult.getValue();
+
+ DateTimeFormatter dtf = DateTimeFormatter.ofPattern(formatValue, Locale.US)
+ .withZone(ZoneId.of(tz));
+
+ try {
+ return new InstantQueryResult(dtf.parse(subjectValue, Instant::from));
+ } catch (final IllegalArgumentException e) {
+ throw new IllegalAttributeException("Invalid instant format: " + formatValue);
+ }
+ }
+
+ @Override
+ public Evaluator<?> getSubjectEvaluator() {
+ return subject;
+ }
+}
diff --git a/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java b/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java
index 5ff24b71e7..87f7e782e5 100644
--- a/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java
+++ b/nifi-commons/nifi-expression-language/src/test/java/org/apache/nifi/attribute/expression/language/TestQuery.java
@@ -38,6 +38,10 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
@@ -48,6 +52,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
import static java.lang.Double.NEGATIVE_INFINITY;
import static java.lang.Double.NaN;
@@ -288,6 +293,56 @@ public class TestQuery {
assertEquals(1384788147678L, result.getValue());
}
+ @Test
+ public void testInstantToNumber() {
+ final Query query = Query.compile("${dateTime:toInstant('yyyy/MM/dd HH:mm:ss.SSS', 'America/New_York'):toNumber()}");
+ final Map<String, String> attributes = new HashMap<>();
+ attributes.put("dateTime", "2013/11/18 10:22:27.678");
+
+ final QueryResult<?> result = query.evaluate(new StandardEvaluationContext(attributes));
+ assertEquals(ResultType.WHOLE_NUMBER, result.getResultType());
+ assertEquals(1384788147678L, result.getValue());
+ }
+
+ @Test
+ public void testInstantToNanos() {
+ final String pattern = "yyyy/MM/dd HH:mm:ss.SSSSSSSSS";
+ final String dateTime = "2022/03/18 10:22:27.678234567";
+ final String zoneId = "America/New_York";
+
+ final Query query = Query.compile(String.format("${dateTime:toInstant('%s', '%s'):toNanos()}", pattern, zoneId));
+ final Map<String, String> attributes = new HashMap<>();
+ attributes.put("dateTime", dateTime);
+
+ final Instant instant = DateTimeFormatter
+ .ofPattern(pattern)
+ .withZone(ZoneId.of(zoneId))
+ .parse(dateTime, Instant::from);
+
+ final QueryResult<?> result = query.evaluate(new StandardEvaluationContext(attributes));
+ assertEquals(ResultType.NUMBER, result.getResultType());
+ assertEquals(TimeUnit.SECONDS.toNanos(instant.getEpochSecond()) + instant.getNano(), result.getValue());
+ }
+
+ @Test
+ public void testInstantToMicros() {
+ final String pattern = "yyyy/MM/dd HH:mm:ss.SSSSSSSSS";
+ final String dateTime = "2022/03/18 10:22:27.678234567";
+ final String zoneId = "America/New_York";
+ final Query query = Query.compile(String.format("${dateTime:toInstant('%s', '%s'):toMicros()}", pattern, zoneId));
+ final Map<String, String> attributes = new HashMap<>();
+ attributes.put("dateTime", dateTime);
+
+ final Instant instant = DateTimeFormatter
+ .ofPattern(pattern)
+ .withZone(ZoneId.of(zoneId))
+ .parse(dateTime, Instant::from);
+
+ final QueryResult<?> result = query.evaluate(new StandardEvaluationContext(attributes));
+ assertEquals(ResultType.NUMBER, result.getResultType());
+ assertEquals(ChronoUnit.MICROS.between(Instant.EPOCH, instant), result.getValue());
+ }
+
@Test
public void testAddOneDayToDate() {
final Map<String, String> attributes = new HashMap<>();
@@ -297,6 +352,17 @@ public class TestQuery {
verifyEquals("${dateTime:toDate('yyyy/MM/dd HH:mm:ss.SSS'):plus(86400000):format('yyyy/MM/dd HH:mm:ss.SSS')}", attributes, "2013/11/19 10:22:27.678");
}
+ @Test
+ public void testAddOneDayToInstant() {
+ final Map<String, String> attributes = new HashMap<>();
+ attributes.put("dateTime", "2013/11/18 10:22:27.678");
+
+ verifyEquals("${dateTime:toInstant('yyyy/MM/dd HH:mm:ss.SSS', 'America/New_York'):toNumber():plus(86400000)" +
+ ":toInstant('yyyy/MM/dd HH:mm:ss.SSS', 'America/New_York'):formatInstant('yyyy/MM/dd HH:mm:ss.SSS', 'America/New_York')}", attributes, "2013/11/19 10:22:27.678");
+ verifyEquals("${dateTime:toInstant('yyyy/MM/dd HH:mm:ss.SSS', 'America/New_York'):plus(86400000)" +
+ ":format('yyyy/MM/dd HH:mm:ss.SSS', 'America/New_York')}", attributes, "2013/11/19 10:22:27.678");
+ }
+
@Test
@Disabled("Requires specific locale")
public void implicitDateConversion() {
@@ -317,6 +383,23 @@ public class TestQuery {
assertEquals(formatted, result.getValue());
}
+ @Test
+ @Disabled("Requires specific locale")
+ public void implicitInstantConversion() {
+ final Instant instant = Instant.now();
+ final Query query = Query.compile("${dateTime:formatInstant('yyyy/MM/dd HH:mm:ss.SSS', 'America/New_York')}");
+ final Map<String, String> attributes = new HashMap<>();
+ attributes.put("dateTime", instant.toString());
+
+ final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.of("America/New_York"));
+ final String formatted = dtf.format(instant);
+
+ final QueryResult<?> result = query.evaluate(new StandardEvaluationContext(attributes));
+ assertEquals(ResultType.STRING, result.getResultType());
+ assertEquals(formatted, result.getValue());
+ }
+
@Test
public void testEmbeddedExpressionsAndQuotes() {
final Map<String, String> attributes = new HashMap<>();
@@ -858,6 +941,26 @@ public class TestQuery {
assertEquals(1, ranges.size());
}
+ @Test
+ public void testInstantEscapeQuotes() {
+ final long timestamp = 1403620278642L;
+ final Map<String, String> attributes = new HashMap<>();
+ attributes.put("date", String.valueOf(timestamp));
+
+ final String format = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
+
+ final String query = "startDateTime=\"${date:toNumber():toInstant('yyyy/MM/dd HH:mm:ss.SSS', 'America/New_York'):formatInstant(\"" + format + "\", 'America/New_York')}\"";
+ final String result = Query.evaluateExpressions(query, attributes, null);
+
+ final String expectedTime = DateTimeFormatter.ofPattern(format, Locale.US)
+ .withZone(ZoneId.of("America/New_York"))
+ .format(Instant.ofEpochMilli(timestamp));
+ assertEquals("startDateTime=\"" + expectedTime + "\"", result);
+
+ final List<Range> ranges = Query.extractExpressionRanges(query);
+ assertEquals(1, ranges.size());
+ }
+
@Test
public void testDateConversion() {
final Map<String, String> attributes = new HashMap<>();
@@ -871,6 +974,19 @@ public class TestQuery {
verifyEquals("${date:toDate():toNumber():toDate():toNumber():toDate():toNumber():format('yyyy')}", attributes, "2014");
}
+ @Test
+ public void testInstantConversion() {
+ final Map<String, String> attributes = new HashMap<>();
+ attributes.put("instant", "1403620278642");
+
+ verifyEquals("${instant:formatInstant('yyyy', 'America/New_York')}", attributes, "2014");
+ verifyEquals("${instant:toInstant():formatInstant('yyyy', 'America/New_York')}", attributes, "2014");
+ verifyEquals("${instant:toNumber():formatInstant('yyyy', 'America/New_York')}", attributes, "2014");
+ verifyEquals("${instant:toNumber():toInstant():formatInstant('yyyy', 'America/New_York')}", attributes, "2014");
+ verifyEquals("${instant:toInstant():toNumber():formatInstant('yyyy', 'America/New_York')}", attributes, "2014");
+ verifyEquals("${instant:toInstant():toNumber():toInstant():toNumber():toInstant():toNumber():formatInstant('yyyy', 'America/New_York')}", attributes, "2014");
+ }
+
@Test
public void testSingleLetterAttribute() {
final Map<String, String> attributes = new HashMap<>();
@@ -1183,6 +1299,39 @@ public class TestQuery {
verifyEquals("${year:append('/'):append(${month}):append('/'):append(${day}):toDate('yyyy/MM/dd'):format('D')}", attributes, "63");
}
+ @Test
+ public void testInstant() {
+ final Calendar now = Calendar.getInstance();
+ final int year = now.get(Calendar.YEAR);
+ final Map<String, String> attributes = new HashMap<>();
+ attributes.put("entryDate", String.valueOf(now.getTimeInMillis()));
+
+ verifyEquals("${entryDate:toNumber():toInstant():formatInstant('yyyy', 'America/New_York')}", attributes, String.valueOf(year));
+
+ // test for not existing attribute (NIFI-1962)
+ assertEquals("", Query.evaluateExpressions("${notExistingAtt:toInstant()}", attributes, null));
+
+ attributes.clear();
+ attributes.put("month", "3");
+ attributes.put("day", "4");
+ attributes.put("year", "2013");
+ attributes.put("hour", "16");
+ attributes.put("minute", "22");
+ attributes.put("second", "59");
+
+ assertEquals("63", Query.evaluateExpressions("${year:append('/'):append(${month}):append('/'):append(${day}):append(' ')" +
+ ":append(${hour}):append(':'):append(${minute}):append(':'):append(${second}):toInstant('yyyy/M/d HH:mm:ss', 'America/New_York')" +
+ ":formatInstant('D', 'America/New_York')}", attributes, null));
+
+ assertEquals("63", Query.evaluateExpressions("${year:append('/'):append('${month}'):append('/'):append('${day}'):append(' ')" +
+ ":append(${hour}):append(':'):append(${minute}):append(':'):append(${second}):toInstant('yyyy/M/d HH:mm:ss', 'America/New_York')" +
+ ":formatInstant('D', 'America/New_York')}", attributes, null));
+
+ verifyEquals("${year:append('/'):append(${month}):append('/'):append(${day}):append(' ')" +
+ ":append(${hour}):append(':'):append(${minute}):append(':'):append(${second}):toInstant('yyyy/M/d HH:mm:ss', 'America/New_York')" +
+ ":formatInstant('D', 'America/New_York')}", attributes, "63");
+ }
+
@Test
public void testAnyAttribute() {
final Map<String, String> attributes = new HashMap<>();
@@ -1693,6 +1842,15 @@ public class TestQuery {
verifyEquals("${blue:toDate('yyyyMMddHHmmss', 'GMT'):format(\"yyyy/MM/dd HH:mm:ss.SSS'Z'\", 'America/Los_Angeles')}", attributes, "2013/09/17 09:26:43.000Z");
}
+ @Test
+ public void testInstantFormatConversion() {
+ final Map<String, String> attributes = new HashMap<>();
+ attributes.put("blue", "20130917162643");
+ verifyEquals("${blue:toInstant('yyyyMMddHHmmss', 'GMT'):formatInstant(\"yyyy/MM/dd HH:mm:ss.SSS'Z'\", 'GMT')}", attributes, "2013/09/17 16:26:43.000Z");
+ verifyEquals("${blue:toInstant('yyyyMMddHHmmss', 'GMT'):formatInstant(\"yyyy/MM/dd HH:mm:ss.SSS'Z'\", 'Europe/Paris')}", attributes, "2013/09/17 18:26:43.000Z");
+ verifyEquals("${blue:toInstant('yyyyMMddHHmmss', 'GMT'):formatInstant(\"yyyy/MM/dd HH:mm:ss.SSS'Z'\", 'America/Los_Angeles')}", attributes, "2013/09/17 09:26:43.000Z");
+ }
+
@Test
public void testNot() {
verifyEquals("${ab:notNull():not()}", new HashMap<String, String>(), true);
diff --git a/nifi-docs/src/main/asciidoc/expression-language-guide.adoc b/nifi-docs/src/main/asciidoc/expression-language-guide.adoc
index 67a95c4619..173100efbf 100644
--- a/nifi-docs/src/main/asciidoc/expression-language-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/expression-language-guide.adoc
@@ -2233,8 +2233,43 @@ In order to run the correct method, the parameter types must be correct. The Exp
|============================================================================
+[[formatInstant]]
+[.function]
+=== formatInstant
+
+*Description*: [.description]#Formats an Instant, number or string according to the format specified by the argument.
+
+The first argument must be a String that is a valid Java DateTimeFormatter format.
+The second argument must be a String that is a valid time zone.
+The Subject is expected to be an Instant, String or Number that represents the number of milliseconds since Midnight GMT on January 1, 1970 or a String with one of the following
+
+DateTimeFormatter syntaxes:#
+
+- _ISO_OFFSET_ such as '2011-12-03T10:15:30+01:00'
+- _ISO_INSTANT_ such as '2011-12-03T10:15:30Z'
+- _RFC_1123_ such as 'Thu, 01 Dec 1994 16:00:00 GMT'
+
+*Subject Type*: [.subject]#Instant or Number or String#
+
+*Arguments*:
+
+- [.argName]#_format_# : [.argDesc]#The format to use in the Java DateTimeFormatter syntax#
+- [.argName]#_time zone_# : [.argDesc]#Optional argument that specifies the time zone to use (in the Java TimeZone syntax)#
+
+*Return Type*: [.returnType]#String#
+*Examples*: If the attribute "time" has the value "1647884009479", timeIsoOffset has the value "2022-12-03T10:15:30+01:00",
+timeIsoInstant has the value "2022-12-03T10:15:30Z" and "timeRFC1123" has the value "Thu, 01 Dec 2022 16:00:00 GMT",
+then the following Expressions will yield the following results:
+.format Examples
+|============================================================================
+| Expression | Value
+| `${time:formatInstant("yyyy/MM/dd HH:mm:ss.SSS\'Z'", "GMT")}` | `2022/03/21 17:33:29.479Z`
+| `${timeIsoOffset:formatInstant("yyyy/MM/dd HH:mm:ss.SSS\'Z'", "Asia/Tokyo")}` | `2022/12/03 18:15:30.000Z`
+| `${timeIsoInstant:formatInstant("yyyy/MM/dd", "Asia/Tokyo")}` | `2022/12/03 19:15:30.000Z`
+| `${timeRFC1123:formatInstant("yyyy/MM/dd HH:mm:ss.SSS\'Z'", "GMT")}` | `2022/12/01 16:00:00.000Z`
+|============================================================================
[.function]
=== toDate
@@ -2264,6 +2299,61 @@ chaining together the two functions: `${date:toDate('MM-dd-yyyy'):format('yyyy/M
+[.function]
+=== toInstant
+
+*Description*: [.description]#Converts a String or Number into an Instant data type, based on the format specified by the argument. In case of
+String format nanosecond precision date parsing is supported. The argument
+must be a String that is a valid Java DateTimeFormatter syntax. The Subject is expected to be a String that is formatted
+according the argument or a number which represents the datetime in milliseconds. The datetime will be evaluated using the
+time zone specified in the second argument.#
+
+*Subject Type*: [.subject]#String or Number#
+
+*Arguments*:
+
+- [.argName]#_format_# : [.argDesc]#The current format to use when parsing the Subject, in the Java DateTimeFormatter syntax.#
+- [.argName]#_time zone_# : [.argDesc]#Required argument that specifies the time zone to use when parsing the Subject, in the Java TimeZone syntax.#
+
+
+*Return Type*: [.returnType]#Instant#
+
+*Examples*: If the attribute "time" has the value "2014/12/31 15:36:03.264Z" then the expression `${time:toInstant("yyyy/MM/dd HH:mm:ss.SSS'Z'", "GMT")}` will result in an Instant data type for
+15:36:03.264 GMT on December 31, 2014.
+
+Often, this function is used in conjunction with the <<formatInstant>> function to change the format of a date/time. For example,
+if the attribute "dateTime" has the value "12-24-2014 12:06:59" and we want to change the format to "2014/12/24", we can do so by
+chaining together the two functions: `${dateTime:toInstant('MM-dd-yyyy HH:mm:ss', 'GMT'):formatInstant('yyyy/MM/dd', 'GMT')}`.
+
+[.function]
+=== toMicros
+
+*Description*: [.description]#Converts an Instant Subject into a Number with microsecond precision#
+
+*Subject Type*: [.subject]#Instant#
+
+*Arguments*: No arguments
+
+*Return Type*: [.returnType]#Number#
+
+*Examples*: If the "dateTime" attribute has the value of "2022/03/18 10:22:27.678234567", then the Expression
+`${dateTime:toInstant('yyyy/MM/dd HH:mm:ss.SSSSSSSSS', 'America/New_York'):toMicros()}` converts the Instant value of
+the attribute to 1647613347678234 microseconds since epoch.
+
+[.function]
+=== toNanos
+
+*Description*: [.description]#Converts an Instant Subject into a Number with nanosecond precision#
+
+*Subject Type*: [.subject]#Instant#
+
+*Arguments*: No arguments
+
+*Return Type*: [.returnType]#Number#
+
+*Examples*: If the "dateTime" attribute has the value of "2022/03/18 10:22:27.678234567", then the Expression
+`${dateTime:toInstant('yyyy/MM/dd HH:mm:ss.SSSSSSSSS', 'America/New_York'):toNanos()}` converts the Instant value of
+the attribute to 1647613347678234567 nanoseconds since epoch.
[.function]
=== now