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

wenchen 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 ef3a470  [SPARK-37705][SQL] Rebase timestamps in the session time zone 
saved in Parquet/Avro metadata
ef3a470 is described below

commit ef3a47038606ea426c15844b0400f5141acd5108
Author: Max Gekk <[email protected]>
AuthorDate: Tue Dec 28 21:39:26 2021 +0800

    [SPARK-37705][SQL] Rebase timestamps in the session time zone saved in 
Parquet/Avro metadata
    
    ### What changes were proposed in this pull request?
    In the PR, I propose to add new metadata key `org.apache.spark.timeZone` 
which Spark writes to Parquet/Avro matadata while performing of datetimes 
rebase in the `LEGACY` mode (see the SQL configs:
    - `spark.sql.parquet.datetimeRebaseModeInWrite`,
    - `spark.sql.parquet.int96RebaseModeInWrite` and
    - `spark.sql.avro.datetimeRebaseModeInWrite`).
    
    The writers uses the current session time zone (see the SQL config 
`spark.sql.session.timeZone`) in rebasing of Parquet/Avro timestamp columns.
    
    At the reader side, Spark tries to get info about the writer's time zone 
from the new metadata property:
    ```
    $ java -jar ~parquet-tools-1.12.0.jar meta 
./part-00000-b0d90bf0-ce60-4b4f-b453-b33f61ab2b2a-c000.snappy.parquet
    ...
    extra:       org.apache.spark.timeZone = America/Los_Angeles
    extra:       org.apache.spark.legacyDateTime =
    ```
    and use it in rebasing timestamps to the Proleptic Gregorian calendar. In 
the case when the reader cannot retrieve the original time zone from 
Parquet/Avro metadata, it uses the default JVM time zone for backward 
compatibility.
    
    ### Why are the changes needed?
    Before the changes, Spark assumes that a writer uses the default JVM time 
zone while rebasing of dates/timestamps. And if a reader and the writer have 
different JVM time zone settings, the reader cannot load such columns in the 
`LEGACY` mode correctly. So, the reader will have full info about writer 
settings after the changes.
    
    ### Does this PR introduce _any_ user-facing change?
    Yes. After the changes, Parquet/Avro writers use the session time zone 
while timestamp rebasing in the `LEGACY` mode instead of the default JVM time 
zone. Need to highlight that the session time zone is set to the JVM time zone 
by default.
    
    ### How was this patch tested?
    1. By running new tests:
    ```
    $ build/sbt "test:testOnly *ParquetRebaseDatetimeV1Suite"
    $ build/sbt "test:testOnly *ParquetRebaseDatetimeV2Suite"
    $ build/sbt "test:testOnly *AvroV1Suite"
    $ build/sbt "test:testOnly *AvroV2Suite"
    ```
    2. And related existing test suites:
    ```
    $ build/sbt "test:testOnly *DateTimeUtilsSuite"
    $ build/sbt "test:testOnly *RebaseDateTimeSuite"
    $ build/sbt "test:testOnly *TimestampFormatterSuite"
    $ build/sbt "avro/test:testOnly 
org.apache.spark.sql.avro.AvroCatalystDataConversionSuite"
    $ build/sbt "test:testOnly *AvroRowReaderSuite"
    $ build/sbt "test:testOnly *AvroSerdeSuite"
    $ build/sbt "test:testOnly *ParquetVectorizedSuite"
    ```
    
    3. Also modified the test `SPARK-31159: rebasing timestamps in write` to 
