This is an automated email from the ASF dual-hosted git repository.

MaxGekk pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new 967895698e0f [SPARK-57528][SQL] Support nanosecond-precision 
timestamps in the `unix_timestamp` function
967895698e0f is described below

commit 967895698e0f30209302aeede069a543ceca61ba
Author: Maxim Gekk <[email protected]>
AuthorDate: Thu Jun 18 21:09:27 2026 +0200

    [SPARK-57528][SQL] Support nanosecond-precision timestamps in the 
`unix_timestamp` function
    
    ### What changes were proposed in this pull request?
    This PR allows `unix_timestamp` / `to_unix_timestamp` to accept the 
nanosecond-precision timestamp types `TIMESTAMP_LTZ(p)` / `TIMESTAMP_NTZ(p)` 
(`p in [7, 9]`, i.e. `AnyTimestampNanoType`) in their timestamp-argument form. 
The result stays whole-second `BIGINT`; the sub-second digits are dropped.
    
    Concretely:
    - Extends the `UnixTime` base (`UnixTimestamp` / `ToUnixTimestamp`) input 
typing to accept `AnyTimestampNanoType` alongside the existing string / date / 
microsecond-timestamp types.
    - Reads `epochMicros` from `TimestampNanosVal` in the timestamp branch of 
both the interpreted (`eval`) and codegen (`doGenCode`) paths, dividing by 
`MICROS_PER_SECOND` exactly like the existing microsecond/NTZ path (plain 
integer division, truncation toward zero), so nanos and micro types behave 
identically.
    - Adds catalyst unit tests (interpreted + codegen), Scala/Java Column API 
end-to-end tests, and SQL golden-file coverage for `TIMESTAMP_NTZ(p)` / 
`TIMESTAMP_LTZ(p)`.
    
    The string-parsing overload `unix_timestamp(str, fmt)` producing nanosecond 
precision is out of scope and tracked separately under Parsing/Formatting.
    
    ### Why are the changes needed?
    Part of the 
