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

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


The following commit(s) were added to refs/heads/master by this push:
     new b66efb8284 Move schema related tests to pinot-spi (#13929)
b66efb8284 is described below

commit b66efb82845ead3daded4c3111a63d6131f35f15
Author: Xiaotian (Jackie) Jiang <17555551+jackie-ji...@users.noreply.github.com>
AuthorDate: Tue Sep 3 22:08:15 2024 -0700

    Move schema related tests to pinot-spi (#13929)
---
 .../pinot/common/data/DateTimeFormatSpecTest.java  | 378 ---------------------
 .../common/data/DateTimeGranularitySpecTest.java   |  86 -----
 .../apache/pinot/common/utils/SchemaUtilsTest.java |  58 ++++
 .../pinot/spi/data/DateTimeFieldSpecUtilsTest.java | 188 ----------
 .../pinot/spi/data/DateTimeFormatSpecTest.java     | 343 +++++++++++++++++++
 .../spi/data/DateTimeGranularitySpecTest.java      |  58 ++++
 .../org/apache/pinot/spi}/data/FieldSpecTest.java  |   8 +-
 .../org/apache/pinot/spi}/data/SchemaTest.java     | 225 +++++++++---
 8 files changed, 639 insertions(+), 705 deletions(-)

diff --git 
a/pinot-common/src/test/java/org/apache/pinot/common/data/DateTimeFormatSpecTest.java
 
b/pinot-common/src/test/java/org/apache/pinot/common/data/DateTimeFormatSpecTest.java
deleted file mode 100644
index dd25994ce4..0000000000
--- 
a/pinot-common/src/test/java/org/apache/pinot/common/data/DateTimeFormatSpecTest.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/**
- * 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.pinot.common.data;
-
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
-import org.apache.pinot.spi.data.DateTimeFieldSpec;
-import org.apache.pinot.spi.data.DateTimeFieldSpec.TimeFormat;
-import org.apache.pinot.spi.data.DateTimeFormatSpec;
-import org.joda.time.DateTimeZone;
-import org.joda.time.format.DateTimeFormat;
-import org.joda.time.format.ISODateTimeFormat;
-import org.testng.Assert;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
-
-/**
- * Tests for DateTimeFormatSpec helper methods
- */
-public class DateTimeFormatSpecTest {
-
-  // Test conversion of a dateTimeColumn value from a format to millis
-  @Test(dataProvider = "testFromFormatToMillisDataProvider")
-  public void testFromFormatToMillis(String format, String formattedValue, 
long expectedTimeMs) {
-    Assert.assertEquals(new 
DateTimeFormatSpec(format).fromFormatToMillis(formattedValue), expectedTimeMs);
-  }
-
-  @DataProvider(name = "testFromFormatToMillisDataProvider")
-  public Object[][] provideTestFromFormatToMillisData() {
-
-    List<Object[]> entries = new ArrayList<>();
-    entries.add(new Object[]{"1:HOURS:EPOCH", "416359", 1498892400000L});
-    entries.add(new Object[]{"1:MILLISECONDS:EPOCH", "1498892400000", 
1498892400000L});
-    entries.add(new Object[]{"1:HOURS:EPOCH", "0", 0L});
-    entries.add(new Object[]{"5:MINUTES:EPOCH", "4996308", 1498892400000L});
-    entries.add(new Object[]{
-        "1:MILLISECONDS:TIMESTAMP", "2017-07-01 00:00:00", 
Timestamp.valueOf("2017-07-01 00:00:00").getTime()
-    });
-    entries.add(new Object[]{"1:MILLISECONDS:TIMESTAMP", "1498892400000", 
1498892400000L});
-    entries.add(new Object[]{
-        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", "20170701",
-        
DateTimeFormat.forPattern("yyyyMMdd").withZoneUTC().parseMillis("20170701")
-    });
-    entries.add(new Object[]{
-        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd tz(America/Chicago)", "20170701", 
DateTimeFormat.forPattern("yyyyMMdd")
-        
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("America/Chicago"))).parseMillis("20170701")
-    });
-    entries.add(new Object[]{
-        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH", "20170701 00",
-        DateTimeFormat.forPattern("yyyyMMdd 
HH").withZoneUTC().parseMillis("20170701 00")
-    });
-    entries.add(new Object[]{
-        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH tz(GMT+0600)", "20170701 00", 
DateTimeFormat.forPattern("yyyyMMdd HH")
-        
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT+0600"))).parseMillis("20170701
 00")
-    });
-    entries.add(new Object[]{"1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH Z", 
"20170701 00 -07:00", 1498892400000L});
-    entries.add(new Object[]{"1:HOURS:SIMPLE_DATE_FORMAT:M/d/yyyy h:mm:ss a", 
"8/7/2017 12:45:50 AM", 1502066750000L});
-    entries.add(new Object[]{"EPOCH|HOURS|1", "416359", 1498892400000L});
-    entries.add(new Object[]{"EPOCH|HOURS", "416359", 1498892400000L});
-    entries.add(new Object[]{"EPOCH|MILLISECONDS|1", "1498892400000", 
1498892400000L});
-    entries.add(new Object[]{"EPOCH|MILLISECONDS", "1498892400000", 
1498892400000L});
-    entries.add(new Object[]{"EPOCH|HOURS|1", "0", 0L});
-    entries.add(new Object[]{"EPOCH|HOURS", "0", 0L});
-    entries.add(new Object[]{"EPOCH|MINUTES|5", "4996308", 1498892400000L});
-    entries.add(new Object[]{
-        "TIMESTAMP", "2017-07-01 00:00:00", Timestamp.valueOf("2017-07-01 
00:00:00").getTime()
-    });
-    entries.add(new Object[]{"TIMESTAMP", "1498892400000", 1498892400000L});
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT", "2017-07-01",
-        
DateTimeFormat.forPattern("yyyyMMdd").withZoneUTC().parseMillis("20170701")
-    });
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT", "2017-07-01T12:45:50",
-        
DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss").withZoneUTC().parseMillis("2017-07-01T12:45:50")
-    });
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT", "2017",
-        DateTimeFormat.forPattern("yyyy").withZoneUTC().parseMillis("2017")
-    });
-
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd", "20170701",
-        
DateTimeFormat.forPattern("yyyyMMdd").withZoneUTC().parseMillis("20170701")
-    });
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd|America/Chicago", "20170701", 
DateTimeFormat.forPattern("yyyyMMdd")
-        
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("America/Chicago"))).parseMillis("20170701")
-    });
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd HH", "20170701 00",
-        DateTimeFormat.forPattern("yyyyMMdd 
HH").withZoneUTC().parseMillis("20170701 00")
-    });
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd HH|GMT+0600", "20170701 00", 
DateTimeFormat.forPattern("yyyyMMdd HH")
-        
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT+0600"))).parseMillis("20170701
 00")