check loading timestamps in the LEGACY mode when the session time zone and JVM 
time zone are different.
    
    4. Generated parquet files by Spark 3.2.0 (the commit 
https://github.com/apache/spark/commit/5d45a415f3a29898d92380380cfd82bfc7f579ea)
 using the test `"SPARK-31806: generate test files for checking compatibility 
with Spark 2.4"`. The parquet files don't contain info about the original time 
zone:
    ```
    $ java -jar ~/Downloads/parquet-tools-1.12.0.jar meta 
sql/core/src/test/resources/test-data/before_1582_timestamp_micros_v3_2_0.snappy.parquet
    file:        
file:/Users/maximgekk/proj/parquet-rebase-save-tz/sql/core/src/test/resources/test-data/before_1582_timestamp_micros_v3_2_0.snappy.parquet
    creator:     parquet-mr version 1.12.1 (build 
2a5c06c58fa987f85aa22170be14d927d5ff6e7d)
    extra:       org.apache.spark.version = 3.2.0
    extra:       org.apache.spark.legacyINT96 =
    extra:       org.apache.spark.sql.parquet.row.metadata = 
{"type":"struct","fields":[{"name":"dict","type":"timestamp","nullable":true,"metadata":{}},{"name":"plain","type":"timestamp","nullable":true,"metadata":{}}]}
    extra:       org.apache.spark.legacyDateTime =
    
    file schema: spark_schema
    
--------------------------------------------------------------------------------
    dict:        OPTIONAL INT64 L:TIMESTAMP(MICROS,true) R:0 D:1
    plain:       OPTIONAL INT64 L:TIMESTAMP(MICROS,true) R:0 D:1
    ```
    By running the test `"SPARK-31159, SPARK-37705: compatibility with Spark 
2.4/3.2 in reading dates/timestamps"`, check loading of mixed parquet files 
generated by Spark 2.4.5/2.4.6 and 3.2.0/master.
    
    5. Generated avro files by Spark 3.2.0 (the commit 
https://github.com/apache/spark/commit/5d45a415f3a29898d92380380cfd82bfc7f579ea)
 using the test `"SPARK-31855: generate test files for checking compatibility 
with Spark 2.4"`. The avro files don't contain info about the original time 
zone:
    ```
    $ java -jar ~/Downloads/avro-tools-1.9.2.jar getmeta 
external/avro/src/test/resources/before_1582_timestamp_micros_v3_2_0.avro
    avro.schema 
{"type":"record","name":"topLevelRecord","fields":[{"name":"dt","type":[{"type":"long","logicalType":"timestamp-micros"},"null"]}]}
    org.apache.spark.version    3.2.0
    avro.codec  snappy
    org.apache.spark.legacyDateTime
    ```
    By running the test `"SPARK-31159, SPARK-37705: compatibility with Spark 
2.4/3.2 in reading dates/timestamps"`, check loading of mixed avro files 
generated by Spark 2.4.5/2.4.6 and 3.2.0/master.
    
    Closes #34973 from MaxGekk/parquet-rebase-save-tz.
    
    Authored-by: Max Gekk <[email protected]>
    Signed-off-by: Wenchen Fan <[email protected]>
---
 .../apache/spark/sql/avro/AvroDeserializer.scala   |  10 +-
 .../org/apache/spark/sql/avro/AvroFileFormat.scala |   2 +-
 .../apache/spark/sql/avro/AvroOutputWriter.scala   |   8 +-
 .../sql/v2/avro/AvroPartitionReaderFactory.scala   |   2 +-
 .../test/resources/before_1582_date_v3_2_0.avro    | Bin 0 -> 264 bytes
 .../before_1582_timestamp_micros_v3_2_0.avro       | Bin 0 -> 282 bytes
 .../before_1582_timestamp_millis_v3_2_0.avro       | Bin 0 -> 308 bytes
 .../sql/avro/AvroCatalystDataConversionSuite.scala |   3 +-
 .../apache/spark/sql/avro/AvroRowReaderSuite.scala |   5 +-
 .../org/apache/spark/sql/avro/AvroSerdeSuite.scala |   8 +-
 .../org/apache/spark/sql/avro/AvroSuite.scala      | 192 ++++++++++++---------
 .../spark/sql/catalyst/util/RebaseDateTime.scala   |  82 ++++++---
 .../parquet/ParquetVectorUpdaterFactory.java       |  75 +++++---
 .../parquet/VectorizedColumnReader.java            |  11 +-
 .../parquet/VectorizedParquetRecordReader.java     |  21 ++-
 .../parquet/VectorizedPlainValuesReader.java       |  10 +-
 .../parquet/VectorizedRleValuesReader.java         |   6 +-
 .../parquet/VectorizedValuesReader.java            |   7 +-
 .../execution/datasources/DataSourceUtils.scala    |  89 ++++++----
 .../datasources/parquet/ParquetFileFormat.scala    |  16 +-
 .../datasources/parquet/ParquetFilters.scala       |  11 +-
 .../datasources/parquet/ParquetReadSupport.scala   |  13 +-
 .../parquet/ParquetRecordMaterializer.scala        |  18 +-
 .../datasources/parquet/ParquetRowConverter.scala  |  24 +--
 .../datasources/parquet/ParquetUtils.scala         |  13 +-
 .../datasources/parquet/ParquetWriteSupport.scala  |  14 +-
 .../v2/parquet/ParquetPartitionReaderFactory.scala |  44 ++---
 .../v2/parquet/ParquetScanBuilder.scala            |   3 +-
 .../main/scala/org/apache/spark/sql/package.scala  |  11 +-
 .../before_1582_date_v3_2_0.snappy.parquet         | Bin 0 -> 822 bytes
 ...1582_timestamp_int96_dict_v3_2_0.snappy.parquet | Bin 0 -> 842 bytes
 ...582_timestamp_int96_plain_v3_2_0.snappy.parquet | Bin 0 -> 779 bytes
 ...ore_1582_timestamp_micros_v3_2_0.snappy.parquet | Bin 0 -> 929 bytes
 ...ore_1582_timestamp_millis_v3_2_0.snappy.parquet | Bin 0 -> 922 bytes
 .../datasources/parquet/ParquetFilterSuite.scala   |   5 +-
 .../parquet/ParquetRebaseDatetimeSuite.scala       | 112 ++++++------
 .../parquet/ParquetVectorizedSuite.scala           |   8 +-
 37 files changed, 510 insertions(+), 313 deletions(-)

diff --git 
a/external/avro/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala 
b/external/avro/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
index 54cd3ba..5bb51a9 100644
--- 
a/external/avro/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
+++ 
b/external/avro/src/main/scala/org/apache/spark/sql/avro/AvroDeserializer.scala
@@ -34,10 +34,12 @@ import org.apache.spark.sql.catalyst.{InternalRow, 
NoopFilters, StructFilters}
 import org.apache.spark.sql.catalyst.expressions.{SpecificInternalRow, 
UnsafeArrayData}
 import org.apache.spark.sql.catalyst.util.{ArrayBasedMapData, ArrayData, 
DateTimeUtils, GenericArrayData}
 import org.apache.spark.sql.catalyst.util.DateTimeConstants.MILLIS_PER_DAY
+import org.apache.spark.sql.catalyst.util.RebaseDateTime.RebaseSpec
 import org.apache.spark.sql.execution.datasources.DataSourceUtils
 import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
 import org.apache.spark.sql.types._
 import org.apache.spark.unsafe.types.UTF8String
+
 /**
  * A deserializer to deserialize data in avro format to data in catalyst 
format.
  */
@@ -45,7 +47,7 @@ private[sql] class AvroDeserializer(
     rootAvroType: Schema,
     rootCatalystType: DataType,
     positionalFieldMatch: Boolean,
-    datetimeRebaseMode: LegacyBehaviorPolicy.Value,
+    datetimeRebaseSpec: RebaseSpec,
     filters: StructFilters) {
 
   def this(
@@ -56,17 +58,17 @@ private[sql] class AvroDeserializer(
       rootAvroType,
       rootCatalystType,
       positionalFieldMatch = false,
-      LegacyBehaviorPolicy.withName(datetimeRebaseMode),
+      RebaseSpec(LegacyBehaviorPolicy.withName(datetimeRebaseMode)),
       new NoopFilters)
   }
 
   private lazy val decimalConversions = new DecimalConversion()
 
   private val dateRebaseFunc = DataSourceUtils.createDateRebaseFuncInRead(
-    datetimeRebaseMode, "Avro")
+    datetimeRebaseSpec.mode, "Avro")
 
   private val timestampRebaseFunc = 
DataSourceUtils.createTimestampRebaseFuncInRead(
-    datetimeRebaseMode, "Avro")
+    datetimeRebaseSpec, "Avro")
 
   private val converter: Any => Option[Any] = try {
     rootCatalystType match {
diff --git 
a/external/avro/src/main/scala/org/apache/spark/sql/avro/AvroFileFormat.scala 
b/external/avro/src/main/scala/org/apache/spark/sql/avro/AvroFileFormat.scala
index 398cb02..a13e062 100755
--- 
a/external/avro/src/main/scala/org/apache/spark/sql/avro/AvroFileFormat.scala
+++ 
b/external/avro/src/main/scala/org/apache/spark/sql/avro/AvroFileFormat.scala
@@ -123,7 +123,7 @@ private[sql] class AvroFileFormat extends FileFormat
 
         reader.sync(file.start)
 
-        val datetimeRebaseMode = DataSourceUtils.datetimeRebaseMode(
+        val datetimeRebaseMode = DataSourceUtils.datetimeRebaseSpec(
           reader.asInstanceOf[DataFileReader[_]].getMetaString,
           datetimeRebaseModeInRead)
 
diff --git 
a/external/avro/src/main/scala/org/apache/spark/sql/avro/AvroOutputWriter.scala 
b/external/avro/src/main/scala/org/apache/spark/sql/avro/AvroOutputWriter.scala
index 2efc4f5..44520bb 100644
--- 
a/external/avro/src/main/scala/org/apache/spark/sql/avro/AvroOutputWriter.scala
+++ 
b/external/avro/src/main/scala/org/apache/spark/sql/avro/AvroOutputWriter.scala
@@ -29,7 +29,7 @@ import org.apache.hadoop.io.NullWritable
 import org.apache.hadoop.mapreduce.{RecordWriter, TaskAttemptContext}
 
 import org.apache.spark.SPARK_VERSION_SHORT
-import org.apache.spark.sql.{SPARK_LEGACY_DATETIME, SPARK_VERSION_METADATA_KEY}
+import org.apache.spark.sql.{SPARK_LEGACY_DATETIME_METADATA_KEY, 
SPARK_TIMEZONE_METADATA_KEY, SPARK_VERSION_METADATA_KEY}
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.execution.datasources.OutputWriter
 import org.apache.spark.sql.internal.SQLConf
@@ -59,9 +59,11 @@ private[avro] class AvroOutputWriter(
   private val recordWriter: RecordWriter[AvroKey[GenericRecord], NullWritable] 
= {
     val fileMeta = Map(SPARK_VERSION_METADATA_KEY -> SPARK_VERSION_SHORT) ++ {
       if (datetimeRebaseMode == LegacyBehaviorPolicy.LEGACY) {
-        Some(SPARK_LEGACY_DATETIME -> "")
+        Map(
+          SPARK_LEGACY_DATETIME_METADATA_KEY -> "",
+          SPARK_TIMEZONE_METADATA_KEY -> SQLConf.get.sessionLocalTimeZone)
       } else {
-        None
+        Map.empty
       }
     }
 
diff --git 
a/external/avro/src/main/scala/org/apache/spark/sql/v2/avro/AvroPartitionReaderFactory.scala
 
b/external/avro/src/main/scala/org/apache/spark/sql/v2/avro/AvroPartitionReaderFactory.scala
index b43c7fa..a4dfdbf 100644
--- 
a/external/avro/src/main/scala/org/apache/spark/sql/v2/avro/AvroPartitionReaderFactory.scala
+++ 
b/external/avro/src/main/scala/org/apache/spark/sql/v2/avro/AvroPartitionReaderFactory.scala
@@ -89,7 +89,7 @@ case class AvroPartitionReaderFactory(
 
       reader.sync(partitionedFile.start)
 
-      val datetimeRebaseMode = DataSourceUtils.datetimeRebaseMode(
+      val datetimeRebaseMode = DataSourceUtils.datetimeRebaseSpec(
         reader.asInstanceOf[DataFileReader[_]].getMetaString,
         datetimeRebaseModeInRead)
 
diff --git a/external/avro/src/test/resources/before_1582_date_v3_2_0.avro 
b/external/avro/src/test/resources/before_1582_date_v3_2_0.avro
new file mode 100644
index 0000000..da319f6
Binary files /dev/null and 
b/external/avro/src/test/resources/before_1582_date_v3_2_0.avro differ
diff --git 
a/external/avro/src/test/resources/before_1582_timestamp_micros_v3_2_0.avro 
b/external/avro/src/test/resources/before_1582_timestamp_micros_v3_2_0.avro
new file mode 100644
index 0000000..27b822b
Binary files /dev/null and 
b/external/avro/src/test/resources/before_1582_timestamp_micros_v3_2_0.avro 
differ
diff --git 
a/external/avro/src/test/resources/before_1582_timestamp_millis_v3_2_0.avro 
b/external/avro/src/test/resources/before_1582_timestamp_millis_v3_2_0.avro
new file mode 100644
index 0000000..e6e2b94
Binary files /dev/null and 
b/external/avro/src/test/resources/before_1582_timestamp_millis_v3_2_0.avro 
differ
diff --git 
a/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroCatalystDataConversionSuite.scala
 
b/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroCatalystDataConversionSuite.scala
index 98a7d89..a43d171 100644
--- 
a/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroCatalystDataConversionSuite.scala
+++ 
b/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroCatalystDataConversionSuite.scala
@@ -29,6 +29,7 @@ import org.apache.spark.sql.{RandomDataGenerator, Row}
 import org.apache.spark.sql.catalyst.{CatalystTypeConverters, InternalRow, 
NoopFilters, OrderedFilters, StructFilters}
 import org.apache.spark.sql.catalyst.expressions.{ExpressionEvalHelper, 
GenericInternalRow, Literal}
 import org.apache.spark.sql.catalyst.util.{ArrayBasedMapData, 
GenericArrayData, MapData}
+import org.apache.spark.sql.catalyst.util.RebaseDateTime.RebaseSpec
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.sources.{EqualTo, Not}
 import org.apache.spark.sql.test.SharedSparkSession
@@ -285,7 +286,7 @@ class AvroCatalystDataConversionSuite extends SparkFunSuite
       schema,
       dataType,
       false,
-      SQLConf.LegacyBehaviorPolicy.CORRECTED,
+      RebaseSpec(SQLConf.LegacyBehaviorPolicy.CORRECTED),
       filters)
     val deserialized = deserializer.deserialize(data)
     expected match {
diff --git 
a/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroRowReaderSuite.scala
 
b/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroRowReaderSuite.scala
index 9bf2e56..890b615 100644
--- 
a/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroRowReaderSuite.scala
+++ 
b/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroRowReaderSuite.scala
@@ -20,7 +20,7 @@ package org.apache.spark.sql.avro
 import java.io._
 import java.net.URI
 
-import org.apache.avro.file.{DataFileReader}
+import org.apache.avro.file.DataFileReader
 import org.apache.avro.generic.{GenericDatumReader, GenericRecord}
 import org.apache.avro.mapred.FsInput
 import org.apache.hadoop.conf.Configuration
@@ -29,6 +29,7 @@ import org.apache.hadoop.fs.Path
 import org.apache.spark.SparkConf
 import org.apache.spark.sql._
 import org.apache.spark.sql.catalyst.{InternalRow, NoopFilters}
+import org.apache.spark.sql.catalyst.util.RebaseDateTime.RebaseSpec
 import org.apache.spark.sql.execution.datasources.v2.BatchScanExec
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy._
@@ -71,7 +72,7 @@ class AvroRowReaderSuite
           reader.getSchema,
           StructType(new StructField("value", IntegerType, true) :: Nil),
           false,
-          CORRECTED,
+          RebaseSpec(CORRECTED),
           new NoopFilters)
         override val stopPosition = fileSize
 
diff --git 
a/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroSerdeSuite.scala 
b/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroSerdeSuite.scala
index 8be3cf0..6d0a734 100644
--- 
a/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroSerdeSuite.scala
+++ 
b/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroSerdeSuite.scala
@@ -21,6 +21,7 @@ import org.apache.avro.generic.GenericRecordBuilder
 
 import org.apache.spark.SparkFunSuite
 import org.apache.spark.sql.catalyst.NoopFilters
+import org.apache.spark.sql.catalyst.util.RebaseDateTime.RebaseSpec
 import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy.CORRECTED
 import org.apache.spark.sql.types.{IntegerType, StructType}
 
@@ -209,7 +210,12 @@ object AvroSerdeSuite {
   }
   private object Deserializer extends SerdeFactory[AvroDeserializer] {
     override def create(sql: StructType, avro: Schema, matchType: MatchType): 
AvroDeserializer =
-      new AvroDeserializer(avro, sql, isPositional(matchType), CORRECTED, new 
NoopFilters)
+      new AvroDeserializer(
+        avro,
+        sql,
+        isPositional(matchType),
+        RebaseSpec(CORRECTED),
+        new NoopFilters)
   }
 
   /**
diff --git 
a/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala 
b/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala
index 11588ea..d85baeb 100644
--- a/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala
+++ b/external/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala
@@ -39,6 +39,7 @@ import org.apache.spark.sql._
 import org.apache.spark.sql.TestingUDT.IntervalData
 import org.apache.spark.sql.catalyst.expressions.AttributeReference
 import org.apache.spark.sql.catalyst.plans.logical.Filter
+import org.apache.spark.sql.catalyst.util.DateTimeTestUtils
 import 
org.apache.spark.sql.catalyst.util.DateTimeTestUtils.{withDefaultTimeZone, LA, 
UTC}
 import org.apache.spark.sql.execution.{FormattedMode, SparkPlan}
 import org.apache.spark.sql.execution.datasources.{CommonFileDataSourceSuite, 
DataSource, FilePartition}
@@ -1786,10 +1787,10 @@ abstract class AvroSuite
   }
 
   // It generates input files for the test below:
-  // "SPARK-31183: compatibility with Spark 2.4 in reading dates/timestamps"
-  ignore("SPARK-31855: generate test files for checking compatibility with 
Spark 2.4") {
+  // "SPARK-31183, SPARK-37705: compatibility with Spark 2.4/3.2 in reading 
dates/timestamps"
+  ignore("SPARK-31855: generate test files for checking compatibility with 
Spark 2.4/3.2") {
     val resourceDir = "external/avro/src/test/resources"
-    val version = "2_4_6"
+    val version = SPARK_VERSION_SHORT.replaceAll("\\.", "_")
     def save(
       in: Seq[String],
       t: String,
@@ -1856,8 +1857,8 @@ abstract class AvroSuite
     }
   }
 
-  test("SPARK-31183: compatibility with Spark 2.4 in reading 
dates/timestamps") {
-    // test reading the existing 2.4 files and new 3.0 files (with rebase 
on/off) together.
+  test("SPARK-31183, SPARK-37705: compatibility with Spark 2.4/3.2 in reading 
dates/timestamps") {
+    // test reading the existing 2.4/3.2 files and new 3.x files (with rebase 
on/off) together.
     def checkReadMixedFiles(
         fileName: String,
         dt: String,
@@ -1865,26 +1866,26 @@ abstract class AvroSuite
         checkDefaultLegacyRead: String => Unit): Unit = {
       withTempPaths(2) { paths =>
         paths.foreach(_.delete())
-        val path2_4 = getResourceAvroFilePath(fileName)
-        val path3_0 = paths(0).getCanonicalPath
-        val path3_0_rebase = paths(1).getCanonicalPath
+        val oldPath = getResourceAvroFilePath(fileName)
+        val path3_x = paths(0).getCanonicalPath
+        val path3_x_rebase = paths(1).getCanonicalPath
         if (dt == "date") {
           val df = 
Seq(dataStr).toDF("str").select($"str".cast("date").as("dt"))
 
-          checkDefaultLegacyRead(path2_4)
+          checkDefaultLegacyRead(oldPath)
 
           withSQLConf(SQLConf.AVRO_REBASE_MODE_IN_WRITE.key -> 
CORRECTED.toString) {
-            df.write.format("avro").mode("overwrite").save(path3_0)
+            df.write.format("avro").mode("overwrite").save(path3_x)
           }
           withSQLConf(SQLConf.AVRO_REBASE_MODE_IN_WRITE.key -> 
LEGACY.toString) {
-            df.write.format("avro").save(path3_0_rebase)
+            df.write.format("avro").save(path3_x_rebase)
           }
 
           // For Avro files written by Spark 3.0, we know the writer info and 
don't need the config
           // to guide the rebase behavior.
           runInMode(Seq(LEGACY)) { options =>
             checkAnswer(
-              spark.read.options(options).format("avro").load(path2_4, 
path3_0, path3_0_rebase),
+              spark.read.options(options).format("avro").load(oldPath, 
path3_x, path3_x_rebase),
               1.to(3).map(_ => Row(java.sql.Date.valueOf(dataStr))))
           }
         } else {
@@ -1901,23 +1902,23 @@ abstract class AvroSuite
 
           // By default we should fail to write ancient datetime values.
           val e = intercept[SparkException] {
-            df.write.format("avro").option("avroSchema", 
avroSchema).save(path3_0)
+            df.write.format("avro").option("avroSchema", 
avroSchema).save(path3_x)
           }
           
assert(e.getCause.getCause.getCause.isInstanceOf[SparkUpgradeException])
-          checkDefaultLegacyRead(path2_4)
+          checkDefaultLegacyRead(oldPath)
 
           withSQLConf(SQLConf.AVRO_REBASE_MODE_IN_WRITE.key -> 
CORRECTED.toString) {
-            df.write.format("avro").option("avroSchema", 
avroSchema).mode("overwrite").save(path3_0)
+            df.write.format("avro").option("avroSchema", 
avroSchema).mode("overwrite").save(path3_x)
           }
           withSQLConf(SQLConf.AVRO_REBASE_MODE_IN_WRITE.key -> 
LEGACY.toString) {
-            df.write.format("avro").option("avroSchema", 
avroSchema).save(path3_0_rebase)
+            df.write.format("avro").option("avroSchema", 
avroSchema).save(path3_x_rebase)
           }
 
           // For Avro files written by Spark 3.0, we know the writer info and 
don't need the config
           // to guide the rebase behavior.
           runInMode(Seq(LEGACY)) { options =>
             checkAnswer(
-              spark.read.options(options).format("avro").load(path2_4, 
path3_0, path3_0_rebase),
+              spark.read.options(options).format("avro").load(oldPath, 
path3_x, path3_x_rebase),
               1.to(3).map(_ => Row(java.sql.Timestamp.valueOf(dataStr))))
           }
         }
@@ -1933,7 +1934,8 @@ abstract class AvroSuite
       // By default we should fail to read ancient datetime values when 
parquet files don't
       // contain Spark version.
       "2_4_5" -> failInRead _,
-      "2_4_6" -> successInRead _
+      "2_4_6" -> successInRead _,
+      "3_2_0" -> successInRead _
     ).foreach { case (version, checkDefaultRead) =>
       checkReadMixedFiles(
         s"before_1582_date_v$version.avro",
@@ -1953,75 +1955,94 @@ abstract class AvroSuite
     }
   }
 
-  test("SPARK-31183: rebasing microseconds timestamps in write") {
-    val tsStr = "1001-01-01 01:02:03.123456"
-    val nonRebased = "1001-01-07 01:09:05.123456"
-    withTempPath { dir =>
-      val path = dir.getAbsolutePath
-      withSQLConf(SQLConf.AVRO_REBASE_MODE_IN_WRITE.key -> LEGACY.toString) {
-        Seq(tsStr).toDF("tsS")
-          .select($"tsS".cast("timestamp").as("ts"))
-          .write.format("avro")
-          .save(path)
-      }
+  test("SPARK-31183, SPARK-37705: rebasing microseconds timestamps in write") {
+    // Ignore the default JVM time zone and use the session time zone instead 
of it in rebasing.
+    DateTimeTestUtils.withDefaultTimeZone(DateTimeTestUtils.JST) {
+      withSQLConf(SQLConf.SESSION_LOCAL_TIMEZONE.key -> 
DateTimeTestUtils.LA.getId) {
+        val tsStr = "1001-01-01 01:02:03.123456"
+        val nonRebased = "1001-01-07 01:09:05.123456"
+        withTempPath { dir =>
+          val path = dir.getAbsolutePath
+          withSQLConf(SQLConf.AVRO_REBASE_MODE_IN_WRITE.key -> 
LEGACY.toString) {
+            Seq(tsStr).toDF("tsS")
+              .select($"tsS".cast("timestamp").as("ts"))
+              .write.format("avro")
+              .save(path)
+          }
 
-      // The file metadata indicates if it needs rebase or not, so we can 
always get the correct
-      // result regardless of the "rebase mode" config.
-      runInMode(Seq(LEGACY, CORRECTED, EXCEPTION)) { options =>
-        checkAnswer(
-          spark.read.options(options).format("avro").load(path),
-          Row(Timestamp.valueOf(tsStr)))
-      }
+          // The file metadata indicates if it needs rebase or not, so we can 
always get the correct
+          // result regardless of the "rebase mode" config.
+          runInMode(Seq(LEGACY, CORRECTED, EXCEPTION)) { options =>
+            checkAnswer(
+              
spark.read.options(options).format("avro").load(path).select($"ts".cast("string")),
+              Row(tsStr))
+          }
 
-      // Force to not rebase to prove the written datetime values are rebased 
and we will get
-      // wrong result if we don't rebase while reading.
-      withSQLConf("spark.test.forceNoRebase" -> "true") {
-        checkAnswer(spark.read.format("avro").load(path), 
Row(Timestamp.valueOf(nonRebased)))
+          // Force to not rebase to prove the written datetime values are 
rebased and we will get
+          // wrong result if we don't rebase while reading.
+          withSQLConf("spark.test.forceNoRebase" -> "true") {
+            checkAnswer(
+              
spark.read.format("avro").load(path).select($"ts".cast("string")),
+              Row(nonRebased))
+          }
+        }
       }
     }
   }
 
-  test("SPARK-31183: rebasing milliseconds timestamps in write") {
-    val tsStr = "1001-01-01 01:02:03.123456"
-    val rebased = "1001-01-01 01:02:03.123"
-    val nonRebased = "1001-01-07 01:09:05.123"
-    Seq(
-      """{"type": "long","logicalType": "timestamp-millis"}""",
-      """"long"""").foreach { tsType =>
-      val timestampSchema = s"""
-        |{
-        |  "namespace": "logical",
-        |  "type": "record",
-        |  "name": "test",
-        |  "fields": [
-        |    {"name": "ts", "type": $tsType}
-        |  ]
-        |}""".stripMargin
-      withTempPath { dir =>
-        val path = dir.getAbsolutePath
-        withSQLConf(SQLConf.AVRO_REBASE_MODE_IN_WRITE.key -> LEGACY.toString) {
-          Seq(tsStr).toDF("tsS")
-            .select($"tsS".cast("timestamp").as("ts"))
-            .write
-            .option("avroSchema", timestampSchema)
-            .format("avro")
-            .save(path)
-        }
-
-        // The file metadata indicates if it needs rebase or not, so we can 
always get the correct
-        // result regardless of the "rebase mode" config.
-        runInMode(Seq(LEGACY, CORRECTED, EXCEPTION)) { options =>
-          checkAnswer(
-            spark.read.options(options).schema("ts 
timestamp").format("avro").load(path),
-            Row(Timestamp.valueOf(rebased)))
-        }
-
-        // Force to not rebase to prove the written datetime values are 
rebased and we will get
-        // wrong result if we don't rebase while reading.
-        withSQLConf("spark.test.forceNoRebase" -> "true") {
-          checkAnswer(
-            spark.read.schema("ts timestamp").format("avro").load(path),
-            Row(Timestamp.valueOf(nonRebased)))
+  test("SPARK-31183, SPARK-37705: rebasing milliseconds timestamps in write") {
+    // Ignore the default JVM time zone and use the session time zone instead 
of it in rebasing.
+    DateTimeTestUtils.withDefaultTimeZone(DateTimeTestUtils.JST) {
+      withSQLConf(SQLConf.SESSION_LOCAL_TIMEZONE.key -> 
DateTimeTestUtils.LA.getId) {
+        val tsStr = "1001-01-01 01:02:03.123456"
+        val rebased = "1001-01-01 01:02:03.123"
+        val nonRebased = "1001-01-07 01:09:05.123"
+        Seq(
+          """{"type": "long","logicalType": "timestamp-millis"}""",
+          """"long"""").foreach { tsType =>
+          val timestampSchema = s"""
+            |{
+            |  "namespace": "logical",
+            |  "type": "record",
+            |  "name": "test",
+            |  "fields": [
+            |    {"name": "ts", "type": $tsType}
+            |  ]
+            |}""".stripMargin
+          withTempPath { dir =>
+            val path = dir.getAbsolutePath
+            withSQLConf(SQLConf.AVRO_REBASE_MODE_IN_WRITE.key -> 
LEGACY.toString) {
+              Seq(tsStr).toDF("tsS")
+                .select($"tsS".cast("timestamp").as("ts"))
+                .write
+                .option("avroSchema", timestampSchema)
+                .format("avro")
+                .save(path)
+            }
+
+            // The file metadata indicates if it needs rebase or not, so we 
can always get
+            // the correct result regardless of the "rebase mode" config.
+            runInMode(Seq(LEGACY, CORRECTED, EXCEPTION)) { options =>
+              checkAnswer(
+                spark.read
+                  .options(options)
+                  .schema("ts timestamp")
+                  .format("avro").load(path)
+                  .select($"ts".cast("string")),
+                Row(rebased))
+            }
+
+            // Force to not rebase to prove the written datetime values are 
rebased and we will get
+            // wrong result if we don't rebase while reading.
+            withSQLConf("spark.test.forceNoRebase" -> "true") {
+              checkAnswer(
+                spark.read
+                  .schema("ts timestamp")
+                  .format("avro").load(path)
+                  .select($"ts".cast("string")),
+                Row(nonRebased))
+            }
+          }
         }
       }
     }
@@ -2069,7 +2090,8 @@ abstract class AvroSuite
     }
   }
 
-  test("SPARK-33163: write the metadata key 
'org.apache.spark.legacyDateTime'") {
+  test("SPARK-33163, SPARK-37705: write the metadata key 
'org.apache.spark.legacyDateTime' " +
+    "and 'org.apache.spark.timeZone'") {
     def saveTs(dir: java.io.File): Unit = {
       Seq(Timestamp.valueOf("2020-10-15 01:02:03")).toDF()
         .repartition(1)
@@ -2080,14 +2102,16 @@ abstract class AvroSuite
     withSQLConf(SQLConf.AVRO_REBASE_MODE_IN_WRITE.key -> LEGACY.toString) {
       withTempPath { dir =>
         saveTs(dir)
-        checkMetaData(dir, SPARK_LEGACY_DATETIME, "")
+        checkMetaData(dir, SPARK_LEGACY_DATETIME_METADATA_KEY, "")
+        checkMetaData(dir, SPARK_TIMEZONE_METADATA_KEY, 
SQLConf.get.sessionLocalTimeZone)
       }
     }
     Seq(CORRECTED, EXCEPTION).foreach { mode =>
       withSQLConf(SQLConf.AVRO_REBASE_MODE_IN_WRITE.key -> mode.toString) {
         withTempPath { dir =>
           saveTs(dir)
-          checkMetaData(dir, SPARK_LEGACY_DATETIME, null)
+          checkMetaData(dir, SPARK_LEGACY_DATETIME_METADATA_KEY, null)
+          checkMetaData(dir, SPARK_TIMEZONE_METADATA_KEY, null)
         }
       }
     }
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/RebaseDateTime.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/RebaseDateTime.scala
index 2999d47..72bb43b 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/RebaseDateTime.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/RebaseDateTime.scala
@@ -29,6 +29,7 @@ import 
com.fasterxml.jackson.module.scala.{DefaultScalaModule, ScalaObjectMapper
 
 import org.apache.spark.sql.catalyst.util.DateTimeConstants._
 import org.apache.spark.sql.catalyst.util.DateTimeUtils._
+import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
 import org.apache.spark.util.Utils
 
 /**
@@ -37,6 +38,13 @@ import org.apache.spark.util.Utils
  * to/from Proleptic Gregorian calendar which is used by Spark since version 
3.0. See SPARK-26651.
  */
 object RebaseDateTime {
+
+  // Specification of rebase operation including `mode` and the time zone in 
which it is performed
+  case class RebaseSpec(mode: LegacyBehaviorPolicy.Value, originTimeZone: 
Option[String] = None) {
+    // Use the default JVM time zone for backward compatibility
+    def timeZone: String = originTimeZone.getOrElse(TimeZone.getDefault.getID)
+  }
+
   /**
    * Rebases days since the epoch from an original to an target calendar, for 
instance,
    * from a hybrid (Julian + Gregorian) to Proleptic Gregorian calendar.
@@ -363,27 +371,23 @@ object RebaseDateTime {
   }
 
   /**
-   * An optimized version of [[rebaseGregorianToJulianMicros(ZoneId, Long)]]. 
This method leverages
-   * the pre-calculated rebasing maps to save calculation. If the rebasing map 
doesn't contain
-   * information about the current JVM system time zone or `micros` is related 
to Before Common Era,
-   * the function falls back to the regular unoptimized version.
-   *
-   * Note: The function assumes that the input micros was derived from a local 
timestamp
-   *       at the default system JVM time zone in Proleptic Gregorian calendar.
+   * An optimized version of [[rebaseGregorianToJulianMicros(TimeZone, 
Long)]]. This method
+   * leverages the pre-calculated rebasing maps to save calculation. If the 
rebasing map doesn't
+   * contain information about the given time zone `timeZoneId` or `micros` is 
related to Before
+   * Common Era, the function falls back to the regular unoptimized version.
    *
+   * @param timeZoneId A string identifier of a time zone.
    * @param micros The number of microseconds since the epoch 
'1970-01-01T00:00:00Z'
    *               in Proleptic Gregorian calendar. It can be negative.
    * @return The rebased microseconds since the epoch in Julian calendar.
    */
-  def rebaseGregorianToJulianMicros(micros: Long): Long = {
+  def rebaseGregorianToJulianMicros(timeZoneId: String, micros: Long): Long = {
     if (micros >= lastSwitchGregorianTs) {
       micros
     } else {
-      val timeZone = TimeZone.getDefault
-      val tzId = timeZone.getID
-      val rebaseRecord = gregJulianRebaseMap.getOrNull(tzId)
+      val rebaseRecord = gregJulianRebaseMap.getOrNull(timeZoneId)
       if (rebaseRecord == null || micros < rebaseRecord.switches(0)) {
-        rebaseGregorianToJulianMicros(timeZone, micros)
+        rebaseGregorianToJulianMicros(TimeZone.getTimeZone(timeZoneId), micros)
       } else {
         rebaseMicros(rebaseRecord, micros)
       }
@@ -391,6 +395,23 @@ object RebaseDateTime {
   }
 
   /**
+   * An optimized version of [[rebaseGregorianToJulianMicros(TimeZone, 
Long)]]. This method
+   * leverages the pre-calculated rebasing maps to save calculation. If the 
rebasing map doesn't
+   * contain information about the current JVM system time zone or `micros` is 
related to Before
+   * Common Era, the function falls back to the regular unoptimized version.
+   *
+   * Note: The function assumes that the input micros was derived from a local 
timestamp
+   *       at the default system JVM time zone in Proleptic Gregorian calendar.
+   *
+   * @param micros The number of microseconds since the epoch 
'1970-01-01T00:00:00Z'
+   *               in Proleptic Gregorian calendar. It can be negative.
+   * @return The rebased microseconds since the epoch in Julian calendar.
+   */
+  def rebaseGregorianToJulianMicros(micros: Long): Long = {
+    rebaseGregorianToJulianMicros(TimeZone.getDefault.getID, micros)
+  }
+
+  /**
    * Converts the given number of microseconds since the epoch 
'1970-01-01T00:00:00Z', to a local
    * date-time in Julian calendar with timezone identified by `zoneId`, 
interprets the result as a
    * local date-time in Proleptic Gregorian calendar with the same timezone, 
and takes microseconds
@@ -464,30 +485,43 @@ object RebaseDateTime {
   final val lastSwitchJulianTs: Long = getLastSwitchTs(julianGregRebaseMap)
 
   /**
-   * An optimized version of [[rebaseJulianToGregorianMicros(ZoneId, Long)]]. 
This method leverages
-   * the pre-calculated rebasing maps to save calculation. If the rebasing map 
doesn't contain
-   * information about the current JVM system time zone or `micros` is related 
to Before Common Era,
-   * the function falls back to the regular unoptimized version.
-   *
-   * Note: The function assumes that the input micros was derived from a local 
timestamp
-   *       at the default system JVM time zone in the Julian calendar.
+   * An optimized version of [[rebaseJulianToGregorianMicros(TimeZone, 
Long)]]. This method
+   * leverages the pre-calculated rebasing maps to save calculation. If the 
rebasing map doesn't
+   * contain information about the given time zone `timeZoneId` or `micros` is 
related to Before
+   * Common Era, the function falls back to the regular unoptimized version.
    *
+   * @param timeZoneId A string identifier of a time zone.
    * @param micros The number of microseconds since the epoch 
'1970-01-01T00:00:00Z'
    *               in the Julian calendar. It can be negative.
    * @return The rebased microseconds since the epoch in Proleptic Gregorian 
calendar.
    */
-  def rebaseJulianToGregorianMicros(micros: Long): Long = {
+  def rebaseJulianToGregorianMicros(timeZoneId: String, micros: Long): Long = {
     if (micros >= lastSwitchJulianTs) {
       micros
     } else {
-      val timeZone = TimeZone.getDefault
-      val tzId = timeZone.getID
-      val rebaseRecord = julianGregRebaseMap.getOrNull(tzId)
+      val rebaseRecord = julianGregRebaseMap.getOrNull(timeZoneId)
       if (rebaseRecord == null || micros < rebaseRecord.switches(0)) {
-        rebaseJulianToGregorianMicros(timeZone, micros)
+        rebaseJulianToGregorianMicros(TimeZone.getTimeZone(timeZoneId), micros)
       } else {
         rebaseMicros(rebaseRecord, micros)
       }
     }
   }
+
+  /**
+   * An optimized version of [[rebaseJulianToGregorianMicros(TimeZone, 
Long)]]. This method
+   * leverages the pre-calculated rebasing maps to save calculation. If the 
rebasing map doesn't
+   * contain information about the current JVM system time zone or `micros` is 
related to Before
+   * Common Era, the function falls back to the regular unoptimized version.
+   *
+   * Note: The function assumes that the input micros was derived from a local 
timestamp
+   *       at the default system JVM time zone in the Julian calendar.
+   *
+   * @param micros The number of microseconds since the epoch 
'1970-01-01T00:00:00Z'
+   *               in the Julian calendar. It can be negative.
+   * @return The rebased microseconds since the epoch in Proleptic Gregorian 
calendar.
+   */
+  def rebaseJulianToGregorianMicros(micros: Long): Long = {
+    rebaseJulianToGregorianMicros(TimeZone.getDefault.getID, micros)
+  }
 }
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 5a5a099..cce5300 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
@@ -45,17 +45,23 @@ public class ParquetVectorUpdaterFactory {
   // The timezone conversion to apply to int96 timestamps. Null if no 
conversion.
   private final ZoneId convertTz;
   private final String datetimeRebaseMode;
+  private final String datetimeRebaseTz;
   private final String int96RebaseMode;
+  private final String int96RebaseTz;
 
   ParquetVectorUpdaterFactory(
       LogicalTypeAnnotation logicalTypeAnnotation,
       ZoneId convertTz,
       String datetimeRebaseMode,
-      String int96RebaseMode) {
+      String datetimeRebaseTz,
+      String int96RebaseMode,
+      String int96RebaseTz) {
     this.logicalTypeAnnotation = logicalTypeAnnotation;
     this.convertTz = convertTz;
     this.datetimeRebaseMode = datetimeRebaseMode;
+    this.datetimeRebaseTz = datetimeRebaseTz;
     this.int96RebaseMode = int96RebaseMode;
+    this.int96RebaseTz = int96RebaseTz;
   }
 
   public ParquetVectorUpdater getUpdater(ColumnDescriptor descriptor, DataType 
sparkType) {
@@ -109,7 +115,7 @@ public class ParquetVectorUpdaterFactory {
             return new LongUpdater();
           } else {
             boolean failIfRebase = "EXCEPTION".equals(datetimeRebaseMode);
-            return new LongWithRebaseUpdater(failIfRebase);
+            return new LongWithRebaseUpdater(failIfRebase, datetimeRebaseTz);
           }
         } else if 
(isTimestampTypeMatched(LogicalTypeAnnotation.TimeUnit.MILLIS)) {
           validateTimestampType(sparkType);
@@ -117,7 +123,7 @@ public class ParquetVectorUpdaterFactory {
             return new LongAsMicrosUpdater();
           } else {
             final boolean failIfRebase = 
"EXCEPTION".equals(datetimeRebaseMode);
-            return new LongAsMicrosRebaseUpdater(failIfRebase);
+            return new LongAsMicrosRebaseUpdater(failIfRebase, 
datetimeRebaseTz);
           }
         } else if (sparkType instanceof DayTimeIntervalType) {
           return new LongUpdater();
@@ -142,13 +148,16 @@ public class ParquetVectorUpdaterFactory {
             if ("CORRECTED".equals(int96RebaseMode)) {
               return new BinaryToSQLTimestampUpdater();
             } else {
-              return new BinaryToSQLTimestampRebaseUpdater(failIfRebase);
+              return new BinaryToSQLTimestampRebaseUpdater(failIfRebase, 
int96RebaseTz);
             }
           } else {
             if ("CORRECTED".equals(int96RebaseMode)) {
               return new BinaryToSQLTimestampConvertTzUpdater(convertTz);
             } else {
-              return new 
BinaryToSQLTimestampConvertTzRebaseUpdater(failIfRebase, convertTz);
+              return new BinaryToSQLTimestampConvertTzRebaseUpdater(
+                failIfRebase,
+                convertTz,
+                int96RebaseTz);
             }
           }
         }
@@ -517,9 +526,11 @@ public class ParquetVectorUpdaterFactory {
 
   private static class LongWithRebaseUpdater implements ParquetVectorUpdater {
     private final boolean failIfRebase;
+    private final String timeZone;
 
-    LongWithRebaseUpdater(boolean failIfRebase) {
+    LongWithRebaseUpdater(boolean failIfRebase, String timeZone) {
       this.failIfRebase = failIfRebase;
+      this.timeZone = timeZone;
     }
 
     @Override
@@ -528,7 +539,7 @@ public class ParquetVectorUpdaterFactory {
         int offset,
         WritableColumnVector values,
         VectorizedValuesReader valuesReader) {
-      valuesReader.readLongsWithRebase(total, values, offset, failIfRebase);
+      valuesReader.readLongsWithRebase(total, values, offset, failIfRebase, 
timeZone);
     }
 
     @Override
@@ -542,7 +553,7 @@ public class ParquetVectorUpdaterFactory {
         WritableColumnVector values,
         VectorizedValuesReader valuesReader) {
       long julianMicros = valuesReader.readLong();
-      values.putLong(offset, rebaseMicros(julianMicros, failIfRebase));
+      values.putLong(offset, rebaseMicros(julianMicros, failIfRebase, 
timeZone));
     }
 
     @Override
@@ -552,7 +563,7 @@ public class ParquetVectorUpdaterFactory {
         WritableColumnVector dictionaryIds,
         Dictionary dictionary) {
       long julianMicros = 
dictionary.decodeToLong(dictionaryIds.getDictId(offset));
-      values.putLong(offset, rebaseMicros(julianMicros, failIfRebase));
+      values.putLong(offset, rebaseMicros(julianMicros, failIfRebase, 
timeZone));
     }
   }
 
@@ -594,9 +605,11 @@ public class ParquetVectorUpdaterFactory {
 
   private static class LongAsMicrosRebaseUpdater implements 
ParquetVectorUpdater {
     private final boolean failIfRebase;
+    private final String timeZone;
 
-    LongAsMicrosRebaseUpdater(boolean failIfRebase) {
+    LongAsMicrosRebaseUpdater(boolean failIfRebase, String timeZone) {
       this.failIfRebase = failIfRebase;
+      this.timeZone = timeZone;
     }
 
     @Override
@@ -621,7 +634,7 @@ public class ParquetVectorUpdaterFactory {
         WritableColumnVector values,
         VectorizedValuesReader valuesReader) {
       long julianMicros = 
DateTimeUtils.millisToMicros(valuesReader.readLong());
-      values.putLong(offset, rebaseMicros(julianMicros, failIfRebase));
+      values.putLong(offset, rebaseMicros(julianMicros, failIfRebase, 
timeZone));
     }
 
     @Override
@@ -632,7 +645,7 @@ public class ParquetVectorUpdaterFactory {
         Dictionary dictionary) {
       long julianMillis = 
dictionary.decodeToLong(dictionaryIds.getDictId(offset));
       long julianMicros = DateTimeUtils.millisToMicros(julianMillis);
-      values.putLong(offset, rebaseMicros(julianMicros, failIfRebase));
+      values.putLong(offset, rebaseMicros(julianMicros, failIfRebase, 
timeZone));
     }
   }
 
@@ -823,9 +836,11 @@ public class ParquetVectorUpdaterFactory {
 
   private static class BinaryToSQLTimestampRebaseUpdater implements 
ParquetVectorUpdater {
     private final boolean failIfRebase;
+    private final String timeZone;
 
-    BinaryToSQLTimestampRebaseUpdater(boolean failIfRebase) {
+    BinaryToSQLTimestampRebaseUpdater(boolean failIfRebase, String timeZone) {
       this.failIfRebase = failIfRebase;
+      this.timeZone = timeZone;
     }
 
     @Override
@@ -851,7 +866,7 @@ public class ParquetVectorUpdaterFactory {
         VectorizedValuesReader valuesReader) {
       // Read 12 bytes for INT96
       long julianMicros = 
ParquetRowConverter.binaryToSQLTimestamp(valuesReader.readBinary(12));
-      long gregorianMicros = rebaseInt96(julianMicros, failIfRebase);
+      long gregorianMicros = rebaseInt96(julianMicros, failIfRebase, timeZone);
       values.putLong(offset, gregorianMicros);
     }
 
@@ -863,7 +878,7 @@ public class ParquetVectorUpdaterFactory {
         Dictionary dictionary) {
       Binary v = dictionary.decodeToBinary(dictionaryIds.getDictId(offset));
       long julianMicros = ParquetRowConverter.binaryToSQLTimestamp(v);
-      long gregorianMicros = rebaseInt96(julianMicros, failIfRebase);
+      long gregorianMicros = rebaseInt96(julianMicros, failIfRebase, timeZone);
       values.putLong(offset, gregorianMicros);
     }
   }
@@ -871,10 +886,15 @@ public class ParquetVectorUpdaterFactory {
   private static class BinaryToSQLTimestampConvertTzRebaseUpdater implements 
ParquetVectorUpdater {
     private final boolean failIfRebase;
     private final ZoneId convertTz;
+    private final String timeZone;
 
-    BinaryToSQLTimestampConvertTzRebaseUpdater(boolean failIfRebase, ZoneId 
convertTz) {
+    BinaryToSQLTimestampConvertTzRebaseUpdater(
+        boolean failIfRebase,
+        ZoneId convertTz,
+        String timeZone) {
       this.failIfRebase = failIfRebase;
       this.convertTz = convertTz;
+      this.timeZone = timeZone;
     }
 
     @Override
@@ -900,7 +920,7 @@ public class ParquetVectorUpdaterFactory {
         VectorizedValuesReader valuesReader) {
       // Read 12 bytes for INT96
       long julianMicros = 
ParquetRowConverter.binaryToSQLTimestamp(valuesReader.readBinary(12));
-      long gregorianMicros = rebaseInt96(julianMicros, failIfRebase);
+      long gregorianMicros = rebaseInt96(julianMicros, failIfRebase, timeZone);
       long adjTime = DateTimeUtils.convertTz(gregorianMicros, convertTz, UTC);
       values.putLong(offset, adjTime);
     }
@@ -913,7 +933,7 @@ public class ParquetVectorUpdaterFactory {
         Dictionary dictionary) {
       Binary v = dictionary.decodeToBinary(dictionaryIds.getDictId(offset));
       long julianMicros = ParquetRowConverter.binaryToSQLTimestamp(v);
-      long gregorianMicros = rebaseInt96(julianMicros, failIfRebase);
+      long gregorianMicros = rebaseInt96(julianMicros, failIfRebase, timeZone);
       long adjTime = DateTimeUtils.convertTz(gregorianMicros, convertTz, UTC);
       values.putLong(offset, adjTime);
     }
@@ -1062,7 +1082,8 @@ public class ParquetVectorUpdaterFactory {
   private static long rebaseTimestamp(
       long julianMicros,
       final boolean failIfRebase,
-      final String format) {
+      final String format,
+      final String timeZone) {
     if (failIfRebase) {
       if (julianMicros < RebaseDateTime.lastSwitchJulianTs()) {
         throw DataSourceUtils.newRebaseExceptionInRead(format);
@@ -1070,16 +1091,22 @@ public class ParquetVectorUpdaterFactory {
         return julianMicros;
       }
     } else {
-      return RebaseDateTime.rebaseJulianToGregorianMicros(julianMicros);
+      return RebaseDateTime.rebaseJulianToGregorianMicros(timeZone, 
julianMicros);
     }
   }
 
-  private static long rebaseMicros(long julianMicros, final boolean 
failIfRebase) {
-    return rebaseTimestamp(julianMicros, failIfRebase, "Parquet");
+  private static long rebaseMicros(
+      long julianMicros,
+      final boolean failIfRebase,
+      final String timeZone) {
+    return rebaseTimestamp(julianMicros, failIfRebase, "Parquet", timeZone);
   }
 
-  private static long rebaseInt96(long julianMicros, final boolean 
failIfRebase) {
-    return rebaseTimestamp(julianMicros, failIfRebase, "Parquet INT96");
+  private static long rebaseInt96(
+      long julianMicros,
+      final boolean failIfRebase,
+      final String timeZone) {
+    return rebaseTimestamp(julianMicros, failIfRebase, "Parquet INT96", 
timeZone);
   }
 
   private boolean shouldConvertTimestamps() {
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 92dea08..cedb2395 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
@@ -93,13 +93,20 @@ public class VectorizedColumnReader {
       PrimitiveIterator.OfLong rowIndexes,
       ZoneId convertTz,
       String datetimeRebaseMode,
-      String int96RebaseMode) throws IOException {
+      String datetimeRebaseTz,
+      String int96RebaseMode,
+      String int96RebaseTz) throws IOException {
     this.descriptor = descriptor;
     this.pageReader = pageReader;
     this.readState = new ParquetReadState(descriptor.getMaxDefinitionLevel(), 
rowIndexes);
     this.logicalTypeAnnotation = logicalTypeAnnotation;
     this.updaterFactory = new ParquetVectorUpdaterFactory(
-        logicalTypeAnnotation, convertTz, datetimeRebaseMode, int96RebaseMode);
+      logicalTypeAnnotation,
+      convertTz,
+      datetimeRebaseMode,
+      datetimeRebaseTz,
+      int96RebaseMode,
+      int96RebaseTz);
 
     DictionaryPage dictionaryPage = pageReader.readDictionaryPage();
     if (dictionaryPage != null) {
diff --git 
a/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedParquetRecordReader.java
 
b/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedParquetRecordReader.java
index 9e0868b..50056bf 100644
--- 
a/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedParquetRecordReader.java
+++ 
b/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedParquetRecordReader.java
@@ -94,11 +94,15 @@ public class VectorizedParquetRecordReader extends 
SpecificParquetRecordReaderBa
    * The mode of rebasing date/timestamp from Julian to Proleptic Gregorian 
calendar.
    */
   private final String datetimeRebaseMode;
+  // The time zone Id in which rebasing of date/timestamp is performed
+  private final String datetimeRebaseTz;
 
   /**
    * The mode of rebasing INT96 timestamp from Julian to Proleptic Gregorian 
calendar.
    */
   private final String int96RebaseMode;
+  // The time zone Id in which rebasing of INT96 is performed
+  private final String int96RebaseTz;
 
   /**
    * columnBatch object that is used for batch decoding. This is created on 
first use and triggers
@@ -131,19 +135,30 @@ public class VectorizedParquetRecordReader extends 
SpecificParquetRecordReaderBa
   public VectorizedParquetRecordReader(
       ZoneId convertTz,
       String datetimeRebaseMode,
+      String datetimeRebaseTz,
       String int96RebaseMode,
+      String int96RebaseTz,
       boolean useOffHeap,
       int capacity) {
     this.convertTz = convertTz;
     this.datetimeRebaseMode = datetimeRebaseMode;
+    this.datetimeRebaseTz = datetimeRebaseTz;
     this.int96RebaseMode = int96RebaseMode;
+    this.int96RebaseTz = int96RebaseTz;
     MEMORY_MODE = useOffHeap ? MemoryMode.OFF_HEAP : MemoryMode.ON_HEAP;
     this.capacity = capacity;
   }
 
   // For test only.
   public VectorizedParquetRecordReader(boolean useOffHeap, int capacity) {
-    this(null, "CORRECTED", "LEGACY", useOffHeap, capacity);
+    this(
+      null,
+      "CORRECTED",
+      "UTC",
+      "LEGACY",
+      ZoneId.systemDefault().getId(),
+      useOffHeap,
+      capacity);
   }
 
   /**
@@ -350,7 +365,9 @@ public class VectorizedParquetRecordReader extends 
SpecificParquetRecordReaderBa
         pages.getRowIndexes().orElse(null),
         convertTz,
         datetimeRebaseMode,
-        int96RebaseMode);
+        datetimeRebaseTz,
+        int96RebaseMode,
+        int96RebaseTz);
     }
     totalCountLoadedSoFar += pages.getRowCount();
   }
diff --git 
a/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedPlainValuesReader.java
 
b/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedPlainValuesReader.java
index 0eb5d65..fb40a13 100644
--- 
a/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedPlainValuesReader.java
+++ 
b/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedPlainValuesReader.java
@@ -206,7 +206,11 @@ public class VectorizedPlainValuesReader extends 
ValuesReader implements Vectori
   // if rebase is not needed.
   @Override
   public final void readLongsWithRebase(
-      int total, WritableColumnVector c, int rowId, boolean failIfRebase) {
+      int total,
+      WritableColumnVector c,
+      int rowId,
+      boolean failIfRebase,
+      String timeZone) {
     int requiredBytes = total * 8;
     ByteBuffer buffer = getBuffer(requiredBytes);
     boolean rebase = false;
@@ -218,7 +222,9 @@ public class VectorizedPlainValuesReader extends 
ValuesReader implements Vectori
         throw DataSourceUtils.newRebaseExceptionInRead("Parquet");
       } else {
         for (int i = 0; i < total; i += 1) {
-          c.putLong(rowId + i, 
RebaseDateTime.rebaseJulianToGregorianMicros(buffer.getLong()));
+          c.putLong(
+            rowId + i,
+            RebaseDateTime.rebaseJulianToGregorianMicros(timeZone, 
buffer.getLong()));
         }
       }
     } else {
diff --git 
a/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedRleValuesReader.java
 
b/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedRleValuesReader.java
index af739a5..cd97fb6 100644
--- 
a/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedRleValuesReader.java
+++ 
b/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedRleValuesReader.java
@@ -354,7 +354,11 @@ public final class VectorizedRleValuesReader extends 
ValuesReader
 
   @Override
   public void readLongsWithRebase(
-      int total, WritableColumnVector c, int rowId, boolean failIfRebase) {
+      int total,
+      WritableColumnVector c,
+      int rowId,
+      boolean failIfRebase,
+      String timeZone) {
     throw new UnsupportedOperationException("only readInts is valid.");
   }
 
diff --git 
a/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedValuesReader.java
 
b/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedValuesReader.java
index fc4eac9..92161f1 100644
--- 
a/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedValuesReader.java
+++ 
b/sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedValuesReader.java
@@ -46,7 +46,12 @@ public interface VectorizedValuesReader {
   void readUnsignedIntegers(int total, WritableColumnVector c, int rowId);
   void readUnsignedLongs(int total, WritableColumnVector c, int rowId);
   void readLongs(int total, WritableColumnVector c, int rowId);
-  void readLongsWithRebase(int total, WritableColumnVector c, int rowId, 
boolean failIfRebase);
+  void readLongsWithRebase(
+      int total,
+      WritableColumnVector c,
+      int rowId,
+      boolean failIfRebase,
+      String timeZone);
   void readFloats(int total, WritableColumnVector c, int rowId);
   void readDoubles(int total, WritableColumnVector c, int rowId);
   void readBinary(int total, WritableColumnVector c, int rowId);
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/DataSourceUtils.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/DataSourceUtils.scala
index 76bc03a..6ceb44a 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/DataSourceUtils.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/DataSourceUtils.scala
@@ -26,10 +26,11 @@ import org.json4s.NoTypeHints
 import org.json4s.jackson.Serialization
 
 import org.apache.spark.SparkUpgradeException
-import org.apache.spark.sql.{SPARK_LEGACY_DATETIME, SPARK_LEGACY_INT96, 
SPARK_VERSION_METADATA_KEY}
+import org.apache.spark.sql.{SPARK_LEGACY_DATETIME_METADATA_KEY, 
SPARK_LEGACY_INT96_METADATA_KEY, SPARK_TIMEZONE_METADATA_KEY, 
SPARK_VERSION_METADATA_KEY}
 import org.apache.spark.sql.catalyst.catalog.{CatalogTable, CatalogUtils}
 import org.apache.spark.sql.catalyst.expressions.{AttributeReference, 
AttributeSet, Expression, ExpressionSet, PredicateHelper}
 import org.apache.spark.sql.catalyst.util.RebaseDateTime
+import org.apache.spark.sql.catalyst.util.RebaseDateTime.RebaseSpec
 import org.apache.spark.sql.errors.{QueryCompilationErrors, 
QueryExecutionErrors}
 import org.apache.spark.sql.execution.datasources.parquet.ParquetOptions
 import org.apache.spark.sql.internal.SQLConf
@@ -109,44 +110,53 @@ object DataSourceUtils extends PredicateHelper {
       case _ => false
     }
 
-  def datetimeRebaseMode(
+  private def getRebaseSpec(
       lookupFileMeta: String => String,
-      modeByConfig: String): LegacyBehaviorPolicy.Value = {
-    if (Utils.isTesting && 
SQLConf.get.getConfString("spark.test.forceNoRebase", "") == "true") {
-      return LegacyBehaviorPolicy.CORRECTED
+      modeByConfig: String,
+      minVersion: String,
+      metadataKey: String): RebaseSpec = {
+    val policy = if (Utils.isTesting &&
+      SQLConf.get.getConfString("spark.test.forceNoRebase", "") == "true") {
+      LegacyBehaviorPolicy.CORRECTED
+    } else {
+      // If there is no version, we return the mode specified by the config.
+      Option(lookupFileMeta(SPARK_VERSION_METADATA_KEY)).map { version =>
+        // Files written by Spark 2.4 and earlier follow the legacy hybrid 
calendar and we need to
+        // rebase the datetime values.
+        // Files written by `minVersion` and latter may also need the rebase 
if they were written
+        // with the "LEGACY" rebase mode.
+        if (version < minVersion || lookupFileMeta(metadataKey) != null) {
+          LegacyBehaviorPolicy.LEGACY
+        } else {
+          LegacyBehaviorPolicy.CORRECTED
+        }
+      }.getOrElse(LegacyBehaviorPolicy.withName(modeByConfig))
+    }
+    policy match {
+      case LegacyBehaviorPolicy.LEGACY =>
+        RebaseSpec(LegacyBehaviorPolicy.LEGACY, 
Option(lookupFileMeta(SPARK_TIMEZONE_METADATA_KEY)))
+      case _ => RebaseSpec(policy)
     }
-    // If there is no version, we return the mode specified by the config.
-    Option(lookupFileMeta(SPARK_VERSION_METADATA_KEY)).map { version =>
-      // Files written by Spark 2.4 and earlier follow the legacy hybrid 
calendar and we need to
-      // rebase the datetime values.
-      // Files written by Spark 3.0 and latter may also need the rebase if 
they were written with
-      // the "LEGACY" rebase mode.
-      if (version < "3.0.0" || lookupFileMeta(SPARK_LEGACY_DATETIME) != null) {
-        LegacyBehaviorPolicy.LEGACY
-      } else {
-        LegacyBehaviorPolicy.CORRECTED
-      }
-    }.getOrElse(LegacyBehaviorPolicy.withName(modeByConfig))
   }
 
-  def int96RebaseMode(
+  def datetimeRebaseSpec(
       lookupFileMeta: String => String,
-      modeByConfig: String): LegacyBehaviorPolicy.Value = {
-    if (Utils.isTesting && 
SQLConf.get.getConfString("spark.test.forceNoRebase", "") == "true") {
-      return LegacyBehaviorPolicy.CORRECTED
-    }
-    // If there is no version, we return the mode specified by the config.
-    Option(lookupFileMeta(SPARK_VERSION_METADATA_KEY)).map { version =>
-      // Files written by Spark 3.0 and earlier follow the legacy hybrid 
calendar and we need to
-      // rebase the INT96 timestamp values.
-      // Files written by Spark 3.1 and latter may also need the rebase if 
they were written with
-      // the "LEGACY" rebase mode.
-      if (version < "3.1.0" || lookupFileMeta(SPARK_LEGACY_INT96) != null) {
-        LegacyBehaviorPolicy.LEGACY
-      } else {
-        LegacyBehaviorPolicy.CORRECTED
-      }
-    }.getOrElse(LegacyBehaviorPolicy.withName(modeByConfig))
+      modeByConfig: String): RebaseSpec = {
+    getRebaseSpec(
+      lookupFileMeta,
+      modeByConfig,
+      "3.0.0",
+      SPARK_LEGACY_DATETIME_METADATA_KEY)
+  }
+
+  def int96RebaseSpec(
+      lookupFileMeta: String => String,
+      modeByConfig: String): RebaseSpec = {
+    getRebaseSpec(
+      lookupFileMeta,
+      modeByConfig,
+      "3.1.0",
+      SPARK_LEGACY_INT96_METADATA_KEY)
   }
 
   def newRebaseExceptionInRead(format: String): SparkUpgradeException = {
@@ -197,14 +207,15 @@ object DataSourceUtils extends PredicateHelper {
   }
 
   def createTimestampRebaseFuncInRead(
-      rebaseMode: LegacyBehaviorPolicy.Value,
-      format: String): Long => Long = rebaseMode match {
+      rebaseSpec: RebaseSpec,
+      format: String): Long => Long = rebaseSpec.mode match {
     case LegacyBehaviorPolicy.EXCEPTION => micros: Long =>
       if (micros < RebaseDateTime.lastSwitchJulianTs) {
         throw DataSourceUtils.newRebaseExceptionInRead(format)
       }
       micros
-    case LegacyBehaviorPolicy.LEGACY => 
RebaseDateTime.rebaseJulianToGregorianMicros
+    case LegacyBehaviorPolicy.LEGACY =>
+      RebaseDateTime.rebaseJulianToGregorianMicros(rebaseSpec.timeZone, _)
     case LegacyBehaviorPolicy.CORRECTED => identity[Long]
   }
 
@@ -216,7 +227,9 @@ object DataSourceUtils extends PredicateHelper {
         throw DataSourceUtils.newRebaseExceptionInWrite(format)
       }
       micros
-    case LegacyBehaviorPolicy.LEGACY => 
RebaseDateTime.rebaseGregorianToJulianMicros
+    case LegacyBehaviorPolicy.LEGACY =>
+      val timeZone = SQLConf.get.sessionLocalTimeZone
+      RebaseDateTime.rebaseGregorianToJulianMicros(timeZone, _)
     case LegacyBehaviorPolicy.CORRECTED => identity[Long]
   }
 
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFileFormat.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFileFormat.scala
index b400e36..4515387 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFileFormat.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFileFormat.scala
@@ -266,7 +266,7 @@ class ParquetFileFormat
 
       lazy val footerFileMetaData =
         ParquetFooterReader.readFooter(sharedConf, filePath, 
SKIP_ROW_GROUPS).getFileMetaData
-      val datetimeRebaseMode = DataSourceUtils.datetimeRebaseMode(
+      val datetimeRebaseSpec = DataSourceUtils.datetimeRebaseSpec(
         footerFileMetaData.getKeyValueMetaData.get,
         datetimeRebaseModeInRead)
       // Try to push down filters when filter push-down is enabled.
@@ -280,7 +280,7 @@ class ParquetFileFormat
           pushDownStringStartWith,
           pushDownInFilterThreshold,
           isCaseSensitive,
-          datetimeRebaseMode)
+          datetimeRebaseSpec)
         filters
           // Collects all converted Parquet filter predicates. Notice that not 
all predicates can be
           // converted (`ParquetFilters.createFilter` returns an `Option`). 
That's why a `flatMap`
@@ -306,7 +306,7 @@ class ParquetFileFormat
           None
         }
 
-      val int96RebaseMode = DataSourceUtils.int96RebaseMode(
+      val int96RebaseSpec = DataSourceUtils.int96RebaseSpec(
         footerFileMetaData.getKeyValueMetaData.get,
         int96RebaseModeInRead)
 
@@ -323,8 +323,10 @@ class ParquetFileFormat
       if (enableVectorizedReader) {
         val vectorizedReader = new VectorizedParquetRecordReader(
           convertTz.orNull,
-          datetimeRebaseMode.toString,
-          int96RebaseMode.toString,
+          datetimeRebaseSpec.mode.toString,
+          datetimeRebaseSpec.timeZone,
+          int96RebaseSpec.mode.toString,
+          int96RebaseSpec.timeZone,
           enableOffHeapColumnVector && taskContext.isDefined,
           capacity)
         // SPARK-37089: We cannot register a task completion listener to close 
this iterator here
@@ -358,8 +360,8 @@ class ParquetFileFormat
         val readSupport = new ParquetReadSupport(
           convertTz,
           enableVectorizedReader = false,
-          datetimeRebaseMode,
-          int96RebaseMode)
+          datetimeRebaseSpec,
+          int96RebaseSpec)
         val reader = if (pushed.isDefined && enableRecordFilter) {
           val parquetFilter = FilterCompat.get(pushed.get, null)
           new ParquetRecordReader[InternalRow](readSupport, parquetFilter)
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFilters.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFilters.scala
index c81d8ad..75060cf 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFilters.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFilters.scala
@@ -35,7 +35,7 @@ import 
org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName
 import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName._
 
 import org.apache.spark.sql.catalyst.util.{CaseInsensitiveMap, DateTimeUtils, 
IntervalUtils}
-import 
org.apache.spark.sql.catalyst.util.RebaseDateTime.{rebaseGregorianToJulianDays, 
rebaseGregorianToJulianMicros}
+import 
org.apache.spark.sql.catalyst.util.RebaseDateTime.{rebaseGregorianToJulianDays, 
rebaseGregorianToJulianMicros, RebaseSpec}
 import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
 import org.apache.spark.sql.sources
 import org.apache.spark.unsafe.types.UTF8String
@@ -51,7 +51,7 @@ class ParquetFilters(
     pushDownStartWith: Boolean,
     pushDownInFilterThreshold: Int,
     caseSensitive: Boolean,
-    datetimeRebaseMode: LegacyBehaviorPolicy.Value) {
+    datetimeRebaseSpec: RebaseSpec) {
   // A map which contains parquet field name and data type, if predicate push 
down applies.
   //
   // Each key in `nameToParquetField` represents a column; `dots` are used as 
separators for
@@ -137,7 +137,7 @@ class ParquetFilters(
       case d: Date => DateTimeUtils.fromJavaDate(d)
       case ld: LocalDate => DateTimeUtils.localDateToDays(ld)
     }
-    datetimeRebaseMode match {
+    datetimeRebaseSpec.mode match {
       case LegacyBehaviorPolicy.LEGACY => 
rebaseGregorianToJulianDays(gregorianDays)
       case _ => gregorianDays
     }
@@ -148,8 +148,9 @@ class ParquetFilters(
       case i: Instant => DateTimeUtils.instantToMicros(i)
       case t: Timestamp => DateTimeUtils.fromJavaTimestamp(t)
     }
-    datetimeRebaseMode match {
-      case LegacyBehaviorPolicy.LEGACY => 
rebaseGregorianToJulianMicros(gregorianMicros)
+    datetimeRebaseSpec.mode match {
+      case LegacyBehaviorPolicy.LEGACY =>
+        rebaseGregorianToJulianMicros(datetimeRebaseSpec.timeZone, 
gregorianMicros)
       case _ => gregorianMicros
     }
   }
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetReadSupport.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetReadSupport.scala
index 597a1bb..bdab0f7 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetReadSupport.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetReadSupport.scala
@@ -32,6 +32,7 @@ import org.apache.parquet.schema.Type.Repetition
 
 import org.apache.spark.internal.Logging
 import org.apache.spark.sql.catalyst.InternalRow
+import org.apache.spark.sql.catalyst.util.RebaseDateTime.RebaseSpec
 import org.apache.spark.sql.errors.QueryExecutionErrors
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
@@ -56,8 +57,8 @@ import org.apache.spark.sql.types._
 class ParquetReadSupport(
     val convertTz: Option[ZoneId],
     enableVectorizedReader: Boolean,
-    datetimeRebaseMode: LegacyBehaviorPolicy.Value,
-    int96RebaseMode: LegacyBehaviorPolicy.Value)
+    datetimeRebaseSpec: RebaseSpec,
+    int96RebaseSpec: RebaseSpec)
   extends ReadSupport[InternalRow] with Logging {
   private var catalystRequestedSchema: StructType = _
 
@@ -68,8 +69,8 @@ class ParquetReadSupport(
     this(
       None,
       enableVectorizedReader = true,
-      datetimeRebaseMode = LegacyBehaviorPolicy.CORRECTED,
-      int96RebaseMode = LegacyBehaviorPolicy.LEGACY)
+      datetimeRebaseSpec = RebaseSpec(LegacyBehaviorPolicy.CORRECTED),
+      int96RebaseSpec = RebaseSpec(LegacyBehaviorPolicy.LEGACY))
   }
 
   /**
@@ -138,8 +139,8 @@ class ParquetReadSupport(
       ParquetReadSupport.expandUDT(catalystRequestedSchema),
       new ParquetToSparkSchemaConverter(conf),
       convertTz,
-      datetimeRebaseMode,
-      int96RebaseMode)
+      datetimeRebaseSpec,
+      int96RebaseSpec)
   }
 }
 
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetRecordMaterializer.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetRecordMaterializer.scala
index 80763ef..2206b43b 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetRecordMaterializer.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetRecordMaterializer.scala
@@ -23,7 +23,7 @@ import org.apache.parquet.io.api.{GroupConverter, 
RecordMaterializer}
 import org.apache.parquet.schema.MessageType
 
 import org.apache.spark.sql.catalyst.InternalRow
-import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
+import org.apache.spark.sql.catalyst.util.RebaseDateTime.RebaseSpec
 import org.apache.spark.sql.types.StructType
 
 /**
@@ -33,18 +33,18 @@ import org.apache.spark.sql.types.StructType
  * @param catalystSchema Catalyst schema of the rows to be constructed
  * @param schemaConverter A Parquet-Catalyst schema converter that helps 
initializing row converters
  * @param convertTz the optional time zone to convert to int96 data
- * @param datetimeRebaseMode the mode of rebasing date/timestamp from Julian 
to Proleptic Gregorian
- *                           calendar
- * @param int96RebaseMode the mode of rebasing INT96 timestamp from Julian to 
Proleptic Gregorian
- *                           calendar
+ * @param datetimeRebaseSpec the specification of rebasing date/timestamp from 
Julian to Proleptic
+ *                           Gregorian calendar: mode + optional original time 
zone
+ * @param int96RebaseSpec the specification of rebasing INT96 timestamp from 
Julian to Proleptic
+ *                        Gregorian calendar
  */
 private[parquet] class ParquetRecordMaterializer(
     parquetSchema: MessageType,
     catalystSchema: StructType,
     schemaConverter: ParquetToSparkSchemaConverter,
     convertTz: Option[ZoneId],
-    datetimeRebaseMode: LegacyBehaviorPolicy.Value,
-    int96RebaseMode: LegacyBehaviorPolicy.Value)
+    datetimeRebaseSpec: RebaseSpec,
+    int96RebaseSpec: RebaseSpec)
   extends RecordMaterializer[InternalRow] {
 
   private val rootConverter = new ParquetRowConverter(
@@ -52,8 +52,8 @@ private[parquet] class ParquetRecordMaterializer(
     parquetSchema,
     catalystSchema,
     convertTz,
-    datetimeRebaseMode,
-    int96RebaseMode,
+    datetimeRebaseSpec,
+    int96RebaseSpec,
     NoopUpdater)
 
   override def getCurrentRecord: InternalRow = rootConverter.currentRecord
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 243bc16..b128983 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
@@ -35,10 +35,10 @@ import org.apache.spark.internal.Logging
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.catalyst.expressions._
 import org.apache.spark.sql.catalyst.util.{ArrayBasedMapData, 
CaseInsensitiveMap, DateTimeUtils, GenericArrayData}
+import org.apache.spark.sql.catalyst.util.RebaseDateTime.RebaseSpec
 import org.apache.spark.sql.errors.QueryExecutionErrors
 import org.apache.spark.sql.execution.datasources.DataSourceUtils
 import org.apache.spark.sql.internal.SQLConf
-import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
 import org.apache.spark.sql.types._
 import org.apache.spark.unsafe.types.UTF8String
 
@@ -127,10 +127,10 @@ private[parquet] class ParquetPrimitiveConverter(val 
updater: ParentContainerUpd
  * @param catalystType Spark SQL schema that corresponds to the Parquet record 
type. User-defined
  *        types should have been expanded.
  * @param convertTz the optional time zone to convert to int96 data
- * @param datetimeRebaseMode the mode of rebasing date/timestamp from Julian 
to Proleptic Gregorian
- *                           calendar
- * @param int96RebaseMode the mode of rebasing INT96 timestamp from Julian to 
Proleptic Gregorian
- *                           calendar
+ * @param datetimeRebaseSpec the specification of rebasing date/timestamp from 
Julian to Proleptic
+ *                           Gregorian calendar: mode + optional original time 
zone
+ * @param int96RebaseSpec the specification of rebasing INT96 timestamp from 
Julian to Proleptic
+ *                        Gregorian calendar
  * @param updater An updater which propagates converted field values to the 
parent container
  */
 private[parquet] class ParquetRowConverter(
@@ -138,8 +138,8 @@ private[parquet] class ParquetRowConverter(
     parquetType: GroupType,
     catalystType: StructType,
     convertTz: Option[ZoneId],
-    datetimeRebaseMode: LegacyBehaviorPolicy.Value,
-    int96RebaseMode: LegacyBehaviorPolicy.Value,
+    datetimeRebaseSpec: RebaseSpec,
+    int96RebaseSpec: RebaseSpec,
     updater: ParentContainerUpdater)
   extends ParquetGroupConverter(updater) with Logging {
 
@@ -191,13 +191,13 @@ private[parquet] class ParquetRowConverter(
   def currentRecord: InternalRow = currentRow
 
   private val dateRebaseFunc = DataSourceUtils.createDateRebaseFuncInRead(
-    datetimeRebaseMode, "Parquet")
+    datetimeRebaseSpec.mode, "Parquet")
 
   private val timestampRebaseFunc = 
DataSourceUtils.createTimestampRebaseFuncInRead(
-    datetimeRebaseMode, "Parquet")
+    datetimeRebaseSpec, "Parquet")
 
   private val int96RebaseFunc = 
DataSourceUtils.createTimestampRebaseFuncInRead(
-    int96RebaseMode, "Parquet INT96")
+    int96RebaseSpec, "Parquet INT96")
 
   // Converters for each field.
   private[this] val fieldConverters: Array[Converter with 
HasParentContainerUpdater] = {
@@ -443,8 +443,8 @@ private[parquet] class ParquetRowConverter(
           parquetType.asGroupType(),
           t,
           convertTz,
-          datetimeRebaseMode,
-          int96RebaseMode,
+          datetimeRebaseSpec,
+          int96RebaseSpec,
           wrappedUpdater)
 
       case t =>
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetUtils.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetUtils.scala
index 5bd712e..87a0d9c 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetUtils.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetUtils.scala
@@ -32,6 +32,7 @@ import org.apache.spark.SparkException
 import org.apache.spark.sql.SparkSession
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.catalyst.expressions.JoinedRow
+import org.apache.spark.sql.catalyst.util.RebaseDateTime.RebaseSpec
 import org.apache.spark.sql.connector.expressions.aggregate.{Aggregation, 
Count, CountStar, Max, Min}
 import org.apache.spark.sql.execution.datasources.AggregatePushDownUtils
 import org.apache.spark.sql.internal.SQLConf.{LegacyBehaviorPolicy, 
PARQUET_AGGREGATE_PUSHDOWN_ENABLED}
@@ -159,7 +160,7 @@ object ParquetUtils {
       aggregation: Aggregation,
       aggSchema: StructType,
       partitionValues: InternalRow,
-      datetimeRebaseMode: LegacyBehaviorPolicy.Value): InternalRow = {
+      datetimeRebaseSpec: RebaseSpec): InternalRow = {
     val (primitiveTypes, values) = getPushedDownAggResult(
       footer, filePath, dataSchema, partitionSchema, aggregation)
 
@@ -173,8 +174,14 @@ object ParquetUtils {
       AggregatePushDownUtils.getSchemaWithoutGroupingExpression(aggSchema, 
aggregation)
 
     val schemaConverter = new ParquetToSparkSchemaConverter
-    val converter = new ParquetRowConverter(schemaConverter, parquetSchema, 
schemaWithoutGroupBy,
-      None, datetimeRebaseMode, LegacyBehaviorPolicy.CORRECTED, NoopUpdater)
+    val converter = new ParquetRowConverter(
+      schemaConverter,
+      parquetSchema,
+      schemaWithoutGroupBy,
+      None,
+      datetimeRebaseSpec,
+      RebaseSpec(LegacyBehaviorPolicy.CORRECTED),
+      NoopUpdater)
     val primitiveTypeNames = primitiveTypes.map(_.getPrimitiveTypeName)
     primitiveTypeNames.zipWithIndex.foreach {
       case (PrimitiveType.PrimitiveTypeName.BOOLEAN, i) =>
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 183ea39..9d38c96 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
@@ -31,7 +31,7 @@ import org.apache.parquet.io.api.{Binary, RecordConsumer}
 
 import org.apache.spark.SPARK_VERSION_SHORT
 import org.apache.spark.internal.Logging
-import org.apache.spark.sql.{SPARK_LEGACY_DATETIME, SPARK_LEGACY_INT96, 
SPARK_VERSION_METADATA_KEY}
+import org.apache.spark.sql.{SPARK_LEGACY_DATETIME_METADATA_KEY, 
SPARK_LEGACY_INT96_METADATA_KEY, SPARK_TIMEZONE_METADATA_KEY, 
SPARK_VERSION_METADATA_KEY}
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.catalyst.expressions.SpecializedGetters
 import org.apache.spark.sql.catalyst.util.DateTimeUtils
@@ -117,15 +117,19 @@ class ParquetWriteSupport extends 
WriteSupport[InternalRow] with Logging {
       ParquetReadSupport.SPARK_METADATA_KEY -> schemaString
     ) ++ {
       if (datetimeRebaseMode == LegacyBehaviorPolicy.LEGACY) {
-        Some(SPARK_LEGACY_DATETIME -> "")
+        Map(
+          SPARK_LEGACY_DATETIME_METADATA_KEY -> "",
+          SPARK_TIMEZONE_METADATA_KEY -> SQLConf.get.sessionLocalTimeZone)
       } else {
-        None
+        Map.empty
       }
     } ++ {
       if (int96RebaseMode == LegacyBehaviorPolicy.LEGACY) {
-        Some(SPARK_LEGACY_INT96 -> "")
+        Map(
+          SPARK_LEGACY_INT96_METADATA_KEY -> "",
+          SPARK_TIMEZONE_METADATA_KEY -> SQLConf.get.sessionLocalTimeZone)
       } else {
-        None
+        Map.empty
       }
     }
 
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/parquet/ParquetPartitionReaderFactory.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/parquet/ParquetPartitionReaderFactory.scala
index ee94e99..41ee98a 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/parquet/ParquetPartitionReaderFactory.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/parquet/ParquetPartitionReaderFactory.scala
@@ -34,13 +34,13 @@ import org.apache.spark.broadcast.Broadcast
 import org.apache.spark.internal.Logging
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.catalyst.util.DateTimeUtils
+import org.apache.spark.sql.catalyst.util.RebaseDateTime.RebaseSpec
 import org.apache.spark.sql.connector.expressions.aggregate.Aggregation
 import org.apache.spark.sql.connector.read.{InputPartition, PartitionReader}
 import org.apache.spark.sql.execution.datasources.{AggregatePushDownUtils, 
DataSourceUtils, PartitionedFile, RecordReaderIterator}
 import org.apache.spark.sql.execution.datasources.parquet._
 import org.apache.spark.sql.execution.datasources.v2._
 import org.apache.spark.sql.internal.SQLConf
-import org.apache.spark.sql.internal.SQLConf.LegacyBehaviorPolicy
 import org.apache.spark.sql.sources.Filter
 import org.apache.spark.sql.types.{AtomicType, StructType}
 import org.apache.spark.sql.vectorized.ColumnarBatch
@@ -96,9 +96,9 @@ case class ParquetPartitionReaderFactory(
     }
   }
 
-  private def getDatetimeRebaseMode(
-      footerFileMetaData: FileMetaData): LegacyBehaviorPolicy.Value = {
-    DataSourceUtils.datetimeRebaseMode(
+  private def getDatetimeRebaseSpec(
+      footerFileMetaData: FileMetaData): RebaseSpec = {
+    DataSourceUtils.datetimeRebaseSpec(
       footerFileMetaData.getKeyValueMetaData.get,
       datetimeRebaseModeInRead)
   }
@@ -133,7 +133,7 @@ case class ParquetPartitionReaderFactory(
           if (footer != null && footer.getBlocks.size > 0) {
             ParquetUtils.createAggInternalRowFromFooter(footer, file.filePath, 
dataSchema,
               partitionSchema, aggregation.get, readDataSchema, 
file.partitionValues,
-              getDatetimeRebaseMode(footer.getFileMetaData))
+              getDatetimeRebaseSpec(footer.getFileMetaData))
           } else {
             null
           }
@@ -176,7 +176,7 @@ case class ParquetPartitionReaderFactory(
           if (footer != null && footer.getBlocks.size > 0) {
             val row = ParquetUtils.createAggInternalRowFromFooter(footer, 
file.filePath,
               dataSchema, partitionSchema, aggregation.get, readDataSchema, 
file.partitionValues,
-              getDatetimeRebaseMode(footer.getFileMetaData))
+              getDatetimeRebaseSpec(footer.getFileMetaData))
             AggregatePushDownUtils.convertAggregatesRowToBatch(
               row, readDataSchema, enableOffHeapColumnVector && 
Option(TaskContext.get()).isDefined)
           } else {
@@ -204,15 +204,15 @@ case class ParquetPartitionReaderFactory(
       buildReaderFunc: (
         FileSplit, InternalRow, TaskAttemptContextImpl,
           Option[FilterPredicate], Option[ZoneId],
-          LegacyBehaviorPolicy.Value,
-          LegacyBehaviorPolicy.Value) => RecordReader[Void, T]): 
RecordReader[Void, T] = {
+          RebaseSpec,
+          RebaseSpec) => RecordReader[Void, T]): RecordReader[Void, T] = {
     val conf = broadcastedConf.value.value
 
     val filePath = new Path(new URI(file.filePath))
     val split = new FileSplit(filePath, file.start, file.length, 
Array.empty[String])
 
     lazy val footerFileMetaData = getFooter(file).getFileMetaData
-    val datetimeRebaseMode = getDatetimeRebaseMode(footerFileMetaData)
+    val datetimeRebaseSpec = getDatetimeRebaseSpec(footerFileMetaData)
     // Try to push down filters when filter push-down is enabled.
     val pushed = if (enableParquetFilterPushDown) {
       val parquetSchema = footerFileMetaData.getSchema
@@ -224,7 +224,7 @@ case class ParquetPartitionReaderFactory(
         pushDownStringStartWith,
         pushDownInFilterThreshold,
         isCaseSensitive,
-        datetimeRebaseMode)
+        datetimeRebaseSpec)
       filters
         // Collects all converted Parquet filter predicates. Notice that not 
all predicates can be
         // converted (`ParquetFilters.createFilter` returns an `Option`). 
That's why a `flatMap`
@@ -257,7 +257,7 @@ case class ParquetPartitionReaderFactory(
     if (pushed.isDefined) {
       
ParquetInputFormat.setFilterPredicate(hadoopAttemptContext.getConfiguration, 
pushed.get)
     }
-    val int96RebaseMode = DataSourceUtils.int96RebaseMode(
+    val int96RebaseSpec = DataSourceUtils.int96RebaseSpec(
       footerFileMetaData.getKeyValueMetaData.get,
       int96RebaseModeInRead)
     val reader = buildReaderFunc(
@@ -266,8 +266,8 @@ case class ParquetPartitionReaderFactory(
       hadoopAttemptContext,
       pushed,
       convertTz,
-      datetimeRebaseMode,
-      int96RebaseMode)
+      datetimeRebaseSpec,
+      int96RebaseSpec)
     reader.initialize(split, hadoopAttemptContext)
     reader
   }
@@ -282,16 +282,16 @@ case class ParquetPartitionReaderFactory(
       hadoopAttemptContext: TaskAttemptContextImpl,
       pushed: Option[FilterPredicate],
       convertTz: Option[ZoneId],
-      datetimeRebaseMode: LegacyBehaviorPolicy.Value,
-      int96RebaseMode: LegacyBehaviorPolicy.Value): RecordReader[Void, 
InternalRow] = {
+      datetimeRebaseSpec: RebaseSpec,
+      int96RebaseSpec: RebaseSpec): RecordReader[Void, InternalRow] = {
     logDebug(s"Falling back to parquet-mr")
     val taskContext = Option(TaskContext.get())
     // ParquetRecordReader returns InternalRow
     val readSupport = new ParquetReadSupport(
       convertTz,
       enableVectorizedReader = false,
-      datetimeRebaseMode,
-      int96RebaseMode)
+      datetimeRebaseSpec,
+      int96RebaseSpec)
     val reader = if (pushed.isDefined && enableRecordFilter) {
       val parquetFilter = FilterCompat.get(pushed.get, null)
       new ParquetRecordReader[InternalRow](readSupport, parquetFilter)
@@ -317,13 +317,15 @@ case class ParquetPartitionReaderFactory(
       hadoopAttemptContext: TaskAttemptContextImpl,
       pushed: Option[FilterPredicate],
       convertTz: Option[ZoneId],
-      datetimeRebaseMode: LegacyBehaviorPolicy.Value,
-      int96RebaseMode: LegacyBehaviorPolicy.Value): 
VectorizedParquetRecordReader = {
+      datetimeRebaseSpec: RebaseSpec,
+      int96RebaseSpec: RebaseSpec): VectorizedParquetRecordReader = {
     val taskContext = Option(TaskContext.get())
     val vectorizedReader = new VectorizedParquetRecordReader(
       convertTz.orNull,
-      datetimeRebaseMode.toString,
-      int96RebaseMode.toString,
+      datetimeRebaseSpec.mode.toString,
+      datetimeRebaseSpec.timeZone,
+      int96RebaseSpec.mode.toString,
+      int96RebaseSpec.timeZone,
       enableOffHeapColumnVector && taskContext.isDefined,
       capacity)
     val iter = new RecordReaderIterator(vectorizedReader)
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/parquet/ParquetScanBuilder.scala
 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/parquet/ParquetScanBuilder.scala
index 74d11b6..1f2f75aeb 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/parquet/ParquetScanBuilder.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/parquet/ParquetScanBuilder.scala
@@ -20,6 +20,7 @@ package org.apache.spark.sql.execution.datasources.v2.parquet
 import scala.collection.JavaConverters._
 
 import org.apache.spark.sql.SparkSession
+import org.apache.spark.sql.catalyst.util.RebaseDateTime.RebaseSpec
 import org.apache.spark.sql.connector.expressions.aggregate.Aggregation
 import org.apache.spark.sql.connector.read.{Scan, SupportsPushDownAggregates}
 import org.apache.spark.sql.execution.datasources.{AggregatePushDownUtils, 
PartitioningAwareFileIndex}
@@ -65,7 +66,7 @@ case class ParquetScanBuilder(
         isCaseSensitive,
         // The rebase mode doesn't matter here because the filters are used to 
determine
         // whether they is convertible.
-        LegacyBehaviorPolicy.CORRECTED)
+        RebaseSpec(LegacyBehaviorPolicy.CORRECTED))
       parquetFilters.convertibleFilters(pushedDataFilters).toArray
     } else {
       Array.empty[Filter]
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/package.scala 
b/sql/core/src/main/scala/org/apache/spark/sql/package.scala
index 022fecf..1794ac5 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/package.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/package.scala
@@ -56,14 +56,21 @@ package object sql {
   private[sql] val SPARK_VERSION_METADATA_KEY = "org.apache.spark.version"
 
   /**
+   * The metadata key which is used to write the current session time zone 
into:
+   * - Parquet file metadata
+   * - Avro file metadata
+   */
+  private[sql] val SPARK_TIMEZONE_METADATA_KEY = "org.apache.spark.timeZone"
+
+  /**
    * Parquet/Avro file metadata key to indicate that the file was written with 
legacy datetime
    * values.
    */
-  private[sql] val SPARK_LEGACY_DATETIME = "org.apache.spark.legacyDateTime"
+  private[sql] val SPARK_LEGACY_DATETIME_METADATA_KEY = 
"org.apache.spark.legacyDateTime"
 
   /**
    * Parquet file metadata key to indicate that the file with INT96 column 
type was written
    * with rebasing.
    */
-  private[sql] val SPARK_LEGACY_INT96 = "org.apache.spark.legacyINT96"
+  private[sql] val SPARK_LEGACY_INT96_METADATA_KEY = 
"org.apache.spark.legacyINT96"
 }
diff --git 
a/sql/core/src/test/resources/test-data/before_1582_date_v3_2_0.snappy.parquet 
b/sql/core/src/test/resources/test-data/before_1582_date_v3_2_0.snappy.parquet
new file mode 100644
index 0000000..25df53e
Binary files /dev/null and 
b/sql/core/src/test/resources/test-data/before_1582_date_v3_2_0.snappy.parquet 
differ
diff --git 
a/sql/core/src/test/resources/test-data/before_1582_timestamp_int96_dict_v3_2_0.snappy.parquet
 
b/sql/core/src/test/resources/test-data/before_1582_timestamp_int96_dict_v3_2_0.snappy.parquet
new file mode 100644
index 0000000..78120fd
Binary files /dev/null and 
b/sql/core/src/test/resources/test-data/before_1582_timestamp_int96_dict_v3_2_0.snappy.parquet
 differ
diff --git 
a/sql/core/src/test/resources/test-data/before_1582_timestamp_int96_plain_v3_2_0.snappy.parquet
 
b/sql/core/src/test/resources/test-data/before_1582_timestamp_int96_plain_v3_2_0.snappy.parquet
new file mode 100644
index 0000000..3f5f85e
Binary files /dev/null and 
b/sql/core/src/test/resources/test-data/before_1582_timestamp_int96_plain_v3_2_0.snappy.parquet
 differ
diff --git 
a/sql/core/src/test/resources/test-data/before_1582_timestamp_micros_v3_2_0.snappy.parquet
 
b/sql/core/src/test/resources/test-data/before_1582_timestamp_micros_v3_2_0.snappy.parquet
new file mode 100644
index 0000000..809f6f5
Binary files /dev/null and 
b/sql/core/src/test/resources/test-data/before_1582_timestamp_micros_v3_2_0.snappy.parquet
 differ
diff --git 
a/sql/core/src/test/resources/test-data/before_1582_timestamp_millis_v3_2_0.snappy.parquet
 
b/sql/core/src/test/resources/test-data/before_1582_timestamp_millis_v3_2_0.snappy.parquet
new file mode 100644
index 0000000..f9cfa4a
Binary files /dev/null and 
b/sql/core/src/test/resources/test-data/before_1582_timestamp_millis_v3_2_0.snappy.parquet
 differ
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFilterSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFilterSuite.scala
index 04c0a68..9b554b6 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFilterSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFilterSuite.scala
@@ -39,6 +39,7 @@ import org.apache.spark.sql.catalyst.dsl.expressions._
 import org.apache.spark.sql.catalyst.expressions._
 import org.apache.spark.sql.catalyst.optimizer.InferFiltersFromConstraints
 import org.apache.spark.sql.catalyst.planning.PhysicalOperation
+import org.apache.spark.sql.catalyst.util.RebaseDateTime.RebaseSpec
 import 
org.apache.spark.sql.connector.catalog.CatalogV2Implicits.parseColumnPath
 import org.apache.spark.sql.execution.ExplainMode
 import org.apache.spark.sql.execution.datasources.{DataSourceStrategy, 
HadoopFsRelation, LogicalRelation, PushableColumnAndNestedColumn}
@@ -77,13 +78,13 @@ abstract class ParquetFilterSuite extends QueryTest with 
ParquetTest with Shared
   protected def createParquetFilters(
       schema: MessageType,
       caseSensitive: Option[Boolean] = None,
-      datetimeRebaseMode: LegacyBehaviorPolicy.Value = 
LegacyBehaviorPolicy.CORRECTED
+      datetimeRebaseSpec: RebaseSpec = 
RebaseSpec(LegacyBehaviorPolicy.CORRECTED)
     ): ParquetFilters =
     new ParquetFilters(schema, conf.parquetFilterPushDownDate, 
conf.parquetFilterPushDownTimestamp,
       conf.parquetFilterPushDownDecimal, 
conf.parquetFilterPushDownStringStartWith,
       conf.parquetFilterPushDownInFilterThreshold,
       caseSensitive.getOrElse(conf.caseSensitiveAnalysis),
-      datetimeRebaseMode)
+      datetimeRebaseSpec)
 
   override def beforeEach(): Unit = {
     super.beforeEach()
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetRebaseDatetimeSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetRebaseDatetimeSuite.scala
index e0c71cd..d9683fa 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetRebaseDatetimeSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetRebaseDatetimeSuite.scala
@@ -20,8 +20,8 @@ package org.apache.spark.sql.execution.datasources.parquet
 import java.nio.file.{Files, Paths, StandardCopyOption}
 import java.sql.{Date, Timestamp}
 
-import org.apache.spark.{SparkConf, SparkException, SparkUpgradeException}
-import org.apache.spark.sql.{QueryTest, Row, SPARK_LEGACY_DATETIME, 
SPARK_LEGACY_INT96}
+import org.apache.spark.{SPARK_VERSION_SHORT, SparkConf, SparkException, 
SparkUpgradeException}
+import org.apache.spark.sql.{QueryTest, Row, 
SPARK_LEGACY_DATETIME_METADATA_KEY, SPARK_LEGACY_INT96_METADATA_KEY, 
SPARK_TIMEZONE_METADATA_KEY}
 import org.apache.spark.sql.catalyst.util.DateTimeTestUtils
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.internal.SQLConf.{LegacyBehaviorPolicy, 
ParquetOutputTimestampType}
@@ -37,10 +37,10 @@ abstract class ParquetRebaseDatetimeSuite
   import testImplicits._
 
   // It generates input files for the test below:
-  // "SPARK-31159: compatibility with Spark 2.4 in reading dates/timestamps"
-  ignore("SPARK-31806: generate test files for checking compatibility with 
Spark 2.4") {
+  // "SPARK-31159, SPARK-37705: compatibility with Spark 2.4/3.2 in reading 
dates/timestamps"
+  ignore("SPARK-31806: generate test files for checking compatibility with 
Spark 2.4/3.2") {
     val resourceDir = "sql/core/src/test/resources/test-data"
-    val version = "2_4_5"
+    val version = SPARK_VERSION_SHORT.replaceAll("\\.", "_")
     val N = 8
     def save(
         in: Seq[(String, String)],
@@ -62,7 +62,10 @@ abstract class ParquetRebaseDatetimeSuite
       }
     }
     DateTimeTestUtils.withDefaultTimeZone(DateTimeTestUtils.LA) {
-      withSQLConf(SQLConf.SESSION_LOCAL_TIMEZONE.key -> 
DateTimeTestUtils.LA.getId) {
+      withSQLConf(
+        SQLConf.SESSION_LOCAL_TIMEZONE.key -> DateTimeTestUtils.LA.getId,
+        SQLConf.PARQUET_REBASE_MODE_IN_WRITE.key -> LEGACY.toString,
+        SQLConf.PARQUET_INT96_REBASE_MODE_IN_WRITE.key -> LEGACY.toString) {
         save(
           (1 to N).map(i => ("1001-01-01", s"1001-01-0$i")),
           "date",
@@ -119,7 +122,7 @@ abstract class ParquetRebaseDatetimeSuite
     }
   }
 
-  test("SPARK-31159: compatibility with Spark 2.4 in reading 
dates/timestamps") {
+  test("SPARK-31159, SPARK-37705: compatibility with Spark 2.4/3.2 in reading 
dates/timestamps") {
     val N = 8
     // test reading the existing 2.4 files and new 3.0 files (with rebase 
on/off) together.
     def checkReadMixedFiles[T](
@@ -133,25 +136,25 @@ abstract class ParquetRebaseDatetimeSuite
         inReadConf: String = SQLConf.PARQUET_REBASE_MODE_IN_READ.key): Unit = {
       withTempPaths(2) { paths =>
         paths.foreach(_.delete())
-        val path2_4 = getResourceParquetFilePath("test-data/" + fileName)
-        val path3_0 = paths(0).getCanonicalPath
-        val path3_0_rebase = paths(1).getCanonicalPath
+        val oldPath = getResourceParquetFilePath("test-data/" + fileName)
+        val path3_x = paths(0).getCanonicalPath
+        val path3_x_rebase = paths(1).getCanonicalPath
         val df = Seq.tabulate(N)(rowFunc).toDF("dict", "plain")
           .select($"dict".cast(catalystType), $"plain".cast(catalystType))
         withSQLConf(SQLConf.PARQUET_OUTPUT_TIMESTAMP_TYPE.key -> tsOutputType) 
{
-          checkDefaultLegacyRead(path2_4)
+          checkDefaultLegacyRead(oldPath)
           withSQLConf(inWriteConf -> CORRECTED.toString) {
-            df.write.mode("overwrite").parquet(path3_0)
+            df.write.mode("overwrite").parquet(path3_x)
           }
           withSQLConf(inWriteConf -> LEGACY.toString) {
-            df.write.parquet(path3_0_rebase)
+            df.write.parquet(path3_x_rebase)
           }
         }
         // For Parquet files written by Spark 3.0, we know the writer info and 
don't need the
         // config to guide the rebase behavior.
         runInMode(inReadConf, Seq(LEGACY)) { options =>
           checkAnswer(
-            spark.read.format("parquet").options(options).load(path2_4, 
path3_0, path3_0_rebase),
+            spark.read.format("parquet").options(options).load(oldPath, 
path3_x, path3_x_rebase),
             (0 until N).flatMap { i =>
               val (dictS, plainS) = rowFunc(i)
               Seq.tabulate(3) { _ =>
@@ -170,7 +173,8 @@ abstract class ParquetRebaseDatetimeSuite
       // By default we should fail to read ancient datetime values when 
parquet files don't
       // contain Spark version.
       "2_4_5" -> failInRead _,
-      "2_4_6" -> successInRead _).foreach { case (version, checkDefaultRead) =>
+      "2_4_6" -> successInRead _,
+      "3_2_0" -> successInRead _).foreach { case (version, checkDefaultRead) =>
       withAllParquetReaders {
         checkReadMixedFiles(
           s"before_1582_date_v$version.snappy.parquet",
@@ -212,7 +216,7 @@ abstract class ParquetRebaseDatetimeSuite
     }
   }
 
-  test("SPARK-31159: rebasing timestamps in write") {
+  test("SPARK-31159, SPARK-37705: rebasing timestamps in write") {
     val N = 8
     Seq(false, true).foreach { dictionaryEncoding =>
       Seq(
@@ -236,34 +240,39 @@ abstract class ParquetRebaseDatetimeSuite
           SQLConf.PARQUET_INT96_REBASE_MODE_IN_READ.key
         )
       ).foreach { case (outType, tsStr, nonRebased, inWriteConf, inReadConf) =>
-        withClue(s"output type $outType") {
-          withSQLConf(SQLConf.PARQUET_OUTPUT_TIMESTAMP_TYPE.key -> outType) {
-            withTempPath { dir =>
-              val path = dir.getAbsolutePath
-              withSQLConf(inWriteConf -> LEGACY.toString) {
-                Seq.tabulate(N)(_ => tsStr).toDF("tsS")
-                  .select($"tsS".cast("timestamp").as("ts"))
-                  .repartition(1)
-                  .write
-                  .option("parquet.enable.dictionary", dictionaryEncoding)
-                  .parquet(path)
-              }
+        // Ignore the default JVM time zone and use the session time zone 
instead of it in rebasing.
+        DateTimeTestUtils.withDefaultTimeZone(DateTimeTestUtils.JST) {
+          withSQLConf(SQLConf.SESSION_LOCAL_TIMEZONE.key -> 
DateTimeTestUtils.LA.getId) {
+            withClue(s"output type $outType") {
+              withSQLConf(SQLConf.PARQUET_OUTPUT_TIMESTAMP_TYPE.key -> 
outType) {
+                withTempPath { dir =>
+                  val path = dir.getAbsolutePath
+                  withSQLConf(inWriteConf -> LEGACY.toString) {
+                    Seq.tabulate(N)(_ => tsStr).toDF("tsS")
+                      .select($"tsS".cast("timestamp").as("ts"))
+                      .repartition(1)
+                      .write
+                      .option("parquet.enable.dictionary", dictionaryEncoding)
+                      .parquet(path)
+                  }
 
-              withAllParquetReaders {
-                // The file metadata indicates if it needs rebase or not, so 
we can always get the
-                // correct result regardless of the "rebase mode" config.
-                runInMode(inReadConf, Seq(LEGACY, CORRECTED, EXCEPTION)) { 
options =>
-                  checkAnswer(
-                    spark.read.options(options).parquet(path),
-                    Seq.tabulate(N)(_ => Row(Timestamp.valueOf(tsStr))))
-                }
+                  withAllParquetReaders {
+                    // The file metadata indicates if it needs rebase or not, 
so we can always get
+                    // the correct result regardless of the "rebase mode" 
config.
+                    runInMode(inReadConf, Seq(LEGACY, CORRECTED, EXCEPTION)) { 
options =>
+                      checkAnswer(
+                        
spark.read.options(options).parquet(path).select($"ts".cast("string")),
+                        Seq.tabulate(N)(_ => Row(tsStr)))
+                    }
 
-                // Force to not rebase to prove the written datetime values 
are rebased
-                // and we will get wrong result if we don't rebase while 
reading.
-                withSQLConf("spark.test.forceNoRebase" -> "true") {
-                  checkAnswer(
-                    spark.read.parquet(path),
-                    Seq.tabulate(N)(_ => Row(Timestamp.valueOf(nonRebased))))
+                    // Force to not rebase to prove the written datetime 
values are rebased
+                    // and we will get wrong result if we don't rebase while 
reading.
+                    withSQLConf("spark.test.forceNoRebase" -> "true") {
+                      checkAnswer(
+                        spark.read.parquet(path).select($"ts".cast("string")),
+                        Seq.tabulate(N)(_ => Row(nonRebased)))
+                    }
+                  }
                 }
               }
             }
@@ -310,7 +319,8 @@ abstract class ParquetRebaseDatetimeSuite
     }
   }
 
-  test("SPARK-33163: write the metadata key 
'org.apache.spark.legacyDateTime'") {
+  test("SPARK-33163, SPARK-37705: write the metadata keys 
'org.apache.spark.legacyDateTime' " +
+    "and 'org.apache.spark.timeZone'") {
     def checkMetadataKey(dir: java.io.File, exists: Boolean): Unit = {
       Seq("timestamp '1000-01-01 01:02:03'", "date '1000-01-01'").foreach { dt 
=>
         withSQLConf(SQLConf.PARQUET_OUTPUT_TIMESTAMP_TYPE.key ->
@@ -322,7 +332,9 @@ abstract class ParquetRebaseDatetimeSuite
             .parquet(dir.getAbsolutePath)
           val metaData = getMetaData(dir)
           val expected = if (exists) Some("") else None
-          assert(metaData.get(SPARK_LEGACY_DATETIME) === expected)
+          assert(metaData.get(SPARK_LEGACY_DATETIME_METADATA_KEY) === expected)
+          val expectedTz = if (exists) Some(SQLConf.get.sessionLocalTimeZone) 
else None
+          assert(metaData.get(SPARK_TIMEZONE_METADATA_KEY) === expectedTz)
         }
       }
     }
@@ -341,7 +353,8 @@ abstract class ParquetRebaseDatetimeSuite
     }
   }
 
-  test("SPARK-33160: write the metadata key 'org.apache.spark.legacyINT96'") {
+  test("SPARK-33160, SPARK-37705: write the metadata key 
'org.apache.spark.legacyINT96' " +
+    "and 'org.apache.spark.timeZone'") {
     def saveTs(dir: java.io.File, ts: String = "1000-01-01 01:02:03"): Unit = {
       Seq(Timestamp.valueOf(ts)).toDF()
         .repartition(1)
@@ -351,13 +364,15 @@ abstract class ParquetRebaseDatetimeSuite
     withSQLConf(SQLConf.PARQUET_INT96_REBASE_MODE_IN_WRITE.key -> 
LEGACY.toString) {
       withTempPath { dir =>
         saveTs(dir)
-        assert(getMetaData(dir)(SPARK_LEGACY_INT96) === "")
+        assert(getMetaData(dir)(SPARK_LEGACY_INT96_METADATA_KEY) === "")
+        assert(getMetaData(dir)(SPARK_TIMEZONE_METADATA_KEY) === 
SQLConf.get.sessionLocalTimeZone)
       }
     }
     withSQLConf(SQLConf.PARQUET_INT96_REBASE_MODE_IN_WRITE.key -> 
CORRECTED.toString) {
       withTempPath { dir =>
         saveTs(dir)
-        assert(getMetaData(dir).get(SPARK_LEGACY_INT96).isEmpty)
+        assert(getMetaData(dir).get(SPARK_LEGACY_INT96_METADATA_KEY).isEmpty)
+        assert(getMetaData(dir).get(SPARK_TIMEZONE_METADATA_KEY).isEmpty)
       }
     }
     withSQLConf(SQLConf.PARQUET_INT96_REBASE_MODE_IN_WRITE.key -> 
EXCEPTION.toString) {
@@ -366,7 +381,8 @@ abstract class ParquetRebaseDatetimeSuite
     withSQLConf(SQLConf.PARQUET_INT96_REBASE_MODE_IN_WRITE.key -> 
EXCEPTION.toString) {
       withTempPath { dir =>
         saveTs(dir, "2020-10-22 01:02:03")
-        assert(getMetaData(dir).get(SPARK_LEGACY_INT96).isEmpty)
+        assert(getMetaData(dir).get(SPARK_LEGACY_INT96_METADATA_KEY).isEmpty)
+        assert(getMetaData(dir).get(SPARK_TIMEZONE_METADATA_KEY).isEmpty)
       }
     }
   }
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetVectorizedSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetVectorizedSuite.scala
index e03eda2..36a52b6 100644
--- 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetVectorizedSuite.scala
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetVectorizedSuite.scala
@@ -293,7 +293,13 @@ class ParquetVectorizedSuite extends QueryTest with 
ParquetTest with SharedSpark
     import collection.JavaConverters._
 
     val recordReader = new VectorizedParquetRecordReader(
-      DateTimeUtils.getZoneId("EST"), "CORRECTED", "CORRECTED", true, 
batchSize)
+      DateTimeUtils.getZoneId("EST"),
+      "CORRECTED",
+      "UTC",
+      "CORRECTED",
+      "UTC",
+      true,
+      batchSize)
     recordReader.initialize(fileSchema, fileSchema,
       TestParquetRowGroupReader(Seq(readStore)), totalRowCount)
 

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

Reply via email to