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

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


The following commit(s) were added to refs/heads/master by this push:
     new fd073191106a [SPARK-57551][SQL] Extend the TIME data type precision to 
nanoseconds (up to 9)
fd073191106a is described below

commit fd073191106aa57a4437f833b3ab47c042924fa0
Author: Maxim Gekk <[email protected]>
AuthorDate: Mon Jun 22 13:47:10 2026 +0200

    [SPARK-57551][SQL] Extend the TIME data type precision to nanoseconds (up 
to 9)
    
    ### What changes were proposed in this pull request?
    
    This PR extends the fractional-seconds precision of the `TIME` data type 
from a maximum of 6 (microseconds) to 9 (nanoseconds), so `TIME(p)` accepts `0 
<= p <= 9`. The internal storage is already nanoseconds-since-midnight (`Long`, 
`TimeType.NANOS_PRECISION = 9` already exists), so this lifts the cap and 
closes the two remaining micros-only code paths:
    
    - `TimeType.MAX_PRECISION` is raised from 6 to 9 (`DEFAULT_PRECISION` stays 
6). The `UNSUPPORTED_TIME_PRECISION` error message and related 
scaladoc/comments are updated to `[0, 9]`.
    - `SparkDateTimeUtils.stringToTime` now keeps the sub-microsecond digits 
(7-9), mirroring the timestamp parser, and `CAST(<string> AS TIME(p))` 
truncates the parsed value to the target precision (interpreted and codegen 
paths).
    - `CurrentTime` accepts precisions up to `MAX_PRECISION`.
    - Parquet I/O emits/reads `TIME(NANOS)` for precision 7..9 (and keeps 
`TIME(MICROS)` for 0..6) across `TimeTypeParquetOps`, `ParquetSchemaConverter`, 
the vectorized `ParquetVectorUpdaterFactory`, and the legacy row/write 
fallbacks. On read, the value is truncated to the requested precision in both 
the vectorized and the row-based readers, so a higher-precision file read with 
a lower precision (e.g. a `TIME(NANOS)` file read as `TIME(7)`) gives the same 
result either way.
    - ORC stores the raw nanosecond `Long` with the catalyst type name 
preserved, so it round-trips 7..9 losslessly without production changes. Avro 
encodes `TIME` as the `time-micros` logical type (SPARK-57581) and has no 
`time-nanos` type yet (upstream AVRO-4043), so a `TIME(7-9)` value written to 
Avro is truncated to microseconds (the declared precision metadata is still 
preserved via `spark.sql.catalyst.type`).
    
    Dictionary-encoded `TIME(NANOS)` columns disable lazy dictionary decoding 
so reads still pass through the truncating path (as `TIME(MICROS)` already 
does); keeping lazy decoding for `TIME` is left to SPARK-57583.
    
    Casts that were already nanosecond-aware (`TIME(p1) -> TIME(p2)`, `TIME -> 
DECIMAL`, `TIME -> integral`, `TIME -> STRING`) work for 7..9 once the cap is 
lifted.
    
    This PR regenerates `time.sql.out` and `cast.sql.out` for pre-existing 
queries whose output changes once 7-9 fractional digits are preserved (e.g. 
`CAST(time '23:59:59.999999999' AS decimal(...))`, `time_trunc(..., 
time'...123456789')`). That is an unavoidable golden-file update for 
already-present queries; the broader golden-file parity effort (new try_cast / 
datetime parsing-formatting / postgreSQL coverage) remains out of scope and is 
tracked by SPARK-57563.
    
    Out of scope (tracked separately): casts to/from TIMESTAMP types 
(SPARK-57552 / SPARK-57554), lazy Parquet dictionary decoding for `TIME` 
(SPARK-57583), and unit-correct Avro encoding of `TIME(7-9)` (blocked upstream 
by AVRO-4043; the micros encoding for precision 0-6 landed in SPARK-57581).
    
    ### Why are the changes needed?
    
    ANSI SQL (ISO/IEC 9075-2, 6.1 `<data type>`) makes the maximum `<time 
precision>` implementation-defined with the sole constraint that it is not less 
than 6, and requires the maximum of `<time precision>` and `<timestamp 
precision>` to be the same implementation-defined value. Spark already supports 
nanosecond timestamps, so to stay ANSI-consistent `TIME` must reach precision 9 
in lockstep.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. `TIME(7)`, `TIME(8)`, and `TIME(9)` can now be declared, parsed, used 
as literals, and round-tripped losslessly through Parquet and ORC. (Avro stores 
`TIME` as `time-micros`, so `TIME(7-9)` is truncated to microseconds over Avro 
until Avro adds a `time-nanos` logical type, AVRO-4043.) Previously these 
precisions raised `UNSUPPORTED_TIME_PRECISION`. The default precision of `TIME` 
is unchanged (6).
    
    ### How was this patch tested?
    
    Extended existing TIME suites to cover precision 7..9 
(`DataTypeParserSuite`, `DataTypeSuite`, `TimeExpressionsSuite`, 
`CastWithAnsiOn/OffSuite`, `TimeFormatterSuite`, `DateTimeUtilsSuite`, 
CSV/JSON/XML expression and function suites, `TimeTypeParquetOpsSuite`, 
`ParquetIOSuite`, `OrcQuerySuite`, `AvroSuite`/`AvroFunctionsSuite`, 
`PartitionedWriteSuite`, `RowJsonSuite`, `SparkConnectPlannerSuite`) and added 
nanosecond Parquet read and round-trip tests for both readers, plus a test that 
[...]
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Cursor
    
    Closes #56622 from MaxGekk/time-sub-micro-precision.
    
    Authored-by: Maxim Gekk <[email protected]>
    Signed-off-by: Max Gekk <[email protected]>
---
 .../src/main/resources/error/error-conditions.json |  8 +-
 .../apache/spark/sql/avro/AvroFunctionsSuite.scala | 17 +++--
 .../org/apache/spark/sql/avro/AvroSuite.scala      | 27 +++++++
 .../sql/catalyst/util/SparkDateTimeUtils.scala     | 20 +++--
 .../spark/sql/catalyst/util/TimeFormatter.scala    |  2 +-
 .../spark/sql/errors/QueryParsingErrors.scala      |  9 +++
 .../org/apache/spark/sql/types/TimeType.scala      | 10 +--
 .../spark/sql/catalyst/expressions/Cast.scala      | 12 ++-
 .../sql/catalyst/expressions/timeExpressions.scala |  6 +-
 .../spark/sql/catalyst/parser/AstBuilder.scala     | 14 +++-
 .../spark/sql/catalyst/types/ops/TimeTypeOps.scala |  2 +-
 .../org/apache/spark/sql/RandomDataGenerator.scala | 10 ++-
 .../scala/org/apache/spark/sql/RowJsonSuite.scala  |  4 +-
 .../sql/catalyst/expressions/CastSuiteBase.scala   | 28 +++++++
 .../catalyst/expressions/CsvExpressionsSuite.scala |  5 +-
 .../expressions/JsonExpressionsSuite.scala         |  5 +-
 .../catalyst/expressions/LiteralGenerator.scala    | 16 ++--
 .../expressions/TimeExpressionsSuite.scala         | 14 +++-
 .../catalyst/expressions/XmlExpressionsSuite.scala |  5 +-
 .../sql/catalyst/parser/DataTypeParserSuite.scala  | 12 ++-
 .../catalyst/parser/ExpressionParserSuite.scala    | 23 ++++++
 .../sql/catalyst/util/DateTimeTestUtils.scala      | 11 ++-
 .../sql/catalyst/util/DateTimeUtilsSuite.scala     | 20 +++++
 .../sql/catalyst/util/TimeFormatterSuite.scala     | 34 ++++++++-
 .../org/apache/spark/sql/types/DataTypeSuite.scala |  6 +-
 .../parquet/ParquetVectorUpdaterFactory.java       | 56 ++++++++++++--
 .../parquet/VectorizedColumnReader.java            |  6 +-
 .../datasources/parquet/ParquetRowConverter.scala  | 17 +++--
 .../parquet/ParquetSchemaConverter.scala           |  9 ++-
 .../datasources/parquet/ParquetWriteSupport.scala  |  5 ++
 .../parquet/types/ops/TimeTypeParquetOps.scala     | 87 ++++++++++++++--------
 .../sql-tests/analyzer-results/cast.sql.out        |  6 +-
 .../analyzer-results/nonansi/cast.sql.out          |  6 +-
 .../sql-tests/analyzer-results/time.sql.out        | 60 +++++++--------
 .../test/resources/sql-tests/results/cast.sql.out  | 12 +--
 .../sql-tests/results/nonansi/cast.sql.out         | 12 +--
 .../test/resources/sql-tests/results/time.sql.out  | 60 +++++++--------
 .../org/apache/spark/sql/CsvFunctionsSuite.scala   |  5 +-
 .../org/apache/spark/sql/JsonFunctionsSuite.scala  |  5 +-
 .../apache/spark/sql/TimeFunctionsSuiteBase.scala  |  2 +-
 .../org/apache/spark/sql/XmlFunctionsSuite.scala   |  5 +-
 .../execution/datasources/orc/OrcQuerySuite.scala  |  7 +-
 .../datasources/parquet/ParquetIOSuite.scala       | 82 ++++++++++++++++++++
 .../types/ops/TimeTypeParquetOpsSuite.scala        | 47 +++++++-----
 .../spark/sql/sources/PartitionedWriteSuite.scala  |  5 +-
 45 files changed, 605 insertions(+), 209 deletions(-)

diff --git a/common/utils/src/main/resources/error/error-conditions.json 
b/common/utils/src/main/resources/error/error-conditions.json
index 8e7125a07c1e..13130579c6a1 100644
--- a/common/utils/src/main/resources/error/error-conditions.json
+++ b/common/utils/src/main/resources/error/error-conditions.json
@@ -5028,6 +5028,12 @@
     ],
     "sqlState" : "22009"
   },
+  "INVALID_TIME_LITERAL_PRECISION" : {
+    "message" : [
+      "The time literal <value> has more than 9 fractional-second digits. The 
maximum supported fractional-second precision of a time literal is 9 
(nanoseconds)."
+    ],
+    "sqlState" : "22023"
+  },
   "INVALID_TIME_TRAVEL_SPEC" : {
     "message" : [
       "Cannot specify both version and timestamp when time travelling the 
table."
@@ -8857,7 +8863,7 @@
   },
   "UNSUPPORTED_TIME_PRECISION" : {
     "message" : [
-      "The seconds precision <precision> of the TIME data type is out of the 
supported range [0, 6]."
+      "The seconds precision <precision> of the TIME data type is out of the 
supported range [0, 9]."
     ],
     "sqlState" : "0A001"
   },
diff --git 
a/connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroFunctionsSuite.scala
 
b/connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroFunctionsSuite.scala
index 4da104fa4647..bcc104aa3e82 100644
--- 
a/connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroFunctionsSuite.scala
+++ 
b/connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroFunctionsSuite.scala
@@ -668,15 +668,18 @@ class AvroFunctionsSuite extends SharedSparkSession {
   }
 
   test("roundtrip in to_avro and from_avro - TIME type with different 
precisions") {
+    // Avro stores TIME as the time-micros logical type, so this lossless 
round-trip is limited to
+    // precision 0-6. TIME(7-9) truncates to microseconds over Avro (no 
time-nanos logical type,
+    // upstream AVRO-4043); that behavior is covered in AvroSuite.
     val df = spark.sql("""
       SELECT
-        TIME'12:34:56' as time_p0,
-        TIME'12:34:56.1' as time_p1,
-        TIME'12:34:56.12' as time_p2,
-        TIME'12:34:56.123' as time_p3,
-        TIME'12:34:56.1234' as time_p4,
-        TIME'12:34:56.12345' as time_p5,
-        TIME'12:34:56.123456' as time_p6
+        CAST(TIME'12:34:56' AS TIME(0)) as time_p0,
+        CAST(TIME'12:34:56.1' AS TIME(1)) as time_p1,
+        CAST(TIME'12:34:56.12' AS TIME(2)) as time_p2,
+        CAST(TIME'12:34:56.123' AS TIME(3)) as time_p3,
+        CAST(TIME'12:34:56.1234' AS TIME(4)) as time_p4,
+        CAST(TIME'12:34:56.12345' AS TIME(5)) as time_p5,
+        CAST(TIME'12:34:56.123456' AS TIME(6)) as time_p6
     """)
 
     val precisions = Seq(0, 1, 2, 3, 4, 5, 6)
diff --git 
a/connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala 
b/connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala
index 98b240cc56b3..588d7e26206f 100644
--- a/connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala
+++ b/connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala
@@ -3322,6 +3322,33 @@ abstract class AvroSuite
     }
   }
 
+  test("SPARK-57551: TIME(7-9) is truncated to microseconds when written to 
Avro") {
+    // Avro has no time-nanos logical type (upstream AVRO-4043), so TIME is 
stored as time-micros.
+    // Writing a TIME(7-9) value therefore drops the sub-microsecond digits, 
while the column's
+    // precision metadata (time(p)) is still preserved via the 
spark.sql.catalyst.type property.
+    withTempPath { dir =>
+      val df = spark.sql("""
+        SELECT
+          CAST(TIME '12:34:56.1234567' AS TIME(7)) as time_p7,
+          CAST(TIME '12:34:56.12345678' AS TIME(8)) as time_p8,
+          CAST(TIME '12:34:56.123456789' AS TIME(9)) as time_p9
+      """)
+
+      df.write.format("avro").save(dir.toString)
+      val readDf = spark.read.format("avro").load(dir.toString)
+
+      // The declared precision is preserved.
+      Seq(7, 8, 9).foreach { p =>
+        assert(readDf.schema(s"time_p$p").dataType == TimeType(p),
+          s"Precision $p should be preserved")
+      }
+
+      // The value reads back truncated to microsecond resolution (.123456789 
-> .123456).
+      val micros = java.time.LocalTime.of(12, 34, 56, 123456000)
+      checkAnswer(readDf, Row(micros, micros, micros))
+    }
+  }
+
   test("SPARK-57581: TIME is written as unit-correct time-micros for external 
readers") {
     // Expected microseconds-since-midnight for TIME'12:34:56.123456' 
truncated to each precision.
     val baseSeconds = (12 * 3600 + 34 * 60 + 56).toLong
diff --git 
a/sql/api/src/main/scala/org/apache/spark/sql/catalyst/util/SparkDateTimeUtils.scala
 
b/sql/api/src/main/scala/org/apache/spark/sql/catalyst/util/SparkDateTimeUtils.scala
index 2b992ba53fe8..430b04d8f3df 100644
--- 
a/sql/api/src/main/scala/org/apache/spark/sql/catalyst/util/SparkDateTimeUtils.scala
+++ 
b/sql/api/src/main/scala/org/apache/spark/sql/catalyst/util/SparkDateTimeUtils.scala
@@ -169,7 +169,7 @@ trait SparkDateTimeUtils {
    * @param nanos
    *   The original time in nanoseconds.
    * @param p
-   *   The fractional second precision (range 0 to 6).
+   *   The fractional second precision (range 0 to 9).
    * @return
    *   The truncated nanosecond value, preserving only `p` fractional digits.
    */
@@ -178,11 +178,16 @@ trait SparkDateTimeUtils {
       TimeType.MIN_PRECISION <= p && p <= TimeType.MAX_PRECISION,
       s"Fractional second precision $p out" +
         s" of range [${TimeType.MIN_PRECISION}..${TimeType.MAX_PRECISION}].")
-    val scale = TimeType.NANOS_PRECISION - p
-    val factor = math.pow(10, scale).toLong
+    val factor = timeTruncationFactors(TimeType.NANOS_PRECISION - p)
     (nanos / factor) * factor
   }
 
+  // Precomputed 10^k for k in [0, NANOS_PRECISION], indexed by the truncation 
scale
+  // (NANOS_PRECISION - p). `truncateTimeToPrecision` runs per value on hot 
read/cast paths, so the
+  // factor is looked up here instead of recomputed with `math.pow` on every 
call.
+  private val timeTruncationFactors: Array[Long] =
+    (0 to TimeType.NANOS_PRECISION).map(k => math.pow(10, k).toLong).toArray
+
   /**
    * Converts the timestamp `micros` from one timezone to another.
    *
@@ -1080,8 +1085,10 @@ trait SparkDateTimeUtils {
         return None
       }
 
-      // Unpack the segments.
-      var (hr, min, sec, ms) = (segments(3), segments(4), segments(5), 
segments(6))
+      // Unpack the segments. `segments(6)` holds microseconds and 
`segments(9)` holds the
+      // sub-microsecond nanosecond remainder (digits 7-9), in [0, 999].
+      var (hr, min, sec, ms, subMicroNanos) =
+        (segments(3), segments(4), segments(5), segments(6), segments(9))
 
       // Handle AM/PM conversion in separate cases.
       if (!hasSuffix) {
@@ -1108,7 +1115,8 @@ trait SparkDateTimeUtils {
         }
       }
 
-      val localTime = LocalTime.of(hr, min, sec, 
MICROSECONDS.toNanos(ms).toInt)
+      val nanoOfSecond = (MICROSECONDS.toNanos(ms) + subMicroNanos).toInt
+      val localTime = LocalTime.of(hr, min, sec, nanoOfSecond)
       Some(localTimeToNanos(localTime))
     } catch {
       case NonFatal(_) => None
diff --git 
a/sql/api/src/main/scala/org/apache/spark/sql/catalyst/util/TimeFormatter.scala 
b/sql/api/src/main/scala/org/apache/spark/sql/catalyst/util/TimeFormatter.scala
index d0438c6ff1b4..a8c5225353d7 100644
--- 
a/sql/api/src/main/scala/org/apache/spark/sql/catalyst/util/TimeFormatter.scala
+++ 
b/sql/api/src/main/scala/org/apache/spark/sql/catalyst/util/TimeFormatter.scala
@@ -68,7 +68,7 @@ class Iso8601TimeFormatter(pattern: String, locale: Locale, 
isParsing: Boolean)
 
 /**
  * The formatter parses/formats times according to the pattern 
`HH:mm:ss.[..fff..]` where
- * `[..fff..]` is a fraction of second up to microsecond resolution. The 
formatter does not output
+ * `[..fff..]` is a fraction of second up to nanosecond resolution. The 
formatter does not output
  * trailing zeros in the fraction. For example, the time `15:00:01.123400` is 
formatted as the
  * string `15:00:01.1234`.
  */
diff --git 
a/sql/api/src/main/scala/org/apache/spark/sql/errors/QueryParsingErrors.scala 
b/sql/api/src/main/scala/org/apache/spark/sql/errors/QueryParsingErrors.scala
index 1cc050f488f6..558bda49a302 100644
--- 
a/sql/api/src/main/scala/org/apache/spark/sql/errors/QueryParsingErrors.scala
+++ 
b/sql/api/src/main/scala/org/apache/spark/sql/errors/QueryParsingErrors.scala
@@ -369,6 +369,15 @@ private[sql] object QueryParsingErrors extends 
DataTypeErrorsBase {
       ctx)
   }
 
