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

omalley pushed a commit to branch branch-1.6
in repository https://gitbox.apache.org/repos/asf/orc.git


The following commit(s) were added to refs/heads/branch-1.6 by this push:
     new a1671f1  ORC-27: Add support for proleptic Gregorian calendar for 
better support of dates before 1600AD.
a1671f1 is described below

commit a1671f1e8abf748178c07e2a03b03f00268c8863
Author: Owen O'Malley <[email protected]>
AuthorDate: Tue Nov 26 09:57:16 2019 -0800

    ORC-27: Add support for proleptic Gregorian calendar for better support of
    dates before 1600AD.
    
    Fixes #455
    
    Signed-off-by: Owen O'Malley <[email protected]>
---
 java/core/src/java/org/apache/orc/OrcConf.java     |  11 +-
 java/core/src/java/org/apache/orc/OrcFile.java     |  40 ++++-
 java/core/src/java/org/apache/orc/Reader.java      |   5 +
 .../src/java/org/apache/orc/StripeStatistics.java  |  15 +-
 .../org/apache/orc/impl/ColumnStatisticsImpl.java  |  61 +++++--
 .../src/java/org/apache/orc/impl/DateUtils.java    | 182 ++++++++++++++++++++
 .../src/java/org/apache/orc/impl/ReaderImpl.java   |  31 ++--
 .../java/org/apache/orc/impl/RecordReaderImpl.java |  15 +-
 .../org/apache/orc/impl/StripeStatisticsImpl.java  |   9 +-
 .../org/apache/orc/impl/TreeReaderFactory.java     |  48 ++++++
 .../src/java/org/apache/orc/impl/TypeUtils.java    |   4 +-
 .../src/java/org/apache/orc/impl/WriterImpl.java   |  34 +++-
 .../orc/impl/reader/ReaderEncryptionVariant.java   |   6 +-
 .../org/apache/orc/impl/writer/DateTreeWriter.java |   9 +
 .../orc/impl/writer/TimestampTreeWriter.java       |   3 +
 .../org/apache/orc/impl/writer/WriterContext.java  |   5 +
 .../org/apache/orc/TestProlepticConversions.java   | 190 +++++++++++++++++++++
 .../test/org/apache/orc/TestStringDictionary.java  |   5 +
 .../org/apache/orc/impl/TestRecordReaderImpl.java  |   7 +-
 java/pom.xml                                       |   2 +-
 .../src/java/org/apache/orc/tools/FileDump.java    |  12 +-
 .../java/org/apache/orc/tools/JsonFileDump.java    |  21 ++-
 .../test/resources/orc-file-dump-bloomfilter.out   |   1 +
 .../test/resources/orc-file-dump-bloomfilter2.out  |   1 +
 .../orc-file-dump-dictionary-threshold.out         |   1 +
 java/tools/src/test/resources/orc-file-dump.json   |   1 +
 java/tools/src/test/resources/orc-file-dump.out    |   1 +
 .../tools/src/test/resources/orc-file-has-null.out |   1 +
 proto/orc_proto.proto                              |  10 ++
 29 files changed, 670 insertions(+), 61 deletions(-)