-    });
-    entries.add(new Object[]{"SIMPLE_DATE_FORMAT|yyyyMMdd HH Z", "20170701 00 
-07:00", 1498892400000L});
-    entries.add(new Object[]{"SIMPLE_DATE_FORMAT|M/d/yyyy h:mm:ss a", 
"8/7/2017 12:45:50 AM", 1502066750000L});
-
-    return entries.toArray(new Object[entries.size()][]);
-  }
-
-  // Test the conversion of a millis value to date time column value in a 
format
-  @Test(dataProvider = "testFromMillisToFormatDataProvider")
-  public void testFromMillisToFormat(String format, long timeMs, String 
expectedFormattedValue) {
-    Assert.assertEquals(new 
DateTimeFormatSpec(format).fromMillisToFormat(timeMs), expectedFormattedValue);
-  }
-
-  @DataProvider(name = "testFromMillisToFormatDataProvider")
-  public Object[][] provideTestFromMillisToFormatData() {
-
-    List<Object[]> entries = new ArrayList<>();
-    entries.add(new Object[]{"1:HOURS:EPOCH", 1498892400000L, "416359"});
-    entries.add(new Object[]{"1:MILLISECONDS:EPOCH", 1498892400000L, 
"1498892400000"});
-    entries.add(new Object[]{"1:HOURS:EPOCH", 0L, "0"});
-    entries.add(new Object[]{"5:MINUTES:EPOCH", 1498892400000L, "4996308"});
-    entries.add(new Object[]{
-        "1:MILLISECONDS:TIMESTAMP", Timestamp.valueOf("2017-07-01 
00:00:00").getTime(), "2017-07-01 00:00:00.0"
-    });
-    entries.add(new Object[]{
-        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", 1498892400000L,
-        
DateTimeFormat.forPattern("yyyyMMdd").withZoneUTC().print(1498892400000L)
-    });
-    entries.add(new Object[]{
-        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd tz(America/New_York)", 
1498892400000L, DateTimeFormat.forPattern("yyyyMMdd")
-        
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("America/New_York"))).print(1498892400000L)
-    });
-    entries.add(new Object[]{
-        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH", 1498892400000L,
-        DateTimeFormat.forPattern("yyyyMMdd 
HH").withZoneUTC().print(1498892400000L)
-    });
-    entries.add(new Object[]{
-        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH tz(IST)", 1498892400000L,
-        DateTimeFormat.forPattern("yyyyMMdd 
HH").withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))).print(
-            1498892400000L)
-    });
-    entries.add(new Object[]{
-        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH Z", 1498892400000L,
-        DateTimeFormat.forPattern("yyyyMMdd HH 
Z").withZoneUTC().print(1498892400000L)
-    });
-    entries.add(new Object[]{
-        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH Z tz(GMT+0500)", 
1498892400000L,
-        DateTimeFormat.forPattern("yyyyMMdd HH Z")
-            
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT+0500"))).print(1498892400000L)
-    });
-    entries.add(new Object[]{
-        "1:HOURS:SIMPLE_DATE_FORMAT:M/d/yyyy h:mm:ss a", 1498892400000L,
-        DateTimeFormat.forPattern("M/d/yyyy h:mm:ss 
a").withZoneUTC().withLocale(Locale.ENGLISH).print(1498892400000L)
-    });
-    entries.add(new Object[]{
-        "1:HOURS:SIMPLE_DATE_FORMAT:M/d/yyyy h a", 1502066750000L,
-        DateTimeFormat.forPattern("M/d/yyyy h 
a").withZoneUTC().withLocale(Locale.ENGLISH).print(1502066750000L)
-    });
-    entries.add(new Object[]{"EPOCH|HOURS|1", 1498892400000L, "416359"});
-    entries.add(new Object[]{"EPOCH|MILLISECONDS|1", 1498892400000L, 
"1498892400000"});
-    entries.add(new Object[]{"EPOCH|HOURS|1", 0L, "0"});
-    entries.add(new Object[]{"EPOCH|MINUTES|5", 1498892400000L, "4996308"});
-    entries.add(new Object[]{
-        "TIMESTAMP", Timestamp.valueOf("2017-07-01 00:00:00").getTime(), 
"2017-07-01 00:00:00.0"
-    });
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd", 1498892400000L,
-        
DateTimeFormat.forPattern("yyyyMMdd").withZoneUTC().print(1498892400000L)
-    });
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd|America/New_York", 1498892400000L, 
DateTimeFormat.forPattern("yyyyMMdd")
-        
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("America/New_York"))).print(1498892400000L)
-    });
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd HH", 1498892400000L,
-        DateTimeFormat.forPattern("yyyyMMdd 
HH").withZoneUTC().print(1498892400000L)
-    });
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd HH|IST", 1498892400000L,
-        DateTimeFormat.forPattern("yyyyMMdd 
HH").withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))).print(
-            1498892400000L)
-    });
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd HH Z", 1498892400000L,
-        DateTimeFormat.forPattern("yyyyMMdd HH 
Z").withZoneUTC().print(1498892400000L)
-    });
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd HH Z|GMT+0500", 1498892400000L,
-        DateTimeFormat.forPattern("yyyyMMdd HH Z")
-            
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT+0500"))).print(1498892400000L)
-    });
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|M/d/yyyy h:mm:ss a", 1498892400000L,
-        DateTimeFormat.forPattern("M/d/yyyy h:mm:ss 
a").withZoneUTC().withLocale(Locale.ENGLISH).print(1498892400000L)
-    });
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|M/d/yyyy h a", 1502066750000L,
-        DateTimeFormat.forPattern("M/d/yyyy h 
a").withZoneUTC().withLocale(Locale.ENGLISH).print(1502066750000L)
-    });
-
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT", 1502066750000L,
-        
ISODateTimeFormat.dateTimeNoMillis().withZoneUTC().withLocale(Locale.ENGLISH).print(1502066750000L)
-    });
-    return entries.toArray(new Object[entries.size()][]);
-  }
-
-  // Test fetching components of a format form a given format
-  @Test(dataProvider = "testGetFromFormatDataProvider")
-  public void testGetFromFormat(String format, int 
columnSizeFromFormatExpected, TimeUnit columnUnitFromFormatExpected,
-      TimeFormat timeFormatFromFormatExpected, String 
sdfPatternFromFormatExpected,
-      DateTimeZone dateTimeZoneFromFormatExpected) {
-
-    DateTimeFormatSpec dateTimeFormatSpec = new DateTimeFormatSpec(format);
-
-    int columnSizeFromFormat = dateTimeFormatSpec.getColumnSize();
-    Assert.assertEquals(columnSizeFromFormat, columnSizeFromFormatExpected);
-
-    TimeUnit columnUnitFromFormat = dateTimeFormatSpec.getColumnUnit();
-    Assert.assertEquals(columnUnitFromFormat, columnUnitFromFormatExpected);
-
-    TimeFormat timeFormatFromFormat = dateTimeFormatSpec.getTimeFormat();
-    Assert.assertEquals(timeFormatFromFormat, timeFormatFromFormatExpected);
-
-    String sdfPatternFromFormat = null;
-    DateTimeZone dateTimeZoneFromFormat = DateTimeZone.UTC;
-    try {
-      sdfPatternFromFormat = dateTimeFormatSpec.getSDFPattern();
-      dateTimeZoneFromFormat = dateTimeFormatSpec.getDateTimezone();
-    } catch (Exception e) {
-      // No sdf pattern
-    }
-    Assert.assertEquals(sdfPatternFromFormat, sdfPatternFromFormatExpected);
-    Assert.assertEquals(dateTimeZoneFromFormat, 
dateTimeZoneFromFormatExpected);
-  }
-
-  @DataProvider(name = "testGetFromFormatDataProvider")
-  public Object[][] provideTestGetFromFormatData() {
-
-    List<Object[]> entries = new ArrayList<>();
-
-    entries.add(
-        new Object[]{"1:MILLISECONDS:TIMESTAMP", 1, TimeUnit.MILLISECONDS, 
TimeFormat.TIMESTAMP, null,
-            DateTimeZone.UTC});
-
-    entries.add(
-        new Object[]{"1:HOURS:EPOCH", 1, TimeUnit.HOURS, 
DateTimeFieldSpec.TimeFormat.EPOCH, null, DateTimeZone.UTC});
-
-    entries.add(new Object[]{
-        "5:MINUTES:EPOCH", 5, TimeUnit.MINUTES, 
DateTimeFieldSpec.TimeFormat.EPOCH, null, DateTimeZone.UTC
-    });
-
-    entries.add(new Object[]{
-        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT,
-        "yyyyMMdd", DateTimeZone.UTC
-    });
-
-    entries.add(new Object[]{
-        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd tz(IST)", 1, TimeUnit.MILLISECONDS,
-        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd", 
DateTimeZone.forTimeZone(
-        TimeZone.getTimeZone("IST"))
-    });
-
-    entries.add(new Object[]{
-        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd          tz(IST)", 1, 
TimeUnit.MILLISECONDS,
-        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd",
-        DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))
-    });
-
-    entries.add(new Object[]{
-        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd tz  (   IST   )  ", 1, 
TimeUnit.MILLISECONDS,
-        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd",
-        DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))
-    });
-
-    entries.add(new Object[]{
-        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH", 1, TimeUnit.MILLISECONDS,
-        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd HH", 
DateTimeZone.UTC
-    });
-
-    entries.add(new Object[]{
-        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH tz(dummy)", 1, 
TimeUnit.MILLISECONDS,
-        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd HH", 
DateTimeZone.UTC
-    });
-
-    entries.add(new Object[]{
-        "1:HOURS:SIMPLE_DATE_FORMAT:M/d/yyyy h:mm:ss a", 1, 
TimeUnit.MILLISECONDS,
-        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "M/d/yyyy h:mm:ss a", 
DateTimeZone.UTC
-    });
-
-    entries.add(new Object[]{
-        "1:HOURS:SIMPLE_DATE_FORMAT:M/d/yyyy h:mm:ss a tz(Asia/Tokyo)", 1, 
TimeUnit.MILLISECONDS,
-        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "M/d/yyyy h:mm:ss a",
-        DateTimeZone.forTimeZone(TimeZone.getTimeZone("Asia/Tokyo"))
-    });
-
-    //test new format
-    entries.add(
-        new Object[]{"TIMESTAMP", 1, TimeUnit.MILLISECONDS, 
TimeFormat.TIMESTAMP, null,
-            DateTimeZone.UTC});
-
-    entries.add(
-        new Object[]{"EPOCH", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.EPOCH, null, DateTimeZone.UTC});
-
-    entries.add(
-        new Object[]{"EPOCH|HOURS|1", 1, TimeUnit.HOURS, 
DateTimeFieldSpec.TimeFormat.EPOCH, null, DateTimeZone.UTC});
-
-    entries.add(new Object[]{
-        "EPOCH|MINUTES|5", 5, TimeUnit.MINUTES, 
DateTimeFieldSpec.TimeFormat.EPOCH, null, DateTimeZone.UTC
-    });
-
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT,
-        null, DateTimeZone.UTC
-    });
-
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT,
-        "yyyyMMdd", DateTimeZone.UTC
-    });
-
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd|IST", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT,
-        "yyyyMMdd", DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))
-    });
-
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd|IST", 1, TimeUnit.MILLISECONDS,
-        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd",
-        DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))
-    });
-
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd|IST", 1, TimeUnit.MILLISECONDS,
-        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd",
-        DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))
-    });
-
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd HH", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT,
-        "yyyyMMdd HH", DateTimeZone.UTC
-    });
-
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|yyyyMMdd HH|dummy", 1, TimeUnit.MILLISECONDS,
-        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd HH", 
DateTimeZone.UTC
-    });
-
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|M/d/yyyy h:mm:ss a", 1, TimeUnit.MILLISECONDS,
-        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "M/d/yyyy h:mm:ss a", 
DateTimeZone.UTC
-    });
-
-    entries.add(new Object[]{
-        "SIMPLE_DATE_FORMAT|M/d/yyyy h:mm:ss a|Asia/Tokyo", 1, 
TimeUnit.MILLISECONDS,
-        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "M/d/yyyy h:mm:ss a",
-        DateTimeZone.forTimeZone(TimeZone.getTimeZone("Asia/Tokyo"))
-    });
-    return entries.toArray(new Object[entries.size()][]);
-  }
-}
diff --git 
a/pinot-common/src/test/java/org/apache/pinot/common/data/DateTimeGranularitySpecTest.java
 