+  def timeLiteralPrecisionExceedsMaxError(
+      value: String,
+      ctx: TypeConstructorContext): Throwable = {
+    new ParseException(
+      errorClass = "INVALID_TIME_LITERAL_PRECISION",
+      messageParameters = Map("value" -> toSQLValue(value)),
+      ctx)
+  }
+
   def literalValueTypeUnsupportedError(
       unsupportedType: String,
       supportedTypes: Seq[String],
diff --git a/sql/api/src/main/scala/org/apache/spark/sql/types/TimeType.scala 
b/sql/api/src/main/scala/org/apache/spark/sql/types/TimeType.scala
index 135ad278438e..b2cc29f5379e 100644
--- a/sql/api/src/main/scala/org/apache/spark/sql/types/TimeType.scala
+++ b/sql/api/src/main/scala/org/apache/spark/sql/types/TimeType.scala
@@ -21,12 +21,12 @@ import org.apache.spark.annotation.Unstable
 import org.apache.spark.sql.errors.DataTypeErrors
 
 /**
- * The time type represents a time value with fields hour, minute, second, up 
to microseconds. The
- * range of times supported is 00:00:00.000000 to 23:59:59.999999.
+ * The time type represents a time value with fields hour, minute, second, up 
to nanoseconds. The
+ * range of times supported is 00:00:00.000000000 to 23:59:59.999999999.
  *
  * @param precision
  *   The time fractional seconds precision which indicates the number of 
decimal digits maintained
- *   following the decimal point in the seconds value. The supported range is 
[0, 6].
+ *   following the decimal point in the seconds value. The supported range is 
[0, 9].
  *
  * @since 4.1.0
  */
@@ -50,9 +50,9 @@ case class TimeType(precision: Int) extends AnyTimeType {
 object TimeType {
   val MIN_PRECISION: Int = 0
   val MICROS_PRECISION: Int = 6
-  val MAX_PRECISION: Int = MICROS_PRECISION
-  val DEFAULT_PRECISION: Int = MICROS_PRECISION
   val NANOS_PRECISION: Int = 9
+  val MAX_PRECISION: Int = NANOS_PRECISION
+  val DEFAULT_PRECISION: Int = MICROS_PRECISION
 
   def apply(): TimeType = new TimeType(DEFAULT_PRECISION)
 }
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala
index 506babb08f34..62bbd65863e8 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/Cast.scala
@@ -965,9 +965,11 @@ case class Cast(
   private[this] def castToTime(from: DataType, to: TimeType): Any => Any = 
from match {
     case _: StringType =>
       if (ansiEnabled) {
-        buildCast[UTF8String](_, s => DateTimeUtils.stringToTimeAnsi(s, 
getContextOrNull()))
+        buildCast[UTF8String](_, s => DateTimeUtils.truncateTimeToPrecision(
+          DateTimeUtils.stringToTimeAnsi(s, getContextOrNull()), to.precision))
       } else {
-        buildCast[UTF8String](_, s => DateTimeUtils.stringToTime(s).orNull)
+        buildCast[UTF8String](_, s => DateTimeUtils.stringToTime(s)
+          .map(DateTimeUtils.truncateTimeToPrecision(_, to.precision)).orNull)
       }
     case _: TimeType =>
       buildCast[Long](_, nanos => DateTimeUtils.truncateTimeToPrecision(nanos, 
to.precision))
@@ -1670,13 +1672,15 @@ case class Cast(
           if (ansiEnabled) {
             val errorContext = getContextOrNullCode(ctx)
             code"""
-              $evPrim = $dateTimeUtilsCls.stringToTimeAnsi($c, $errorContext);
+              $evPrim = $dateTimeUtilsCls.truncateTimeToPrecision(
+                $dateTimeUtilsCls.stringToTimeAnsi($c, $errorContext), 
${to.precision});
             """
           } else {
             code"""
               scala.Option<Long> $longOpt = $dateTimeUtilsCls.stringToTime($c);
               if ($longOpt.isDefined()) {
-                $evPrim = ((Long) $longOpt.get()).longValue();
+                $evPrim = $dateTimeUtilsCls.truncateTimeToPrecision(
+                  ((Long) $longOpt.get()).longValue(), ${to.precision});
               } else {
                 $evNull = true;
               }
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/timeExpressions.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/timeExpressions.scala
index 85afdf72eecc..1c9d6b335e8d 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/timeExpressions.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/timeExpressions.scala
@@ -467,7 +467,7 @@ object SecondExpressionBuilder extends ExpressionBuilder {
   """,
   arguments = """
     Arguments:
-      * precision - An optional integer literal in the range [0..6], 
indicating how many
+      * precision - An optional integer literal in the range [0..9], 
indicating how many
                     fractional digits of seconds to include. If omitted, the 
default is 6.
   """,
   examples = """
@@ -531,12 +531,12 @@ case class CurrentTime(
     precisionValue match {
       case n: Number =>
         val p = n.intValue()
-        if (p < TimeType.MIN_PRECISION || p > TimeType.MICROS_PRECISION) {
+        if (p < TimeType.MIN_PRECISION || p > TimeType.MAX_PRECISION) {
           return DataTypeMismatch(
             errorSubClass = "VALUE_OUT_OF_RANGE",
             messageParameters = Map(
               "exprName" -> toSQLId("precision"),
-              "valueRange" -> s"[${TimeType.MIN_PRECISION}, 
${TimeType.MICROS_PRECISION}]",
+              "valueRange" -> s"[${TimeType.MIN_PRECISION}, 
${TimeType.MAX_PRECISION}]",
               "currentValue" -> toSQLValue(p, IntegerType)
             )
           )
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
index 4dc63760e6a2..43e094b5ccec 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
@@ -4136,7 +4136,19 @@ class AstBuilder extends DataTypeAstBuilder
         val zoneId = getZoneId(conf.sessionLocalTimeZone)
         val specialDate = convertSpecialDate(value, zoneId).map(Literal(_, 
DateType))
         specialDate.getOrElse(toLiteral(stringToDate, DateType))
-      case TIME => toLiteral(stringToTime, TimeType())
+      case TIME =>
+        // ANSI SQL (ISO/IEC 9075-2, Subclause 5.3, Syntax Rule 26): the 
fractional-seconds
+        // precision of a time literal is the number of digits in its seconds 
fraction. A literal
+        // with 7-9 fractional digits becomes a nanosecond-precision literal; 
<= 6 digits keep the
+        // default microsecond precision; more than 9 digits is rejected.
+        val p = fractionalSecondsDigits(value)
+        if (p > TimeType.MAX_PRECISION) {
+          throw QueryParsingErrors.timeLiteralPrecisionExceedsMaxError(value, 
ctx)
+        } else if (p > TimeType.MICROS_PRECISION) {
+          toLiteral(stringToTime, TimeType(p))
+        } else {
+          toLiteral(stringToTime, TimeType())
+        }
       case TIMESTAMP_NTZ =>
         nanosLiteralOpt(constructTimestampNTZNanosLiteral).getOrElse {
           convertSpecialTimestampNTZ(value, 
getZoneId(conf.sessionLocalTimeZone))
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/types/ops/TimeTypeOps.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/types/ops/TimeTypeOps.scala
index d1700aad05cf..3ff1d6c9165a 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/types/ops/TimeTypeOps.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/types/ops/TimeTypeOps.scala
@@ -49,7 +49,7 @@ import org.apache.spark.sql.types.{ObjectType, TimeType}
  *   - Values stored as Long nanoseconds since midnight
  *   - Range: 0 to 86,399,999,999,999
  *   - External type: java.time.LocalTime
- *   - Precision (0-6) affects display only, not storage
+ *   - Precision (0-9) affects display only, not storage
  *
  * @param t
  *   The TimeType with precision information
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/RandomDataGenerator.scala 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/RandomDataGenerator.scala
index 289b2ff6851f..1390155fc7b6 100644
--- a/sql/catalyst/src/test/scala/org/apache/spark/sql/RandomDataGenerator.scala
+++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/RandomDataGenerator.scala
@@ -318,15 +318,19 @@ object RandomDataGenerator {
             .map(s => TimestampNanosTestUtils.parseSpecialNanosLTZ(s, 
ZoneId.systemDefault()))
             .map(i => Instant.ofEpochSecond(i.getEpochSecond, 
truncate(i.getNano).toLong))
         )
-      case _: TimeType =>
+      case t: TimeType =>
         val specialTimes = Seq(
           "00:00:00",
-          "23:59:59.999999"
+          "23:59:59.999999",
+          "23:59:59.999999999"
         )
         randomNumeric[LocalTime](
           rand,
           (rand: Random) => {
-            DateTimeUtils.nanosToLocalTime(rand.between(0, 24 * 60 * 60 * 1000 
* 1000L) * 1000L)
+            // The full valid range is [0, 86_399_999_999_999] nanoseconds 
since midnight.
+            val nanos = DateTimeUtils.truncateTimeToPrecision(
+              rand.between(0L, 24 * 60 * 60 * 1000 * 1000 * 1000L), 
t.precision)
+            DateTimeUtils.nanosToLocalTime(nanos)
           },
           specialTimes.map(LocalTime.parse)
         )
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/RowJsonSuite.scala 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/RowJsonSuite.scala
index 31d967f3da37..6b228848feaf 100644
--- a/sql/catalyst/src/test/scala/org/apache/spark/sql/RowJsonSuite.scala
+++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/RowJsonSuite.scala
@@ -134,11 +134,13 @@ class RowJsonSuite extends SparkFunSuite with SQLHelper {
   test("SPARK-57338: TIME column renders the external LocalTime in JSON") {
     assert(timeRowJson(LocalTime.of(12, 13, 14), TimeType.MICROS_PRECISION) ===
       JObject("a" -> JString("12:13:14")))
-    // The fraction is rendered up to microsecond resolution with trailing 
zeros trimmed.
+    // The fraction is rendered up to nanosecond resolution with trailing 
zeros trimmed.
     assert(timeRowJson(LocalTime.of(1, 2, 3, 123456000), 
TimeType.MICROS_PRECISION) ===
       JObject("a" -> JString("01:02:03.123456")))
     assert(timeRowJson(LocalTime.of(10, 30, 0, 100000000), 
TimeType.MICROS_PRECISION) ===
       JObject("a" -> JString("10:30:00.1")))
+    assert(timeRowJson(LocalTime.of(1, 2, 3, 123456789), 
TimeType.NANOS_PRECISION) ===
+      JObject("a" -> JString("01:02:03.123456789")))
     assert(timeRowJson(LocalTime.MIDNIGHT, TimeType.MICROS_PRECISION) ===
       JObject("a" -> JString("00:00:00")))
   }
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuiteBase.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuiteBase.scala
index 980c68f65f20..122554a18886 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuiteBase.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CastSuiteBase.scala
@@ -2328,6 +2328,16 @@ abstract class CastSuiteBase extends SparkFunSuite with 
ExpressionEvalHelper {
       TimeType(5)), localTime(23, 59, 0, 999990))
     checkEvaluation(cast(Literal.create("23:59:59.000001     "),
       TimeType(6)), localTime(23, 59, 59, 1))
+    // Nanosecond precisions 7, 8, 9.
+    checkEvaluation(cast(Literal.create("01:02:03.1234567"),
+      TimeType(7)), localTime(1, 2, 3, 123456, 700))
+    checkEvaluation(cast(Literal.create("01:02:03.12345678"),
+      TimeType(8)), localTime(1, 2, 3, 123456, 780))
+    checkEvaluation(cast(Literal.create("01:02:03.123456789"),
+      TimeType(9)), localTime(1, 2, 3, 123456, 789))
+    // More fractional digits than the target precision are truncated.
+    checkEvaluation(cast(Literal.create("01:02:03.123456789"),
+      TimeType(7)), localTime(1, 2, 3, 123456, 700))
   }
 
   test("context independent foldable") {
@@ -2474,6 +2484,16 @@ abstract class CastSuiteBase extends SparkFunSuite with 
ExpressionEvalHelper {
       localTime(11, 58, 59, 123400))
     checkEvaluation(cast(Literal(localTime(19, 2, 3, 765000), TimeType(3)), 
TimeType(2)),
       localTime(19, 2, 3, 760000))
+    // Nanosecond precisions 7, 8, 9.
+    checkEvaluation(cast(Literal(localTime(23, 59, 59, 999999, 999), 
TimeType(9)), TimeType(9)),
+      localTime(23, 59, 59, 999999, 999))
+    checkEvaluation(cast(Literal(localTime(1, 2, 3, 123456, 789), 
TimeType(9)), TimeType(8)),
+      localTime(1, 2, 3, 123456, 780))
+    checkEvaluation(cast(Literal(localTime(1, 2, 3, 123456, 789), 
TimeType(9)), TimeType(7)),
+      localTime(1, 2, 3, 123456, 700))
+    // Truncate nanosecond value down to the microsecond precision.
+    checkEvaluation(cast(Literal(localTime(1, 2, 3, 123456, 789), 
TimeType(9)), TimeType(6)),
+      localTime(1, 2, 3, 123456))
 
     for (sp <- TimeType.MIN_PRECISION to TimeType.MAX_PRECISION) {
       for (tp <- TimeType.MIN_PRECISION to TimeType.MAX_PRECISION) {
@@ -2642,5 +2662,13 @@ abstract class CastSuiteBase extends SparkFunSuite with 
ExpressionEvalHelper {
     checkEvaluation(cast(oneTwoThreeTime5, LongType), 3723L)
     checkEvaluation(cast(maxTime4, IntegerType), 86399)
     checkEvaluation(cast(maxTime4, LongType), 86399L)
+
+    // Nanosecond precisions: sub-second fraction is truncated to whole 
seconds.
+    val nanos9 = Literal.create(LocalTime.of(0, 0, 17, 999999999), TimeType(9))
+    checkEvaluation(cast(nanos9, IntegerType), 17)
+    checkEvaluation(cast(nanos9, LongType), 17L)
+    val maxNanos = Literal.create(LocalTime.of(23, 59, 59, 999999999), 
TimeType(9))
+    checkEvaluation(cast(maxNanos, IntegerType), 86399)
+    checkEvaluation(cast(maxNanos, LongType), 86399L)
   }
 }
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CsvExpressionsSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CsvExpressionsSuite.scala
index d168735d4045..631b08b5395f 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CsvExpressionsSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/CsvExpressionsSuite.scala
@@ -268,7 +268,10 @@ class CsvExpressionsSuite extends SparkFunSuite with 
ExpressionEvalHelper {
       (3, LocalTime.of(14, 30, 45, 123000000), "14:30:45.123"),
       (4, LocalTime.of(14, 30, 45, 123400000), "14:30:45.1234"),
       (5, LocalTime.of(14, 30, 45, 123450000), "14:30:45.12345"),
-      (6, LocalTime.of(14, 30, 45, 123456000), "14:30:45.123456")
+      (6, LocalTime.of(14, 30, 45, 123456000), "14:30:45.123456"),
+      (7, LocalTime.of(14, 30, 45, 123456700), "14:30:45.1234567"),
+      (8, LocalTime.of(14, 30, 45, 123456780), "14:30:45.12345678"),
+      (9, LocalTime.of(14, 30, 45, 123456789), "14:30:45.123456789")
     )
 
     testData.foreach { case (precision, timeValue, timeStr) =>
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/JsonExpressionsSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/JsonExpressionsSuite.scala
index ece8fbbd8265..7f6ae46be3a8 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/JsonExpressionsSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/JsonExpressionsSuite.scala
@@ -916,7 +916,10 @@ class JsonExpressionsSuite extends SparkFunSuite with 
ExpressionEvalHelper {
       ("14:30:45.123", 3),
       ("14:30:45.1234", 4),
       ("14:30:45.12345", 5),
-      ("23:59:59.999999", 6)
+      ("23:59:59.999999", 6),
+      ("14:30:45.1234567", 7),
+      ("14:30:45.12345678", 8),
+      ("23:59:59.999999999", 9)
     )
 
     testCases.foreach { case (timeStr, precision) =>
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/LiteralGenerator.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/LiteralGenerator.scala
index 1e082e0f1163..d40c597404b0 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/LiteralGenerator.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/LiteralGenerator.scala
@@ -24,9 +24,9 @@ import java.util.concurrent.TimeUnit
 import org.scalacheck.{Arbitrary, Gen}
 import org.scalatest.Assertions._
 
-import 
org.apache.spark.sql.catalyst.util.DateTimeConstants.{MICROS_PER_MILLIS, 
MILLIS_PER_DAY, NANOS_PER_MICROS}
+import 
org.apache.spark.sql.catalyst.util.DateTimeConstants.{MICROS_PER_MILLIS, 
MILLIS_PER_DAY}
 import org.apache.spark.sql.catalyst.util.DateTimeUtils
-import org.apache.spark.sql.catalyst.util.DateTimeUtils.{instantToMicros, 
localTimeToNanos, nanosToMicros}
+import org.apache.spark.sql.catalyst.util.DateTimeUtils.{instantToMicros, 
localTimeToNanos}
 import org.apache.spark.sql.catalyst.util.TimestampNanosTestUtils
 import org.apache.spark.sql.types._
 import org.apache.spark.unsafe.types.{CalendarInterval, TimestampNanosVal}
@@ -125,12 +125,12 @@ object LiteralGenerator {
       yield Literal.create(new Date(day * MILLIS_PER_DAY), DateType)
   }
 
-  lazy val timeLiteralGen: Gen[Literal] = {
-    // Valid range for TimeType is [00:00:00, 23:59:59.999999]
-    val minTime = nanosToMicros(localTimeToNanos(LocalTime.MIN)) * 
NANOS_PER_MICROS
-    val maxTime = nanosToMicros(localTimeToNanos(LocalTime.MAX)) * 
NANOS_PER_MICROS
+  def timeLiteralGen(precision: Int): Gen[Literal] = {
+    // Valid range for TimeType is [00:00:00, 23:59:59.999999999] (nanoseconds 
since midnight).
+    val minTime = localTimeToNanos(LocalTime.MIN)
+    val maxTime = localTimeToNanos(LocalTime.MAX)
     for { t <- Gen.choose(minTime, maxTime) }
-      yield Literal(t, TimeType())
+      yield Literal(DateTimeUtils.truncateTimeToPrecision(t, precision), 
TimeType(precision))
   }
 
   private def millisGen = {
@@ -262,7 +262,7 @@ object LiteralGenerator {
       case DoubleType => doubleLiteralGen
       case FloatType => floatLiteralGen
       case DateType => dateLiteralGen
-      case _: TimeType => timeLiteralGen
+      case t: TimeType => timeLiteralGen(t.precision)
       case TimestampType => timestampLiteralGen
       case TimestampNTZType => timestampNTZLiteralGen
       case t: TimestampNTZNanosType => timestampNTZNanosLiteralGen(t.precision)
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/TimeExpressionsSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/TimeExpressionsSuite.scala
index a806e2c9419c..6db6115a1e5e 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/TimeExpressionsSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/TimeExpressionsSuite.scala
@@ -82,7 +82,7 @@ class TimeExpressionsSuite extends SparkFunSuite with 
ExpressionEvalHelper {
     assert(builtExprForTime.checkInputDataTypes().isSuccess)
 
     // test TIME-typed child should build HoursOfTime for all allowed custom 
precision values
-    (TimeType.MIN_PRECISION to TimeType.MICROS_PRECISION).foreach { precision 
=>
+    (TimeType.MIN_PRECISION to TimeType.MAX_PRECISION).foreach { precision =>
       val timeExpr = Literal(localTime(12, 58, 59), TimeType(precision))
       val builtExpr = HourExpressionBuilder.build("hour", Seq(timeExpr))
 
@@ -151,7 +151,7 @@ class TimeExpressionsSuite extends SparkFunSuite with 
ExpressionEvalHelper {
     assert(builtExprForTime.checkInputDataTypes().isSuccess)
 
     // test TIME-typed child should build MinutesOfTime for all allowed custom 
precision values
-    (TimeType.MIN_PRECISION to TimeType.MICROS_PRECISION).foreach { precision 
=>
+    (TimeType.MIN_PRECISION to TimeType.MAX_PRECISION).foreach { precision =>
       val timeExpr = Literal(localTime(12, 58, 59), TimeType(precision))
       val builtExpr = MinuteExpressionBuilder.build("minute", Seq(timeExpr))
 
@@ -315,6 +315,14 @@ class TimeExpressionsSuite extends SparkFunSuite with 
ExpressionEvalHelper {
     assert(expr.dataType == TimeType(2))
     assert(expr.checkInputDataTypes() == TypeCheckSuccess)
 
+    // test nanosecond precisions 7, 8, 9 are valid
+    (TimeType.MICROS_PRECISION + 1 to TimeType.MAX_PRECISION).foreach { p =>
+      expr = CurrentTime(Literal(p))
+      assert(expr.precision == p, s"Precision should be $p")
+      assert(expr.dataType == TimeType(p))
+      assert(expr.checkInputDataTypes() == TypeCheckSuccess)
+    }
+
     // test out of range precision => checkInputDataTypes fails
     expr = CurrentTime(Literal(2 + 8))
     assert(expr.checkInputDataTypes() ==
@@ -322,7 +330,7 @@ class TimeExpressionsSuite extends SparkFunSuite with 
ExpressionEvalHelper {
         errorSubClass = "VALUE_OUT_OF_RANGE",
         messageParameters = Map(
           "exprName" -> toSQLId("precision"),
-          "valueRange" -> s"[${TimeType.MIN_PRECISION}, 
${TimeType.MICROS_PRECISION}]",
+          "valueRange" -> s"[${TimeType.MIN_PRECISION}, 
${TimeType.MAX_PRECISION}]",
           "currentValue" -> toSQLValue(10, IntegerType)
         )
       )
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/XmlExpressionsSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/XmlExpressionsSuite.scala
index fb64b6fd7410..54bef646739f 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/XmlExpressionsSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/XmlExpressionsSuite.scala
@@ -449,7 +449,10 @@ class XmlExpressionsSuite extends SparkFunSuite with 
ExpressionEvalHelper {
       (3, "14:30:45.123"),
       (4, "14:30:45.1234"),
       (5, "14:30:45.12345"),
-      (6, "14:30:45.123456")
+      (6, "14:30:45.123456"),
+      (7, "14:30:45.1234567"),
+      (8, "14:30:45.12345678"),
+      (9, "23:59:59.999999999")
     )
 
     testData.foreach { case (precision, timeStr) =>
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DataTypeParserSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DataTypeParserSuite.scala
index 084bdbc460b7..6f9488ab4e52 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DataTypeParserSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/DataTypeParserSuite.scala
@@ -64,6 +64,10 @@ class DataTypeParserSuite extends SparkFunSuite with 
SQLHelper {
   checkDataType("time(0) without time zone", TimeType(0))
   checkDataType("TIME(6)", TimeType(6))
   checkDataType("TIME(6) WITHOUT TIME ZONE", TimeType(6))
+  checkDataType("time(7)", TimeType(7))
+  checkDataType("TIME(8)", TimeType(8))
+  checkDataType("time(9)", TimeType(9))
+  checkDataType("TIME(9) WITHOUT TIME ZONE", TimeType(9))
   checkDataType("timestamp", TimestampType)
   checkDataType("TIMESTAMP WITH LOCAL TIME ZONE", TimestampType)
   checkDataType("TIMESTAMP WITHOUT TIME ZONE", TimestampNTZType)
@@ -280,16 +284,16 @@ class DataTypeParserSuite extends SparkFunSuite with 
SQLHelper {
   test("unsupported precision of the time data type") {
     checkError(
       exception = intercept[SparkException] {
-        CatalystSqlParser.parseDataType("time(9)")
+        CatalystSqlParser.parseDataType("time(10)")
       },
       condition = "UNSUPPORTED_TIME_PRECISION",
-      parameters = Map("precision" -> "9"))
+      parameters = Map("precision" -> "10"))
     checkError(
       exception = intercept[SparkException] {
-        CatalystSqlParser.parseDataType("time(8) without time zone")
+        CatalystSqlParser.parseDataType("time(11) without time zone")
       },
       condition = "UNSUPPORTED_TIME_PRECISION",
-      parameters = Map("precision" -> "8"))
+      parameters = Map("precision" -> "11"))
     checkError(
       exception = intercept[ParseException] {
         CatalystSqlParser.parseDataType("time(-1)")
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/ExpressionParserSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/ExpressionParserSuite.scala
index 636d16c78615..30bed5ca7649 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/ExpressionParserSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/ExpressionParserSuite.scala
@@ -1367,6 +1367,18 @@ class ExpressionParserSuite extends AnalysisTest {
     assertEqual("tIme '12:13:14'", Literal(LocalTime.parse("12:13:14")))
     assertEqual("TIME'23:59:59.999999'", 
Literal(LocalTime.parse("23:59:59.999999")))
 
+    // ANSI SQL: the literal precision is the number of fractional-second 
digits. 7-9 digits
+    // produce a nanosecond-precision TIME literal; <= 6 digits keep the 
default precision (6).
+    assertEqual(
+      "TIME '12:34:56.1234567'",
+      Literal.create(LocalTime.parse("12:34:56.123456700"), TimeType(7)))
+    assertEqual(
+      "TIME '12:34:56.12345678'",
+      Literal.create(LocalTime.parse("12:34:56.123456780"), TimeType(8)))
+    assertEqual(
+      "TIME '23:59:59.999999999'",
+      Literal.create(LocalTime.parse("23:59:59.999999999"), TimeType(9)))
+
     checkError(
       exception = parseException("time '12-13.14'"),
       condition = "INVALID_TYPED_LITERAL",
@@ -1376,6 +1388,17 @@ class ExpressionParserSuite extends AnalysisTest {
         fragment = "time '12-13.14'",
         start = 0,
         stop = 14))
+
+    // More than 9 fractional-second digits is rejected.
+    checkError(
+      exception = parseException("TIME '12:34:56.1234567890'"),
+      condition = "INVALID_TIME_LITERAL_PRECISION",
+      sqlState = "22023",
+      parameters = Map("value" -> "'12:34:56.1234567890'"),
+      context = ExpectedContext(
+        fragment = "TIME '12:34:56.1234567890'",
+        start = 0,
+        stop = 25))
   }
 
   test("collate expression origin") {
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/DateTimeTestUtils.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/DateTimeTestUtils.scala
index b17a22778801..a3f9fcaf8265 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/DateTimeTestUtils.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/DateTimeTestUtils.scala
@@ -113,14 +113,17 @@ object DateTimeTestUtils {
     result
   }
 
-  // Returns nanoseconds since midnight
+  // Returns nanoseconds since midnight. `micros` sets the microsecond part; 
`nanos` adds the
+  // sub-microsecond remainder (digits 7-9), e.g. localTime(1, 2, 3, 987654, 
321) is 01:02:03
+  // .987654321.
   def localTime(
       hour: Byte = 0,
       minute: Byte = 0,
       sec: Byte = 0,
-      micros: Int = 0): Long = {
-    val nanos = TimeUnit.MICROSECONDS.toNanos(micros).toInt
-    val localTime = LocalTime.of(hour, minute, sec, nanos)
+      micros: Int = 0,
+      nanos: Int = 0): Long = {
+    val nanoOfSecond = TimeUnit.MICROSECONDS.toNanos(micros).toInt + nanos
+    val localTime = LocalTime.of(hour, minute, sec, nanoOfSecond)
     localTimeToNanos(localTime)
   }
 }
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/DateTimeUtilsSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/DateTimeUtilsSuite.scala
index 8db143507819..0a6c9123a20e 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/DateTimeUtilsSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/DateTimeUtilsSuite.scala
@@ -1382,6 +1382,17 @@ class DateTimeUtilsSuite extends SparkFunSuite with 
Matchers with SQLHelper {
       Some(localTime(hour = 23, minute = 59, sec = 59, micros = 1)))
     checkStringToTime("23:59:59.999999",
       Some(localTime(hour = 23, minute = 59, sec = 59, micros = 999999)))
+    // Nanosecond resolution (7-9 fractional digits).
+    checkStringToTime("23:59:59.0000001",
+      Some(localTime(hour = 23, minute = 59, sec = 59, micros = 0, nanos = 
100)))
+    checkStringToTime("23:59:59.00000012",
+      Some(localTime(hour = 23, minute = 59, sec = 59, micros = 0, nanos = 
120)))
+    checkStringToTime("23:59:59.000000123",
+      Some(localTime(hour = 23, minute = 59, sec = 59, micros = 0, nanos = 
123)))
+    checkStringToTime("23:59:59.999999999",
+      Some(localTime(hour = 23, minute = 59, sec = 59, micros = 999999, nanos 
= 999)))
+    checkStringToTime("12:34:56.123456789",
+      Some(localTime(hour = 12, minute = 34, sec = 56, micros = 123456, nanos 
= 789)))
 
     checkStringToTime("1:2:3.0", Some(localTime(hour = 1, minute = 2, sec = 
3)))
     checkStringToTime("T1:02:3.04", Some(localTime(hour = 1, minute = 2, sec = 
3, micros = 40000)))
@@ -1601,6 +1612,15 @@ class DateTimeUtilsSuite extends SparkFunSuite with 
Matchers with SQLHelper {
       localTime(23, 59, 59, 120000))
     assert(truncateTimeToPrecision(localTime(23, 59, 59, 987654), 1) ==
       localTime(23, 59, 59, 900000))
+    // Nanosecond precisions 7, 8, 9.
+    assert(truncateTimeToPrecision(localTime(23, 59, 59, 999999, 999), 9) ==
+      localTime(23, 59, 59, 999999, 999))
+    assert(truncateTimeToPrecision(localTime(23, 59, 59, 999999, 999), 8) ==
+      localTime(23, 59, 59, 999999, 990))
+    assert(truncateTimeToPrecision(localTime(23, 59, 59, 999999, 999), 7) ==
+      localTime(23, 59, 59, 999999, 900))
+    assert(truncateTimeToPrecision(localTime(23, 59, 59, 999999, 999), 6) ==
+      localTime(23, 59, 59, 999999))
   }
 
   test("add day-time interval to time") {
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/TimeFormatterSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/TimeFormatterSuite.scala
index 9a707d80d248..e7e7136202e9 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/TimeFormatterSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/TimeFormatterSuite.scala
@@ -41,7 +41,16 @@ class TimeFormatterSuite extends SparkFunSuite with 
SQLHelper {
         ((12 * 3600 + 34 * 60 + 56) * 1000000000L + 789012000),
       ("23:59:59.000000", "HH:mm:ss.SSSSSS") -> (23 * 3600 + 59 * 60 + 59) * 
1000000000L,
       ("23:59:59.999999", "HH:mm:ss.SSSSSS") ->
-        ((23 * 3600 + 59 * 60 + 59) * 1000000000L + 999999000)
+        ((23 * 3600 + 59 * 60 + 59) * 1000000000L + 999999000),
+      // Nanosecond resolution (7-9 fractional digits).
+      ("12:34:56.7890123", "HH:mm:ss.SSSSSSS") ->
+        ((12 * 3600 + 34 * 60 + 56) * 1000000000L + 789012300),
+      ("12:34:56.78901234", "HH:mm:ss.SSSSSSSS") ->
+        ((12 * 3600 + 34 * 60 + 56) * 1000000000L + 789012340),
+      ("12:34:56.789012345", "HH:mm:ss.SSSSSSSSS") ->
+        ((12 * 3600 + 34 * 60 + 56) * 1000000000L + 789012345),
+      ("23:59:59.999999999", "HH:mm:ss.SSSSSSSSS") ->
+        ((23 * 3600 + 59 * 60 + 59) * 1000000000L + 999999999)
     ).foreach { case ((inputStr, pattern), expectedMicros) =>
       val formatter = TimeFormatter(format = pattern, isParsing = true)
       assert(formatter.parse(inputStr) === expectedMicros)
@@ -73,7 +82,12 @@ class TimeFormatterSuite extends SparkFunSuite with 
SQLHelper {
         "12:34:56.789012",
       ((23 * 3600 + 59 * 60 + 59) * 1000000000L, "HH:mm:ss.SSSSSS") -> 
"23:59:59.000000",
       ((23 * 3600 + 59 * 60 + 59) * 1000000000L + 999999000, 
"HH:mm:ss.SSSSSS") ->
-        "23:59:59.999999"
+        "23:59:59.999999",
+      // Nanosecond resolution (7-9 fractional digits).
+      ((12 * 3600 + 34 * 60 + 56) * 1000000000L + 789012345, 
"HH:mm:ss.SSSSSSSSS") ->
+        "12:34:56.789012345",
+      ((23 * 3600 + 59 * 60 + 59) * 1000000000L + 999999999, 
"HH:mm:ss.SSSSSSSSS") ->
+        "23:59:59.999999999"
     ).foreach { case ((micros, pattern), expectedStr) =>
       val formatter = TimeFormatter(format = pattern)
       assert(formatter.format(micros) === expectedStr)
@@ -128,6 +142,15 @@ class TimeFormatterSuite extends SparkFunSuite with 
SQLHelper {
       assert(formatter.format(nanos) === tsStr)
       assert(formatter.format(nanosToLocalTime(nanos)) === tsStr)
     }
+    // Sub-microsecond fractions (digits 7-9) are formatted without trailing 
zeros.
+    Seq(
+      100L -> "00:00:00.0000001",
+      120L -> "00:00:00.00000012",
+      123L -> "00:00:00.000000123",
+      999999999L -> "00:00:00.999999999").foreach { case (nanos, tsStr) =>
+      assert(formatter.format(nanos) === tsStr)
+      assert(formatter.format(nanosToLocalTime(nanos)) === tsStr)
+    }
   }
 
   test("missing am/pm field") {
@@ -163,7 +186,12 @@ class TimeFormatterSuite extends SparkFunSuite with 
SQLHelper {
       "00:00:00.000001" -> localTime(micros = 1),
       "01:02:03" -> localTime(1, 2, 3),
       "1:2:3.999999" -> localTime(1, 2, 3, 999999),
-      "23:59:59.1" -> localTime(23, 59, 59, 100000)
+      "23:59:59.1" -> localTime(23, 59, 59, 100000),
+      // Nanosecond resolution (7-9 fractional digits).
+      "1:2:3.9999999" -> localTime(1, 2, 3, 999999, 900),
+      "1:2:3.99999999" -> localTime(1, 2, 3, 999999, 990),
+      "1:2:3.999999999" -> localTime(1, 2, 3, 999999, 999),
+      "23:59:59.123456789" -> localTime(23, 59, 59, 123456, 789)
     ).foreach { case (inputStr, micros) =>
       assert(formatter.parse(inputStr) === micros)
     }
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DataTypeSuite.scala 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DataTypeSuite.scala
index ff395ea70566..8b90f618dc6d 100644
--- a/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DataTypeSuite.scala
+++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DataTypeSuite.scala
@@ -1459,7 +1459,7 @@ class DataTypeSuite extends SparkFunSuite with SQLHelper {
   }
 
   test("Parse time(n) as TimeType(n)") {
-    0 to 6 foreach { n =>
+    TimeType.MIN_PRECISION to TimeType.MAX_PRECISION foreach { n =>
       assert(DataType.fromJson(s"\"time($n)\"") == TimeType(n))
       val expectedStructType = StructType(Seq(StructField("t", TimeType(n))))
       assert(DataType.fromDDL(s"t time($n)") == expectedStructType)
@@ -1467,10 +1467,10 @@ class DataTypeSuite extends SparkFunSuite with 
SQLHelper {
 
     checkError(
       exception = intercept[SparkIllegalArgumentException] {
-        DataType.fromJson("\"time(9)\"")
+        DataType.fromJson("\"time(10)\"")
       },
       condition = "INVALID_JSON_DATA_TYPE",
-      parameters = Map("invalidType" -> "time(9)"))
+      parameters = Map("invalidType" -> "time(10)"))
     checkError(
       exception = intercept[ParseException] {
         DataType.fromDDL("t time(-1)")
diff --git 
a/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/ParquetVectorUpdaterFactory.java
 
b/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/ParquetVectorUpdaterFactory.java
index 8a72d2e5e731..35936dc80e88 100644
--- 
a/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/ParquetVectorUpdaterFactory.java
+++ 
b/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/ParquetVectorUpdaterFactory.java
@@ -165,8 +165,17 @@ public class ParquetVectorUpdaterFactory {
           return new LongUpdater();
         } else if (canReadAsDecimal(descriptor, sparkType)) {
           return new LongToDecimalUpdater(descriptor, (DecimalType) sparkType);
-        } else if (sparkType instanceof TimeType) {
-          return new LongAsNanosUpdater();
+        } else if (sparkType instanceof TimeType &&
+          isTimeTypeMatched(LogicalTypeAnnotation.TimeUnit.NANOS)) {
+          // TIME(NANOS) is stored as nanoseconds since midnight, matching the 
internal
+          // representation, so no unit conversion is needed; the decoded 
value is truncated to
+          // the requested precision (consistent with the row-based 
ParquetRowConverter path).
+          return new TimeUpdater(((TimeType) sparkType).precision(), /* 
fileStoresNanos = */ true);
+        } else if (sparkType instanceof TimeType &&
+          isTimeTypeMatched(LogicalTypeAnnotation.TimeUnit.MICROS)) {
+          // TIME(MICROS) is converted to nanoseconds, then truncated to the 
requested precision
+          // (consistent with the row-based ParquetRowConverter path).
+          return new TimeUpdater(((TimeType) sparkType).precision(), /* 
fileStoresNanos = */ false);
         }
       }
       case FLOAT -> {
@@ -883,7 +892,40 @@ public class ParquetVectorUpdaterFactory {
     }
   }
 
-  private static class LongAsNanosUpdater implements ParquetVectorUpdater {
+  // Reads an INT64 TIME column into the internal nanoseconds-since-midnight 
representation and
+  // truncates it to the requested TimeType precision. `fileStoresNanos` 
selects the on-disk unit:
+  // TIME(NANOS) stores nanos directly (identity), TIME(MICROS) stores micros 
(converted to nanos).
+  // Mirrors the row-based ParquetRowConverter path so the vectorized and 
non-vectorized readers
+  // agree on the decoded value, including when the requested precision is 
lower than the on-disk
+  // value's precision.
+  // 10^k for k in [0, 9] (TimeType.NANOS_PRECISION), indexed by the 
truncation scale
+  // (NANOS_PRECISION - p), used to truncate a nanosecond TIME value to the 
requested
+  // fractional-second precision. Length - 1 equals TimeType.NANOS_PRECISION.
+  private static final long[] TIME_TRUNCATION_FACTORS = {
+    1L, 10L, 100L, 1_000L, 10_000L, 100_000L,
+    1_000_000L, 10_000_000L, 100_000_000L, 1_000_000_000L
+  };
+
+  private static class TimeUpdater implements ParquetVectorUpdater {
+    // The truncation step for the requested precision. The precision is 
constant per column, so the
+    // factor is looked up once here rather than recomputed per value via the 
math.pow in
+    // DateTimeUtils.truncateTimeToPrecision (this is the vectorized hot loop).
+    private final long truncationFactor;
+    private final boolean fileStoresNanos;
+
+    TimeUpdater(int precision, boolean fileStoresNanos) {
+      this.fileStoresNanos = fileStoresNanos;
+      // scale = NANOS_PRECISION - precision; NANOS_PRECISION == 
factors.length - 1.
+      int scale = TIME_TRUNCATION_FACTORS.length - 1 - precision;
+      this.truncationFactor = TIME_TRUNCATION_FACTORS[scale];
+    }
+
+    private long toTruncatedNanos(long value) {
+      long nanos = fileStoresNanos ? value : 
DateTimeUtils.microsToNanos(value);
+      // Equivalent to DateTimeUtils.truncateTimeToPrecision with the factor 
hoisted.
+      return (nanos / truncationFactor) * truncationFactor;
+    }
+
     @Override
     public void readValues(
         int total,
@@ -892,7 +934,7 @@ public class ParquetVectorUpdaterFactory {
         VectorizedValuesReader valuesReader) {
       valuesReader.readLongs(total, values, offset);
       for (int i = 0; i < total; i++) {
-        values.putLong(offset + i, 
DateTimeUtils.microsToNanos(values.getLong(offset + i)));
+        values.putLong(offset + i, toTruncatedNanos(values.getLong(offset + 
i)));
       }
     }
 
@@ -906,7 +948,7 @@ public class ParquetVectorUpdaterFactory {
         int offset,
         WritableColumnVector values,
         VectorizedValuesReader valuesReader) {
-      values.putLong(offset, 
DateTimeUtils.microsToNanos(valuesReader.readLong()));
+      values.putLong(offset, toTruncatedNanos(valuesReader.readLong()));
     }
 
     @Override
@@ -915,8 +957,8 @@ public class ParquetVectorUpdaterFactory {
         WritableColumnVector values,
         WritableColumnVector dictionaryIds,
         Dictionary dictionary) {
-      long micros = dictionary.decodeToLong(dictionaryIds.getDictId(offset));
-      values.putLong(offset, DateTimeUtils.microsToNanos(micros));
+      long value = dictionary.decodeToLong(dictionaryIds.getDictId(offset));
+      values.putLong(offset, toTruncatedNanos(value));
     }
   }
 
diff --git 
a/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedColumnReader.java
 
b/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedColumnReader.java
index 971edfec3b11..eb7f1bd4d27d 100644
--- 
a/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedColumnReader.java
+++ 
b/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedColumnReader.java
@@ -166,9 +166,13 @@ public class VectorizedColumnReader {
       }
       case INT64: {
         boolean isDecimal = sparkType instanceof DecimalType;
+        // TIME columns (both MICROS and NANOS) need per-value processing in 
the updater: a unit
+        // conversion for MICROS and/or truncation to the requested precision. 
Lazy dictionary
+        // decoding would bypass the updater, so it must be disabled for them.
         boolean needsUpcast = (isDecimal && 
!DecimalType.is64BitDecimalType(sparkType)) ||
           updaterFactory.isTimestampTypeMatched(TimeUnit.MILLIS) ||
-          updaterFactory.isTimeTypeMatched(TimeUnit.MICROS);
+          updaterFactory.isTimeTypeMatched(TimeUnit.MICROS) ||
+          updaterFactory.isTimeTypeMatched(TimeUnit.NANOS);
         boolean needsRebase = 
updaterFactory.isTimestampTypeMatched(TimeUnit.MICROS) &&
           !"CORRECTED".equals(datetimeRebaseMode);
         isSupported = !needsUpcast && !needsRebase && 
!needsDecimalScaleRebase(sparkType);
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetRowConverter.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetRowConverter.scala
index c3016d929ac9..2200179f5a9e 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetRowConverter.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetRowConverter.scala
@@ -526,14 +526,19 @@ private[parquet] class ParquetRowConverter(
           }
         }
 
-      case _: TimeType
-        if 
parquetType.getLogicalTypeAnnotation.isInstanceOf[TimeLogicalTypeAnnotation] &&
-          parquetType.getLogicalTypeAnnotation
-            .asInstanceOf[TimeLogicalTypeAnnotation].getUnit == 
TimeUnit.MICROS =>
+      case t: TimeType
+        if 
parquetType.getLogicalTypeAnnotation.isInstanceOf[TimeLogicalTypeAnnotation] && 
{
+          val unit = parquetType.getLogicalTypeAnnotation
+            .asInstanceOf[TimeLogicalTypeAnnotation].getUnit
+          unit == TimeUnit.MICROS || unit == TimeUnit.NANOS
+        } =>
+        val fileStoresNanos = parquetType.getLogicalTypeAnnotation
+          .asInstanceOf[TimeLogicalTypeAnnotation].getUnit == TimeUnit.NANOS
+        val precision = t.precision
         new ParquetPrimitiveConverter(updater) {
           override def addLong(value: Long): Unit = {
-            val nanos = DateTimeUtils.microsToNanos(value)
-            this.updater.setLong(nanos)
+            val nanos = if (fileStoresNanos) value else 
DateTimeUtils.microsToNanos(value)
+            this.updater.setLong(DateTimeUtils.truncateTimeToPrecision(nanos, 
precision))
           }
         }
 
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetSchemaConverter.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetSchemaConverter.scala
index ba5261200464..a0acc40f9e01 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetSchemaConverter.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetSchemaConverter.scala
@@ -342,6 +342,9 @@ class ParquetToSparkSchemaConverter(
           case time: TimeLogicalTypeAnnotation
             if time.getUnit == TimeUnit.MICROS && !time.isAdjustedToUTC =>
             TimeType(TimeType.MICROS_PRECISION)
+          case time: TimeLogicalTypeAnnotation
+            if time.getUnit == TimeUnit.NANOS && !time.isAdjustedToUTC =>
+            TimeType(TimeType.NANOS_PRECISION)
           case _ => illegalType()
         }
 
@@ -710,9 +713,11 @@ class SparkToParquetSchemaConverter(
         Types.primitive(INT32, repetition)
           .as(LogicalTypeAnnotation.dateType()).named(field.name)
 
-      case _: TimeType =>
+      case t: TimeType =>
+        // Precision 0..6 is stored as TIME(MICROS); precision 7..9 as 
TIME(NANOS).
+        val unit = if (t.precision > TimeType.MICROS_PRECISION) TimeUnit.NANOS 
else TimeUnit.MICROS
         Types.primitive(INT64, repetition)
-          .as(LogicalTypeAnnotation.timeType(false, 
TimeUnit.MICROS)).named(field.name)
+          .as(LogicalTypeAnnotation.timeType(false, unit)).named(field.name)
 
       // NOTE: Spark SQL can write timestamp values to Parquet using INT96, 
TIMESTAMP_MICROS or
       // TIMESTAMP_MILLIS. TIMESTAMP_MICROS is recommended but INT96 is the 
default to keep the
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetWriteSupport.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetWriteSupport.scala
index 18c0a47facda..c7d7426a94ea 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetWriteSupport.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetWriteSupport.scala
@@ -307,6 +307,11 @@ class ParquetWriteSupport extends 
WriteSupport[InternalRow] with Logging {
           recordConsumer.addLong(
             timestampNanosToEpochNanos(row.getTimestampNTZNanos(ordinal), 
isNtz = true))
 
+      case t: TimeType if t.precision > TimeType.MICROS_PRECISION =>
+        // Precision 7..9 is stored as TIME(NANOS); internal storage is 
already nanos.
+        (row: SpecializedGetters, ordinal: Int) =>
+          recordConsumer.addLong(row.getLong(ordinal))
+
       case _: TimeType =>
         (row: SpecializedGetters, ordinal: Int) =>
           
recordConsumer.addLong(DateTimeUtils.nanosToMicros(row.getLong(ordinal)))
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/types/ops/TimeTypeParquetOps.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/types/ops/TimeTypeParquetOps.scala
index 96c7bc30a1da..f155d35378a8 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/types/ops/TimeTypeParquetOps.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/types/ops/TimeTypeParquetOps.scala
@@ -33,27 +33,35 @@ import org.apache.spark.sql.types.{DataType, TimeType}
 /**
  * Parquet operations for TimeType.
  *
- * TimeType is a primitive Long-backed type stored in Parquet as INT64 with the
- * TIME(isAdjustedToUTC=false, unit=MICROS) logical type annotation.
+ * TimeType is a primitive Long-backed type stored in Parquet as INT64 with a
+ * TIME(isAdjustedToUTC=false) logical type annotation. The annotation unit 
depends on the
+ * requested precision: precision 0..6 uses TIME(MICROS), precision 7..9 uses 
TIME(NANOS).
  *
  * IMPORTANT - internal vs Parquet representation:
  *   - Spark internal: nanoseconds since midnight (Long)
- *   - Parquet storage: microseconds since midnight (INT64)
- *   - Write path: nanos -> micros (DateTimeUtils.nanosToMicros)
- *   - Read path: micros -> nanos (DateTimeUtils.microsToNanos)
+ *   - Parquet storage: microseconds (precision <= 6) or nanoseconds 
(precision >= 7) since midnight
+ *   - Write path: nanos -> micros (DateTimeUtils.nanosToMicros) for 
TIME(MICROS); identity for
+ *     TIME(NANOS)
+ *   - Read path: micros -> nanos (DateTimeUtils.microsToNanos) for 
TIME(MICROS); identity for
+ *     TIME(NANOS), then truncated to the requested precision
  *
  * @param t the TimeType with precision information
  * @since 4.3.0
  */
 case class TimeTypeParquetOps(t: TimeType) extends ParquetTypeOps {
 
+  // Precision 7..9 is stored as TIME(NANOS); precision 0..6 as TIME(MICROS).
+  private def storesNanos: Boolean = t.precision > TimeType.MICROS_PRECISION
+
   // ==================== Schema Conversion ====================
 
   override def convertToParquetType(
-      fieldName: String, repetition: Repetition, inShredded: Boolean): Type =
+      fieldName: String, repetition: Repetition, inShredded: Boolean): Type = {
+    val unit = if (storesNanos) TimeUnit.NANOS else TimeUnit.MICROS
     Types.primitive(INT64, repetition)
-      .as(LogicalTypeAnnotation.timeType(false, TimeUnit.MICROS))
+      .as(LogicalTypeAnnotation.timeType(false, unit))
       .named(fieldName)
+  }
 
   // ==================== Value Write ====================
 
@@ -63,8 +71,14 @@ case class TimeTypeParquetOps(t: TimeType) extends 
ParquetTypeOps {
   ): (SpecializedGetters, Int) => Unit =
     // Evaluate the supplier at write time (not creation time) because 
recordConsumer
     // is null during init() and set later in prepareForWrite().
-    (row: SpecializedGetters, ordinal: Int) =>
-      
recordConsumer().addLong(DateTimeUtils.nanosToMicros(row.getLong(ordinal)))
+    if (storesNanos) {
+      // Internal storage is already nanoseconds since midnight, so write it 
unchanged.
+      (row: SpecializedGetters, ordinal: Int) =>
+        recordConsumer().addLong(row.getLong(ordinal))
+    } else {
+      (row: SpecializedGetters, ordinal: Int) =>
+        
recordConsumer().addLong(DateTimeUtils.nanosToMicros(row.getLong(ordinal)))
+    }
 
   // ==================== Row-Based Read ====================
 
@@ -73,14 +87,16 @@ case class TimeTypeParquetOps(t: TimeType) extends 
ParquetTypeOps {
       updater: ParentContainerUpdater): Converter with 
HasParentContainerUpdater = {
     // Framework-first dispatch in ParquetRowConverter routes here whenever the
     // requested Spark type is TimeType, regardless of the actual Parquet 
encoding.
-    // Without this guard, files whose column is raw INT64, INT64 TIME(NANOS),
-    // INT64 TIMESTAMP(MICROS), INT32 TIME(MILLIS), etc. would silently decode 
as
-    // microsToNanos(value) and produce wrong results. Mirrors the inline guard
-    // that existed in ParquetRowConverter before the framework dispatch.
+    // Without this guard, files whose column is raw INT64, INT64 
TIMESTAMP(MICROS),
+    // INT32 TIME(MILLIS), etc. would silently decode as microsToNanos(value) 
and
+    // produce wrong results.
     TimeTypeParquetOps.requireCompatibleParquetType(t, parquetType)
+    val fileStoresNanos = TimeTypeParquetOps.isNanosTime(parquetType)
+    val precision = t.precision
     new ParquetPrimitiveConverter(updater) {
       override def addLong(value: Long): Unit = {
-        this.updater.setLong(DateTimeUtils.microsToNanos(value))
+        val nanos = if (fileStoresNanos) value else 
DateTimeUtils.microsToNanos(value)
+        this.updater.setLong(DateTimeUtils.truncateTimeToPrecision(nanos, 
precision))
       }
     }
   }
@@ -93,14 +109,27 @@ case class TimeTypeParquetOps(t: TimeType) extends 
ParquetTypeOps {
 private[ops] object TimeTypeParquetOps {
 
   /**
-   * Validates that a Parquet field can be decoded as TimeType. TimeType is 
written
-   * as INT64 with TIME(MICROS, isAdjustedToUTC=false). On read, any INT64 
TIME(MICROS)
-   * column is accepted regardless of the isAdjustedToUTC flag: Spark's 
zone-less TimeType
-   * decodes the raw micros-of-day identically either way, matching the legacy
-   * ParquetRowConverter guard (see SPARK-57416). Any other encoding (raw 
INT64, INT64
-   * TIME(NANOS), INT32 TIME(MILLIS), INT64 TIMESTAMP(_), decimal-annotated, 
etc.) cannot
-   * be decoded as TimeType - throw the same error as the legacy 
ParquetRowConverter path
-   * so reads fail loudly instead of silently misinterpreting bytes.
+   * Whether the Parquet field is an INT64 TIME(NANOS) column. The 
isAdjustedToUTC flag is
+   * intentionally ignored: Spark's TimeType is zone-less, so a TIME(NANOS) 
value decodes to the
+   * same nanos-of-day regardless of the flag (consistent with 
requireCompatibleParquetType and
+   * the legacy reader; see SPARK-57416).
+   */
+  private[ops] def isNanosTime(parquetType: Type): Boolean =
+    parquetType.getLogicalTypeAnnotation match {
+      case t: LogicalTypeAnnotation.TimeLogicalTypeAnnotation =>
+        t.getUnit == TimeUnit.NANOS
+      case _ => false
+    }
+
+  /**
+   * Validates that a Parquet field can be decoded as TimeType. TimeType is 
written as INT64 with
+   * TIME(MICROS, isAdjustedToUTC=false) for precision 0..6 and TIME(NANOS, 
isAdjustedToUTC=false)
+   * for precision 7..9. On read, any INT64 TIME(MICROS) or TIME(NANOS) column 
is accepted
+   * regardless of the isAdjustedToUTC flag: Spark's zone-less TimeType 
decodes the raw
+   * time-of-day identically either way, matching the legacy 
ParquetRowConverter guard (see
+   * SPARK-57416). Any other encoding (raw INT64, INT32 TIME(MILLIS), INT64 
TIMESTAMP(_),
+   * decimal-annotated, etc.) cannot be decoded as TimeType - throw the same 
error as the legacy
+   * ParquetRowConverter path so reads fail loudly instead of silently 
misinterpreting bytes.
    */
   private[ops] def requireCompatibleParquetType(
       sparkType: TimeType, parquetType: Type): Unit = {
@@ -108,13 +137,13 @@ private[ops] object TimeTypeParquetOps {
       parquetType.asPrimitiveType.getPrimitiveTypeName == INT64 &&
       (parquetType.getLogicalTypeAnnotation match {
         case t: LogicalTypeAnnotation.TimeLogicalTypeAnnotation =>
-          // Accept both isAdjustedToUTC=false and =true. Spark's TimeType is 
zone-less
-          // local time, so the UTC-adjustment flag carries no extra 
information on read:
-          // the raw micros-of-day value decodes identically either way. 
Mirroring the
-          // legacy ParquetRowConverter guard (which only checked the TIME 
annotation and
-          // the MICROS unit) keeps the framework row-based read path 
consistent with both
-          // the legacy row-based reader and the still-lenient vectorized 
reader. SPARK-57416.
-          t.getUnit == TimeUnit.MICROS
+          // Accept both MICROS (precision 0..6) and NANOS (precision 7..9), 
and both
+          // isAdjustedToUTC=false and =true. Spark's TimeType is zone-less 
local time, so the
+          // UTC-adjustment flag carries no extra information on read: the raw 
time-of-day value
+          // decodes identically either way. Mirroring the legacy 
ParquetRowConverter guard keeps
+          // the framework row-based read path consistent with the legacy and 
vectorized readers.
+          // SPARK-57416.
+          t.getUnit == TimeUnit.MICROS || t.getUnit == TimeUnit.NANOS
         case _ => false
       })
     if (!ok) {
diff --git 
a/sql/core/src/test/resources/sql-tests/analyzer-results/cast.sql.out 
b/sql/core/src/test/resources/sql-tests/analyzer-results/cast.sql.out
index a3c8e894f8a9..2ad6e040585b 100644
--- a/sql/core/src/test/resources/sql-tests/analyzer-results/cast.sql.out
+++ b/sql/core/src/test/resources/sql-tests/analyzer-results/cast.sql.out
@@ -1676,14 +1676,14 @@ Project [cast(23:59:59.999999 as decimal(11,6)) AS 
CAST(TIME '23:59:59.999999' A
 -- !query
 SELECT CAST(time '23:59:59.999999999' AS decimal(14, 9))
 -- !query analysis
-Project [cast(23:59:59.999999 as decimal(14,9)) AS CAST(TIME '23:59:59.999999' 
AS DECIMAL(14,9))#x]
+Project [cast(23:59:59.999999999 as decimal(14,9)) AS CAST(TIME 
'23:59:59.999999999' AS DECIMAL(14,9))#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT CAST(time '23:59:59.999999999' AS decimal(20, 10))
 -- !query analysis
-Project [cast(23:59:59.999999 as decimal(20,10)) AS CAST(TIME 
'23:59:59.999999' AS DECIMAL(20,10))#x]
+Project [cast(23:59:59.999999999 as decimal(20,10)) AS CAST(TIME 
'23:59:59.999999999' AS DECIMAL(20,10))#x]
 +- OneRowRelation
 
 
@@ -1732,5 +1732,5 @@ Project [cast(23:59:59.999999 as decimal(11,5)) AS 
CAST(TIME '23:59:59.999999' A
 -- !query
 SELECT CAST(time '23:59:59.999999999' AS decimal(14, 8))
 -- !query analysis
-Project [cast(23:59:59.999999 as decimal(14,8)) AS CAST(TIME '23:59:59.999999' 
AS DECIMAL(14,8))#x]
+Project [cast(23:59:59.999999999 as decimal(14,8)) AS CAST(TIME 
'23:59:59.999999999' AS DECIMAL(14,8))#x]
 +- OneRowRelation
diff --git 
a/sql/core/src/test/resources/sql-tests/analyzer-results/nonansi/cast.sql.out 
b/sql/core/src/test/resources/sql-tests/analyzer-results/nonansi/cast.sql.out
index b18f5739fd68..c48776a1de31 100644
--- 
a/sql/core/src/test/resources/sql-tests/analyzer-results/nonansi/cast.sql.out
+++ 
b/sql/core/src/test/resources/sql-tests/analyzer-results/nonansi/cast.sql.out
@@ -1523,14 +1523,14 @@ Project [cast(23:59:59.999999 as decimal(11,6)) AS 
CAST(TIME '23:59:59.999999' A
 -- !query
 SELECT CAST(time '23:59:59.999999999' AS decimal(14, 9))
 -- !query analysis
-Project [cast(23:59:59.999999 as decimal(14,9)) AS CAST(TIME '23:59:59.999999' 
AS DECIMAL(14,9))#x]
+Project [cast(23:59:59.999999999 as decimal(14,9)) AS CAST(TIME 
'23:59:59.999999999' AS DECIMAL(14,9))#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT CAST(time '23:59:59.999999999' AS decimal(20, 10))
 -- !query analysis
-Project [cast(23:59:59.999999 as decimal(20,10)) AS CAST(TIME 
'23:59:59.999999' AS DECIMAL(20,10))#x]
+Project [cast(23:59:59.999999999 as decimal(20,10)) AS CAST(TIME 
'23:59:59.999999999' AS DECIMAL(20,10))#x]
 +- OneRowRelation
 
 
@@ -1579,5 +1579,5 @@ Project [cast(23:59:59.999999 as decimal(11,5)) AS 
CAST(TIME '23:59:59.999999' A
 -- !query
 SELECT CAST(time '23:59:59.999999999' AS decimal(14, 8))
 -- !query analysis
-Project [cast(23:59:59.999999 as decimal(14,8)) AS CAST(TIME '23:59:59.999999' 
AS DECIMAL(14,8))#x]
+Project [cast(23:59:59.999999999 as decimal(14,8)) AS CAST(TIME 
'23:59:59.999999999' AS DECIMAL(14,8))#x]
 +- OneRowRelation
diff --git 
a/sql/core/src/test/resources/sql-tests/analyzer-results/time.sql.out 
b/sql/core/src/test/resources/sql-tests/analyzer-results/time.sql.out
index 8c5c55bfa0a0..d69a6df9eb5e 100644
--- a/sql/core/src/test/resources/sql-tests/analyzer-results/time.sql.out
+++ b/sql/core/src/test/resources/sql-tests/analyzer-results/time.sql.out
@@ -959,35 +959,35 @@ Project [time_trunc(MICROSECOND, 12:34:56.123456) AS 
time_trunc(MICROSECOND, TIM
 -- !query
 SELECT time_trunc('HOUR', time'12:34:56.123456789')
 -- !query analysis
-Project [time_trunc(HOUR, 12:34:56.123456) AS time_trunc(HOUR, TIME 
'12:34:56.123456')#x]
+Project [time_trunc(HOUR, 12:34:56.123456789) AS time_trunc(HOUR, TIME 
'12:34:56.123456789')#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_trunc('MINUTE', time'12:34:56.123456789')
 -- !query analysis
-Project [time_trunc(MINUTE, 12:34:56.123456) AS time_trunc(MINUTE, TIME 
'12:34:56.123456')#x]
+Project [time_trunc(MINUTE, 12:34:56.123456789) AS time_trunc(MINUTE, TIME 
'12:34:56.123456789')#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_trunc('SECOND', time'12:34:56.123456789')
 -- !query analysis
-Project [time_trunc(SECOND, 12:34:56.123456) AS time_trunc(SECOND, TIME 
'12:34:56.123456')#x]
+Project [time_trunc(SECOND, 12:34:56.123456789) AS time_trunc(SECOND, TIME 
'12:34:56.123456789')#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_trunc('MILLISECOND', time'12:34:56.123456789')
 -- !query analysis
-Project [time_trunc(MILLISECOND, 12:34:56.123456) AS time_trunc(MILLISECOND, 
TIME '12:34:56.123456')#x]
+Project [time_trunc(MILLISECOND, 12:34:56.123456789) AS 
time_trunc(MILLISECOND, TIME '12:34:56.123456789')#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_trunc('MICROSECOND', time'12:34:56.123456789')
 -- !query analysis
-Project [time_trunc(MICROSECOND, 12:34:56.123456) AS time_trunc(MICROSECOND, 
TIME '12:34:56.123456')#x]
+Project [time_trunc(MICROSECOND, 12:34:56.123456789) AS 
time_trunc(MICROSECOND, TIME '12:34:56.123456789')#x]
 +- OneRowRelation
 
 
@@ -1064,70 +1064,70 @@ Project [time_trunc(MICROSECOND, 00:00:00) AS 
time_trunc(MICROSECOND, TIME '00:0
 -- !query
 SELECT time_trunc('HOUR', time'00:00:00.000000001')
 -- !query analysis
-Project [time_trunc(HOUR, 00:00:00) AS time_trunc(HOUR, TIME '00:00:00')#x]
+Project [time_trunc(HOUR, 00:00:00.000000001) AS time_trunc(HOUR, TIME 
'00:00:00.000000001')#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_trunc('MINUTE', time'00:00:00.000000001')
 -- !query analysis
-Project [time_trunc(MINUTE, 00:00:00) AS time_trunc(MINUTE, TIME '00:00:00')#x]
+Project [time_trunc(MINUTE, 00:00:00.000000001) AS time_trunc(MINUTE, TIME 
'00:00:00.000000001')#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_trunc('SECOND', time'00:00:00.000000001')
 -- !query analysis
-Project [time_trunc(SECOND, 00:00:00) AS time_trunc(SECOND, TIME '00:00:00')#x]
+Project [time_trunc(SECOND, 00:00:00.000000001) AS time_trunc(SECOND, TIME 
'00:00:00.000000001')#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_trunc('MILLISECOND', time'00:00:00.000000001')
 -- !query analysis
-Project [time_trunc(MILLISECOND, 00:00:00) AS time_trunc(MILLISECOND, TIME 
'00:00:00')#x]
+Project [time_trunc(MILLISECOND, 00:00:00.000000001) AS 
time_trunc(MILLISECOND, TIME '00:00:00.000000001')#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_trunc('MICROSECOND', time'00:00:00.000000001')
 -- !query analysis
-Project [time_trunc(MICROSECOND, 00:00:00) AS time_trunc(MICROSECOND, TIME 
'00:00:00')#x]
+Project [time_trunc(MICROSECOND, 00:00:00.000000001) AS 
time_trunc(MICROSECOND, TIME '00:00:00.000000001')#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_trunc('HOUR', time'23:59:59.999999999')
 -- !query analysis
-Project [time_trunc(HOUR, 23:59:59.999999) AS time_trunc(HOUR, TIME 
'23:59:59.999999')#x]
+Project [time_trunc(HOUR, 23:59:59.999999999) AS time_trunc(HOUR, TIME 
'23:59:59.999999999')#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_trunc('MINUTE', time'23:59:59.999999999')
 -- !query analysis
-Project [time_trunc(MINUTE, 23:59:59.999999) AS time_trunc(MINUTE, TIME 
'23:59:59.999999')#x]
+Project [time_trunc(MINUTE, 23:59:59.999999999) AS time_trunc(MINUTE, TIME 
'23:59:59.999999999')#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_trunc('SECOND', time'23:59:59.999999999')
 -- !query analysis
-Project [time_trunc(SECOND, 23:59:59.999999) AS time_trunc(SECOND, TIME 
'23:59:59.999999')#x]
+Project [time_trunc(SECOND, 23:59:59.999999999) AS time_trunc(SECOND, TIME 
'23:59:59.999999999')#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_trunc('MILLISECOND', time'23:59:59.999999999')
 -- !query analysis
-Project [time_trunc(MILLISECOND, 23:59:59.999999) AS time_trunc(MILLISECOND, 
TIME '23:59:59.999999')#x]
+Project [time_trunc(MILLISECOND, 23:59:59.999999999) AS 
time_trunc(MILLISECOND, TIME '23:59:59.999999999')#x]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_trunc('MICROSECOND', time'23:59:59.999999999')
 -- !query analysis
-Project [time_trunc(MICROSECOND, 23:59:59.999999) AS time_trunc(MICROSECOND, 
TIME '23:59:59.999999')#x]
+Project [time_trunc(MICROSECOND, 23:59:59.999999999) AS 
time_trunc(MICROSECOND, TIME '23:59:59.999999999')#x]
 +- OneRowRelation
 
 
@@ -1533,35 +1533,35 @@ Project [time_diff(MICROSECOND, 00:00:00, 
12:34:56.123456) AS time_diff(MICROSEC
 -- !query
 SELECT time_diff('HOUR', time'00:00:00', time'12:34:56.123456789')
 -- !query analysis
-Project [time_diff(HOUR, 00:00:00, 12:34:56.123456) AS time_diff(HOUR, TIME 
'00:00:00', TIME '12:34:56.123456')#xL]
+Project [time_diff(HOUR, 00:00:00, 12:34:56.123456789) AS time_diff(HOUR, TIME 
'00:00:00', TIME '12:34:56.123456789')#xL]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_diff('MINUTE', time'00:00:00', time'12:34:56.123456789')
 -- !query analysis
-Project [time_diff(MINUTE, 00:00:00, 12:34:56.123456) AS time_diff(MINUTE, 
TIME '00:00:00', TIME '12:34:56.123456')#xL]
+Project [time_diff(MINUTE, 00:00:00, 12:34:56.123456789) AS time_diff(MINUTE, 
TIME '00:00:00', TIME '12:34:56.123456789')#xL]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_diff('SECOND', time'00:00:00', time'12:34:56.123456789')
 -- !query analysis
-Project [time_diff(SECOND, 00:00:00, 12:34:56.123456) AS time_diff(SECOND, 
TIME '00:00:00', TIME '12:34:56.123456')#xL]
+Project [time_diff(SECOND, 00:00:00, 12:34:56.123456789) AS time_diff(SECOND, 
TIME '00:00:00', TIME '12:34:56.123456789')#xL]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_diff('MILLISECOND', time'00:00:00', time'12:34:56.123456789')
 -- !query analysis
-Project [time_diff(MILLISECOND, 00:00:00, 12:34:56.123456) AS 
time_diff(MILLISECOND, TIME '00:00:00', TIME '12:34:56.123456')#xL]
+Project [time_diff(MILLISECOND, 00:00:00, 12:34:56.123456789) AS 
time_diff(MILLISECOND, TIME '00:00:00', TIME '12:34:56.123456789')#xL]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_diff('MICROSECOND', time'00:00:00', time'12:34:56.123456789')
 -- !query analysis
-Project [time_diff(MICROSECOND, 00:00:00, 12:34:56.123456) AS 
time_diff(MICROSECOND, TIME '00:00:00', TIME '12:34:56.123456')#xL]
+Project [time_diff(MICROSECOND, 00:00:00, 12:34:56.123456789) AS 
time_diff(MICROSECOND, TIME '00:00:00', TIME '12:34:56.123456789')#xL]
 +- OneRowRelation
 
 
@@ -1638,70 +1638,70 @@ Project [time_diff(MICROSECOND, 00:00:00, 00:00:00) AS 
time_diff(MICROSECOND, TI
 -- !query
 SELECT time_diff('HOUR', time'00:00:00', time'00:00:00.000000001')
 -- !query analysis
-Project [time_diff(HOUR, 00:00:00, 00:00:00) AS time_diff(HOUR, TIME 
'00:00:00', TIME '00:00:00')#xL]
+Project [time_diff(HOUR, 00:00:00, 00:00:00.000000001) AS time_diff(HOUR, TIME 
'00:00:00', TIME '00:00:00.000000001')#xL]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_diff('MINUTE', time'00:00:00', time'00:00:00.000000001')
 -- !query analysis
-Project [time_diff(MINUTE, 00:00:00, 00:00:00) AS time_diff(MINUTE, TIME 
'00:00:00', TIME '00:00:00')#xL]
+Project [time_diff(MINUTE, 00:00:00, 00:00:00.000000001) AS time_diff(MINUTE, 
TIME '00:00:00', TIME '00:00:00.000000001')#xL]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_diff('SECOND', time'00:00:00', time'00:00:00.000000001')
 -- !query analysis
-Project [time_diff(SECOND, 00:00:00, 00:00:00) AS time_diff(SECOND, TIME 
'00:00:00', TIME '00:00:00')#xL]
+Project [time_diff(SECOND, 00:00:00, 00:00:00.000000001) AS time_diff(SECOND, 
TIME '00:00:00', TIME '00:00:00.000000001')#xL]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_diff('MILLISECOND', time'00:00:00', time'00:00:00.000000001')
 -- !query analysis
-Project [time_diff(MILLISECOND, 00:00:00, 00:00:00) AS time_diff(MILLISECOND, 
TIME '00:00:00', TIME '00:00:00')#xL]
+Project [time_diff(MILLISECOND, 00:00:00, 00:00:00.000000001) AS 
time_diff(MILLISECOND, TIME '00:00:00', TIME '00:00:00.000000001')#xL]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_diff('MICROSECOND', time'00:00:00', time'00:00:00.000000001')
 -- !query analysis
-Project [time_diff(MICROSECOND, 00:00:00, 00:00:00) AS time_diff(MICROSECOND, 
TIME '00:00:00', TIME '00:00:00')#xL]
+Project [time_diff(MICROSECOND, 00:00:00, 00:00:00.000000001) AS 
time_diff(MICROSECOND, TIME '00:00:00', TIME '00:00:00.000000001')#xL]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_diff('HOUR', time'00:00:00', time'23:59:59.999999999')
 -- !query analysis
-Project [time_diff(HOUR, 00:00:00, 23:59:59.999999) AS time_diff(HOUR, TIME 
'00:00:00', TIME '23:59:59.999999')#xL]
+Project [time_diff(HOUR, 00:00:00, 23:59:59.999999999) AS time_diff(HOUR, TIME 
'00:00:00', TIME '23:59:59.999999999')#xL]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_diff('MINUTE', time'00:00:00', time'23:59:59.999999999')
 -- !query analysis
-Project [time_diff(MINUTE, 00:00:00, 23:59:59.999999) AS time_diff(MINUTE, 
TIME '00:00:00', TIME '23:59:59.999999')#xL]
+Project [time_diff(MINUTE, 00:00:00, 23:59:59.999999999) AS time_diff(MINUTE, 
TIME '00:00:00', TIME '23:59:59.999999999')#xL]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_diff('SECOND', time'00:00:00', time'23:59:59.999999999')
 -- !query analysis
-Project [time_diff(SECOND, 00:00:00, 23:59:59.999999) AS time_diff(SECOND, 
TIME '00:00:00', TIME '23:59:59.999999')#xL]
+Project [time_diff(SECOND, 00:00:00, 23:59:59.999999999) AS time_diff(SECOND, 
TIME '00:00:00', TIME '23:59:59.999999999')#xL]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_diff('MILLISECOND', time'00:00:00', time'23:59:59.999999999')
 -- !query analysis
-Project [time_diff(MILLISECOND, 00:00:00, 23:59:59.999999) AS 
time_diff(MILLISECOND, TIME '00:00:00', TIME '23:59:59.999999')#xL]
+Project [time_diff(MILLISECOND, 00:00:00, 23:59:59.999999999) AS 
time_diff(MILLISECOND, TIME '00:00:00', TIME '23:59:59.999999999')#xL]
 +- OneRowRelation
 
 
 -- !query
 SELECT time_diff('MICROSECOND', time'00:00:00', time'23:59:59.999999999')
 -- !query analysis
-Project [time_diff(MICROSECOND, 00:00:00, 23:59:59.999999) AS 
time_diff(MICROSECOND, TIME '00:00:00', TIME '23:59:59.999999')#xL]
+Project [time_diff(MICROSECOND, 00:00:00, 23:59:59.999999999) AS 
time_diff(MICROSECOND, TIME '00:00:00', TIME '23:59:59.999999999')#xL]
 +- OneRowRelation
 
 
diff --git a/sql/core/src/test/resources/sql-tests/results/cast.sql.out 
b/sql/core/src/test/resources/sql-tests/results/cast.sql.out
index 24f9668b3b17..a6d3cee116aa 100644
--- a/sql/core/src/test/resources/sql-tests/results/cast.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/cast.sql.out
@@ -2753,17 +2753,17 @@ struct<CAST(TIME '23:59:59.999999' AS 
DECIMAL(11,6)):decimal(11,6)>
 -- !query
 SELECT CAST(time '23:59:59.999999999' AS decimal(14, 9))
 -- !query schema
-struct<CAST(TIME '23:59:59.999999' AS DECIMAL(14,9)):decimal(14,9)>
+struct<CAST(TIME '23:59:59.999999999' AS DECIMAL(14,9)):decimal(14,9)>
 -- !query output
-86399.999999000
+86399.999999999
 
 
 -- !query
 SELECT CAST(time '23:59:59.999999999' AS decimal(20, 10))
 -- !query schema
-struct<CAST(TIME '23:59:59.999999' AS DECIMAL(20,10)):decimal(20,10)>
+struct<CAST(TIME '23:59:59.999999999' AS DECIMAL(20,10)):decimal(20,10)>
 -- !query output
-86399.9999990000
+86399.9999999990
 
 
 -- !query
@@ -2868,6 +2868,6 @@ struct<CAST(TIME '23:59:59.999999' AS 
DECIMAL(11,5)):decimal(11,5)>
 -- !query
 SELECT CAST(time '23:59:59.999999999' AS decimal(14, 8))
 -- !query schema
-struct<CAST(TIME '23:59:59.999999' AS DECIMAL(14,8)):decimal(14,8)>
+struct<CAST(TIME '23:59:59.999999999' AS DECIMAL(14,8)):decimal(14,8)>
 -- !query output
-86399.99999900
+86400.00000000
diff --git a/sql/core/src/test/resources/sql-tests/results/nonansi/cast.sql.out 
b/sql/core/src/test/resources/sql-tests/results/nonansi/cast.sql.out
index 4d66796a577b..1bc9c12b0421 100644
--- a/sql/core/src/test/resources/sql-tests/results/nonansi/cast.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/nonansi/cast.sql.out
@@ -1804,17 +1804,17 @@ struct<CAST(TIME '23:59:59.999999' AS 
DECIMAL(11,6)):decimal(11,6)>
 -- !query
 SELECT CAST(time '23:59:59.999999999' AS decimal(14, 9))
 -- !query schema
-struct<CAST(TIME '23:59:59.999999' AS DECIMAL(14,9)):decimal(14,9)>
+struct<CAST(TIME '23:59:59.999999999' AS DECIMAL(14,9)):decimal(14,9)>
 -- !query output
-86399.999999000
+86399.999999999
 
 
 -- !query
 SELECT CAST(time '23:59:59.999999999' AS decimal(20, 10))
 -- !query schema
-struct<CAST(TIME '23:59:59.999999' AS DECIMAL(20,10)):decimal(20,10)>
+struct<CAST(TIME '23:59:59.999999999' AS DECIMAL(20,10)):decimal(20,10)>
 -- !query output
-86399.9999990000
+86399.9999999990
 
 
 -- !query
@@ -1868,6 +1868,6 @@ struct<CAST(TIME '23:59:59.999999' AS 
DECIMAL(11,5)):decimal(11,5)>
 -- !query
 SELECT CAST(time '23:59:59.999999999' AS decimal(14, 8))
 -- !query schema
-struct<CAST(TIME '23:59:59.999999' AS DECIMAL(14,8)):decimal(14,8)>
+struct<CAST(TIME '23:59:59.999999999' AS DECIMAL(14,8)):decimal(14,8)>
 -- !query output
-86399.99999900
+86400.00000000
diff --git a/sql/core/src/test/resources/sql-tests/results/time.sql.out 
b/sql/core/src/test/resources/sql-tests/results/time.sql.out
index 5767be09ece5..c61c37f27cd3 100644
--- a/sql/core/src/test/resources/sql-tests/results/time.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/time.sql.out
@@ -1122,7 +1122,7 @@ struct<time_trunc(MICROSECOND, TIME 
'12:34:56.123456'):time(6)>
 -- !query
 SELECT time_trunc('HOUR', time'12:34:56.123456789')
 -- !query schema
-struct<time_trunc(HOUR, TIME '12:34:56.123456'):time(6)>
+struct<time_trunc(HOUR, TIME '12:34:56.123456789'):time(9)>
 -- !query output
 12:00:00
 
@@ -1130,7 +1130,7 @@ struct<time_trunc(HOUR, TIME '12:34:56.123456'):time(6)>
 -- !query
 SELECT time_trunc('MINUTE', time'12:34:56.123456789')
 -- !query schema
-struct<time_trunc(MINUTE, TIME '12:34:56.123456'):time(6)>
+struct<time_trunc(MINUTE, TIME '12:34:56.123456789'):time(9)>
 -- !query output
 12:34:00
 
@@ -1138,7 +1138,7 @@ struct<time_trunc(MINUTE, TIME '12:34:56.123456'):time(6)>
 -- !query
 SELECT time_trunc('SECOND', time'12:34:56.123456789')
 -- !query schema
-struct<time_trunc(SECOND, TIME '12:34:56.123456'):time(6)>
+struct<time_trunc(SECOND, TIME '12:34:56.123456789'):time(9)>
 -- !query output
 12:34:56
 
@@ -1146,7 +1146,7 @@ struct<time_trunc(SECOND, TIME '12:34:56.123456'):time(6)>
 -- !query
 SELECT time_trunc('MILLISECOND', time'12:34:56.123456789')
 -- !query schema
-struct<time_trunc(MILLISECOND, TIME '12:34:56.123456'):time(6)>
+struct<time_trunc(MILLISECOND, TIME '12:34:56.123456789'):time(9)>
 -- !query output
 12:34:56.123
 
@@ -1154,7 +1154,7 @@ struct<time_trunc(MILLISECOND, TIME 
'12:34:56.123456'):time(6)>
 -- !query
 SELECT time_trunc('MICROSECOND', time'12:34:56.123456789')
 -- !query schema
-struct<time_trunc(MICROSECOND, TIME '12:34:56.123456'):time(6)>
+struct<time_trunc(MICROSECOND, TIME '12:34:56.123456789'):time(9)>
 -- !query output
 12:34:56.123456
 
@@ -1242,7 +1242,7 @@ struct<time_trunc(MICROSECOND, TIME '00:00:00'):time(6)>
 -- !query
 SELECT time_trunc('HOUR', time'00:00:00.000000001')
 -- !query schema
-struct<time_trunc(HOUR, TIME '00:00:00'):time(6)>
+struct<time_trunc(HOUR, TIME '00:00:00.000000001'):time(9)>
 -- !query output
 00:00:00
 
@@ -1250,7 +1250,7 @@ struct<time_trunc(HOUR, TIME '00:00:00'):time(6)>
 -- !query
 SELECT time_trunc('MINUTE', time'00:00:00.000000001')
 -- !query schema
-struct<time_trunc(MINUTE, TIME '00:00:00'):time(6)>
+struct<time_trunc(MINUTE, TIME '00:00:00.000000001'):time(9)>
 -- !query output
 00:00:00
 
@@ -1258,7 +1258,7 @@ struct<time_trunc(MINUTE, TIME '00:00:00'):time(6)>
 -- !query
 SELECT time_trunc('SECOND', time'00:00:00.000000001')
 -- !query schema
-struct<time_trunc(SECOND, TIME '00:00:00'):time(6)>
+struct<time_trunc(SECOND, TIME '00:00:00.000000001'):time(9)>
 -- !query output
 00:00:00
 
@@ -1266,7 +1266,7 @@ struct<time_trunc(SECOND, TIME '00:00:00'):time(6)>
 -- !query
 SELECT time_trunc('MILLISECOND', time'00:00:00.000000001')
 -- !query schema
-struct<time_trunc(MILLISECOND, TIME '00:00:00'):time(6)>
+struct<time_trunc(MILLISECOND, TIME '00:00:00.000000001'):time(9)>
 -- !query output
 00:00:00
 
@@ -1274,7 +1274,7 @@ struct<time_trunc(MILLISECOND, TIME '00:00:00'):time(6)>
 -- !query
 SELECT time_trunc('MICROSECOND', time'00:00:00.000000001')
 -- !query schema
-struct<time_trunc(MICROSECOND, TIME '00:00:00'):time(6)>
+struct<time_trunc(MICROSECOND, TIME '00:00:00.000000001'):time(9)>
 -- !query output
 00:00:00
 
@@ -1282,7 +1282,7 @@ struct<time_trunc(MICROSECOND, TIME '00:00:00'):time(6)>
 -- !query
 SELECT time_trunc('HOUR', time'23:59:59.999999999')
 -- !query schema
-struct<time_trunc(HOUR, TIME '23:59:59.999999'):time(6)>
+struct<time_trunc(HOUR, TIME '23:59:59.999999999'):time(9)>
 -- !query output
 23:00:00
 
@@ -1290,7 +1290,7 @@ struct<time_trunc(HOUR, TIME '23:59:59.999999'):time(6)>
 -- !query
 SELECT time_trunc('MINUTE', time'23:59:59.999999999')
 -- !query schema
-struct<time_trunc(MINUTE, TIME '23:59:59.999999'):time(6)>
+struct<time_trunc(MINUTE, TIME '23:59:59.999999999'):time(9)>
 -- !query output
 23:59:00
 
@@ -1298,7 +1298,7 @@ struct<time_trunc(MINUTE, TIME '23:59:59.999999'):time(6)>
 -- !query
 SELECT time_trunc('SECOND', time'23:59:59.999999999')
 -- !query schema
-struct<time_trunc(SECOND, TIME '23:59:59.999999'):time(6)>
+struct<time_trunc(SECOND, TIME '23:59:59.999999999'):time(9)>
 -- !query output
 23:59:59
 
@@ -1306,7 +1306,7 @@ struct<time_trunc(SECOND, TIME '23:59:59.999999'):time(6)>
 -- !query
 SELECT time_trunc('MILLISECOND', time'23:59:59.999999999')
 -- !query schema
-struct<time_trunc(MILLISECOND, TIME '23:59:59.999999'):time(6)>
+struct<time_trunc(MILLISECOND, TIME '23:59:59.999999999'):time(9)>
 -- !query output
 23:59:59.999
 
@@ -1314,7 +1314,7 @@ struct<time_trunc(MILLISECOND, TIME 
'23:59:59.999999'):time(6)>
 -- !query
 SELECT time_trunc('MICROSECOND', time'23:59:59.999999999')
 -- !query schema
-struct<time_trunc(MICROSECOND, TIME '23:59:59.999999'):time(6)>
+struct<time_trunc(MICROSECOND, TIME '23:59:59.999999999'):time(9)>
 -- !query output
 23:59:59.999999
 
@@ -1843,7 +1843,7 @@ struct<time_diff(MICROSECOND, TIME '00:00:00', TIME 
'12:34:56.123456'):bigint>
 -- !query
 SELECT time_diff('HOUR', time'00:00:00', time'12:34:56.123456789')
 -- !query schema
-struct<time_diff(HOUR, TIME '00:00:00', TIME '12:34:56.123456'):bigint>
+struct<time_diff(HOUR, TIME '00:00:00', TIME '12:34:56.123456789'):bigint>
 -- !query output
 12
 
@@ -1851,7 +1851,7 @@ struct<time_diff(HOUR, TIME '00:00:00', TIME 
'12:34:56.123456'):bigint>
 -- !query
 SELECT time_diff('MINUTE', time'00:00:00', time'12:34:56.123456789')
 -- !query schema
-struct<time_diff(MINUTE, TIME '00:00:00', TIME '12:34:56.123456'):bigint>
+struct<time_diff(MINUTE, TIME '00:00:00', TIME '12:34:56.123456789'):bigint>
 -- !query output
 754
 
@@ -1859,7 +1859,7 @@ struct<time_diff(MINUTE, TIME '00:00:00', TIME 
'12:34:56.123456'):bigint>
 -- !query
 SELECT time_diff('SECOND', time'00:00:00', time'12:34:56.123456789')
 -- !query schema
-struct<time_diff(SECOND, TIME '00:00:00', TIME '12:34:56.123456'):bigint>
+struct<time_diff(SECOND, TIME '00:00:00', TIME '12:34:56.123456789'):bigint>
 -- !query output
 45296
 
@@ -1867,7 +1867,7 @@ struct<time_diff(SECOND, TIME '00:00:00', TIME 
'12:34:56.123456'):bigint>
 -- !query
 SELECT time_diff('MILLISECOND', time'00:00:00', time'12:34:56.123456789')
 -- !query schema
-struct<time_diff(MILLISECOND, TIME '00:00:00', TIME '12:34:56.123456'):bigint>
+struct<time_diff(MILLISECOND, TIME '00:00:00', TIME 
'12:34:56.123456789'):bigint>
 -- !query output
 45296123
 
@@ -1875,7 +1875,7 @@ struct<time_diff(MILLISECOND, TIME '00:00:00', TIME 
'12:34:56.123456'):bigint>
 -- !query
 SELECT time_diff('MICROSECOND', time'00:00:00', time'12:34:56.123456789')
 -- !query schema
-struct<time_diff(MICROSECOND, TIME '00:00:00', TIME '12:34:56.123456'):bigint>
+struct<time_diff(MICROSECOND, TIME '00:00:00', TIME 
'12:34:56.123456789'):bigint>
 -- !query output
 45296123456
 
@@ -1963,7 +1963,7 @@ struct<time_diff(MICROSECOND, TIME '00:00:00', TIME 
'00:00:00'):bigint>
 -- !query
 SELECT time_diff('HOUR', time'00:00:00', time'00:00:00.000000001')
 -- !query schema
-struct<time_diff(HOUR, TIME '00:00:00', TIME '00:00:00'):bigint>
+struct<time_diff(HOUR, TIME '00:00:00', TIME '00:00:00.000000001'):bigint>
 -- !query output
 0
 
@@ -1971,7 +1971,7 @@ struct<time_diff(HOUR, TIME '00:00:00', TIME 
'00:00:00'):bigint>
 -- !query
 SELECT time_diff('MINUTE', time'00:00:00', time'00:00:00.000000001')
 -- !query schema
-struct<time_diff(MINUTE, TIME '00:00:00', TIME '00:00:00'):bigint>
+struct<time_diff(MINUTE, TIME '00:00:00', TIME '00:00:00.000000001'):bigint>
 -- !query output
 0
 
@@ -1979,7 +1979,7 @@ struct<time_diff(MINUTE, TIME '00:00:00', TIME 
'00:00:00'):bigint>
 -- !query
 SELECT time_diff('SECOND', time'00:00:00', time'00:00:00.000000001')
 -- !query schema
-struct<time_diff(SECOND, TIME '00:00:00', TIME '00:00:00'):bigint>
+struct<time_diff(SECOND, TIME '00:00:00', TIME '00:00:00.000000001'):bigint>
 -- !query output
 0
 
@@ -1987,7 +1987,7 @@ struct<time_diff(SECOND, TIME '00:00:00', TIME 
'00:00:00'):bigint>
 -- !query
 SELECT time_diff('MILLISECOND', time'00:00:00', time'00:00:00.000000001')
 -- !query schema
-struct<time_diff(MILLISECOND, TIME '00:00:00', TIME '00:00:00'):bigint>
+struct<time_diff(MILLISECOND, TIME '00:00:00', TIME 
'00:00:00.000000001'):bigint>
 -- !query output
 0
 
@@ -1995,7 +1995,7 @@ struct<time_diff(MILLISECOND, TIME '00:00:00', TIME 
'00:00:00'):bigint>
 -- !query
 SELECT time_diff('MICROSECOND', time'00:00:00', time'00:00:00.000000001')
 -- !query schema
-struct<time_diff(MICROSECOND, TIME '00:00:00', TIME '00:00:00'):bigint>
+struct<time_diff(MICROSECOND, TIME '00:00:00', TIME 
'00:00:00.000000001'):bigint>
 -- !query output
 0
 
@@ -2003,7 +2003,7 @@ struct<time_diff(MICROSECOND, TIME '00:00:00', TIME 
'00:00:00'):bigint>
 -- !query
 SELECT time_diff('HOUR', time'00:00:00', time'23:59:59.999999999')
 -- !query schema
-struct<time_diff(HOUR, TIME '00:00:00', TIME '23:59:59.999999'):bigint>
+struct<time_diff(HOUR, TIME '00:00:00', TIME '23:59:59.999999999'):bigint>
 -- !query output
 23
 
@@ -2011,7 +2011,7 @@ struct<time_diff(HOUR, TIME '00:00:00', TIME 
'23:59:59.999999'):bigint>
 -- !query
 SELECT time_diff('MINUTE', time'00:00:00', time'23:59:59.999999999')
 -- !query schema
-struct<time_diff(MINUTE, TIME '00:00:00', TIME '23:59:59.999999'):bigint>
+struct<time_diff(MINUTE, TIME '00:00:00', TIME '23:59:59.999999999'):bigint>
 -- !query output
 1439
 
@@ -2019,7 +2019,7 @@ struct<time_diff(MINUTE, TIME '00:00:00', TIME 
'23:59:59.999999'):bigint>
 -- !query
 SELECT time_diff('SECOND', time'00:00:00', time'23:59:59.999999999')
 -- !query schema
-struct<time_diff(SECOND, TIME '00:00:00', TIME '23:59:59.999999'):bigint>
+struct<time_diff(SECOND, TIME '00:00:00', TIME '23:59:59.999999999'):bigint>
 -- !query output
 86399
 
@@ -2027,7 +2027,7 @@ struct<time_diff(SECOND, TIME '00:00:00', TIME 
'23:59:59.999999'):bigint>
 -- !query
 SELECT time_diff('MILLISECOND', time'00:00:00', time'23:59:59.999999999')
 -- !query schema
-struct<time_diff(MILLISECOND, TIME '00:00:00', TIME '23:59:59.999999'):bigint>
+struct<time_diff(MILLISECOND, TIME '00:00:00', TIME 
'23:59:59.999999999'):bigint>
 -- !query output
 86399999
 
@@ -2035,7 +2035,7 @@ struct<time_diff(MILLISECOND, TIME '00:00:00', TIME 
'23:59:59.999999'):bigint>
 -- !query
 SELECT time_diff('MICROSECOND', time'00:00:00', time'23:59:59.999999999')
 -- !query schema
-struct<time_diff(MICROSECOND, TIME '00:00:00', TIME '23:59:59.999999'):bigint>
+struct<time_diff(MICROSECOND, TIME '00:00:00', TIME 
'23:59:59.999999999'):bigint>
 -- !query output
 86399999999
 
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/CsvFunctionsSuite.scala 
b/sql/core/src/test/scala/org/apache/spark/sql/CsvFunctionsSuite.scala
index a70ef16bdf71..32802a7b7282 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/CsvFunctionsSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/CsvFunctionsSuite.scala
@@ -929,7 +929,10 @@ class CsvFunctionsSuite extends SharedSparkSession {
       (3, LocalTime.of(14, 30, 45, 123000000), "14:30:45.123"),
       (4, LocalTime.of(14, 30, 45, 123400000), "14:30:45.1234"),
       (5, LocalTime.of(14, 30, 45, 123450000), "14:30:45.12345"),
-      (6, LocalTime.of(14, 30, 45, 123456000), "14:30:45.123456")
+      (6, LocalTime.of(14, 30, 45, 123456000), "14:30:45.123456"),
+      (7, LocalTime.of(14, 30, 45, 123456700), "14:30:45.1234567"),
+      (8, LocalTime.of(14, 30, 45, 123456780), "14:30:45.12345678"),
+      (9, LocalTime.of(14, 30, 45, 123456789), "14:30:45.123456789")
     )
 
     testData.foreach { case (precision, time, timeStr) =>
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/JsonFunctionsSuite.scala 
b/sql/core/src/test/scala/org/apache/spark/sql/JsonFunctionsSuite.scala
index 50d5b4be24ee..a5f5a9a0e917 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/JsonFunctionsSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/JsonFunctionsSuite.scala
@@ -1732,7 +1732,10 @@ class JsonFunctionsSuite extends SharedSparkSession {
       (3, LocalTime.of(14, 30, 45, 123000000), "14:30:45.123"),
       (4, LocalTime.of(14, 30, 45, 123400000), "14:30:45.1234"),
       (5, LocalTime.of(14, 30, 45, 123450000), "14:30:45.12345"),
-      (6, LocalTime.of(14, 30, 45, 123456000), "14:30:45.123456")
+      (6, LocalTime.of(14, 30, 45, 123456000), "14:30:45.123456"),
+      (7, LocalTime.of(14, 30, 45, 123456700), "14:30:45.1234567"),
+      (8, LocalTime.of(14, 30, 45, 123456780), "14:30:45.12345678"),
+      (9, LocalTime.of(14, 30, 45, 123456789), "14:30:45.123456789")
     )
 
     testData.foreach { case (precision, time, timeStr) =>
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/TimeFunctionsSuiteBase.scala 
b/sql/core/src/test/scala/org/apache/spark/sql/TimeFunctionsSuiteBase.scala
index bd89dfa45dec..29395eba2dc1 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/TimeFunctionsSuiteBase.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/TimeFunctionsSuiteBase.scala
@@ -87,7 +87,7 @@ abstract class TimeFunctionsSuiteBase extends 
SharedSparkSession {
   }
 
   test("SPARK-52882: current_time function with specified precision") {
-    (0 to 6).foreach { precision: Int =>
+    (0 to TimeType.MAX_PRECISION).foreach { precision: Int =>
       // Create a dummy DataFrame with a single row to test the 
current_time(precision) function.
       val df = spark.range(1)
 
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/XmlFunctionsSuite.scala 
b/sql/core/src/test/scala/org/apache/spark/sql/XmlFunctionsSuite.scala
index f87bd2df5164..ef875064e0ff 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/XmlFunctionsSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/XmlFunctionsSuite.scala
@@ -591,7 +591,10 @@ class XmlFunctionsSuite extends SharedSparkSession {
       (3, LocalTime.of(14, 30, 45, 123000000), "14:30:45.123"),
       (4, LocalTime.of(14, 30, 45, 123400000), "14:30:45.1234"),
       (5, LocalTime.of(14, 30, 45, 123450000), "14:30:45.12345"),
-      (6, LocalTime.of(14, 30, 45, 123456000), "14:30:45.123456")
+      (6, LocalTime.of(14, 30, 45, 123456000), "14:30:45.123456"),
+      (7, LocalTime.of(14, 30, 45, 123456700), "14:30:45.1234567"),
+      (8, LocalTime.of(14, 30, 45, 123456780), "14:30:45.12345678"),
+      (9, LocalTime.of(14, 30, 45, 123456789), "14:30:45.123456789")
     )
 
     testData.foreach { case (precision, time, timeStr) =>
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/orc/OrcQuerySuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/orc/OrcQuerySuite.scala
index c4e041e13ebd..909ebf5daf39 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/orc/OrcQuerySuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/orc/OrcQuerySuite.scala
@@ -1146,13 +1146,16 @@ abstract class OrcQuerySuite extends OrcQueryTest with 
SharedSparkSession {
           CAST(TIME'12:34:56.123' AS TIME(3)) as time_p3,
           CAST(TIME'12:34:56.1234' AS TIME(4)) as time_p4,
           CAST(TIME'12:34:56.12345' AS TIME(5)) as time_p5,
-          CAST(TIME'12:34:56.123456' AS TIME(6)) as time_p6
+          CAST(TIME'12:34:56.123456' AS TIME(6)) as time_p6,
+          CAST(TIME'12:34:56.1234567' AS TIME(7)) as time_p7,
+          CAST(TIME'12:34:56.12345678' AS TIME(8)) as time_p8,
+          CAST(TIME'12:34:56.123456789' AS TIME(9)) as time_p9
       """)
 
       df.write.mode("overwrite").orc(path)
       val result = spark.read.orc(path)
 
-      (0 to 6).foreach { p =>
+      (0 to TimeType.MAX_PRECISION).foreach { p =>
         assert(result.schema(s"time_p$p").dataType == TimeType(p))
       }
       checkAnswer(result, df)
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetIOSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetIOSuite.scala
index 3a06b59b6a18..6b41da841e56 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetIOSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetIOSuite.scala
@@ -1927,6 +1927,88 @@ class ParquetIOSuite extends ParquetTest with 
SharedSparkSession {
     }
   }
 
+  test("Read TimeType for the logical TIME(NANOS) type") {
+    val schema = MessageTypeParser.parseMessageType(
+      """message root {
+        |  required int64 time_nanos(TIME(NANOS,false));
+        |}""".stripMargin)
+
+    for (dictEnabled <- Seq(true, false)) {
+      withTempDir { dir =>
+        val tablePath = new Path(s"${dir.getCanonicalPath}/times.parquet")
+        val numRecords = 100
+
+        val writer = createParquetWriter(schema, tablePath, dictionaryEnabled 
= dictEnabled)
+        (0 until numRecords).foreach { _ =>
+          val record = new SimpleGroup(schema)
+          // Internal storage is nanoseconds since midnight; TIME(NANOS) 
writes it unchanged.
+          record.add(0, localTime(23, 59, 59, 123456, 789))
+          writer.write(record)
+        }
+        writer.close
+
+        withAllParquetReaders {
+          val df = spark.read.parquet(tablePath.toString)
+          assertResult(df.schema) {
+            new StructType().add("time_nanos", 
TimeType(TimeType.NANOS_PRECISION))
+          }
+          val lt = LocalTime.of(23, 59, 59, 123456789)
+          val expected = (0 until numRecords).map { _ => lt }.toDF()
+          checkAnswer(df, expected)
+        }
+      }
+    }
+  }
+
+  test("TimeType nanosecond round-trip through Parquet") {
+    withAllParquetReaders {
+      Seq(7, 8, 9).foreach { p =>
+        withTempPath { path =>
+          val df = spark.sql(
+            s"SELECT CAST(TIME '23:59:59.123456789' AS TIME($p)) AS t")
+          df.write.parquet(path.getCanonicalPath)
+          val readBack = spark.read.parquet(path.getCanonicalPath)
+          assert(readBack.schema.head.dataType === TimeType(p))
+          checkAnswer(readBack, df)
+        }
+      }
+    }
+  }
+
+  test("Read TIME with a lower precision than the stored value truncates in 
both readers") {
+    // A raw TIME(NANOS) file carrying full nanosecond precision (.123456789), 
read back with an
+    // explicit lower precision (TIME(7)). Both the vectorized and the 
row-based reader must drop
+    // the sub-100ns digits (.123456789 -> .1234567); otherwise the requested 
type's precision is
+    // violated. Covers both the dictionary and plain decode paths.
+    val schema = MessageTypeParser.parseMessageType(
+      """message root {
+        |  required int64 time_nanos(TIME(NANOS,false));
+        |}""".stripMargin)
+    val readSchema = new StructType().add("time_nanos", TimeType(7))
+    val expected = LocalTime.of(23, 59, 59, 123456700)
+
+    for (dictEnabled <- Seq(true, false)) {
+      withTempDir { dir =>
+        val tablePath = new Path(s"${dir.getCanonicalPath}/times.parquet")
+        val numRecords = 100
+
+        val writer = createParquetWriter(schema, tablePath, dictionaryEnabled 
= dictEnabled)
+        (0 until numRecords).foreach { _ =>
+          val record = new SimpleGroup(schema)
+          record.add(0, localTime(23, 59, 59, 123456, 789))
+          writer.write(record)
+        }
+        writer.close
+
+        withAllParquetReaders {
+          val df = spark.read.schema(readSchema).parquet(tablePath.toString)
+          assertResult(df.schema)(readSchema)
+          checkAnswer(df, (0 until numRecords).map(_ => Row(expected)))
+        }
+      }
+    }
+  }
+
   // Deterministic INT32 sample shared by the INT32 widening tests below. 
Mixes sign,
   // zero, and MIN/MAX boundaries to catch sign-extension and precision 
regressions.
   private def widenSampleAt(i: Int): Int = i % 5 match {
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/types/ops/TimeTypeParquetOpsSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/types/ops/TimeTypeParquetOpsSuite.scala
index 36221cec8b2c..fa44297103ea 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/types/ops/TimeTypeParquetOpsSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/types/ops/TimeTypeParquetOpsSuite.scala
@@ -28,26 +28,25 @@ import org.apache.spark.sql.types.TimeType
 /**
  * Unit tests for [[TimeTypeParquetOps.requireCompatibleParquetType]].
  *
- * TimeType is written to Parquet as INT64 TIME(MICROS, 
isAdjustedToUTC=false). The
- * read-path guard accepts any INT64 TIME(MICROS) column - both 
isAdjustedToUTC values -
- * and rejects every other primitive/annotation combination so that reading 
fails loudly
- * rather than silently mis-decoding (e.g. interpreting NANOS as MICROS, which 
would be
- * off by 1000x).
+ * TimeType is stored in Parquet as INT64 TIME(MICROS, isAdjustedToUTC=false) 
for precision
+ * 0..6 and INT64 TIME(NANOS, isAdjustedToUTC=false) for precision 7..9. The 
read-path guard
+ * accepts both of those local-time encodings and rejects every other 
primitive/annotation
+ * combination so that reading fails loudly rather than silently mis-decoding.
  *
  * SPARK-57416: the guard accepts isAdjustedToUTC=true to mirror the legacy
- * ParquetRowConverter guard (which only checks the TIME annotation and the 
MICROS unit).
- * Spark's TimeType is zone-less local time, so the flag carries no extra 
information on
- * read and the raw micros-of-day value decodes identically either way. This 
keeps the
- * framework read path consistent with both the legacy row-based reader and 
the vectorized
- * reader.
+ * ParquetRowConverter guard (which only checks the TIME annotation and unit). 
Spark's
+ * TimeType is zone-less local time, so the flag carries no extra information 
on read and the
+ * raw time-of-day value decodes identically either way. This keeps the 
framework read path
+ * consistent with both the legacy row-based reader and the vectorized reader.
  */
 class TimeTypeParquetOpsSuite extends SparkFunSuite {
 
   private val timeMicros = TimeType(TimeType.MICROS_PRECISION)
+  private val timeNanos = TimeType(TimeType.NANOS_PRECISION)
 
   // ---------- accept ----------
 
-  test("accepts INT64 TIME(MICROS, isAdjustedToUTC=false) - the canonical 
encoding") {
+  test("accepts INT64 TIME(MICROS, isAdjustedToUTC=false) - the canonical 
micros encoding") {
     val field = Types.primitive(INT64, REQUIRED)
       .as(LogicalTypeAnnotation.timeType(false, TimeUnit.MICROS))
       .named("c")
@@ -68,17 +67,29 @@ class TimeTypeParquetOpsSuite extends SparkFunSuite {
     TimeTypeParquetOps.requireCompatibleParquetType(timeMicros, field)
   }
 
-  // ---------- the primary reject paths ----------
-
-  test("rejects raw INT64 with no logical type annotation") {
-    val field = Types.primitive(INT64, REQUIRED).named("c")
-    assertRejects(timeMicros, field)
+  test("accepts INT64 TIME(NANOS, isAdjustedToUTC=false) - the canonical nanos 
encoding") {
+    val field = Types.primitive(INT64, REQUIRED)
+      .as(LogicalTypeAnnotation.timeType(false, TimeUnit.NANOS))
+      .named("c")
+    // Must not throw.
+    TimeTypeParquetOps.requireCompatibleParquetType(timeNanos, field)
   }
 
-  test("rejects INT64 TIME(NANOS, isAdjustedToUTC=false)") {
+  test("accepts INT64 TIME(NANOS, isAdjustedToUTC=true) - matches legacy 
lenient read") {
+    // Same zone-less reasoning as the MICROS case (SPARK-57416): the 
UTC-adjustment flag
+    // carries no information for Spark's local-time TimeType, so a NANOS 
column is accepted
+    // regardless of the flag.
     val field = Types.primitive(INT64, REQUIRED)
-      .as(LogicalTypeAnnotation.timeType(false, TimeUnit.NANOS))
+      .as(LogicalTypeAnnotation.timeType(true, TimeUnit.NANOS))
       .named("c")
+    // Must not throw.
+    TimeTypeParquetOps.requireCompatibleParquetType(timeNanos, field)
+  }
+
+  // ---------- the primary reject paths ----------
+
+  test("rejects raw INT64 with no logical type annotation") {
+    val field = Types.primitive(INT64, REQUIRED).named("c")
     assertRejects(timeMicros, field)
   }
 
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/sources/PartitionedWriteSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/sources/PartitionedWriteSuite.scala
index 5d9a7b0aafc7..fb37ffaa7aa2 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/sources/PartitionedWriteSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/sources/PartitionedWriteSuite.scala
@@ -232,7 +232,10 @@ class PartitionedWriteSuite extends SharedSparkSession {
       "00:01:02.999999" -> TimeType(6),
       "12:00:00" -> TimeType(1),
       "23:59:59.000001" -> TimeType(),
-      "23:59:59.999999" -> TimeType(6)
+      "23:59:59.999999" -> TimeType(6),
+      "00:00:00.0000019" -> TimeType(7),
+      "12:34:56.12345678" -> TimeType(8),
+      "23:59:59.999999999" -> TimeType(9)
     ).foreach { case (timeStr, timeType) =>
       withTempPath { f =>
         val df = sql(s"select 0 AS id, cast('$timeStr' as ${timeType.sql}) AS 
tt")


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

Reply via email to