[SPARK-56822](https://issues.apache.org/jira/browse/SPARK-56822) umbrella 
(timestamps with nanosecond precision). `unix_timestamp` already accepts 
microsecond timestamp families but rejected the new nanosecond-precision 
timestamp types, leaving valid conversions unsupported.
    
    ### Does this PR introduce _any_ user-facing change?
    Yes. `unix_timestamp(timeExp)` / `to_unix_timestamp(timeExp)` now accept 
`TIMESTAMP_LTZ(p)` / `TIMESTAMP_NTZ(p)` and return the whole-second `BIGINT`. 
This is a change only within the unreleased nanosecond-timestamp preview; 
existing microsecond / date / string behavior is unchanged.
    
    Example:
    
    ```sql
    SELECT unix_timestamp(TIMESTAMP_LTZ '2008-12-25 15:30:00.123456789');
    -- 1230219000
    ```
    
    ### How was this patch tested?
    - `build/sbt 'catalyst/testOnly 
org.apache.spark.sql.catalyst.expressions.DateExpressionsSuite'`
    - `build/sbt 'sql/testOnly 
org.apache.spark.sql.TimestampNanosFunctionsAnsiOnSuite 
org.apache.spark.sql.TimestampNanosFunctionsAnsiOffSuite'`
    - `SPARK_GENERATE_GOLDEN_FILES=1 build/sbt 'sql/testOnly 
org.apache.spark.sql.SQLQueryTestSuite -- -z "nanos"'`
    
    ### Was this patch authored or co-authored using generative AI tooling?
    Generated-by: Cursor
    
    Closes #56593 from MaxGekk/nanos-unix_timestamp.
    
    Authored-by: Maxim Gekk <[email protected]>
    Signed-off-by: Max Gekk <[email protected]>
    (cherry picked from commit 98266a39df2693f2604575b5030cf3222d722543)
    Signed-off-by: Max Gekk <[email protected]>
---
 .../catalyst/expressions/datetimeExpressions.scala | 24 ++++++++++
 .../expressions/DateExpressionsSuite.scala         | 45 +++++++++++++++++
 .../analyzer-results/timestamp-ltz-nanos.sql.out   | 49 +++++++++++++++++++
 .../analyzer-results/timestamp-ntz-nanos.sql.out   | 42 ++++++++++++++++
 .../sql-tests/inputs/timestamp-ltz-nanos.sql       | 14 ++++++
 .../sql-tests/inputs/timestamp-ntz-nanos.sql       | 12 +++++
 .../sql-tests/results/timestamp-ltz-nanos.sql.out  | 56 ++++++++++++++++++++++
 .../sql-tests/results/timestamp-ntz-nanos.sql.out  | 48 +++++++++++++++++++
 .../sql/TimestampNanosFunctionsSuiteBase.scala     | 48 +++++++++++++++++++
 9 files changed, 338 insertions(+)

diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/datetimeExpressions.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/datetimeExpressions.scala
index f7d4ae2364bf..bdbc071e576c 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/datetimeExpressions.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/datetimeExpressions.scala
@@ -1429,6 +1429,8 @@ abstract class ToTimestamp
           daysToMicros(t.asInstanceOf[Int], zoneId) / downScaleFactor
         case TimestampType | TimestampNTZType =>
           t.asInstanceOf[Long] / downScaleFactor
+        case _: AnyTimestampNanoType =>
+          t.asInstanceOf[TimestampNanosVal].epochMicros / downScaleFactor
         case _: StringType =>
           val fmt = right.eval(input)
           if (fmt == null) {
@@ -1521,6 +1523,15 @@ abstract class ToTimestamp
           if (!${ev.isNull}) {
             ${ev.value} = ${eval1.value} / $downScaleFactor;
           }""")
+      case _: AnyTimestampNanoType =>
+        val eval1 = left.genCode(ctx)
+        ev.copy(code = code"""
+          ${eval1.code}
+          boolean ${ev.isNull} = ${eval1.isNull};
+          $javaType ${ev.value} = ${CodeGenerator.defaultValue(dataType)};
+          if (!${ev.isNull}) {
+            ${ev.value} = ${eval1.value}.epochMicros / $downScaleFactor;
+          }""")
       case DateType =>
         val zid = ctx.addReferenceObj("zoneId", zoneId, 
classOf[ZoneId].getName)
         val dtu = DateTimeUtils.getClass.getName.stripSuffix("$")
@@ -1538,6 +1549,19 @@ abstract class ToTimestamp
 
 abstract class UnixTime extends ToTimestamp {
   override val downScaleFactor: Long = MICROS_PER_SECOND
+
+  // In addition to the input types accepted by the base `ToTimestamp`, the 
timestamp-argument
+  // form of `unix_timestamp` / `to_unix_timestamp` also accepts the 
nanosecond-precision
+  // timestamp types. The result stays whole-second BIGINT, so the sub-second 
digits are dropped.
+  override def inputTypes: Seq[AbstractDataType] =
+    Seq(
+      TypeCollection(
+        StringTypeWithCollation(supportsTrimCollation = true),
+        DateType,
+        TimestampType,
+        TimestampNTZType,
+        AnyTimestampNanoType),
+      StringTypeWithCollation(supportsTrimCollation = true))
 }
 
 /**
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala
index 783e6e856359..20f3dea2ec22 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala
@@ -1067,6 +1067,51 @@ class DateExpressionsSuite extends SparkFunSuite with 
ExpressionEvalHelper {
       ToUnixTimestamp(Literal("2015-07-24"), Literal("\""), UTC_OPT) :: Nil)
   }
 
+  test("SPARK-57528: unix_timestamp / to_unix_timestamp over 
nanosecond-precision timestamps") {
+    import org.apache.spark.sql.catalyst.util.TimestampNanosTestUtils._
+    val fmt = Literal("yyyy-MM-dd HH:mm:ss")
+
+    // A post-epoch value with non-zero sub-second digits: 2008-12-25 
15:30:00.123456789 ->
+    // 1230219000. unix_timestamp does not apply a zone shift, so the NTZ 
wall-clock value and the
+    // LTZ instant at the same UTC reading produce the same whole-second 
result.
+    val ntz = localDateTimeToNanosVal(timestampNTZ(2008, 12, 25, 15, 30, 0, 
123456789))
+    val ltz = 
instantToNanosVal(Instant.parse("2008-12-25T15:30:00.123456789Z"))
+    foreachNanosPrecision { p =>
+      checkEvaluation(
+        UnixTimestamp(Literal.create(ntz, TimestampNTZNanosType(p)), fmt, 
UTC_OPT), 1230219000L)
+      checkEvaluation(
+        ToUnixTimestamp(Literal.create(ntz, TimestampNTZNanosType(p)), fmt, 
UTC_OPT), 1230219000L)
+      checkEvaluation(
+        UnixTimestamp(Literal.create(ltz, TimestampLTZNanosType(p)), fmt, 
UTC_OPT), 1230219000L)
+      checkEvaluation(
+        ToUnixTimestamp(Literal.create(ltz, TimestampLTZNanosType(p)), fmt, 
UTC_OPT), 1230219000L)
+    }
+
+    // The format is ignored for the timestamp-argument form: a deliberately 
invalid format still
+    // yields the same whole-second result, since the timestamp branch never 
consults the format.
+    val badFmt = Literal("not-a-format")
+    foreachNanosPrecision { p =>
+      checkEvaluation(
+        UnixTimestamp(Literal.create(ntz, TimestampNTZNanosType(p)), badFmt, 
UTC_OPT),
+        1230219000L)
+      checkEvaluation(
+        ToUnixTimestamp(Literal.create(ltz, TimestampLTZNanosType(p)), badFmt, 
UTC_OPT),
+        1230219000L)
+    }
+
+    // Pre-epoch sub-second value: 1969-12-31 23:59:59.5 has epochMicros 
-500000, which divides to
+    // 0 (truncation toward zero), matching the existing microsecond-timestamp 
behavior.
+    val preEpoch = localDateTimeToNanosVal(timestampNTZ(1969, 12, 31, 23, 59, 
59, 500000000))
+    checkEvaluation(
+      UnixTimestamp(Literal.create(preEpoch, TimestampNTZNanosType(9)), fmt, 
UTC_OPT), 0L)
+
+    // NULL input.
+    checkEvaluation(
+      UnixTimestamp(Literal.create(null, TimestampNTZNanosType(9)), fmt, 
UTC_OPT), null)
+    checkEvaluation(
+      ToUnixTimestamp(Literal.create(null, TimestampLTZNanosType(9)), fmt, 
UTC_OPT), null)
+  }
+
   test("datediff") {
     checkEvaluation(
       DateDiff(Literal(Date.valueOf("2015-07-24")), 
Literal(Date.valueOf("2015-07-21"))), 3)
diff --git 
a/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ltz-nanos.sql.out
 
b/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ltz-nanos.sql.out
index 8582e5175301..bd98d67a3c5f 100644
--- 
a/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ltz-nanos.sql.out
+++ 
b/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ltz-nanos.sql.out
@@ -647,3 +647,52 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
     "fragment" : "TIMESTAMP_LTZ '2020-01-02 03:04:05.123456789 UTC' + INTERVAL 
'1' MONTH"
   } ]
 }
+
+
+-- !query
+SELECT unix_timestamp(TIMESTAMP_LTZ '2020-01-01 13:24:35.123456789')
+-- !query analysis
+Project [unix_timestamp(2020-01-01 13:24:35.123456789, yyyy-MM-dd HH:mm:ss, 
Some(America/Los_Angeles), true) AS unix_timestamp(TIMESTAMP_LTZ '2020-01-01 
13:24:35.123456789', yyyy-MM-dd HH:mm:ss)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT to_unix_timestamp(TIMESTAMP_LTZ '2020-01-01 13:24:35.123456789')
+-- !query analysis
+Project [to_unix_timestamp(2020-01-01 13:24:35.123456789, yyyy-MM-dd HH:mm:ss, 
Some(America/Los_Angeles), true) AS to_unix_timestamp(TIMESTAMP_LTZ '2020-01-01 
13:24:35.123456789', yyyy-MM-dd HH:mm:ss)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT unix_timestamp(TIMESTAMP_LTZ '2020-01-01 13:24:35.123456789 UTC')
+-- !query analysis
+Project [unix_timestamp(2020-01-01 05:24:35.123456789, yyyy-MM-dd HH:mm:ss, 
Some(America/Los_Angeles), true) AS unix_timestamp(TIMESTAMP_LTZ '2020-01-01 
05:24:35.123456789', yyyy-MM-dd HH:mm:ss)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT to_unix_timestamp('2020-01-01 13:24:35.000000001 UTC' :: 
timestamp_ltz(9))
+-- !query analysis
+Project [to_unix_timestamp(cast(2020-01-01 13:24:35.000000001 UTC as 
timestamp_ltz(9)), yyyy-MM-dd HH:mm:ss, Some(America/Los_Angeles), true) AS 
to_unix_timestamp(CAST(2020-01-01 13:24:35.000000001 UTC AS TIMESTAMP_LTZ(9)), 
yyyy-MM-dd HH:mm:ss)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT unix_timestamp('2020-01-01 13:24:35.999999999' :: timestamp_ltz(7))
+-- !query analysis
+Project [unix_timestamp(cast(2020-01-01 13:24:35.999999999 as 
timestamp_ltz(7)), yyyy-MM-dd HH:mm:ss, Some(America/Los_Angeles), true) AS 
unix_timestamp(CAST(2020-01-01 13:24:35.999999999 AS TIMESTAMP_LTZ(7)), 
yyyy-MM-dd HH:mm:ss)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT unix_timestamp(TIMESTAMP_LTZ '1969-12-31 23:59:59.500000000 UTC')
+-- !query analysis
+Project [unix_timestamp(1969-12-31 15:59:59.5, yyyy-MM-dd HH:mm:ss, 
Some(America/Los_Angeles), true) AS unix_timestamp(TIMESTAMP_LTZ '1969-12-31 
15:59:59.500000000', yyyy-MM-dd HH:mm:ss)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT unix_timestamp(NULL :: timestamp_ltz(9)), to_unix_timestamp(NULL :: 
timestamp_ltz(9))
+-- !query analysis
+Project [unix_timestamp(cast(null as timestamp_ltz(9)), yyyy-MM-dd HH:mm:ss, 
Some(America/Los_Angeles), true) AS unix_timestamp(CAST(NULL AS 
TIMESTAMP_LTZ(9)), yyyy-MM-dd HH:mm:ss)#xL, to_unix_timestamp(cast(null as 
timestamp_ltz(9)), yyyy-MM-dd HH:mm:ss, Some(America/Los_Angeles), true) AS 
to_unix_timestamp(CAST(NULL AS TIMESTAMP_LTZ(9)), yyyy-MM-dd HH:mm:ss)#xL]
++- OneRowRelation
diff --git 
a/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ntz-nanos.sql.out
 
b/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ntz-nanos.sql.out
index f7bf8f3ffd94..5bd6f7e6a76f 100644
--- 
a/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ntz-nanos.sql.out
+++ 
b/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ntz-nanos.sql.out
@@ -574,3 +574,45 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
     "fragment" : "TIMESTAMP_NTZ '2020-01-02 03:04:05.123456789' + INTERVAL '1' 
MONTH"
   } ]
 }
+
+
+-- !query
+SELECT unix_timestamp(TIMESTAMP_NTZ '2020-01-01 13:24:35.123456789')
+-- !query analysis
+Project [unix_timestamp(2020-01-01 13:24:35.123456789, yyyy-MM-dd HH:mm:ss, 
Some(America/Los_Angeles), true) AS unix_timestamp(TIMESTAMP_NTZ '2020-01-01 
13:24:35.123456789', yyyy-MM-dd HH:mm:ss)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT to_unix_timestamp(TIMESTAMP_NTZ '2020-01-01 13:24:35.123456789')
+-- !query analysis
+Project [to_unix_timestamp(2020-01-01 13:24:35.123456789, yyyy-MM-dd HH:mm:ss, 
Some(America/Los_Angeles), true) AS to_unix_timestamp(TIMESTAMP_NTZ '2020-01-01 
13:24:35.123456789', yyyy-MM-dd HH:mm:ss)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT unix_timestamp('2020-01-01 13:24:35.999999999' :: timestamp_ntz(7))
+-- !query analysis
+Project [unix_timestamp(cast(2020-01-01 13:24:35.999999999 as 
timestamp_ntz(7)), yyyy-MM-dd HH:mm:ss, Some(America/Los_Angeles), true) AS 
unix_timestamp(CAST(2020-01-01 13:24:35.999999999 AS TIMESTAMP_NTZ(7)), 
yyyy-MM-dd HH:mm:ss)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT to_unix_timestamp('2020-01-01 13:24:35.000000001' :: timestamp_ntz(9))
+-- !query analysis
+Project [to_unix_timestamp(cast(2020-01-01 13:24:35.000000001 as 
timestamp_ntz(9)), yyyy-MM-dd HH:mm:ss, Some(America/Los_Angeles), true) AS 
to_unix_timestamp(CAST(2020-01-01 13:24:35.000000001 AS TIMESTAMP_NTZ(9)), 
yyyy-MM-dd HH:mm:ss)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT unix_timestamp(TIMESTAMP_NTZ '1969-12-31 23:59:59.500000000')
+-- !query analysis
+Project [unix_timestamp(1969-12-31 23:59:59.5, yyyy-MM-dd HH:mm:ss, 
Some(America/Los_Angeles), true) AS unix_timestamp(TIMESTAMP_NTZ '1969-12-31 
23:59:59.500000000', yyyy-MM-dd HH:mm:ss)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT unix_timestamp(NULL :: timestamp_ntz(9)), to_unix_timestamp(NULL :: 
timestamp_ntz(9))
+-- !query analysis
+Project [unix_timestamp(cast(null as timestamp_ntz(9)), yyyy-MM-dd HH:mm:ss, 
Some(America/Los_Angeles), true) AS unix_timestamp(CAST(NULL AS 
TIMESTAMP_NTZ(9)), yyyy-MM-dd HH:mm:ss)#xL, to_unix_timestamp(cast(null as 
timestamp_ntz(9)), yyyy-MM-dd HH:mm:ss, Some(America/Los_Angeles), true) AS 
to_unix_timestamp(CAST(NULL AS TIMESTAMP_NTZ(9)), yyyy-MM-dd HH:mm:ss)#xL]
++- OneRowRelation
diff --git 
a/sql/core/src/test/resources/sql-tests/inputs/timestamp-ltz-nanos.sql 
b/sql/core/src/test/resources/sql-tests/inputs/timestamp-ltz-nanos.sql
index 7e8ab11f633f..73c6022e354e 100644
--- a/sql/core/src/test/resources/sql-tests/inputs/timestamp-ltz-nanos.sql
+++ b/sql/core/src/test/resources/sql-tests/inputs/timestamp-ltz-nanos.sql
@@ -173,3 +173,17 @@ SELECT TIMESTAMP_LTZ '1960-01-02 03:04:05.123456789 UTC' +
 -- operator overload.
 SELECT TIMESTAMP_LTZ '2020-01-02 03:04:05.123456789 UTC' + make_interval(0, 1, 
0, 2, 0, 0, 0);
 SELECT TIMESTAMP_LTZ '2020-01-02 03:04:05.123456789 UTC' + INTERVAL '1' MONTH;
+
+-- SPARK-57528: unix_timestamp / to_unix_timestamp over nanosecond-precision 
values. The result is
+-- whole-second BIGINT; the sub-second digits are dropped. A literal without 
an explicit zone is
+-- read in the session time zone (America/Los_Angeles, UTC-08:00); an 
explicit-zone literal fixes
+-- the instant directly.
+SELECT unix_timestamp(TIMESTAMP_LTZ '2020-01-01 13:24:35.123456789');
+SELECT to_unix_timestamp(TIMESTAMP_LTZ '2020-01-01 13:24:35.123456789');
+SELECT unix_timestamp(TIMESTAMP_LTZ '2020-01-01 13:24:35.123456789 UTC');
+SELECT to_unix_timestamp('2020-01-01 13:24:35.000000001 UTC' :: 
timestamp_ltz(9));
+SELECT unix_timestamp('2020-01-01 13:24:35.999999999' :: timestamp_ltz(7));
+-- Pre-epoch value exercises the negative-epoch path (truncation toward zero).
+SELECT unix_timestamp(TIMESTAMP_LTZ '1969-12-31 23:59:59.500000000 UTC');
+-- NULL nanosecond timestamp.
+SELECT unix_timestamp(NULL :: timestamp_ltz(9)), to_unix_timestamp(NULL :: 
timestamp_ltz(9));
diff --git 
a/sql/core/src/test/resources/sql-tests/inputs/timestamp-ntz-nanos.sql 
b/sql/core/src/test/resources/sql-tests/inputs/timestamp-ntz-nanos.sql
index 0568f671e4bf..016af6b24763 100644
--- a/sql/core/src/test/resources/sql-tests/inputs/timestamp-ntz-nanos.sql
+++ b/sql/core/src/test/resources/sql-tests/inputs/timestamp-ntz-nanos.sql
@@ -149,3 +149,15 @@ SELECT TIMESTAMP_NTZ '1960-01-02 03:04:05.123456789' + 
INTERVAL '0 00:00:00.0000
 -- operator overload.
 SELECT TIMESTAMP_NTZ '2020-01-02 03:04:05.123456789' + make_interval(0, 1, 0, 
2, 0, 0, 0);
 SELECT TIMESTAMP_NTZ '2020-01-02 03:04:05.123456789' + INTERVAL '1' MONTH;
+
+-- SPARK-57528: unix_timestamp / to_unix_timestamp over nanosecond-precision 
values. The result is
+-- whole-second BIGINT; the sub-second digits are dropped and NTZ applies no 
zone shift, so the
+-- wall-clock value is read as the epoch instant.
+SELECT unix_timestamp(TIMESTAMP_NTZ '2020-01-01 13:24:35.123456789');
+SELECT to_unix_timestamp(TIMESTAMP_NTZ '2020-01-01 13:24:35.123456789');
+SELECT unix_timestamp('2020-01-01 13:24:35.999999999' :: timestamp_ntz(7));
+SELECT to_unix_timestamp('2020-01-01 13:24:35.000000001' :: timestamp_ntz(9));
+-- Pre-epoch value exercises the negative-epoch path (truncation toward zero).
+SELECT unix_timestamp(TIMESTAMP_NTZ '1969-12-31 23:59:59.500000000');
+-- NULL nanosecond timestamp.
+SELECT unix_timestamp(NULL :: timestamp_ntz(9)), to_unix_timestamp(NULL :: 
timestamp_ntz(9));
diff --git 
a/sql/core/src/test/resources/sql-tests/results/timestamp-ltz-nanos.sql.out 
b/sql/core/src/test/resources/sql-tests/results/timestamp-ltz-nanos.sql.out
index 7850cfc9bfef..b5eef6a1ac93 100644
--- a/sql/core/src/test/resources/sql-tests/results/timestamp-ltz-nanos.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/timestamp-ltz-nanos.sql.out
@@ -726,3 +726,59 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
     "fragment" : "TIMESTAMP_LTZ '2020-01-02 03:04:05.123456789 UTC' + INTERVAL 
'1' MONTH"
   } ]
 }
+
+
+-- !query
+SELECT unix_timestamp(TIMESTAMP_LTZ '2020-01-01 13:24:35.123456789')
+-- !query schema
+struct<unix_timestamp(TIMESTAMP_LTZ '2020-01-01 13:24:35.123456789', 
yyyy-MM-dd HH:mm:ss):bigint>
+-- !query output
+1577913875
+
+
+-- !query
+SELECT to_unix_timestamp(TIMESTAMP_LTZ '2020-01-01 13:24:35.123456789')
+-- !query schema
+struct<to_unix_timestamp(TIMESTAMP_LTZ '2020-01-01 13:24:35.123456789', 
yyyy-MM-dd HH:mm:ss):bigint>
+-- !query output
+1577913875
+
+
+-- !query
+SELECT unix_timestamp(TIMESTAMP_LTZ '2020-01-01 13:24:35.123456789 UTC')
+-- !query schema
+struct<unix_timestamp(TIMESTAMP_LTZ '2020-01-01 05:24:35.123456789', 
yyyy-MM-dd HH:mm:ss):bigint>
+-- !query output
+1577885075
+
+
+-- !query
+SELECT to_unix_timestamp('2020-01-01 13:24:35.000000001 UTC' :: 
timestamp_ltz(9))
+-- !query schema
+struct<to_unix_timestamp(CAST(2020-01-01 13:24:35.000000001 UTC AS 
TIMESTAMP_LTZ(9)), yyyy-MM-dd HH:mm:ss):bigint>
+-- !query output
+1577885075
+
+
+-- !query
+SELECT unix_timestamp('2020-01-01 13:24:35.999999999' :: timestamp_ltz(7))
+-- !query schema
+struct<unix_timestamp(CAST(2020-01-01 13:24:35.999999999 AS TIMESTAMP_LTZ(7)), 
yyyy-MM-dd HH:mm:ss):bigint>
+-- !query output
+1577913875
+
+
+-- !query
+SELECT unix_timestamp(TIMESTAMP_LTZ '1969-12-31 23:59:59.500000000 UTC')
+-- !query schema
+struct<unix_timestamp(TIMESTAMP_LTZ '1969-12-31 15:59:59.500000000', 
yyyy-MM-dd HH:mm:ss):bigint>
+-- !query output
+0
+
+
+-- !query
+SELECT unix_timestamp(NULL :: timestamp_ltz(9)), to_unix_timestamp(NULL :: 
timestamp_ltz(9))
+-- !query schema
+struct<unix_timestamp(CAST(NULL AS TIMESTAMP_LTZ(9)), yyyy-MM-dd 
HH:mm:ss):bigint,to_unix_timestamp(CAST(NULL AS TIMESTAMP_LTZ(9)), yyyy-MM-dd 
HH:mm:ss):bigint>
+-- !query output
+NULL   NULL
diff --git 
a/sql/core/src/test/resources/sql-tests/results/timestamp-ntz-nanos.sql.out 
b/sql/core/src/test/resources/sql-tests/results/timestamp-ntz-nanos.sql.out
index 9f64ed3b229f..a3d0d237e818 100644
--- a/sql/core/src/test/resources/sql-tests/results/timestamp-ntz-nanos.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/timestamp-ntz-nanos.sql.out
@@ -644,3 +644,51 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
     "fragment" : "TIMESTAMP_NTZ '2020-01-02 03:04:05.123456789' + INTERVAL '1' 
MONTH"
   } ]
 }
+
+
+-- !query
+SELECT unix_timestamp(TIMESTAMP_NTZ '2020-01-01 13:24:35.123456789')
+-- !query schema
+struct<unix_timestamp(TIMESTAMP_NTZ '2020-01-01 13:24:35.123456789', 
yyyy-MM-dd HH:mm:ss):bigint>
+-- !query output
+1577885075
+
+
+-- !query
+SELECT to_unix_timestamp(TIMESTAMP_NTZ '2020-01-01 13:24:35.123456789')
+-- !query schema
+struct<to_unix_timestamp(TIMESTAMP_NTZ '2020-01-01 13:24:35.123456789', 
yyyy-MM-dd HH:mm:ss):bigint>
+-- !query output
+1577885075
+
+
+-- !query
+SELECT unix_timestamp('2020-01-01 13:24:35.999999999' :: timestamp_ntz(7))
+-- !query schema
+struct<unix_timestamp(CAST(2020-01-01 13:24:35.999999999 AS TIMESTAMP_NTZ(7)), 
yyyy-MM-dd HH:mm:ss):bigint>
+-- !query output
+1577885075
+
+
+-- !query
+SELECT to_unix_timestamp('2020-01-01 13:24:35.000000001' :: timestamp_ntz(9))
+-- !query schema
+struct<to_unix_timestamp(CAST(2020-01-01 13:24:35.000000001 AS 
TIMESTAMP_NTZ(9)), yyyy-MM-dd HH:mm:ss):bigint>
+-- !query output
+1577885075
+
+
+-- !query
+SELECT unix_timestamp(TIMESTAMP_NTZ '1969-12-31 23:59:59.500000000')
+-- !query schema
+struct<unix_timestamp(TIMESTAMP_NTZ '1969-12-31 23:59:59.500000000', 
yyyy-MM-dd HH:mm:ss):bigint>
+-- !query output
+0
+
+
+-- !query
+SELECT unix_timestamp(NULL :: timestamp_ntz(9)), to_unix_timestamp(NULL :: 
timestamp_ntz(9))
+-- !query schema
+struct<unix_timestamp(CAST(NULL AS TIMESTAMP_NTZ(9)), yyyy-MM-dd 
HH:mm:ss):bigint,to_unix_timestamp(CAST(NULL AS TIMESTAMP_NTZ(9)), yyyy-MM-dd 
HH:mm:ss):bigint>
+-- !query output
+NULL   NULL
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/TimestampNanosFunctionsSuiteBase.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/TimestampNanosFunctionsSuiteBase.scala
index 8b47efb85f0d..cfda0007fe93 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/TimestampNanosFunctionsSuiteBase.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/TimestampNanosFunctionsSuiteBase.scala
@@ -239,6 +239,54 @@ abstract class TimestampNanosFunctionsSuiteBase extends 
SharedSparkSession {
         Row(null, null, null, null))
     }
   }
+
+  test("SPARK-57528: unix_timestamp / to_unix_timestamp over 
nanosecond-precision timestamps") {
+    // unix_timestamp returns whole-second BIGINT and applies no zone shift to 
a timestamp
+    // argument, so the sub-second digits are dropped and the nanos result 
equals the
+    // microsecond-timestamp result.
+    val ntzStr = "2020-01-01T13:24:35.123456789"
+    val ltzStr = "2020-01-01T21:24:35.987654321Z"
+    Seq(7, 8, 9).foreach { p =>
+      val ntzNano = ntzNanos(ntzStr, p)
+      val ntzMicro = spark.createDataFrame(
+        spark.sparkContext.parallelize(Seq(Row(LocalDateTime.parse(ntzStr)))),
+        new StructType().add("c", TimestampNTZType))
+      checkAnswer(
+        ntzNano.select(unix_timestamp(col("c")), to_unix_timestamp(col("c"))),
+        ntzMicro.select(unix_timestamp(col("c")), to_unix_timestamp(col("c"))))
+      // 2020-01-01 13:24:35 read as the wall-clock instant -> 1577885075 
epoch seconds.
+      checkAnswer(
+        ntzNano.select(unix_timestamp(col("c")), to_unix_timestamp(col("c"))),
+        Row(1577885075L, 1577885075L))
+
+      val ltzNano = ltzNanos(ltzStr, p)
+      val ltzMicro = spark.createDataFrame(
+        spark.sparkContext.parallelize(Seq(Row(Instant.parse(ltzStr)))),
+        new StructType().add("c", TimestampType))
+      checkAnswer(
+        ltzNano.select(unix_timestamp(col("c")), to_unix_timestamp(col("c"))),
+        ltzMicro.select(unix_timestamp(col("c")), to_unix_timestamp(col("c"))))
+      // 2020-01-01 21:24:35 UTC -> 1577913875 epoch seconds.
+      checkAnswer(
+        ltzNano.select(unix_timestamp(col("c")), to_unix_timestamp(col("c"))),
+        Row(1577913875L, 1577913875L))
+    }
+  }
+
+  test("SPARK-57528: unix_timestamp / to_unix_timestamp over NULL nanosecond 
timestamps") {
+    Seq(7, 8, 9).foreach { p =>
+      val ntz = spark.createDataFrame(
+        spark.sparkContext.parallelize(Seq(Row(null))),
+        new StructType().add("c", TimestampNTZNanosType(p)))
+      val ltz = spark.createDataFrame(
+        spark.sparkContext.parallelize(Seq(Row(null))),
+        new StructType().add("c", TimestampLTZNanosType(p)))
+      checkAnswer(
+        ntz.select(unix_timestamp(col("c")), to_unix_timestamp(col("c"))), 
Row(null, null))
+      checkAnswer(
+        ltz.select(unix_timestamp(col("c")), to_unix_timestamp(col("c"))), 
Row(null, null))
+    }
+  }
 }
 
 // Runs the nanosecond timestamp function tests with ANSI mode enabled 
explicitly.


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to