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