b/pinot-common/src/test/java/org/apache/pinot/common/data/DateTimeGranularitySpecTest.java
deleted file mode 100644
index d6a2f62223..0000000000
--- 
a/pinot-common/src/test/java/org/apache/pinot/common/data/DateTimeGranularitySpecTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * 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.pinot.common.data;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import org.apache.pinot.spi.data.DateTimeGranularitySpec;
-import org.testng.Assert;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
-
-public class DateTimeGranularitySpecTest {
-
-  // Test construct granularity from components
-  @Test(dataProvider = "testConstructGranularityDataProvider")
-  public void testConstructGranularity(int size, TimeUnit unit, 
DateTimeGranularitySpec granularityExpected) {
-    DateTimeGranularitySpec granularityActual = null;
-    try {
-      granularityActual = new DateTimeGranularitySpec(size, unit);
-    } catch (Exception e) {
-      // invalid arguments
-    }
-    Assert.assertEquals(granularityActual, granularityExpected);
-  }
-
-  @DataProvider(name = "testConstructGranularityDataProvider")
-  public Object[][] provideTestConstructGranularityData() {
-
-    List<Object[]> entries = new ArrayList<>();
-
-    entries.add(new Object[]{1, TimeUnit.HOURS, new 
DateTimeGranularitySpec("1:HOURS")});
-    entries.add(new Object[]{5, TimeUnit.MINUTES, new 
DateTimeGranularitySpec("5:MINUTES")});
-    entries.add(new Object[]{0, TimeUnit.HOURS, null});
-    entries.add(new Object[]{-1, TimeUnit.HOURS, null});
-    entries.add(new Object[]{1, null, null});
-
-    return entries.toArray(new Object[entries.size()][]);
-  }
-
-  // Test granularity to millis
-  @Test(dataProvider = "testGranularityToMillisDataProvider")
-  public void testGranularityToMillis(String granularity, Long millisExpected) 
{
-    Long millisActual = null;
-    DateTimeGranularitySpec granularitySpec = null;
-    try {
-      granularitySpec = new DateTimeGranularitySpec(granularity);
-      millisActual = granularitySpec.granularityToMillis();
-    } catch (Exception e) {
-      // invalid arguments
-    }
-    Assert.assertEquals(millisActual, millisExpected);
-  }
-
-  @DataProvider(name = "testGranularityToMillisDataProvider")
-  public Object[][] provideTestGranularityToMillisData() {
-
-    List<Object[]> entries = new ArrayList<>();
-
-    entries.add(new Object[]{"1:HOURS", 3600000L});
-    entries.add(new Object[]{"1:MILLISECONDS", 1L});
-    entries.add(new Object[]{"15:MINUTES", 900000L});
-    entries.add(new Object[]{"0:HOURS", null});
-    entries.add(new Object[]{null, null});
-    entries.add(new Object[]{"1:DUMMY", null});
-
-    return entries.toArray(new Object[entries.size()][]);
-  }
-}
diff --git 
a/pinot-common/src/test/java/org/apache/pinot/common/utils/SchemaUtilsTest.java 
b/pinot-common/src/test/java/org/apache/pinot/common/utils/SchemaUtilsTest.java
new file mode 100644
index 0000000000..90a554b26c
--- /dev/null
+++ 
b/pinot-common/src/test/java/org/apache/pinot/common/utils/SchemaUtilsTest.java
@@ -0,0 +1,58 @@
+/**
+ * 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.pinot.common.utils;
+
+import java.io.File;
+import java.net.URL;
+import org.apache.pinot.spi.data.Schema;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertNotNull;
+
+
+public class SchemaUtilsTest {
+
+  @Test
+  public void testSchemaSerDe()
+      throws Exception {
+    URL resourceUrl = 
getClass().getClassLoader().getResource("schemaTest.schema");
+    assertNotNull(resourceUrl);
+    Schema schema = Schema.fromFile(new File(resourceUrl.getFile()));
+
+    Schema schemaToCompare = Schema.fromString(schema.toPrettyJsonString());
+    assertEquals(schemaToCompare, schema);
+    assertEquals(schemaToCompare.hashCode(), schema.hashCode());
+
+    schemaToCompare = Schema.fromString(schema.toSingleLineJsonString());
+    assertEquals(schemaToCompare, schema);
+    assertEquals(schemaToCompare.hashCode(), schema.hashCode());
+
+    schemaToCompare = SchemaUtils.fromZNRecord(SchemaUtils.toZNRecord(schema));
+    assertEquals(schemaToCompare, schema);
+    assertEquals(schemaToCompare.hashCode(), schema.hashCode());
+
+    // When setting new fields, schema string should be updated
+    String jsonSchema = schemaToCompare.toSingleLineJsonString();
+    schemaToCompare.setSchemaName("newSchema");
+    String jsonSchemaToCompare = schemaToCompare.toSingleLineJsonString();
+    assertNotEquals(jsonSchemaToCompare, jsonSchema);
+  }
+}
diff --git 
a/pinot-spi/src/test/java/org/apache/pinot/spi/data/DateTimeFieldSpecUtilsTest.java
 
b/pinot-spi/src/test/java/org/apache/pinot/spi/data/DateTimeFieldSpecUtilsTest.java
deleted file mode 100644
index 837cd1db9d..0000000000
--- 
a/pinot-spi/src/test/java/org/apache/pinot/spi/data/DateTimeFieldSpecUtilsTest.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/**
- * 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.pinot.spi.data;
-
-import java.util.concurrent.TimeUnit;
-import org.apache.pinot.spi.data.FieldSpec.DataType;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-
-/**
- * Tests the conversion of a {@link TimeFieldSpec} to an equivalent {@link 
DateTimeFieldSpec}
- */
-public class DateTimeFieldSpecUtilsTest {
-
-  @Test
-  public void testConversionFromTimeToDateTimeSpec() {
-    TimeFieldSpec timeFieldSpec;
-    DateTimeFieldSpec expectedDateTimeFieldSpec;
-    DateTimeFieldSpec actualDateTimeFieldSpec;
-
-    /* 1] only incoming */
-
-    // incoming epoch millis
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.LONG, 
TimeUnit.MILLISECONDS, "incoming"));
-    expectedDateTimeFieldSpec =
-        new DateTimeFieldSpec("incoming", DataType.LONG, 
"1:MILLISECONDS:EPOCH", "1:MILLISECONDS");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // incoming epoch hours
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.INT, 
TimeUnit.HOURS, "incoming"));
-    expectedDateTimeFieldSpec = new DateTimeFieldSpec("incoming", 
DataType.INT, "1:HOURS:EPOCH", "1:HOURS");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // Simple date format
-    timeFieldSpec = new TimeFieldSpec(
-        new TimeGranularitySpec(DataType.INT, TimeUnit.DAYS, 
"SIMPLE_DATE_FORMAT:yyyyMMdd", "incoming"));
-    expectedDateTimeFieldSpec =
-        new DateTimeFieldSpec("incoming", DataType.INT, 
"1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", "1:DAYS");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // simple date format STRING
-    timeFieldSpec = new TimeFieldSpec(
-        new TimeGranularitySpec(DataType.STRING, TimeUnit.DAYS, 
"SIMPLE_DATE_FORMAT:yyyy-MM-dd hh-mm-ss", "incoming"));
-    expectedDateTimeFieldSpec =
-        new DateTimeFieldSpec("incoming", DataType.STRING, 
"1:DAYS:SIMPLE_DATE_FORMAT:yyyy-MM-dd hh-mm-ss", "1:DAYS");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // time unit size
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.LONG, 
5, TimeUnit.MINUTES, "incoming"));
-    expectedDateTimeFieldSpec = new DateTimeFieldSpec("incoming", 
DataType.LONG, "5:MINUTES:EPOCH", "5:MINUTES");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // transform function
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.INT, 
TimeUnit.HOURS, "incoming"));
-    timeFieldSpec.setTransformFunction("toEpochHours(timestamp)");
-    expectedDateTimeFieldSpec =
-        new DateTimeFieldSpec("incoming", DataType.INT, "1:HOURS:EPOCH", 
"1:HOURS", null, "toEpochHours(timestamp)");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    /* 2] incoming + outgoing */
-
-    // same incoming and outgoing
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.LONG, 
TimeUnit.HOURS, "time"),
-        new TimeGranularitySpec(DataType.LONG, TimeUnit.HOURS, "time"));
-    expectedDateTimeFieldSpec = new DateTimeFieldSpec("time", DataType.LONG, 
"1:HOURS:EPOCH", "1:HOURS");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // same incoming and outgoing - simple date format
-    timeFieldSpec =
-        new TimeFieldSpec(new TimeGranularitySpec(DataType.LONG, 
TimeUnit.DAYS, "SIMPLE_DATE_FORMAT:yyyyMMdd", "time"),
-            new TimeGranularitySpec(DataType.LONG, TimeUnit.DAYS, 
"SIMPLE_DATE_FORMAT:yyyyMMdd", "time"));
-    expectedDateTimeFieldSpec =
-        new DateTimeFieldSpec("time", DataType.LONG, 
"1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", "1:DAYS");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // millis to hours
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.LONG, 
TimeUnit.MILLISECONDS, "incoming"),
-        new TimeGranularitySpec(DataType.LONG, TimeUnit.HOURS, "outgoing"));
-    expectedDateTimeFieldSpec =
-        new DateTimeFieldSpec("outgoing", DataType.LONG, "1:HOURS:EPOCH", 
"1:HOURS", null, "toEpochHours(incoming)");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // millis to bucketed minutes
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.LONG, 
TimeUnit.MILLISECONDS, "incoming"),
-        new TimeGranularitySpec(DataType.LONG, 10, TimeUnit.MINUTES, 
"outgoing"));
-    expectedDateTimeFieldSpec = new DateTimeFieldSpec("outgoing", 
DataType.LONG, "10:MINUTES:EPOCH", "10:MINUTES", null,
-        "toEpochMinutesBucket(incoming, 10)");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // days to millis
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.INT, 
TimeUnit.DAYS, "incoming"),
-        new TimeGranularitySpec(DataType.LONG, TimeUnit.MILLISECONDS, 
"outgoing"));
-    expectedDateTimeFieldSpec =
-        new DateTimeFieldSpec("outgoing", DataType.LONG, 
"1:MILLISECONDS:EPOCH", "1:MILLISECONDS", null,
-            "fromEpochDays(incoming)");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // bucketed minutes to millis
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.LONG, 
5, TimeUnit.MINUTES, "incoming"),
-        new TimeGranularitySpec(DataType.LONG, TimeUnit.MILLISECONDS, 
"outgoing"));
-    expectedDateTimeFieldSpec =
-        new DateTimeFieldSpec("outgoing", DataType.LONG, 
"1:MILLISECONDS:EPOCH", "1:MILLISECONDS", null,
-            "fromEpochMinutesBucket(incoming, 5)");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // hours to days
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.INT, 
TimeUnit.HOURS, "incoming"),
-        new TimeGranularitySpec(DataType.INT, TimeUnit.DAYS, "outgoing"));
-    expectedDateTimeFieldSpec = new DateTimeFieldSpec("outgoing", 
DataType.INT, "1:DAYS:EPOCH", "1:DAYS", null,
-        "toEpochDays(fromEpochHours(incoming))");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // minutes to hours
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.LONG, 
TimeUnit.MINUTES, "incoming"),
-        new TimeGranularitySpec(DataType.LONG, TimeUnit.HOURS, "outgoing"));
-    expectedDateTimeFieldSpec = new DateTimeFieldSpec("outgoing", 
DataType.LONG, "1:HOURS:EPOCH", "1:HOURS", null,
-        "toEpochHours(fromEpochMinutes(incoming))");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // bucketed minutes to days
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.LONG, 
10, TimeUnit.MINUTES, "incoming"),
-        new TimeGranularitySpec(DataType.LONG, TimeUnit.DAYS, "outgoing"));
-    expectedDateTimeFieldSpec = new DateTimeFieldSpec("outgoing", 
DataType.LONG, "1:DAYS:EPOCH", "1:DAYS", null,
-        "toEpochDays(fromEpochMinutesBucket(incoming, 10))");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // seconds to bucketed minutes
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.LONG, 
TimeUnit.SECONDS, "incoming"),
-        new TimeGranularitySpec(DataType.LONG, 5, TimeUnit.MINUTES, 
"outgoing"));
-    expectedDateTimeFieldSpec = new DateTimeFieldSpec("outgoing", 
DataType.LONG, "5:MINUTES:EPOCH", "5:MINUTES", null,
-        "toEpochMinutesBucket(fromEpochSeconds(incoming), 5)");
-    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
-
-    // simple date format to millis
-    timeFieldSpec = new TimeFieldSpec(
-        new TimeGranularitySpec(DataType.LONG, TimeUnit.DAYS, 
"SIMPLE_DATE_FORMAT:yyyyMMdd", "incoming"),
-        new TimeGranularitySpec(DataType.LONG, TimeUnit.MILLISECONDS, 
"outgoing"));
-    try {
-      Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-      Assert.fail();
-    } catch (Exception e) {
-      // expected
-    }
-
-    // hours to simple date format
-    timeFieldSpec = new TimeFieldSpec(new TimeGranularitySpec(DataType.LONG, 
TimeUnit.HOURS, "incoming"),
-        new TimeGranularitySpec(DataType.INT, TimeUnit.HOURS, 
"SIMPLE_DATE_FORMAT:yyyyMMddhh", "outgoing"));
-    try {
-      Schema.convertToDateTimeFieldSpec(timeFieldSpec);
-      Assert.fail();
-    } catch (Exception e) {
-      // expected
-    }
-  }
-}
diff --git 
a/pinot-spi/src/test/java/org/apache/pinot/spi/data/DateTimeFormatSpecTest.java 
b/pinot-spi/src/test/java/org/apache/pinot/spi/data/DateTimeFormatSpecTest.java
index 9bf1c122d4..898e182fb3 100644
--- 
a/pinot-spi/src/test/java/org/apache/pinot/spi/data/DateTimeFormatSpecTest.java
+++ 
b/pinot-spi/src/test/java/org/apache/pinot/spi/data/DateTimeFormatSpecTest.java
@@ -18,9 +18,16 @@
  */
 package org.apache.pinot.spi.data;
 
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
 import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
 import org.joda.time.DateTimeZone;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.ISODateTimeFormat;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