diff --git a/java/core/src/java/org/apache/orc/OrcConf.java 
b/java/core/src/java/org/apache/orc/OrcConf.java
index 7cca1db..bf83431 100644
--- a/java/core/src/java/org/apache/orc/OrcConf.java
+++ b/java/core/src/java/org/apache/orc/OrcConf.java
@@ -165,7 +165,16 @@ public enum OrcConf {
     Integer.MAX_VALUE - 1024, "When reading stripes >2GB, specify max limit 
for the chunk size."),
   ENCRYPTION("orc.encrypt", "orc.encrypt", null, "The list of keys and columns 
to encrypt with"),
   DATA_MASK("orc.mask", "orc.mask", null, "The masks to apply to the encrypted 
columns"),
-  KEY_PROVIDER("orc.key.provider", "orc.key.provider", "hadoop", "The kind of 
KeyProvider to use for encryption.")
+  KEY_PROVIDER("orc.key.provider", "orc.key.provider", "hadoop", "The kind of 
KeyProvider to use for encryption."),
+  PROLEPTIC_GREGORIAN("orc.proleptic.gregorian", "orc.proleptic.gregorian", 
false,
+      "Should we read and write dates & times using the proleptic Gregorian 
calendar\n" +
+          "instead of the hybrid Julian Gregorian? Hive before 3.1 and Spark 
before 3.0\n" +
+          "used hybrid."),
+  PROLEPTIC_GREGORIAN_DEFAULT("orc.proleptic.gregorian.default",
+      "orc.proleptic.gregorian.default", false,
+      "This value controls whether pre-ORC 27 files are using the hybrid or 
proleptic\n" +
+      "calendar. Only Hive 3.1 and the C++ library wrote using the proleptic, 
so hybrid\n" +
+      "is the default.")
   ;
 
   private final String attribute;
diff --git a/java/core/src/java/org/apache/orc/OrcFile.java 
b/java/core/src/java/org/apache/orc/OrcFile.java
index e1dced0..45249ef 100644
--- a/java/core/src/java/org/apache/orc/OrcFile.java
+++ b/java/core/src/java/org/apache/orc/OrcFile.java
@@ -38,7 +38,6 @@ import org.apache.orc.impl.OrcTail;
 import org.apache.orc.impl.ReaderImpl;
 import org.apache.orc.impl.WriterImpl;
 import org.apache.orc.impl.WriterInternal;
-import org.apache.orc.impl.reader.ReaderEncryptionVariant;
 import org.apache.orc.impl.writer.WriterImplV2;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -283,9 +282,11 @@ public class OrcFile {
     // For now keeping this around to avoid complex surgery
     private FileMetadata fileMetadata;
     private boolean useUTCTimestamp;
+    private boolean useProlepticGregorian;
 
     public ReaderOptions(Configuration conf) {
       this.conf = conf;
+      this.useProlepticGregorian = 
OrcConf.PROLEPTIC_GREGORIAN.getBoolean(conf);
     }
 
     public ReaderOptions filesystem(FileSystem fs) {
@@ -313,6 +314,18 @@ public class OrcFile {
       return this;
     }
 
+    /**
+     * Should the reader convert dates and times to the proleptic Gregorian
+     * calendar?
+     * @param newValue should it use the proleptic Gregorian calendar?
+     * @return this
+     */
+    public ReaderOptions convertToProlepticGregorian(boolean newValue) {
+      this.useProlepticGregorian = newValue;
+      return this;
+    }
+
+
     public Configuration getConfiguration() {
       return conf;
     }
@@ -354,6 +367,9 @@ public class OrcFile {
       return useUTCTimestamp;
     }
 
+    public boolean getConvertToProlepticGregorian() {
+      return useProlepticGregorian;
+    }
   }
 
   public static ReaderOptions readerOptions(Configuration conf) {
@@ -434,6 +450,7 @@ public class OrcFile {
     private String encryption;
     private String masks;
     private KeyProvider provider;
+    private boolean useProlepticGregorian;
     private Map<String, HadoopShims.KeyMetadata> keyOverrides = new 
HashMap<>();
 
     protected WriterOptions(Properties tableProperties, Configuration conf) {
@@ -479,6 +496,7 @@ public class OrcFile {
           
OrcConf.WRITE_VARIABLE_LENGTH_BLOCKS.getBoolean(tableProperties,conf);
       directEncodingColumns = OrcConf.DIRECT_ENCODING_COLUMNS.getString(
           tableProperties, conf);
+      useProlepticGregorian = OrcConf.PROLEPTIC_GREGORIAN.getBoolean(conf);
     }
 
     /**
@@ -800,6 +818,17 @@ public class OrcFile {
       return this;
     }
 
+    /**
+     * Should the writer use the proleptic Gregorian calendar for
+     * times and dates.
+     * @param newValue true if we should use the proleptic calendar
+     * @return this
+     */
+    public WriterOptions setProlepticGregorian(boolean newValue) {
+      this.useProlepticGregorian = newValue;
+      return this;
+    }
+
     public KeyProvider getKeyProvider() {
       return provider;
     }
@@ -919,6 +948,10 @@ public class OrcFile {
     public Map<String, HadoopShims.KeyMetadata> getKeyOverrides() {
       return keyOverrides;
     }
+
+    public boolean getProlepticGregorian() {
+      return useProlepticGregorian;
+    }
   }
 
   /**
@@ -1129,6 +1162,11 @@ public class OrcFile {
       LOG.info("Can't merge {} because it has different encryption variants", 
path);
       return false;
     }
+    if (firstReader.writerUsedProlepticGregorian() !=
+            reader.writerUsedProlepticGregorian()) {
+      LOG.info("Can't merge {} because it uses a different calendar", path);
+      return false;
+    }
     return true;
   }
 
diff --git a/java/core/src/java/org/apache/orc/Reader.java 
b/java/core/src/java/org/apache/orc/Reader.java
index 4fbffdb..9be6f90 100644
--- a/java/core/src/java/org/apache/orc/Reader.java
+++ b/java/core/src/java/org/apache/orc/Reader.java
@@ -495,4 +495,9 @@ public interface Reader extends Closeable {
    * @return Serialized file metadata read from disk for the purposes of 
caching, etc.
    */
   ByteBuffer getSerializedFileFooter();
+
+  /**
+   * Was the file written using the proleptic Gregorian calendar.
+   */
+  boolean writerUsedProlepticGregorian();
 }
diff --git a/java/core/src/java/org/apache/orc/StripeStatistics.java 
b/java/core/src/java/org/apache/orc/StripeStatistics.java
index f2f26ad..d02bc33 100644
--- a/java/core/src/java/org/apache/orc/StripeStatistics.java
+++ b/java/core/src/java/org/apache/orc/StripeStatistics.java
@@ -19,6 +19,7 @@
 package org.apache.orc;
 
 import org.apache.orc.impl.ColumnStatisticsImpl;
+import org.apache.orc.impl.ReaderImpl;
 
 import java.util.List;
 
@@ -28,15 +29,19 @@ import java.util.List;
 public class StripeStatistics {
   protected final List<OrcProto.ColumnStatistics> cs;
   protected final TypeDescription schema;
+  private final ReaderImpl reader;
 
-  public StripeStatistics(List<OrcProto.ColumnStatistics> list) {
-    this(null, list);
+  public StripeStatistics(List<OrcProto.ColumnStatistics> list,
+                          ReaderImpl reader) {
+    this(null, list, reader);
   }
 
   public StripeStatistics(TypeDescription schema,
-                          List<OrcProto.ColumnStatistics> list) {
+                          List<OrcProto.ColumnStatistics> list,
+                          ReaderImpl reader) {
     this.schema = schema;
     this.cs = list;
+    this.reader = reader;
   }
 
   private int getBase() {
@@ -53,7 +58,9 @@ public class StripeStatistics {
     int base = getBase();
     for (int c = 0; c < result.length; ++c) {
       TypeDescription column = schema == null ? null : schema.findSubtype(base 
+ c);
-      result[c] = ColumnStatisticsImpl.deserialize(column, cs.get(c));
+      result[c] = reader == null
+                      ? ColumnStatisticsImpl.deserialize(column, cs.get(c))
+                      : ColumnStatisticsImpl.deserialize(column, cs.get(c), 
reader);
     }
     return result;
   }
diff --git a/java/core/src/java/org/apache/orc/impl/ColumnStatisticsImpl.java 
b/java/core/src/java/org/apache/orc/impl/ColumnStatisticsImpl.java
index b73cafe..ad58bdd 100644
--- a/java/core/src/java/org/apache/orc/impl/ColumnStatisticsImpl.java
+++ b/java/core/src/java/org/apache/orc/impl/ColumnStatisticsImpl.java
@@ -1479,15 +1479,19 @@ public class ColumnStatisticsImpl implements 
ColumnStatistics {
     DateStatisticsImpl() {
     }
 
-    DateStatisticsImpl(OrcProto.ColumnStatistics stats) {
+    DateStatisticsImpl(OrcProto.ColumnStatistics stats,
+                       boolean writerUsedProlepticGregorian,
+                       boolean convertToProlepticGregorian) {
       super(stats);
       OrcProto.DateStatistics dateStats = stats.getDateStatistics();
       // min,max values serialized/deserialized as int (days since epoch)
       if (dateStats.hasMaximum()) {
-        maximum = dateStats.getMaximum();
+        maximum = DateUtils.convertDate(dateStats.getMaximum(),
+            writerUsedProlepticGregorian, convertToProlepticGregorian);
       }
       if (dateStats.hasMinimum()) {
-        minimum = dateStats.getMinimum();
+        minimum = DateUtils.convertDate(dateStats.getMinimum(),
+            writerUsedProlepticGregorian, convertToProlepticGregorian);
       }
     }
 
@@ -1640,23 +1644,31 @@ public class ColumnStatisticsImpl implements 
ColumnStatistics {
     TimestampStatisticsImpl() {
     }
 
-    TimestampStatisticsImpl(OrcProto.ColumnStatistics stats) {
+    TimestampStatisticsImpl(OrcProto.ColumnStatistics stats,
+                            boolean writerUsedProlepticGregorian,
+                            boolean convertToProlepticGregorian) {
       super(stats);
       OrcProto.TimestampStatistics timestampStats = 
stats.getTimestampStatistics();
       // min,max values serialized/deserialized as int (milliseconds since 
epoch)
       if (timestampStats.hasMaximum()) {
-        maximum = SerializationUtils.convertToUtc(TimeZone.getDefault(),
-            timestampStats.getMaximum());
+        maximum = DateUtils.convertTime(
+            SerializationUtils.convertToUtc(TimeZone.getDefault(),
+               timestampStats.getMaximum()),
+            writerUsedProlepticGregorian, convertToProlepticGregorian);
       }
       if (timestampStats.hasMinimum()) {
-        minimum = SerializationUtils.convertToUtc(TimeZone.getDefault(),
-            timestampStats.getMinimum());
+        minimum = DateUtils.convertTime(
+            SerializationUtils.convertToUtc(TimeZone.getDefault(),
+                timestampStats.getMinimum()),
+            writerUsedProlepticGregorian, convertToProlepticGregorian);
       }
       if (timestampStats.hasMaximumUtc()) {
-        maximum = timestampStats.getMaximumUtc();
+        maximum = DateUtils.convertTime(timestampStats.getMaximumUtc(),
+            writerUsedProlepticGregorian, convertToProlepticGregorian);
       }
       if (timestampStats.hasMinimumUtc()) {
-        minimum = timestampStats.getMinimumUtc();
+        minimum = DateUtils.convertTime(timestampStats.getMinimumUtc(),
+            writerUsedProlepticGregorian, convertToProlepticGregorian);
       }
     }
 
@@ -1795,8 +1807,10 @@ public class ColumnStatisticsImpl implements 
ColumnStatistics {
     TimestampInstantStatisticsImpl() {
     }
 
-    TimestampInstantStatisticsImpl(OrcProto.ColumnStatistics stats) {
-      super(stats);
+    TimestampInstantStatisticsImpl(OrcProto.ColumnStatistics stats,
+                                   boolean writerUsedProlepticGregorian,
+                                   boolean convertToProlepticGregorian) {
+      super(stats, writerUsedProlepticGregorian, convertToProlepticGregorian);
     }
 
     @Override
@@ -2008,6 +2022,20 @@ public class ColumnStatisticsImpl implements 
ColumnStatistics {
 
   public static ColumnStatisticsImpl deserialize(TypeDescription schema,
                                                  OrcProto.ColumnStatistics 
stats) {
+    return deserialize(schema, stats, false, false);
+  }
+
+  public static ColumnStatisticsImpl deserialize(TypeDescription schema,
+                                                 OrcProto.ColumnStatistics 
stats,
+                                                 ReaderImpl reader) {
+    return deserialize(schema, stats, reader.writerUsedProlepticGregorian(),
+        reader.options.getConvertToProlepticGregorian());
+  }
+
+  public static ColumnStatisticsImpl deserialize(TypeDescription schema,
+                                                 OrcProto.ColumnStatistics 
stats,
+                                                 boolean 
writerUsedProlepticGregorian,
+                                                 boolean 
convertToProlepticGregorian) {
     if (stats.hasBucketStatistics()) {
       return new BooleanStatisticsImpl(stats);
     } else if (stats.hasIntStatistics()) {
@@ -2026,12 +2054,15 @@ public class ColumnStatisticsImpl implements 
ColumnStatistics {
         return new DecimalStatisticsImpl(stats);
       }
     } else if (stats.hasDateStatistics()) {
-      return new DateStatisticsImpl(stats);
+      return new DateStatisticsImpl(stats, writerUsedProlepticGregorian,
+          convertToProlepticGregorian);
     } else if (stats.hasTimestampStatistics()) {
       return schema == null ||
                  schema.getCategory() == TypeDescription.Category.TIMESTAMP ?
-                 new TimestampStatisticsImpl(stats) :
-                 new TimestampInstantStatisticsImpl(stats);
+                 new TimestampStatisticsImpl(stats,
+                     writerUsedProlepticGregorian, 
convertToProlepticGregorian) :
+                 new TimestampInstantStatisticsImpl(stats,
+                     writerUsedProlepticGregorian, 
convertToProlepticGregorian);
     } else if(stats.hasBinaryStatistics()) {
       return new BinaryStatisticsImpl(stats);
     } else {
diff --git a/java/core/src/java/org/apache/orc/impl/DateUtils.java 
b/java/core/src/java/org/apache/orc/impl/DateUtils.java
new file mode 100644
index 0000000..8ac574c
--- /dev/null
+++ b/java/core/src/java/org/apache/orc/impl/DateUtils.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.orc.impl;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Conversion utilities from the hybrid Julian/Gregorian calendar to/from the
+ * proleptic Gregorian.
+ *
+ * The semantics here are to hold the string representation constant and change
+ * the epoch offset rather than holding the instant in time constant and change
+ * the string representation.
+ *
+ * These utilities will be fast for the common case (> 1582 AD), but slow for
+ * old dates.
+ */
+public class DateUtils {
+  private static SimpleDateFormat createFormatter(String fmt,
+                                                 GregorianCalendar calendar) {
+    SimpleDateFormat result = new SimpleDateFormat(fmt);
+    result.setCalendar(calendar);
+    return result;
+  }
+
+  private static final String DATE = "yyyy-MM-dd";
+  private static final String TIME = DATE + " HH:mm:ss";
+  private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
+  private static final GregorianCalendar HYBRID = new GregorianCalendar();
+  private static final ThreadLocal<SimpleDateFormat> HYBRID_DATE_FORMAT =
+      ThreadLocal.withInitial(() -> createFormatter(DATE, HYBRID));
+  private static final ThreadLocal<SimpleDateFormat> HYBRID_TIME_FORMAT =
+      ThreadLocal.withInitial(() -> createFormatter(TIME, HYBRID));
+  private static final long SWITCHOVER_MILLIS;
+  private static final long SWITCHOVER_DAYS;
+  private static final GregorianCalendar PROLEPTIC = new GregorianCalendar();
+  private static final ThreadLocal<SimpleDateFormat> PROLEPTIC_DATE_FORMAT =
+      ThreadLocal.withInitial(() -> createFormatter(DATE, PROLEPTIC));
+  private static final ThreadLocal<SimpleDateFormat> PROLEPTIC_TIME_FORMAT =
+      ThreadLocal.withInitial(() -> createFormatter(TIME, PROLEPTIC));
+
+  static {
+    HYBRID.setTimeZone(UTC);
+    PROLEPTIC.setTimeZone(UTC);
+    PROLEPTIC.setGregorianChange(new Date(Long.MIN_VALUE));
+
+    // Get the last day where the two calendars agree with each other.
+    try {
+      SWITCHOVER_MILLIS = 
HYBRID_DATE_FORMAT.get().parse("1582-10-15").getTime();
+      SWITCHOVER_DAYS = TimeUnit.MILLISECONDS.toDays(SWITCHOVER_MILLIS);
+    } catch (ParseException e) {
+      throw new IllegalArgumentException("Can't parse switch over date", e);
+    }
+  }
+
+  /**
+   * Convert an epoch day from the hybrid Julian/Gregorian calendar to the
+   * proleptic Gregorian.
+   * @param hybrid day of epoch in the hybrid Julian/Gregorian
+   * @return day of epoch in the proleptic Gregorian
+   */
+  public static int convertDateToProleptic(int hybrid) {
+    int proleptic = hybrid;
+    if (hybrid < SWITCHOVER_DAYS) {
+      String dateStr = HYBRID_DATE_FORMAT.get().format(
+          new Date(TimeUnit.DAYS.toMillis(hybrid)));
+      try {
+        proleptic = (int) TimeUnit.MILLISECONDS.toDays(
+            PROLEPTIC_DATE_FORMAT.get().parse(dateStr).getTime());
+      } catch (ParseException e) {
+        throw new IllegalArgumentException("Can't parse " + dateStr, e);
+      }
+    }
+    return proleptic;
+  }
+
+  /**
+   * Convert an epoch day from the proleptic Gregorian calendar to the hybrid
+   * Julian/Gregorian.
+   * @param proleptic day of epoch in the proleptic Gregorian
+   * @return day of epoch in the hybrid Julian/Gregorian
+   */
+  public static int convertDateToHybrid(int proleptic) {
+    int hyrbid = proleptic;
+    if (proleptic < SWITCHOVER_DAYS) {
+      String dateStr = PROLEPTIC_DATE_FORMAT.get().format(
+          new Date(TimeUnit.DAYS.toMillis(proleptic)));
+      try {
+        hyrbid = (int) TimeUnit.MILLISECONDS.toDays(
+            HYBRID_DATE_FORMAT.get().parse(dateStr).getTime());
+      } catch (ParseException e) {
+        throw new IllegalArgumentException("Can't parse " + dateStr, e);
+      }
+    }
+    return hyrbid;
+  }
+
+  public static int convertDate(int original,
+                                boolean fromProleptic,
+                                boolean toProleptic) {
+    if (fromProleptic != toProleptic) {
+      return toProleptic
+                 ? convertDateToProleptic(original)
+                 : convertDateToHybrid(original);
+    } else {
+      return original;
+    }
+  }
+
+  public static long convertTime(long original,
+                                 boolean fromProleptic,
+                                 boolean toProleptic) {
+    if (fromProleptic != toProleptic) {
+      return toProleptic
+                 ? convertTimeToProleptic(original)
+                 : convertTimeToHybrid(original);
+    } else {
+      return original;
+    }
+  }
+  /**
+   * Convert epoch millis from the hybrid Julian/Gregorian calendar to the
+   * proleptic Gregorian.
+   * @param hybrid millis of epoch in the hybrid Julian/Gregorian
+   * @return millis of epoch in the proleptic Gregorian
+   */
+  public static long convertTimeToProleptic(long hybrid) {
+    long proleptic = hybrid;
+    if (hybrid < SWITCHOVER_MILLIS) {
+      String dateStr = HYBRID_TIME_FORMAT.get().format(new Date(hybrid));
+      try {
+        proleptic = PROLEPTIC_TIME_FORMAT.get().parse(dateStr).getTime();
+      } catch (ParseException e) {
+        throw new IllegalArgumentException("Can't parse " + dateStr, e);
+      }
+    }
+    return proleptic;
+  }
+
+  /**
+   * Convert epoch millis from the proleptic Gregorian calendar to the hybrid
+   * Julian/Gregorian.
+   * @param proleptic millis of epoch in the proleptic Gregorian
+   * @return millis of epoch in the hybrid Julian/Gregorian
+   */
+  public static long convertTimeToHybrid(long proleptic) {
+    long hybrid = proleptic;
+    if (proleptic < SWITCHOVER_MILLIS) {
+      String dateStr = PROLEPTIC_TIME_FORMAT.get().format(new Date(proleptic));
+      try {
+        hybrid = HYBRID_TIME_FORMAT.get().parse(dateStr).getTime();
+      } catch (ParseException e) {
+        throw new IllegalArgumentException("Can't parse " + dateStr, e);
+      }
+    }
+    return hybrid;
+  }
+
+  private DateUtils() {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/java/core/src/java/org/apache/orc/impl/ReaderImpl.java 
b/java/core/src/java/org/apache/orc/impl/ReaderImpl.java
index a4c73c7..3017a33 100644
--- a/java/core/src/java/org/apache/orc/impl/ReaderImpl.java
+++ b/java/core/src/java/org/apache/orc/impl/ReaderImpl.java
@@ -31,6 +31,7 @@ import org.apache.orc.CompressionKind;
 import org.apache.orc.DataMaskDescription;
 import org.apache.orc.EncryptionVariant;
 import org.apache.orc.FileMetadata;
+import org.apache.orc.OrcConf;
 import org.apache.orc.OrcFile;
 import org.apache.orc.OrcUtils;
 import org.apache.orc.Reader;
@@ -299,7 +300,8 @@ public class ReaderImpl implements Reader {
         if (codec != null) {
           compression.withCodec(codec).withBufferSize(bufferSize);
         }
-        return ((ReaderEncryptionVariant) variant).getStripeStatistics(null, 
compression);
+        return ((ReaderEncryptionVariant) variant).getStripeStatistics(null,
+            compression, this);
       }
     }
   }
@@ -347,10 +349,10 @@ public class ReaderImpl implements Reader {
     return result;
   }
 
-  private static ColumnStatistics[] decryptFileStats(ReaderEncryptionVariant 
encryption,
-                                                    InStream.StreamOptions 
compression,
-                                                    OrcProto.Footer footer
-                                                    ) throws IOException {
+  private ColumnStatistics[] decryptFileStats(ReaderEncryptionVariant 
encryption,
+                                              InStream.StreamOptions 
compression,
+                                              OrcProto.Footer footer
+                                              ) throws IOException {
     Key key = encryption.getFileFooterKey();
     if (key == null) {
       return null;
@@ -373,19 +375,20 @@ public class ReaderImpl implements Reader {
       TypeDescription root = encryption.getRoot();
       for(int i= 0; i < result.length; ++i){
         result[i] = 
ColumnStatisticsImpl.deserialize(root.findSubtype(root.getId() + i),
-            decrypted.getColumn(i));
+            decrypted.getColumn(i), this);
       }
       return result;
     }
   }
 
-  public static ColumnStatistics[] deserializeStats(
+  public ColumnStatistics[] deserializeStats(
       TypeDescription schema,
       List<OrcProto.ColumnStatistics> fileStats) {
     ColumnStatistics[] result = new ColumnStatistics[fileStats.size()];
     for(int i=0; i < result.length; ++i) {
       TypeDescription subschema = schema == null ? null : 
schema.findSubtype(i);
-      result[i] = ColumnStatisticsImpl.deserialize(subschema, 
fileStats.get(i));
+      result[i] = ColumnStatisticsImpl.deserialize(subschema, fileStats.get(i),
+          this);
     }
     return result;
   }
@@ -737,6 +740,14 @@ public class ReaderImpl implements Reader {
   }
 
   @Override
+  public boolean writerUsedProlepticGregorian() {
+    OrcProto.Footer footer = tail.getFooter();
+    return footer.hasCalendar()
+               ? footer.getCalendar() == 
OrcProto.CalendarKind.PROLEPTIC_GREGORIAN
+               : OrcConf.PROLEPTIC_GREGORIAN_DEFAULT.getBoolean(conf);
+  }
+
+  @Override
   public Options options() {
     return new Options(conf);
   }
@@ -894,7 +905,7 @@ public class ReaderImpl implements Reader {
       List<StripeStatistics> result = new ArrayList<>(list.size());
       for (OrcProto.StripeStatistics ss : stripeStatistics) {
         result.add(new StripeStatisticsImpl(schema,
-            new ArrayList<>(ss.getColStatsList())));
+            new ArrayList<>(ss.getColStatsList()), this));
       }
       return result;
     }
@@ -926,7 +937,7 @@ public class ReaderImpl implements Reader {
               if (variant != null) {
                 TypeDescription variantType = variant.getRoot();
                 List<StripeStatistics> colStats =
-                    variant.getStripeStatistics(included, options);
+                    variant.getStripeStatistics(included, options, this);
                 for(int sub = c; sub <= variantType.getMaximumId(); ++sub) {
                   if (included == null || included[sub]) {
                     for(int s = 0; s < colStats.size(); ++s) {
diff --git a/java/core/src/java/org/apache/orc/impl/RecordReaderImpl.java 
b/java/core/src/java/org/apache/orc/impl/RecordReaderImpl.java
index de55c85..f3b2079 100644
--- a/java/core/src/java/org/apache/orc/impl/RecordReaderImpl.java
+++ b/java/core/src/java/org/apache/orc/impl/RecordReaderImpl.java
@@ -32,7 +32,6 @@ import org.apache.orc.BooleanColumnStatistics;
 import org.apache.orc.CollectionColumnStatistics;
 import org.apache.orc.ColumnStatistics;
 import org.apache.orc.CompressionCodec;
-import org.apache.orc.CompressionKind;
 import org.apache.orc.DataReader;
 import org.apache.orc.DateColumnStatistics;
 import org.apache.orc.DecimalColumnStatistics;
@@ -166,7 +165,9 @@ public class RecordReaderImpl implements RecordReader {
           rowIndexStride,
           evolution,
           writerVersion,
-          fileReader.useUTCTimestamp);
+          fileReader.useUTCTimestamp,
+          fileReader.writerUsedProlepticGregorian(),
+          fileReader.options.getConvertToProlepticGregorian());
     } else {
       sargApp = null;
     }
@@ -223,6 +224,8 @@ public class RecordReaderImpl implements RecordReader {
           .skipCorrupt(skipCorrupt)
           .fileFormat(fileReader.getFileVersion())
           .useUTCTimestamp(fileReader.useUTCTimestamp)
+          .setProlepticGregorian(fileReader.writerUsedProlepticGregorian(),
+              fileReader.options.getConvertToProlepticGregorian())
           .setEncryption(encryption);
     reader = TreeReaderFactory.createTreeReader(evolution.getReaderSchema(),
         readerContext);
@@ -868,15 +871,21 @@ public class RecordReaderImpl implements RecordReader {
     private SchemaEvolution evolution;
     private final long[] exceptionCount;
     private final boolean useUTCTimestamp;
+    private final boolean writerUsedProlepticGregorian;
+    private final boolean convertToProlepticGregorian;
 
     public SargApplier(SearchArgument sarg,
                        long rowIndexStride,
                        SchemaEvolution evolution,
                        OrcFile.WriterVersion writerVersion,
-                       boolean useUTCTimestamp) {
+                       boolean useUTCTimestamp,
+                       boolean writerUsedProlepticGregorian,
+                       boolean convertToProlepticGregorian) {
       this.writerVersion = writerVersion;
       this.sarg = sarg;
       sargLeaves = sarg.getLeaves();
+      this.writerUsedProlepticGregorian = writerUsedProlepticGregorian;
+      this.convertToProlepticGregorian = convertToProlepticGregorian;
       filterColumns = mapSargColumnsToOrcInternalColIdx(sargLeaves,
                                                         evolution);
       this.rowIndexStride = rowIndexStride;
diff --git a/java/core/src/java/org/apache/orc/impl/StripeStatisticsImpl.java 
b/java/core/src/java/org/apache/orc/impl/StripeStatisticsImpl.java
index f4a83cd..50557fb 100644
--- a/java/core/src/java/org/apache/orc/impl/StripeStatisticsImpl.java
+++ b/java/core/src/java/org/apache/orc/impl/StripeStatisticsImpl.java
@@ -27,12 +27,13 @@ import java.util.List;
 
 public class StripeStatisticsImpl extends StripeStatistics  {
   public StripeStatisticsImpl(TypeDescription schema,
-                              List<OrcProto.ColumnStatistics> list) {
-    super(schema, list);
+                              List<OrcProto.ColumnStatistics> list,
+                              ReaderImpl reader) {
+    super(schema, list, reader);
   }
 
-  public StripeStatisticsImpl(TypeDescription schema) {
-    super(schema, createList(schema));
+  public StripeStatisticsImpl(TypeDescription schema, ReaderImpl reader) {
+    super(schema, createList(schema), reader);
   }
 
   /**
diff --git a/java/core/src/java/org/apache/orc/impl/TreeReaderFactory.java 
b/java/core/src/java/org/apache/orc/impl/TreeReaderFactory.java
index e2bf1f4..3f66eaf 100644
--- a/java/core/src/java/org/apache/orc/impl/TreeReaderFactory.java
+++ b/java/core/src/java/org/apache/orc/impl/TreeReaderFactory.java
@@ -30,6 +30,7 @@ import java.util.TimeZone;
 import org.apache.hadoop.hive.common.type.HiveDecimal;
 import org.apache.hadoop.hive.ql.exec.vector.BytesColumnVector;
 import org.apache.hadoop.hive.ql.exec.vector.ColumnVector;
+import org.apache.hadoop.hive.ql.exec.vector.DateColumnVector;
 import org.apache.hadoop.hive.ql.exec.vector.Decimal64ColumnVector;
 import org.apache.hadoop.hive.ql.exec.vector.DecimalColumnVector;
 import org.apache.hadoop.hive.ql.exec.vector.DoubleColumnVector;
@@ -65,6 +66,10 @@ public class TreeReaderFactory {
     OrcFile.Version getFileFormat();
 
     ReaderEncryption getEncryption();
+
+    boolean useProlepticGregorian();
+
+    boolean fileUsedProlepticGregorian();
   }
 
   public static class ReaderContext implements Context {
@@ -74,6 +79,8 @@ public class TreeReaderFactory {
     private String writerTimezone;
     private OrcFile.Version fileFormat;
     private ReaderEncryption encryption;
+    private boolean useProlepticGregorian;
+    private boolean fileUsedProlepticGregorian;
 
     public ReaderContext setSchemaEvolution(SchemaEvolution evolution) {
       this.evolution = evolution;
@@ -105,6 +112,13 @@ public class TreeReaderFactory {
       return this;
     }
 
+    public ReaderContext setProlepticGregorian(boolean file,
+                                               boolean reader) {
+      this.useProlepticGregorian = reader;
+      this.fileUsedProlepticGregorian = file;
+      return this;
+    }
+
     @Override
     public SchemaEvolution getSchemaEvolution() {
       return evolution;
@@ -134,6 +148,16 @@ public class TreeReaderFactory {
     public ReaderEncryption getEncryption() {
       return encryption;
     }
+
+    @Override
+    public boolean useProlepticGregorian() {
+      return useProlepticGregorian;
+    }
+
+    @Override
+    public boolean fileUsedProlepticGregorian() {
+      return fileUsedProlepticGregorian;
+    }
   }
 
   public abstract static class TreeReader {
@@ -897,6 +921,8 @@ public class TreeReaderFactory {
     private TimeZone writerTimeZone;
     private boolean hasSameTZRules;
     private ThreadLocal<DateFormat> threadLocalDateFormat;
+    private final boolean useProleptic;
+    private final boolean fileUsesProleptic;
 
     TimestampTreeReader(int columnId, Context context,
                         boolean instantType) throws IOException {
@@ -938,6 +964,8 @@ public class TreeReaderFactory {
           this.nanos = createIntegerReader(encoding.getKind(), nanosStream, 
false, context);
         }
       }
+      fileUsesProleptic = context.fileUsedProlepticGregorian();
+      useProleptic = context.useProlepticGregorian();
     }
 
     @Override
@@ -1011,6 +1039,7 @@ public class TreeReaderFactory {
                            boolean[] isNull,
                            final int batchSize) throws IOException {
       TimestampColumnVector result = (TimestampColumnVector) previousVector;
+      result.changeCalendar(fileUsesProleptic, false);
       super.nextVector(previousVector, isNull, batchSize);
 
       result.setIsUTC(context.getUseUTCTimestamp());
@@ -1039,6 +1068,7 @@ public class TreeReaderFactory {
           }
         }
       }
+      result.changeCalendar(useProleptic, true);
     }
 
     private static int parseNanos(long serialized) {
@@ -1062,6 +1092,9 @@ public class TreeReaderFactory {
 
   public static class DateTreeReader extends TreeReader {
     protected IntegerReader reader = null;
+    private final boolean needsDateColumnVector;
+    private final boolean useProleptic;
+    private final boolean fileUsesProleptic;
 
     DateTreeReader(int columnId, Context context) throws IOException {
       this(columnId, null, null, null, context);
@@ -1070,6 +1103,10 @@ public class TreeReaderFactory {
     protected DateTreeReader(int columnId, InStream present, InStream data,
         OrcProto.ColumnEncoding encoding, Context context) throws IOException {
       super(columnId, present, context);
+      useProleptic = context.useProlepticGregorian();
+      fileUsesProleptic = context.fileUsedProlepticGregorian();
+      // if either side is proleptic, we need a DateColumnVector
+      needsDateColumnVector = useProleptic || fileUsesProleptic;
       if (data != null && encoding != null) {
         checkEncoding(encoding);
         reader = createIntegerReader(encoding.getKind(), data, true, context);
@@ -1110,12 +1147,23 @@ public class TreeReaderFactory {
                            boolean[] isNull,
                            final int batchSize) throws IOException {
       final LongColumnVector result = (LongColumnVector) previousVector;
+      if (needsDateColumnVector) {
+        if (result instanceof DateColumnVector) {
+          ((DateColumnVector) result).changeCalendar(fileUsesProleptic, false);
+        } else {
+          throw new IllegalArgumentException("Can't use LongColumnVector to " +
+                                                 "read proleptic Gregorian 
dates.");
+        }
+      }
 
       // Read present/isNull stream
       super.nextVector(result, isNull, batchSize);
 
       // Read value entries based on isNull entries
       reader.nextVector(result, result.vector, batchSize);
+      if (needsDateColumnVector) {
+        ((DateColumnVector) result).changeCalendar(useProleptic, true);
+      }
     }
 
     @Override
diff --git a/java/core/src/java/org/apache/orc/impl/TypeUtils.java 
b/java/core/src/java/org/apache/orc/impl/TypeUtils.java
index 32d58ef..0a1e9c3 100644
--- a/java/core/src/java/org/apache/orc/impl/TypeUtils.java
+++ b/java/core/src/java/org/apache/orc/impl/TypeUtils.java
@@ -20,6 +20,7 @@ package org.apache.orc.impl;
 
 import org.apache.hadoop.hive.ql.exec.vector.BytesColumnVector;
 import org.apache.hadoop.hive.ql.exec.vector.ColumnVector;
+import org.apache.hadoop.hive.ql.exec.vector.DateColumnVector;
 import org.apache.hadoop.hive.ql.exec.vector.Decimal64ColumnVector;
 import org.apache.hadoop.hive.ql.exec.vector.DecimalColumnVector;
 import org.apache.hadoop.hive.ql.exec.vector.DoubleColumnVector;
@@ -45,8 +46,9 @@ public class TypeUtils {
     case SHORT:
     case INT:
     case LONG:
-    case DATE:
       return new LongColumnVector(maxSize);
+    case DATE:
+      return new DateColumnVector(maxSize);
     case TIMESTAMP:
     case TIMESTAMP_INSTANT:
       return new TimestampColumnVector(maxSize);
diff --git a/java/core/src/java/org/apache/orc/impl/WriterImpl.java 
b/java/core/src/java/org/apache/orc/impl/WriterImpl.java
index 31a401a..3d9f380 100644
--- a/java/core/src/java/org/apache/orc/impl/WriterImpl.java
+++ b/java/core/src/java/org/apache/orc/impl/WriterImpl.java
@@ -139,6 +139,7 @@ public class WriterImpl implements WriterInternal, 
MemoryManager.Callback {
   // do we need to include the current encryption keys in the next stripe
   // information
   private boolean needKeyFlush;
+  private final boolean useProlepticGregorian;
 
   public WriterImpl(FileSystem fs,
                     Path path,
@@ -147,6 +148,7 @@ public class WriterImpl implements WriterInternal, 
MemoryManager.Callback {
     this.conf = opts.getConfiguration();
     // clone it so that we can annotate it with encryption
     this.schema = opts.getSchema().clone();
+    useProlepticGregorian = opts.getProlepticGregorian();
     int numColumns = schema.getMaximumId() + 1;
     if (!opts.isEnforceBufferSize()) {
       opts.bufferSize(getEstimatedBufferSize(opts.getStripeSize(), numColumns,
@@ -468,6 +470,11 @@ public class WriterImpl implements WriterInternal, 
MemoryManager.Callback {
     public double getDictionaryKeySizeThreshold(int columnId) {
       return directEncodingColumns[columnId] ? 0.0 : 
dictionaryKeySizeThreshold;
     }
+
+    @Override
+    public boolean getProlepticGregorian() {
+      return useProlepticGregorian;
+    }
   }
 
 
@@ -633,6 +640,11 @@ public class WriterImpl implements WriterInternal, 
MemoryManager.Callback {
     rawDataSize = computeRawDataSize();
     // serialize the types
     writeTypes(builder, schema);
+    if (hasDateOrTime(schema)) {
+      builder.setCalendar(useProlepticGregorian
+                              ? OrcProto.CalendarKind.PROLEPTIC_GREGORIAN
+                              : OrcProto.CalendarKind.JULIAN_GREGORIAN);
+    }
     // add the stripe information
     for(OrcProto.StripeInformation stripe: stripes) {
       builder.addStripes(stripe);
@@ -766,7 +778,8 @@ public class WriterImpl implements WriterInternal, 
MemoryManager.Callback {
                            ) throws IOException {
     appendStripe(stripe, offset, length, stripeInfo,
         new StripeStatistics[]{
-            new StripeStatisticsImpl(schema, 
stripeStatistics.getColStatsList())});
+            new StripeStatisticsImpl(schema, 
stripeStatistics.getColStatsList(),
+                null)});
   }
 
   @Override
@@ -853,6 +866,25 @@ public class WriterImpl implements WriterInternal, 
MemoryManager.Callback {
     return false;
   }
 
+  private static boolean hasDateOrTime(TypeDescription schema) {
+    switch (schema.getCategory()) {
+    case TIMESTAMP:
+    case TIMESTAMP_INSTANT:
+    case DATE:
+      return true;
+    default:
+    }
+    List<TypeDescription> children = schema.getChildren();
+    if (children != null) {
+      for(TypeDescription child: children) {
+        if (hasDateOrTime(child)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
   private WriterEncryptionKey getKey(String keyName,
                                      KeyProvider provider) throws IOException {
     WriterEncryptionKey result = keys.get(keyName);
diff --git 
a/java/core/src/java/org/apache/orc/impl/reader/ReaderEncryptionVariant.java 
b/java/core/src/java/org/apache/orc/impl/reader/ReaderEncryptionVariant.java
index 182de63..97fbd04 100644
--- a/java/core/src/java/org/apache/orc/impl/reader/ReaderEncryptionVariant.java
+++ b/java/core/src/java/org/apache/orc/impl/reader/ReaderEncryptionVariant.java
@@ -31,6 +31,7 @@ import org.apache.orc.impl.InStream;
 import org.apache.orc.impl.KeyProvider;
 import org.apache.orc.impl.BufferChunk;
 import org.apache.orc.impl.LocalKey;
+import org.apache.orc.impl.ReaderImpl;
 import org.apache.orc.impl.StripeStatisticsImpl;
 import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
@@ -230,11 +231,12 @@ public class ReaderEncryptionVariant implements 
EncryptionVariant {
    * @return the stripe statistics for this variant.
    */
   public List<StripeStatistics> getStripeStatistics(boolean[] columns,
-                                                    InStream.StreamOptions 
compression
+                                                    InStream.StreamOptions 
compression,
+                                                    ReaderImpl reader
                                                     ) throws IOException {
     StripeStatisticsImpl[] result = new StripeStatisticsImpl[stripeCount];
     for(int s=0; s < result.length; ++s) {
-      result[s] = new StripeStatisticsImpl(column);
+      result[s] = new StripeStatisticsImpl(column, reader);
     }
     // create the objects
     long offset = stripeStatsOffset;
diff --git a/java/core/src/java/org/apache/orc/impl/writer/DateTreeWriter.java 
b/java/core/src/java/org/apache/orc/impl/writer/DateTreeWriter.java
index bc81d45..cc2bdb3 100644
--- a/java/core/src/java/org/apache/orc/impl/writer/DateTreeWriter.java
+++ b/java/core/src/java/org/apache/orc/impl/writer/DateTreeWriter.java
@@ -19,6 +19,7 @@
 package org.apache.orc.impl.writer;
 
 import org.apache.hadoop.hive.ql.exec.vector.ColumnVector;
+import org.apache.hadoop.hive.ql.exec.vector.DateColumnVector;
 import org.apache.hadoop.hive.ql.exec.vector.LongColumnVector;
 import org.apache.hadoop.hive.ql.util.JavaDataModel;
 import org.apache.orc.OrcProto;
@@ -34,6 +35,7 @@ import java.io.IOException;
 public class DateTreeWriter extends TreeWriterBase {
   private final IntegerWriter writer;
   private final boolean isDirectV2;
+  private final boolean useProleptic;
 
   public DateTreeWriter(TypeDescription schema,
                         WriterEncryptionVariant encryption,
@@ -46,6 +48,7 @@ public class DateTreeWriter extends TreeWriterBase {
     if (rowIndexPosition != null) {
       recordPosition(rowIndexPosition);
     }
+    useProleptic = writer.getProlepticGregorian();
   }
 
   @Override
@@ -53,6 +56,12 @@ public class DateTreeWriter extends TreeWriterBase {
                          int length) throws IOException {
     super.writeBatch(vector, offset, length);
     LongColumnVector vec = (LongColumnVector) vector;
+    if (vector instanceof  DateColumnVector) {
+      ((DateColumnVector) vec).changeCalendar(useProleptic, true);
+    } else if (useProleptic) {
+      throw new IllegalArgumentException("Can't use LongColumnVector to write" 
+
+                                             " proleptic dates");
+    }
     if (vector.isRepeating) {
       if (vector.noNulls || !vector.isNull[0]) {
         int value = (int) vec.vector[0];
diff --git 
a/java/core/src/java/org/apache/orc/impl/writer/TimestampTreeWriter.java 
b/java/core/src/java/org/apache/orc/impl/writer/TimestampTreeWriter.java
index 00313b5..e23413f 100644
--- a/java/core/src/java/org/apache/orc/impl/writer/TimestampTreeWriter.java
+++ b/java/core/src/java/org/apache/orc/impl/writer/TimestampTreeWriter.java
@@ -47,6 +47,7 @@ public class TimestampTreeWriter extends TreeWriterBase {
   private final boolean alwaysUTC;
   private final TimeZone localTimezone;
   private final long epoch;
+  private final boolean useProleptic;
 
   public TimestampTreeWriter(TypeDescription schema,
                              WriterEncryptionVariant encryption,
@@ -81,6 +82,7 @@ public class TimestampTreeWriter extends TreeWriterBase {
     } catch (ParseException e) {
       throw new IOException("Unable to create base timestamp tree writer", e);
     }
+    useProleptic = writer.getProlepticGregorian();
   }
 
   @Override
@@ -96,6 +98,7 @@ public class TimestampTreeWriter extends TreeWriterBase {
                          int length) throws IOException {
     super.writeBatch(vector, offset, length);
     TimestampColumnVector vec = (TimestampColumnVector) vector;
+    vec.changeCalendar(useProleptic, true);
     if (vector.isRepeating) {
       if (vector.noNulls || !vector.isNull[0]) {
         // ignore the bottom three digits from the vec.time field
diff --git a/java/core/src/java/org/apache/orc/impl/writer/WriterContext.java 
b/java/core/src/java/org/apache/orc/impl/writer/WriterContext.java
index 73542ad..ebb1ebb 100644
--- a/java/core/src/java/org/apache/orc/impl/writer/WriterContext.java
+++ b/java/core/src/java/org/apache/orc/impl/writer/WriterContext.java
@@ -144,4 +144,9 @@ public interface WriterContext {
    */
   double getDictionaryKeySizeThreshold(int columnId);
 
+  /**
+   * Should we write the data using the proleptic Gregorian calendar?
+   * @return true if we should use the proleptic Gregorian calendar
+   */
+  boolean getProlepticGregorian();
 }
diff --git a/java/core/src/test/org/apache/orc/TestProlepticConversions.java 
b/java/core/src/test/org/apache/orc/TestProlepticConversions.java
new file mode 100644
index 0000000..fa1719b
--- /dev/null
+++ b/java/core/src/test/org/apache/orc/TestProlepticConversions.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.orc;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hive.ql.exec.vector.DateColumnVector;
+import org.apache.hadoop.hive.ql.exec.vector.TimestampColumnVector;
+import org.apache.hadoop.hive.ql.exec.vector.VectorizedRowBatch;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(Parameterized.class)
+public class TestProlepticConversions {
+
+  @Parameterized.Parameter
+  public boolean writerProlepticGregorian;
+
+  @Parameterized.Parameter(1)
+  public boolean readerProlepticGregorian;
+
+  @Parameterized.Parameters
+  public static Collection<Object[]> getParameters() {
+    List<Object[]> result = new ArrayList<>();
+    final boolean[] BOOLEANS = new boolean[]{false, true};
+    for(Boolean writer: BOOLEANS) {
+      for (Boolean reader: BOOLEANS) {
+        result.add(new Object[]{writer, reader});
+      }
+    }
+    return result;
+  }
+
+  private Path workDir = new Path(System.getProperty("test.tmp.dir",
+      "target" + File.separator + "test" + File.separator + "tmp"));
+
+  private final Configuration conf;
+  private final TimeZone UTC = TimeZone.getTimeZone("UTC");
+  private final GregorianCalendar PROLEPTIC = new GregorianCalendar();
+  private final GregorianCalendar HYBRID = new GregorianCalendar();
+  {
+    conf = new Configuration();
+    PROLEPTIC.setTimeZone(UTC);
+    PROLEPTIC.setGregorianChange(new Date(Long.MIN_VALUE));
+    HYBRID.setTimeZone(UTC);
+  }
+
+  private FileSystem fs;
+  private Path testFilePath;
+
+  @Rule
+  public TestName testCaseName = new TestName();
+
+  @Before
+  public void setupPath() throws Exception {
+    fs = FileSystem.getLocal(conf);
+    testFilePath = new Path(workDir, "TestProlepticConversion." +
+       testCaseName.getMethodName().replaceFirst("\\[[0-9]+]", "") + ".orc");
+    fs.delete(testFilePath, false);
+  }
+
+  private SimpleDateFormat createParser(String format, GregorianCalendar 
calendar) {
+    SimpleDateFormat result = new SimpleDateFormat(format);
+    result.setCalendar(calendar);
+    return result;
+  }
+
+  @Test
+  public void testReadWrite() throws Exception {
+    TypeDescription schema = TypeDescription.fromString(
+        "struct<d:date,t:timestamp,i:timestamp with local time zone>");
+    try (Writer writer = OrcFile.createWriter(testFilePath,
+        OrcFile.writerOptions(conf)
+            .setSchema(schema)
+            .fileSystem(fs)
+            .setProlepticGregorian(writerProlepticGregorian))) {
+      VectorizedRowBatch batch = schema.createRowBatchV2();
+      batch.size = 1024;
+      DateColumnVector d = (DateColumnVector) batch.cols[0];
+      TimestampColumnVector t = (TimestampColumnVector) batch.cols[1];
+      TimestampColumnVector i = (TimestampColumnVector) batch.cols[2];
+      d.changeCalendar(writerProlepticGregorian, false);
+      t.changeCalendar(writerProlepticGregorian, false);
+      i.changeCalendar(writerProlepticGregorian, false);
+      GregorianCalendar cal = writerProlepticGregorian ? PROLEPTIC : HYBRID;
+      SimpleDateFormat dateFormat = createParser("yyyy-MM-dd", cal);
+      SimpleDateFormat timeFormat = createParser("yyyy-MM-dd HH:mm:ss", cal);
+      for(int r=0; r < batch.size; ++r) {
+        d.vector[r] = TimeUnit.MILLISECONDS.toDays(
+            dateFormat.parse(String.format("%04d-01-23", r * 2 + 
1)).getTime());
+        Date val = timeFormat.parse(
+            String.format("%04d-03-21 %02d:12:34", 2 * r + 1, r % 24));
+        t.time[r] = val.getTime();
+        t.nanos[r] = 0;
+        i.time[r] = val.getTime();
+        i.nanos[r] = 0;
+      }
+      writer.addRowBatch(batch);
+    }
+    try (Reader reader = OrcFile.createReader(testFilePath,
+        OrcFile.readerOptions(conf)
+            .filesystem(fs)
+            .convertToProlepticGregorian(readerProlepticGregorian));
+         RecordReader rows = reader.rows(reader.options())) {
+      assertEquals(writerProlepticGregorian, 
reader.writerUsedProlepticGregorian());
+      VectorizedRowBatch batch = reader.getSchema().createRowBatchV2();
+      DateColumnVector d = (DateColumnVector) batch.cols[0];
+      TimestampColumnVector t = (TimestampColumnVector) batch.cols[1];
+      TimestampColumnVector i = (TimestampColumnVector) batch.cols[2];
+      GregorianCalendar cal = readerProlepticGregorian ? PROLEPTIC : HYBRID;
+      SimpleDateFormat dateFormat = createParser("yyyy-MM-dd", cal);
+      SimpleDateFormat timeFormat = createParser("yyyy-MM-dd HH:mm:ss", cal);
+
+      // Check the file statistics
+      ColumnStatistics[] colStats = reader.getStatistics();
+      DateColumnStatistics dStats = (DateColumnStatistics) colStats[1];
+      TimestampColumnStatistics tStats = (TimestampColumnStatistics) 
colStats[2];
+      TimestampColumnStatistics iStats = (TimestampColumnStatistics) 
colStats[3];
+      assertEquals("0001-01-23", dateFormat.format(dStats.getMinimum()));
+      assertEquals("2047-01-23", dateFormat.format(dStats.getMaximum()));
+      assertEquals("0001-03-21 00:12:34", 
timeFormat.format(tStats.getMinimum()));
+      assertEquals("2047-03-21 15:12:34", 
timeFormat.format(tStats.getMaximum()));
+      assertEquals("0001-03-21 00:12:34", 
timeFormat.format(iStats.getMinimum()));
+      assertEquals("2047-03-21 15:12:34", 
timeFormat.format(iStats.getMaximum()));
+
+      // Check the stripe stats
+      List<StripeStatistics> stripeStats = reader.getStripeStatistics();
+      assertEquals(1, stripeStats.size());
+      colStats = stripeStats.get(0).getColumnStatistics();
+      dStats = (DateColumnStatistics) colStats[1];
+      tStats = (TimestampColumnStatistics) colStats[2];
+      iStats = (TimestampColumnStatistics) colStats[3];
+      assertEquals("0001-01-23", dateFormat.format(dStats.getMinimum()));
+      assertEquals("2047-01-23", dateFormat.format(dStats.getMaximum()));
+      assertEquals("0001-03-21 00:12:34", 
timeFormat.format(tStats.getMinimum()));
+      assertEquals("2047-03-21 15:12:34", 
timeFormat.format(tStats.getMaximum()));
+      assertEquals("0001-03-21 00:12:34", 
timeFormat.format(iStats.getMinimum()));
+      assertEquals("2047-03-21 15:12:34", 
timeFormat.format(iStats.getMaximum()));
+
+      // Check the data
+      assertTrue(rows.nextBatch(batch));
+      assertEquals(1024, batch.size);
+      // Ensure the column vectors are using the right calendar
+      assertEquals(readerProlepticGregorian, d.isUsingProlepticCalendar());
+      assertEquals(readerProlepticGregorian, t.usingProlepticCalendar());
+      assertEquals(readerProlepticGregorian, i.usingProlepticCalendar());
+      for(int r=0; r < batch.size; ++r) {
+        String expectedD = String.format("%04d-01-23", r * 2 + 1);
+        String expectedT = String.format("%04d-03-21 %02d:12:34", 2 * r + 1, r 
% 24);
+        assertEquals("row " + r, expectedD, dateFormat.format(
+            new Date(TimeUnit.DAYS.toMillis(d.vector[r]))));
+        assertEquals("row " + r, expectedT, 
timeFormat.format(t.asScratchTimestamp(r)));
+        assertEquals("row " + r, expectedT, 
timeFormat.format(i.asScratchTimestamp(r)));
+      }
+    }
+  }
+}
diff --git a/java/core/src/test/org/apache/orc/TestStringDictionary.java 
b/java/core/src/test/org/apache/orc/TestStringDictionary.java
index 9ffe533..ec7d547 100644
--- a/java/core/src/test/org/apache/orc/TestStringDictionary.java
+++ b/java/core/src/test/org/apache/orc/TestStringDictionary.java
@@ -270,6 +270,11 @@ public class TestStringDictionary {
     public double getDictionaryKeySizeThreshold(int column) {
       return OrcConf.DICTIONARY_KEY_SIZE_THRESHOLD.getDouble(conf);
     }
+
+    @Override
+    public boolean getProlepticGregorian() {
+      return false;
+    }
   }
 
   @Test
diff --git a/java/core/src/test/org/apache/orc/impl/TestRecordReaderImpl.java 
b/java/core/src/test/org/apache/orc/impl/TestRecordReaderImpl.java
index 83e4399..dc12715 100644
--- a/java/core/src/test/org/apache/orc/impl/TestRecordReaderImpl.java
+++ b/java/core/src/test/org/apache/orc/impl/TestRecordReaderImpl.java
@@ -2141,7 +2141,7 @@ public class TestRecordReaderImpl {
             .end().build();
     RecordReaderImpl.SargApplier applier =
         new RecordReaderImpl.SargApplier(sarg, 1000, evolution,
-            OrcFile.WriterVersion.ORC_135, false);
+            OrcFile.WriterVersion.ORC_135, false, false, false);
     OrcProto.StripeInformation stripe =
         OrcProto.StripeInformation.newBuilder().setNumberOfRows(4000).build();
     OrcProto.RowIndex[] indexes = new OrcProto.RowIndex[3];
@@ -2190,7 +2190,7 @@ public class TestRecordReaderImpl {
             .end().build();
     RecordReaderImpl.SargApplier applier =
         new RecordReaderImpl.SargApplier(sarg, 1000, evolution,
-            OrcFile.WriterVersion.ORC_135, false);
+            OrcFile.WriterVersion.ORC_135, false, false, false);
     OrcProto.StripeInformation stripe =
         OrcProto.StripeInformation.newBuilder().setNumberOfRows(3000).build();
     OrcProto.RowIndex[] indexes = new OrcProto.RowIndex[3];
@@ -2238,7 +2238,8 @@ public class TestRecordReaderImpl {
         .equals("y", PredicateLeaf.Type.BOOLEAN, true).end().build();
 
     RecordReaderImpl.SargApplier applier =
-        new RecordReaderImpl.SargApplier(sarg, 1000, evo, 
OrcFile.WriterVersion.ORC_135, false);
+        new RecordReaderImpl.SargApplier(sarg, 1000, evo,
+            OrcFile.WriterVersion.ORC_135, false, false, false);
 
     OrcProto.StripeInformation stripe =
         OrcProto.StripeInformation.newBuilder().setNumberOfRows(2000).build();
diff --git a/java/pom.xml b/java/pom.xml
index dbbaea0..bb810dc 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -70,7 +70,7 @@
 
     <min.hadoop.version>2.2.0</min.hadoop.version>
     <hadoop.version>2.7.3</hadoop.version>
-    <storage-api.version>2.6.0</storage-api.version>
+    <storage-api.version>2.7.1</storage-api.version>
     <zookeeper.version>3.4.6</zookeeper.version>
   </properties>
 
diff --git a/java/tools/src/java/org/apache/orc/tools/FileDump.java 
b/java/tools/src/java/org/apache/orc/tools/FileDump.java
index 6c8061a..92711eb 100644
--- a/java/tools/src/java/org/apache/orc/tools/FileDump.java
+++ b/java/tools/src/java/org/apache/orc/tools/FileDump.java
@@ -39,6 +39,7 @@ import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.PathFilter;
 import org.apache.hadoop.hdfs.DistributedFileSystem;
+import org.apache.orc.impl.ReaderImpl;
 import org.apache.orc.util.BloomFilter;
 import org.apache.orc.util.BloomFilterIO;
 import org.apache.orc.ColumnStatistics;
@@ -47,7 +48,6 @@ import org.apache.orc.OrcFile;
 import org.apache.orc.Reader;
 import org.apache.orc.TypeDescription;
 import org.apache.orc.Writer;
-import org.apache.orc.impl.AcidStats;
 import org.apache.orc.impl.ColumnStatisticsImpl;
 import org.apache.orc.impl.OrcAcidUtils;
 import org.apache.orc.impl.OrcIndex;
@@ -345,6 +345,9 @@ public final class FileDump {
     if (reader.getCompressionKind() != CompressionKind.NONE) {
       System.out.println("Compression size: " + reader.getCompressionSize());
     }
+    System.out.println("Calendar: " + (reader.writerUsedProlepticGregorian()
+                           ? "Proleptic Gregorian"
+                           : "Julian/Gregorian"));
     System.out.println("Type: " + reader.getSchema().toString());
     printTypeAnnotations(reader.getSchema(), "root");
     System.out.println("\nStripe Statistics:");
@@ -418,7 +421,7 @@ public final class FileDump {
         for (int col : rowIndexCols) {
           StringBuilder buf = new StringBuilder();
           String rowIdxString = getFormattedRowIndices(col,
-              indices.getRowGroupIndex(), schema);
+              indices.getRowGroupIndex(), schema, (ReaderImpl) reader);
           buf.append(rowIdxString);
           String bloomFilString = getFormattedBloomFilters(col, indices,
               reader.getWriterVersion(),
@@ -697,7 +700,8 @@ public final class FileDump {
 
   private static String getFormattedRowIndices(int col,
                                                OrcProto.RowIndex[] 
rowGroupIndex,
-                                               TypeDescription schema) {
+                                               TypeDescription schema,
+                                               ReaderImpl reader) {
     StringBuilder buf = new StringBuilder();
     OrcProto.RowIndex index;
     buf.append("    Row group indices for column ").append(col).append(":");
@@ -720,7 +724,7 @@ public final class FileDump {
         buf.append("no stats at ");
       } else {
         ColumnStatistics cs =
-            ColumnStatisticsImpl.deserialize(colSchema, colStats);
+            ColumnStatisticsImpl.deserialize(colSchema, colStats, reader);
         buf.append(cs.toString());
       }
       buf.append(" positions: ");
diff --git a/java/tools/src/java/org/apache/orc/tools/JsonFileDump.java 
b/java/tools/src/java/org/apache/orc/tools/JsonFileDump.java
index 54124dc..61849f9 100644
--- a/java/tools/src/java/org/apache/orc/tools/JsonFileDump.java
+++ b/java/tools/src/java/org/apache/orc/tools/JsonFileDump.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -31,6 +31,7 @@ import org.apache.orc.Reader;
 import org.apache.orc.TypeDescription;
 import org.apache.orc.impl.AcidStats;
 import org.apache.orc.impl.OrcAcidUtils;
+import org.apache.orc.impl.ReaderImpl;
 import org.apache.orc.impl.RecordReaderImpl;
 import org.apache.orc.util.BloomFilter;
 import org.codehaus.jettison.json.JSONArray;
@@ -53,16 +54,12 @@ import org.codehaus.jettison.json.JSONException;
 import org.codehaus.jettison.json.JSONObject;
 import org.codehaus.jettison.json.JSONStringer;
 import org.codehaus.jettison.json.JSONWriter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * File dump tool with json formatted output.
  */
 public class JsonFileDump {
 
-  private static final Logger LOG = 
LoggerFactory.getLogger(JsonFileDump.class);
-
   public static void printJsonMetaData(List<String> files,
       Configuration conf,
       List<Integer> rowIndexCols, boolean prettyPrint, boolean printTimeZone)
@@ -100,7 +97,9 @@ public class JsonFileDump {
         writer.key("schemaString").value(reader.getSchema().toString());
         writer.key("schema");
         writeSchema(writer, reader.getSchema());
-
+        writer.key("calendar").value(reader.writerUsedProlepticGregorian()
+                                         ? "proleptic Gregorian"
+                                         : "Julian/Gregorian");
         writer.key("stripeStatistics").array();
         List<StripeStatistics> stripeStatistics = reader.getStripeStatistics();
         for (int n = 0; n < stripeStatistics.size(); n++) {
@@ -191,7 +190,7 @@ public class JsonFileDump {
               writer.object();
               writer.key("columnId").value(col);
               writeRowGroupIndexes(writer, col, indices.getRowGroupIndex(),
-                  reader.getSchema());
+                  reader.getSchema(), (ReaderImpl) reader);
               writeBloomFilterIndexes(writer, col, indices,
                   reader.getWriterVersion(),
                   reader.getSchema().findSubtype(col).getCategory(),
@@ -442,9 +441,9 @@ public class JsonFileDump {
   }
 
   private static void writeRowGroupIndexes(JSONWriter writer, int col,
-      OrcProto.RowIndex[] rowGroupIndex, TypeDescription schema)
-      throws JSONException {
-
+                                           OrcProto.RowIndex[] rowGroupIndex,
+                                           TypeDescription schema,
+                                           ReaderImpl reader) throws 
JSONException {
     OrcProto.RowIndex index;
     if (rowGroupIndex == null || (col >= rowGroupIndex.length) ||
         ((index = rowGroupIndex[col]) == null)) {
@@ -461,7 +460,7 @@ public class JsonFileDump {
       }
       OrcProto.ColumnStatistics colStats = entry.getStatistics();
       writeColumnStatistics(writer, ColumnStatisticsImpl.deserialize(
-          schema.findSubtype(col), colStats));
+          schema.findSubtype(col), colStats, reader));
       writer.key("positions").array();
       for (int posIx = 0; posIx < entry.getPositionsCount(); ++posIx) {
         writer.value(entry.getPositions(posIx));
diff --git a/java/tools/src/test/resources/orc-file-dump-bloomfilter.out 
b/java/tools/src/test/resources/orc-file-dump-bloomfilter.out
index 31a322f..d46682b 100644
--- a/java/tools/src/test/resources/orc-file-dump-bloomfilter.out
+++ b/java/tools/src/test/resources/orc-file-dump-bloomfilter.out
@@ -3,6 +3,7 @@ File Version: 0.12 with ORC_14
 Rows: 21000
 Compression: ZLIB
 Compression size: 4096
+Calendar: Julian/Gregorian
 Type: struct<i:int,l:bigint,s:string>
 Attributes on root
   test1: value1
diff --git a/java/tools/src/test/resources/orc-file-dump-bloomfilter2.out 
b/java/tools/src/test/resources/orc-file-dump-bloomfilter2.out
index b63b4e7..0943a05 100644
--- a/java/tools/src/test/resources/orc-file-dump-bloomfilter2.out
+++ b/java/tools/src/test/resources/orc-file-dump-bloomfilter2.out
@@ -3,6 +3,7 @@ File Version: 0.12 with ORC_14
 Rows: 21000
 Compression: ZLIB
 Compression size: 4096
+Calendar: Julian/Gregorian
 Type: struct<i:int,l:bigint,s:string>
 
 Stripe Statistics:
diff --git 
a/java/tools/src/test/resources/orc-file-dump-dictionary-threshold.out 
b/java/tools/src/test/resources/orc-file-dump-dictionary-threshold.out
index 1ae53df..7b96b44 100644
--- a/java/tools/src/test/resources/orc-file-dump-dictionary-threshold.out
+++ b/java/tools/src/test/resources/orc-file-dump-dictionary-threshold.out
@@ -3,6 +3,7 @@ File Version: 0.12 with ORC_14
 Rows: 21000
 Compression: ZLIB
 Compression size: 4096
+Calendar: Julian/Gregorian
 Type: struct<i:int,l:bigint,s:string>
 
 Stripe Statistics:
diff --git a/java/tools/src/test/resources/orc-file-dump.json 
b/java/tools/src/test/resources/orc-file-dump.json
index 8487d6e..cc7cccf 100644
--- a/java/tools/src/test/resources/orc-file-dump.json
+++ b/java/tools/src/test/resources/orc-file-dump.json
@@ -28,6 +28,7 @@
       }
     }
   },
+  "calendar": "Julian\/Gregorian",
   "stripeStatistics": [
     {
       "stripeNumber": 1,
diff --git a/java/tools/src/test/resources/orc-file-dump.out 
b/java/tools/src/test/resources/orc-file-dump.out
index dd7291c..1fca5c5 100644
--- a/java/tools/src/test/resources/orc-file-dump.out
+++ b/java/tools/src/test/resources/orc-file-dump.out
@@ -3,6 +3,7 @@ File Version: 0.12 with ORC_14
 Rows: 21000
 Compression: ZLIB
 Compression size: 4096
+Calendar: Julian/Gregorian
 Type: struct<i:int,l:bigint,s:string>
 
 Stripe Statistics:
diff --git a/java/tools/src/test/resources/orc-file-has-null.out 
b/java/tools/src/test/resources/orc-file-has-null.out
index 65f8814..e1a5413 100644
--- a/java/tools/src/test/resources/orc-file-has-null.out
+++ b/java/tools/src/test/resources/orc-file-has-null.out
@@ -3,6 +3,7 @@ File Version: 0.12 with ORC_14
 Rows: 20000
 Compression: ZLIB
 Compression size: 4096
+Calendar: Julian/Gregorian
 Type: struct<bytes1:binary,string1:string>
 
 Stripe Statistics:
diff --git a/proto/orc_proto.proto b/proto/orc_proto.proto
index 09d752e..170f077 100644
--- a/proto/orc_proto.proto
+++ b/proto/orc_proto.proto
@@ -340,6 +340,15 @@ message Encryption {
   optional KeyProviderKind keyProvider = 4;
 }
 
+enum CalendarKind {
+  UNKNOWN_CALENDAR = 0;
+   // The Java default calendar changes from Julian to Gregorian
+   // in 1583.
+  JULIAN_GREGORIAN = 1;
+  // A calendar that extends the Gregorian calendar back forever.
+  PROLEPTIC_GREGORIAN = 2;
+}
+
 message Footer {
   optional uint64 headerLength = 1;
   optional uint64 contentLength = 2;
@@ -359,6 +368,7 @@ message Footer {
 
   // information about the encryption in this file
   optional Encryption encryption = 10;
+  optional CalendarKind calendar = 11;
 }
 
 enum CompressionKind {

Reply via email to