@@ -30,6 +37,342 @@ import static org.testng.Assert.assertThrows;
 
 public class DateTimeFormatSpecTest {
 
+  // Test conversion of a dateTimeColumn value from a format to millis
+  @Test(dataProvider = "testFromFormatToMillisDataProvider")
+  public void testFromFormatToMillis(String format, String formattedValue, 
long expectedTimeMs) {
+    assertEquals(new 
DateTimeFormatSpec(format).fromFormatToMillis(formattedValue), expectedTimeMs);
+  }
+
+  @DataProvider(name = "testFromFormatToMillisDataProvider")
+  public Object[][] provideTestFromFormatToMillisData() {
+
+    List<Object[]> entries = new ArrayList<>();
+    entries.add(new Object[]{"1:HOURS:EPOCH", "416359", 1498892400000L});
+    entries.add(new Object[]{"1:MILLISECONDS:EPOCH", "1498892400000", 
1498892400000L});
+    entries.add(new Object[]{"1:HOURS:EPOCH", "0", 0L});
+    entries.add(new Object[]{"5:MINUTES:EPOCH", "4996308", 1498892400000L});
+    entries.add(new Object[]{
+        "1:MILLISECONDS:TIMESTAMP", "2017-07-01 00:00:00", 
Timestamp.valueOf("2017-07-01 00:00:00").getTime()
+    });
+    entries.add(new Object[]{"1:MILLISECONDS:TIMESTAMP", "1498892400000", 
1498892400000L});
+    entries.add(new Object[]{
+        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", "20170701",
+        
DateTimeFormat.forPattern("yyyyMMdd").withZoneUTC().parseMillis("20170701")
+    });
+    entries.add(new Object[]{
+        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd tz(America/Chicago)", "20170701", 
DateTimeFormat.forPattern("yyyyMMdd")
+        
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("America/Chicago"))).parseMillis("20170701")
+    });
+    entries.add(new Object[]{
+        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH", "20170701 00",
+        DateTimeFormat.forPattern("yyyyMMdd 
HH").withZoneUTC().parseMillis("20170701 00")
+    });
+    entries.add(new Object[]{
+        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH tz(GMT+0600)", "20170701 00", 
DateTimeFormat.forPattern("yyyyMMdd HH")
+        
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT+0600"))).parseMillis("20170701
 00")
+    });
+    entries.add(new Object[]{"1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH Z", 
"20170701 00 -07:00", 1498892400000L});
+    entries.add(new Object[]{"1:HOURS:SIMPLE_DATE_FORMAT:M/d/yyyy h:mm:ss a", 
"8/7/2017 12:45:50 AM", 1502066750000L});
+    entries.add(new Object[]{"EPOCH|HOURS|1", "416359", 1498892400000L});
+    entries.add(new Object[]{"EPOCH|HOURS", "416359", 1498892400000L});
+    entries.add(new Object[]{"EPOCH|MILLISECONDS|1", "1498892400000", 
1498892400000L});
+    entries.add(new Object[]{"EPOCH|MILLISECONDS", "1498892400000", 
1498892400000L});
+    entries.add(new Object[]{"EPOCH|HOURS|1", "0", 0L});
+    entries.add(new Object[]{"EPOCH|HOURS", "0", 0L});
+    entries.add(new Object[]{"EPOCH|MINUTES|5", "4996308", 1498892400000L});
+    entries.add(new Object[]{
+        "TIMESTAMP", "2017-07-01 00:00:00", Timestamp.valueOf("2017-07-01 
00:00:00").getTime()
+    });
+    entries.add(new Object[]{"TIMESTAMP", "1498892400000", 1498892400000L});
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT", "2017-07-01",
+        
DateTimeFormat.forPattern("yyyyMMdd").withZoneUTC().parseMillis("20170701")
+    });
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT", "2017-07-01T12:45:50",
+        
DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss").withZoneUTC().parseMillis("2017-07-01T12:45:50")
+    });
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT", "2017",
+        DateTimeFormat.forPattern("yyyy").withZoneUTC().parseMillis("2017")
+    });
+
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd", "20170701",
+        
DateTimeFormat.forPattern("yyyyMMdd").withZoneUTC().parseMillis("20170701")
+    });
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd|America/Chicago", "20170701", 
DateTimeFormat.forPattern("yyyyMMdd")
+        
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("America/Chicago"))).parseMillis("20170701")
+    });
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd HH", "20170701 00",
+        DateTimeFormat.forPattern("yyyyMMdd 
HH").withZoneUTC().parseMillis("20170701 00")
+    });
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd HH|GMT+0600", "20170701 00", 
DateTimeFormat.forPattern("yyyyMMdd HH")
+        
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT+0600"))).parseMillis("20170701
 00")
+    });
+    entries.add(new Object[]{"SIMPLE_DATE_FORMAT|yyyyMMdd HH Z", "20170701 00 
-07:00", 1498892400000L});
+    entries.add(new Object[]{"SIMPLE_DATE_FORMAT|M/d/yyyy h:mm:ss a", 
"8/7/2017 12:45:50 AM", 1502066750000L});
+
+    return entries.toArray(new Object[entries.size()][]);
+  }
+
+  // Test the conversion of a millis value to date time column value in a 
format
+  @Test(dataProvider = "testFromMillisToFormatDataProvider")
+  public void testFromMillisToFormat(String format, long timeMs, String 
expectedFormattedValue) {
+    assertEquals(new DateTimeFormatSpec(format).fromMillisToFormat(timeMs), 
expectedFormattedValue);
+  }
+
+  @DataProvider(name = "testFromMillisToFormatDataProvider")
+  public Object[][] provideTestFromMillisToFormatData() {
+
+    List<Object[]> entries = new ArrayList<>();
+    entries.add(new Object[]{"1:HOURS:EPOCH", 1498892400000L, "416359"});
+    entries.add(new Object[]{"1:MILLISECONDS:EPOCH", 1498892400000L, 
"1498892400000"});
+    entries.add(new Object[]{"1:HOURS:EPOCH", 0L, "0"});
+    entries.add(new Object[]{"5:MINUTES:EPOCH", 1498892400000L, "4996308"});
+    entries.add(new Object[]{
+        "1:MILLISECONDS:TIMESTAMP", Timestamp.valueOf("2017-07-01 
00:00:00").getTime(), "2017-07-01 00:00:00.0"
+    });
+    entries.add(new Object[]{
+        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", 1498892400000L,
+        
DateTimeFormat.forPattern("yyyyMMdd").withZoneUTC().print(1498892400000L)
+    });
+    entries.add(new Object[]{
+        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd tz(America/New_York)", 
1498892400000L, DateTimeFormat.forPattern("yyyyMMdd")
+        
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("America/New_York"))).print(1498892400000L)
+    });
+    entries.add(new Object[]{
+        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH", 1498892400000L,
+        DateTimeFormat.forPattern("yyyyMMdd 
HH").withZoneUTC().print(1498892400000L)
+    });
+    entries.add(new Object[]{
+        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH tz(IST)", 1498892400000L,
+        DateTimeFormat.forPattern("yyyyMMdd 
HH").withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))).print(
+            1498892400000L)
+    });
+    entries.add(new Object[]{
+        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH Z", 1498892400000L,
+        DateTimeFormat.forPattern("yyyyMMdd HH 
Z").withZoneUTC().print(1498892400000L)
+    });
+    entries.add(new Object[]{
+        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH Z tz(GMT+0500)", 
1498892400000L,
+        DateTimeFormat.forPattern("yyyyMMdd HH Z")
+            
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT+0500"))).print(1498892400000L)
+    });
+    entries.add(new Object[]{
+        "1:HOURS:SIMPLE_DATE_FORMAT:M/d/yyyy h:mm:ss a", 1498892400000L,
+        DateTimeFormat.forPattern("M/d/yyyy h:mm:ss 
a").withZoneUTC().withLocale(Locale.ENGLISH).print(1498892400000L)
+    });
+    entries.add(new Object[]{
+        "1:HOURS:SIMPLE_DATE_FORMAT:M/d/yyyy h a", 1502066750000L,
+        DateTimeFormat.forPattern("M/d/yyyy h 
a").withZoneUTC().withLocale(Locale.ENGLISH).print(1502066750000L)
+    });
+    entries.add(new Object[]{"EPOCH|HOURS|1", 1498892400000L, "416359"});
+    entries.add(new Object[]{"EPOCH|MILLISECONDS|1", 1498892400000L, 
"1498892400000"});
+    entries.add(new Object[]{"EPOCH|HOURS|1", 0L, "0"});
+    entries.add(new Object[]{"EPOCH|MINUTES|5", 1498892400000L, "4996308"});
+    entries.add(new Object[]{
+        "TIMESTAMP", Timestamp.valueOf("2017-07-01 00:00:00").getTime(), 
"2017-07-01 00:00:00.0"
+    });
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd", 1498892400000L,
+        
DateTimeFormat.forPattern("yyyyMMdd").withZoneUTC().print(1498892400000L)
+    });
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd|America/New_York", 1498892400000L, 
DateTimeFormat.forPattern("yyyyMMdd")
+        
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("America/New_York"))).print(1498892400000L)
+    });
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd HH", 1498892400000L,
+        DateTimeFormat.forPattern("yyyyMMdd 
HH").withZoneUTC().print(1498892400000L)
+    });
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd HH|IST", 1498892400000L,
+        DateTimeFormat.forPattern("yyyyMMdd 
HH").withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))).print(
+            1498892400000L)
+    });
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd HH Z", 1498892400000L,
+        DateTimeFormat.forPattern("yyyyMMdd HH 
Z").withZoneUTC().print(1498892400000L)
+    });
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd HH Z|GMT+0500", 1498892400000L,
+        DateTimeFormat.forPattern("yyyyMMdd HH Z")
+            
.withZone(DateTimeZone.forTimeZone(TimeZone.getTimeZone("GMT+0500"))).print(1498892400000L)
+    });
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|M/d/yyyy h:mm:ss a", 1498892400000L,
+        DateTimeFormat.forPattern("M/d/yyyy h:mm:ss 
a").withZoneUTC().withLocale(Locale.ENGLISH).print(1498892400000L)
+    });
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|M/d/yyyy h a", 1502066750000L,
+        DateTimeFormat.forPattern("M/d/yyyy h 
a").withZoneUTC().withLocale(Locale.ENGLISH).print(1502066750000L)
+    });
+
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT", 1502066750000L,
+        
ISODateTimeFormat.dateTimeNoMillis().withZoneUTC().withLocale(Locale.ENGLISH).print(1502066750000L)
+    });
+    return entries.toArray(new Object[entries.size()][]);
+  }
+
+  // Test fetching components of a format form a given format
+  @Test(dataProvider = "testGetFromFormatDataProvider")
+  public void testGetFromFormat(String format, int 
columnSizeFromFormatExpected, TimeUnit columnUnitFromFormatExpected,
+      DateTimeFieldSpec.TimeFormat timeFormatFromFormatExpected, String 
sdfPatternFromFormatExpected,
+      DateTimeZone dateTimeZoneFromFormatExpected) {
+
+    DateTimeFormatSpec dateTimeFormatSpec = new DateTimeFormatSpec(format);
+
+    int columnSizeFromFormat = dateTimeFormatSpec.getColumnSize();
+    assertEquals(columnSizeFromFormat, columnSizeFromFormatExpected);
+
+    TimeUnit columnUnitFromFormat = dateTimeFormatSpec.getColumnUnit();
+    assertEquals(columnUnitFromFormat, columnUnitFromFormatExpected);
+
+    DateTimeFieldSpec.TimeFormat timeFormatFromFormat = 
dateTimeFormatSpec.getTimeFormat();
+    assertEquals(timeFormatFromFormat, timeFormatFromFormatExpected);
+
+    String sdfPatternFromFormat = null;
+    DateTimeZone dateTimeZoneFromFormat = DateTimeZone.UTC;
+    try {
+      sdfPatternFromFormat = dateTimeFormatSpec.getSDFPattern();
+      dateTimeZoneFromFormat = dateTimeFormatSpec.getDateTimezone();
+    } catch (Exception e) {
+      // No sdf pattern
+    }
+    assertEquals(sdfPatternFromFormat, sdfPatternFromFormatExpected);
+    assertEquals(dateTimeZoneFromFormat, dateTimeZoneFromFormatExpected);
+  }
+
+  @DataProvider(name = "testGetFromFormatDataProvider")
+  public Object[][] provideTestGetFromFormatData() {
+
+    List<Object[]> entries = new ArrayList<>();
+
+    entries.add(
+        new Object[]{"1:MILLISECONDS:TIMESTAMP", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.TIMESTAMP, null,
+            DateTimeZone.UTC});
+
+    entries.add(
+        new Object[]{"1:HOURS:EPOCH", 1, TimeUnit.HOURS, 
DateTimeFieldSpec.TimeFormat.EPOCH, null, DateTimeZone.UTC});
+
+    entries.add(new Object[]{
+        "5:MINUTES:EPOCH", 5, TimeUnit.MINUTES, 
DateTimeFieldSpec.TimeFormat.EPOCH, null, DateTimeZone.UTC
+    });
+
+    entries.add(new Object[]{
+        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT,
+        "yyyyMMdd", DateTimeZone.UTC
+    });
+
+    entries.add(new Object[]{
+        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd tz(IST)", 1, TimeUnit.MILLISECONDS,
+        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd", 
DateTimeZone.forTimeZone(
+        TimeZone.getTimeZone("IST"))
+    });
+
+    entries.add(new Object[]{
+        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd          tz(IST)", 1, 
TimeUnit.MILLISECONDS,
+        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd",
+        DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))
+    });
+
+    entries.add(new Object[]{
+        "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd tz  (   IST   )  ", 1, 
TimeUnit.MILLISECONDS,
+        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd",
+        DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))
+    });
+
+    entries.add(new Object[]{
+        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH", 1, TimeUnit.MILLISECONDS,
+        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd HH", 
DateTimeZone.UTC
+    });
+
+    entries.add(new Object[]{
+        "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd HH tz(dummy)", 1, 
TimeUnit.MILLISECONDS,
+        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd HH", 
DateTimeZone.UTC
+    });
+
+    entries.add(new Object[]{
+        "1:HOURS:SIMPLE_DATE_FORMAT:M/d/yyyy h:mm:ss a", 1, 
TimeUnit.MILLISECONDS,
+        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "M/d/yyyy h:mm:ss a", 
DateTimeZone.UTC
+    });
+
+    entries.add(new Object[]{
+        "1:HOURS:SIMPLE_DATE_FORMAT:M/d/yyyy h:mm:ss a tz(Asia/Tokyo)", 1, 
TimeUnit.MILLISECONDS,
+        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "M/d/yyyy h:mm:ss a",
+        DateTimeZone.forTimeZone(TimeZone.getTimeZone("Asia/Tokyo"))
+    });
+
+    //test new format
+    entries.add(
+        new Object[]{"TIMESTAMP", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.TIMESTAMP, null,
+            DateTimeZone.UTC});
+
+    entries.add(
+        new Object[]{"EPOCH", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.EPOCH, null, DateTimeZone.UTC});
+
+    entries.add(
+        new Object[]{"EPOCH|HOURS|1", 1, TimeUnit.HOURS, 
DateTimeFieldSpec.TimeFormat.EPOCH, null, DateTimeZone.UTC});
+
+    entries.add(new Object[]{
+        "EPOCH|MINUTES|5", 5, TimeUnit.MINUTES, 
DateTimeFieldSpec.TimeFormat.EPOCH, null, DateTimeZone.UTC
+    });
+
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT,
+        null, DateTimeZone.UTC
+    });
+
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT,
+        "yyyyMMdd", DateTimeZone.UTC
+    });
+
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd|IST", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT,
+        "yyyyMMdd", DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))
+    });
+
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd|IST", 1, TimeUnit.MILLISECONDS,
+        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd",
+        DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))
+    });
+
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd|IST", 1, TimeUnit.MILLISECONDS,
+        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd",
+        DateTimeZone.forTimeZone(TimeZone.getTimeZone("IST"))
+    });
+
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd HH", 1, TimeUnit.MILLISECONDS, 
DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT,
+        "yyyyMMdd HH", DateTimeZone.UTC
+    });
+
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|yyyyMMdd HH|dummy", 1, TimeUnit.MILLISECONDS,
+        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "yyyyMMdd HH", 
DateTimeZone.UTC
+    });
+
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|M/d/yyyy h:mm:ss a", 1, TimeUnit.MILLISECONDS,
+        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "M/d/yyyy h:mm:ss a", 
DateTimeZone.UTC
+    });
+
+    entries.add(new Object[]{
+        "SIMPLE_DATE_FORMAT|M/d/yyyy h:mm:ss a|Asia/Tokyo", 1, 
TimeUnit.MILLISECONDS,
+        DateTimeFieldSpec.TimeFormat.SIMPLE_DATE_FORMAT, "M/d/yyyy h:mm:ss a",
+        DateTimeZone.forTimeZone(TimeZone.getTimeZone("Asia/Tokyo"))
+    });
+    return entries.toArray(new Object[entries.size()][]);
+  }
+
   @Test
   public void testDateTimeFormatSpec() {
     DateTimeFormatSpec dateTimeFormatSpec = new 
DateTimeFormatSpec("5:DAYS:EPOCH");
diff --git 
a/pinot-spi/src/test/java/org/apache/pinot/spi/data/DateTimeGranularitySpecTest.java
 
b/pinot-spi/src/test/java/org/apache/pinot/spi/data/DateTimeGranularitySpecTest.java
index ed60c5d915..45cbc000a4 100644
--- 
a/pinot-spi/src/test/java/org/apache/pinot/spi/data/DateTimeGranularitySpecTest.java
+++ 
b/pinot-spi/src/test/java/org/apache/pinot/spi/data/DateTimeGranularitySpecTest.java
@@ -19,7 +19,10 @@
 
 package org.apache.pinot.spi.data;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
@@ -28,6 +31,61 @@ import static org.testng.Assert.assertThrows;
 
 public class DateTimeGranularitySpecTest {
 
+  // Test construct granularity from components
+  @Test(dataProvider = "testConstructGranularityDataProvider")
+  public void testConstructGranularity(int size, TimeUnit unit, 
DateTimeGranularitySpec granularityExpected) {
+    DateTimeGranularitySpec granularityActual = null;
+    try {
+      granularityActual = new DateTimeGranularitySpec(size, unit);
+    } catch (Exception e) {
+      // invalid arguments
+    }
+    assertEquals(granularityActual, granularityExpected);
+  }
+
+  @DataProvider(name = "testConstructGranularityDataProvider")
+  public Object[][] provideTestConstructGranularityData() {
+
+    List<Object[]> entries = new ArrayList<>();
+
+    entries.add(new Object[]{1, TimeUnit.HOURS, new 
DateTimeGranularitySpec("1:HOURS")});
+    entries.add(new Object[]{5, TimeUnit.MINUTES, new 
DateTimeGranularitySpec("5:MINUTES")});
+    entries.add(new Object[]{0, TimeUnit.HOURS, null});
+    entries.add(new Object[]{-1, TimeUnit.HOURS, null});
+    entries.add(new Object[]{1, null, null});
+
+    return entries.toArray(new Object[entries.size()][]);
+  }
+
+  // Test granularity to millis
+  @Test(dataProvider = "testGranularityToMillisDataProvider")
+  public void testGranularityToMillis(String granularity, Long millisExpected) 
{
+    Long millisActual = null;
+    DateTimeGranularitySpec granularitySpec = null;
+    try {
+      granularitySpec = new DateTimeGranularitySpec(granularity);
+      millisActual = granularitySpec.granularityToMillis();
+    } catch (Exception e) {
+      // invalid arguments
+    }
+    assertEquals(millisActual, millisExpected);
+  }
+
+  @DataProvider(name = "testGranularityToMillisDataProvider")
+  public Object[][] provideTestGranularityToMillisData() {
+
+    List<Object[]> entries = new ArrayList<>();
+
+    entries.add(new Object[]{"1:HOURS", 3600000L});
+    entries.add(new Object[]{"1:MILLISECONDS", 1L});
+    entries.add(new Object[]{"15:MINUTES", 900000L});
+    entries.add(new Object[]{"0:HOURS", null});
+    entries.add(new Object[]{null, null});
+    entries.add(new Object[]{"1:DUMMY", null});
+
+    return entries.toArray(new Object[entries.size()][]);
+  }
+
   @Test
   public void testDateTimeGranularitySpec() {
     // Old format
diff --git 
a/pinot-common/src/test/java/org/apache/pinot/common/data/FieldSpecTest.java 
b/pinot-spi/src/test/java/org/apache/pinot/spi/data/FieldSpecTest.java
similarity index 98%
rename from 
pinot-common/src/test/java/org/apache/pinot/common/data/FieldSpecTest.java
rename to pinot-spi/src/test/java/org/apache/pinot/spi/data/FieldSpecTest.java
index 98f03b2ed9..10f7463990 100644
--- a/pinot-common/src/test/java/org/apache/pinot/common/data/FieldSpecTest.java
+++ b/pinot-spi/src/test/java/org/apache/pinot/spi/data/FieldSpecTest.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.pinot.common.data;
+package org.apache.pinot.spi.data;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import java.io.IOException;
@@ -27,12 +27,6 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Random;
 import java.util.concurrent.TimeUnit;
-import org.apache.pinot.spi.data.DateTimeFieldSpec;
-import org.apache.pinot.spi.data.DimensionFieldSpec;
-import org.apache.pinot.spi.data.FieldSpec;
-import org.apache.pinot.spi.data.MetricFieldSpec;
-import org.apache.pinot.spi.data.TimeFieldSpec;
-import org.apache.pinot.spi.data.TimeGranularitySpec;
 import org.apache.pinot.spi.utils.JsonUtils;
 import org.testng.Assert;
 import org.testng.annotations.DataProvider;
diff --git 
a/pinot-common/src/test/java/org/apache/pinot/common/data/SchemaTest.java 
b/pinot-spi/src/test/java/org/apache/pinot/spi/data/SchemaTest.java
similarity index 72%
rename from 
pinot-common/src/test/java/org/apache/pinot/common/data/SchemaTest.java
rename to pinot-spi/src/test/java/org/apache/pinot/spi/data/SchemaTest.java
index e8fd128729..c4a2aee5be 100644
--- a/pinot-common/src/test/java/org/apache/pinot/common/data/SchemaTest.java
+++ b/pinot-spi/src/test/java/org/apache/pinot/spi/data/SchemaTest.java
@@ -16,23 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.pinot.common.data;
+package org.apache.pinot.spi.data;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import java.io.File;
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.net.URL;
 import java.sql.Timestamp;
 import java.util.concurrent.TimeUnit;
-import org.apache.pinot.common.utils.SchemaUtils;
-import org.apache.pinot.spi.data.DateTimeFieldSpec;
-import org.apache.pinot.spi.data.DimensionFieldSpec;
-import org.apache.pinot.spi.data.FieldSpec;
-import org.apache.pinot.spi.data.MetricFieldSpec;
-import org.apache.pinot.spi.data.Schema;
-import org.apache.pinot.spi.data.TimeFieldSpec;
-import org.apache.pinot.spi.data.TimeGranularitySpec;
 import org.apache.pinot.spi.data.TimeGranularitySpec.TimeFormat;
 import org.apache.pinot.spi.utils.BytesUtils;
 import org.apache.pinot.spi.utils.JsonUtils;
@@ -43,12 +33,12 @@ import org.testng.annotations.Test;
 import org.testng.collections.Lists;
 
 
+@SuppressWarnings("deprecation")
 public class SchemaTest {
   public static final Logger LOGGER = 
LoggerFactory.getLogger(SchemaTest.class);
 
   @Test
-  public void testValidation()
-      throws Exception {
+  public void testValidation() {
     Schema schemaToValidate;
 
     schemaToValidate = new Schema();
@@ -296,32 +286,6 @@ public class SchemaTest {
     Assert.assertEquals(schema11, schema12);
   }
 
-  @Test
-  public void testSerializeDeserialize()
-      throws Exception {
-    URL resourceUrl = 
getClass().getClassLoader().getResource("schemaTest.schema");
-    Assert.assertNotNull(resourceUrl);
-    Schema schema = Schema.fromFile(new File(resourceUrl.getFile()));
-
-    Schema schemaToCompare = Schema.fromString(schema.toPrettyJsonString());
-    Assert.assertEquals(schemaToCompare, schema);
-    Assert.assertEquals(schemaToCompare.hashCode(), schema.hashCode());
-
-    schemaToCompare = Schema.fromString(schema.toSingleLineJsonString());
-    Assert.assertEquals(schemaToCompare, schema);
-    Assert.assertEquals(schemaToCompare.hashCode(), schema.hashCode());
-
-    schemaToCompare = SchemaUtils.fromZNRecord(SchemaUtils.toZNRecord(schema));
-    Assert.assertEquals(schemaToCompare, schema);
-    Assert.assertEquals(schemaToCompare.hashCode(), schema.hashCode());
-
-    // When setting new fields, schema string should be updated
-    String jsonSchema = schemaToCompare.toSingleLineJsonString();
-    schemaToCompare.setSchemaName("newSchema");
-    String jsonSchemaToCompare = schemaToCompare.toSingleLineJsonString();
-    Assert.assertNotEquals(jsonSchemaToCompare, jsonSchema);
-  }
-
   @Test
   public void testSerializeDeserializeOptions()
       throws IOException {
@@ -358,13 +322,9 @@ public class SchemaTest {
   }
 
   @Test
-  public void testTimestampFormatOverride()
-      throws Exception {
-    URL resourceUrl = 
getClass().getClassLoader().getResource("schemaTest.schema");
-    Assert.assertNotNull(resourceUrl);
-    Schema schema = Schema.fromFile(new File(resourceUrl.getFile()));
-    DateTimeFieldSpec fieldSpec = schema.getDateTimeSpec("dateTime3");
-    Assert.assertNotNull(fieldSpec);
+  public void testTimestampFormatOverride() {
+    DateTimeFieldSpec fieldSpec =
+        new DateTimeFieldSpec("dateTime", FieldSpec.DataType.TIMESTAMP, 
"1:MILLISECONDS:EPOCH", "1:SECONDS");
     Assert.assertEquals(fieldSpec.getFormat(), "TIMESTAMP");
   }
 
@@ -392,6 +352,178 @@ public class SchemaTest {
     Assert.assertEquals(actualSchema.hashCode(), expectedSchema.hashCode());
   }
 
+  @Test
+  public void testConversionFromTimeToDateTimeSpec() {
+    TimeFieldSpec timeFieldSpec;
+    DateTimeFieldSpec expectedDateTimeFieldSpec;
+    DateTimeFieldSpec actualDateTimeFieldSpec;
+
+    /* 1] only incoming */
+
+    // incoming epoch millis
+    timeFieldSpec =
+        new TimeFieldSpec(new TimeGranularitySpec(FieldSpec.DataType.LONG, 
TimeUnit.MILLISECONDS, "incoming"));
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("incoming", FieldSpec.DataType.LONG, 
"1:MILLISECONDS:EPOCH", "1:MILLISECONDS");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // incoming epoch hours
+    timeFieldSpec = new TimeFieldSpec(new 
TimeGranularitySpec(FieldSpec.DataType.INT, TimeUnit.HOURS, "incoming"));
+    expectedDateTimeFieldSpec = new DateTimeFieldSpec("incoming", 
FieldSpec.DataType.INT, "1:HOURS:EPOCH", "1:HOURS");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // Simple date format
+    timeFieldSpec = new TimeFieldSpec(
+        new TimeGranularitySpec(FieldSpec.DataType.INT, TimeUnit.DAYS, 
"SIMPLE_DATE_FORMAT:yyyyMMdd", "incoming"));
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("incoming", FieldSpec.DataType.INT, 
"1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", "1:DAYS");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // simple date format STRING
+    timeFieldSpec = new TimeFieldSpec(
+        new TimeGranularitySpec(FieldSpec.DataType.STRING, TimeUnit.DAYS, 
"SIMPLE_DATE_FORMAT:yyyy-MM-dd hh-mm-ss",
+            "incoming"));
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("incoming", FieldSpec.DataType.STRING, 
"1:DAYS:SIMPLE_DATE_FORMAT:yyyy-MM-dd hh-mm-ss",
+            "1:DAYS");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // time unit size
+    timeFieldSpec =
+        new TimeFieldSpec(new TimeGranularitySpec(FieldSpec.DataType.LONG, 5, 
TimeUnit.MINUTES, "incoming"));
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("incoming", FieldSpec.DataType.LONG, 
"5:MINUTES:EPOCH", "5:MINUTES");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // transform function
+    timeFieldSpec = new TimeFieldSpec(new 
TimeGranularitySpec(FieldSpec.DataType.INT, TimeUnit.HOURS, "incoming"));
+    timeFieldSpec.setTransformFunction("toEpochHours(timestamp)");
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("incoming", FieldSpec.DataType.INT, 
"1:HOURS:EPOCH", "1:HOURS", null,
+            "toEpochHours(timestamp)");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    /* 2] incoming + outgoing */
+
+    // same incoming and outgoing
+    timeFieldSpec = new TimeFieldSpec(new 
TimeGranularitySpec(FieldSpec.DataType.LONG, TimeUnit.HOURS, "time"),
+        new TimeGranularitySpec(FieldSpec.DataType.LONG, TimeUnit.HOURS, 
"time"));
+    expectedDateTimeFieldSpec = new DateTimeFieldSpec("time", 
FieldSpec.DataType.LONG, "1:HOURS:EPOCH", "1:HOURS");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // same incoming and outgoing - simple date format
+    timeFieldSpec = new TimeFieldSpec(
+        new TimeGranularitySpec(FieldSpec.DataType.LONG, TimeUnit.DAYS, 
"SIMPLE_DATE_FORMAT:yyyyMMdd", "time"),
+        new TimeGranularitySpec(FieldSpec.DataType.LONG, TimeUnit.DAYS, 
"SIMPLE_DATE_FORMAT:yyyyMMdd", "time"));
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("time", FieldSpec.DataType.LONG, 
"1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", "1:DAYS");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // millis to hours
+    timeFieldSpec =
+        new TimeFieldSpec(new TimeGranularitySpec(FieldSpec.DataType.LONG, 
TimeUnit.MILLISECONDS, "incoming"),
+            new TimeGranularitySpec(FieldSpec.DataType.LONG, TimeUnit.HOURS, 
"outgoing"));
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("outgoing", FieldSpec.DataType.LONG, 
"1:HOURS:EPOCH", "1:HOURS", null,
+            "toEpochHours(incoming)");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // millis to bucketed minutes
+    timeFieldSpec =
+        new TimeFieldSpec(new TimeGranularitySpec(FieldSpec.DataType.LONG, 
TimeUnit.MILLISECONDS, "incoming"),
+            new TimeGranularitySpec(FieldSpec.DataType.LONG, 10, 
TimeUnit.MINUTES, "outgoing"));
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("outgoing", FieldSpec.DataType.LONG, 
"10:MINUTES:EPOCH", "10:MINUTES", null,
+            "toEpochMinutesBucket(incoming, 10)");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // days to millis
+    timeFieldSpec = new TimeFieldSpec(new 
TimeGranularitySpec(FieldSpec.DataType.INT, TimeUnit.DAYS, "incoming"),
+        new TimeGranularitySpec(FieldSpec.DataType.LONG, 
TimeUnit.MILLISECONDS, "outgoing"));
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("outgoing", FieldSpec.DataType.LONG, 
"1:MILLISECONDS:EPOCH", "1:MILLISECONDS", null,
+            "fromEpochDays(incoming)");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // bucketed minutes to millis
+    timeFieldSpec = new TimeFieldSpec(new 
TimeGranularitySpec(FieldSpec.DataType.LONG, 5, TimeUnit.MINUTES, "incoming"),
+        new TimeGranularitySpec(FieldSpec.DataType.LONG, 
TimeUnit.MILLISECONDS, "outgoing"));
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("outgoing", FieldSpec.DataType.LONG, 
"1:MILLISECONDS:EPOCH", "1:MILLISECONDS", null,
+            "fromEpochMinutesBucket(incoming, 5)");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // hours to days
+    timeFieldSpec = new TimeFieldSpec(new 
TimeGranularitySpec(FieldSpec.DataType.INT, TimeUnit.HOURS, "incoming"),
+        new TimeGranularitySpec(FieldSpec.DataType.INT, TimeUnit.DAYS, 
"outgoing"));
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("outgoing", FieldSpec.DataType.INT, 
"1:DAYS:EPOCH", "1:DAYS", null,
+            "toEpochDays(fromEpochHours(incoming))");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // minutes to hours
+    timeFieldSpec = new TimeFieldSpec(new 
TimeGranularitySpec(FieldSpec.DataType.LONG, TimeUnit.MINUTES, "incoming"),
+        new TimeGranularitySpec(FieldSpec.DataType.LONG, TimeUnit.HOURS, 
"outgoing"));
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("outgoing", FieldSpec.DataType.LONG, 
"1:HOURS:EPOCH", "1:HOURS", null,
+            "toEpochHours(fromEpochMinutes(incoming))");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // bucketed minutes to days
+    timeFieldSpec =
+        new TimeFieldSpec(new TimeGranularitySpec(FieldSpec.DataType.LONG, 10, 
TimeUnit.MINUTES, "incoming"),
+            new TimeGranularitySpec(FieldSpec.DataType.LONG, TimeUnit.DAYS, 
"outgoing"));
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("outgoing", FieldSpec.DataType.LONG, 
"1:DAYS:EPOCH", "1:DAYS", null,
+            "toEpochDays(fromEpochMinutesBucket(incoming, 10))");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // seconds to bucketed minutes
+    timeFieldSpec = new TimeFieldSpec(new 
TimeGranularitySpec(FieldSpec.DataType.LONG, TimeUnit.SECONDS, "incoming"),
+        new TimeGranularitySpec(FieldSpec.DataType.LONG, 5, TimeUnit.MINUTES, 
"outgoing"));
+    expectedDateTimeFieldSpec =
+        new DateTimeFieldSpec("outgoing", FieldSpec.DataType.LONG, 
"5:MINUTES:EPOCH", "5:MINUTES", null,
+            "toEpochMinutesBucket(fromEpochSeconds(incoming), 5)");
+    actualDateTimeFieldSpec = Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+    Assert.assertEquals(actualDateTimeFieldSpec, expectedDateTimeFieldSpec);
+
+    // simple date format to millis
+    timeFieldSpec = new TimeFieldSpec(
+        new TimeGranularitySpec(FieldSpec.DataType.LONG, TimeUnit.DAYS, 
"SIMPLE_DATE_FORMAT:yyyyMMdd", "incoming"),
+        new TimeGranularitySpec(FieldSpec.DataType.LONG, 
TimeUnit.MILLISECONDS, "outgoing"));
+    try {
+      Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+      Assert.fail();
+    } catch (Exception e) {
+      // expected
+    }
+
+    // hours to simple date format
+    timeFieldSpec = new TimeFieldSpec(new 
TimeGranularitySpec(FieldSpec.DataType.LONG, TimeUnit.HOURS, "incoming"),
+        new TimeGranularitySpec(FieldSpec.DataType.INT, TimeUnit.HOURS, 
"SIMPLE_DATE_FORMAT:yyyyMMddhh", "outgoing"));
+    try {
+      Schema.convertToDateTimeFieldSpec(timeFieldSpec);
+      Assert.fail();
+    } catch (Exception e) {
+      // expected
+    }
+  }
+
   @Test
   public void testSchemaBackwardCompatibility() {
     Schema oldSchema = new 
Schema.SchemaBuilder().addSingleValueDimension("svDimension", 
FieldSpec.DataType.INT)
@@ -402,6 +534,7 @@ public class SchemaTest {
         .addTime(new TimeGranularitySpec(FieldSpec.DataType.LONG, 
TimeUnit.DAYS, "time"), null)
         .addDateTime("dateTime", FieldSpec.DataType.LONG, "1:HOURS:EPOCH", 
"1:HOURS").build();
 
+    //noinspection DataFlowIssue
     Assert.assertThrows(NullPointerException.class, () -> 
oldSchema.isBackwardCompatibleWith(null));
 
     // remove column


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org
For additional commands, e-mail: commits-h...@pinot.apache.org

Reply via email to