Repository: jtablesaw/tablesaw
Pull request #518 (2019-05-02 20:01:20)

Add InstantColumn
Thanks for contributing.

- [X] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](https://developercertificate.org)

## Description

I refactored a lot of existing code from `LocalDateTime` into a new `temporal` package to reduce copy/pasted code. If we do end up deciding we want to add a `ZonedDateTime` hopefully it should help with that as well.

Closes https://github.com/jtablesaw/tablesaw/issues/516


Patch:
diff --git a/core/src/main/java/tech/tablesaw/api/ColumnType.java b/core/src/main/java/tech/tablesaw/api/ColumnType.java
index 78d3d286..60609e34 100644
--- a/core/src/main/java/tech/tablesaw/api/ColumnType.java
+++ b/core/src/main/java/tech/tablesaw/api/ColumnType.java
@@ -1,12 +1,17 @@
 package tech.tablesaw.api;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import com.google.common.base.Preconditions;
+
 import tech.tablesaw.columns.AbstractColumnParser;
 import tech.tablesaw.columns.Column;
 import tech.tablesaw.columns.SkipColumnType;
 import tech.tablesaw.columns.booleans.BooleanColumnType;
 import tech.tablesaw.columns.dates.DateColumnType;
 import tech.tablesaw.columns.datetimes.DateTimeColumnType;
+import tech.tablesaw.columns.instant.InstantColumnType;
 import tech.tablesaw.columns.numbers.DoubleColumnType;
 import tech.tablesaw.columns.numbers.FloatColumnType;
 import tech.tablesaw.columns.numbers.IntColumnType;
@@ -17,9 +22,6 @@ import tech.tablesaw.columns.strings.TextColumnType;
 import tech.tablesaw.columns.times.TimeColumnType;
 import tech.tablesaw.io.ReadOptions;
 
-import java.util.HashMap;
-import java.util.Map;
-
 public interface ColumnType {
 
     Map<String, ColumnType> values = new HashMap<>();
@@ -33,8 +35,9 @@ public interface ColumnType {
     StringColumnType STRING = StringColumnType.instance();
     DoubleColumnType DOUBLE = DoubleColumnType.instance();
     DateColumnType LOCAL_DATE = DateColumnType.instance();
-    DateTimeColumnType LOCAL_DATE_TIME = DateTimeColumnType.instance();
     TimeColumnType LOCAL_TIME = TimeColumnType.instance();
+    DateTimeColumnType LOCAL_DATE_TIME = DateTimeColumnType.instance();
+    InstantColumnType INSTANT = InstantColumnType.instance();
     TextColumnType TEXT = TextColumnType.instance();
     SkipColumnType SKIP = SkipColumnType.instance();
 
diff --git a/core/src/main/java/tech/tablesaw/api/DateTimeColumn.java b/core/src/main/java/tech/tablesaw/api/DateTimeColumn.java
index 7f5a5aac..b001a062 100644
--- a/core/src/main/java/tech/tablesaw/api/DateTimeColumn.java
+++ b/core/src/main/java/tech/tablesaw/api/DateTimeColumn.java
@@ -14,7 +14,27 @@
 
 package tech.tablesaw.api;
 
+import java.nio.ByteBuffer;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
 import com.google.common.base.Preconditions;
+
 import it.unimi.dsi.fastutil.ints.IntComparator;
 import it.unimi.dsi.fastutil.longs.LongArrayList;
 import it.unimi.dsi.fastutil.longs.LongArrays;
@@ -27,35 +47,18 @@ import tech.tablesaw.columns.AbstractColumnParser;
 import tech.tablesaw.columns.Column;
 import tech.tablesaw.columns.datetimes.DateTimeColumnFormatter;
 import tech.tablesaw.columns.datetimes.DateTimeColumnType;
-import tech.tablesaw.columns.datetimes.DateTimeFillers;
 import tech.tablesaw.columns.datetimes.DateTimeFilters;
 import tech.tablesaw.columns.datetimes.DateTimeMapFunctions;
 import tech.tablesaw.columns.datetimes.PackedLocalDateTime;
+import tech.tablesaw.columns.temporal.TemporalFillers;
 import tech.tablesaw.selection.Selection;
 import tech.tablesaw.sorting.comparators.DescendingLongComparator;
 
-import java.nio.ByteBuffer;
-import java.sql.Timestamp;
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-
 /**
  * A column in a table that contains long-integer encoded (packed) local date-time values
  */
 public class DateTimeColumn extends AbstractColumn<LocalDateTime>
-    implements DateTimeMapFunctions, DateTimeFilters, DateTimeFillers<DateTimeColumn>,
+    implements DateTimeMapFunctions, DateTimeFilters, TemporalFillers<LocalDateTime, DateTimeColumn>,
         CategoricalColumn<LocalDateTime> {
 
     private final LongComparator reverseLongComparator = DescendingLongComparator.instance();
@@ -375,7 +378,7 @@ public class DateTimeColumn extends AbstractColumn<LocalDateTime>
      * Returns an array where each entry is the difference, measured in seconds,
      * between the LocalDateTime and midnight, January 1, 1970 UTC.
      *
-     * If a value is missing, Long.MIN_VALUE is used
+     * If a value is missing, DateTimeColumnType.missingValueIndicator() is used
      */
     public long[] asEpochSecondArray() {
         return asEpochSecondArray(ZoneOffset.UTC);
@@ -384,14 +387,14 @@ public class DateTimeColumn extends AbstractColumn<LocalDateTime>
     /**
      * Returns the seconds from epoch for each value as an array based on the given offset
      *
-     * If a value is missing, Long.MIN_VALUE is used
+     * If a value is missing, DateTimeColumnType.missingValueIndicator() is used
      */
     public long[] asEpochSecondArray(ZoneOffset offset) {
         long[] output = new long[data.size()];
         for (int i = 0; i < data.size(); i++) {
             LocalDateTime dateTime = PackedLocalDateTime.asLocalDateTime(data.getLong(i));
             if (dateTime == null) {
-                output[i] = Long.MIN_VALUE;
+                output[i] = DateTimeColumnType.missingValueIndicator();
             } else {
                 output[i] = dateTime.toEpochSecond(offset);
             }
@@ -403,7 +406,7 @@ public class DateTimeColumn extends AbstractColumn<LocalDateTime>
      * Returns an array where each entry is the difference, measured in milliseconds,
      * between the LocalDateTime and midnight, January 1, 1970 UTC.
      *
-     * If a missing value is encountered, Long.MIN_VALUE is inserted in the array
+     * If a missing value is encountered, DateTimeColumnType.missingValueIndicator() is inserted in the array
      */
     public long[] asEpochMillisArray() {
         return asEpochMillisArray(ZoneOffset.UTC);
@@ -413,14 +416,14 @@ public class DateTimeColumn extends AbstractColumn<LocalDateTime>
      * Returns an array where each entry is the difference, measured in milliseconds,
      * between the LocalDateTime and midnight, January 1, 1970 UTC.
      *
-     * If a missing value is encountered, Long.MIN_VALUE is inserted in the array
+     * If a missing value is encountered, DateTimeColumnType.missingValueIndicator() is inserted in the array
      */
     public long[] asEpochMillisArray(ZoneOffset offset) {
         long[] output = new long[data.size()];
         for (int i = 0; i < data.size(); i++) {
             LocalDateTime dateTime = PackedLocalDateTime.asLocalDateTime(data.getLong(i));
             if (dateTime == null) {
-                output[i] = Long.MIN_VALUE;
+                output[i] = DateTimeColumnType.missingValueIndicator();
             } else {
                 output[i] = dateTime.toInstant(offset).toEpochMilli();
             }
@@ -428,6 +431,23 @@ public class DateTimeColumn extends AbstractColumn<LocalDateTime>
         return output;
     }
 
+    public InstantColumn asInstantColumn() {
+        return asInstantColumn(ZoneOffset.UTC);
+    }
+
+    public InstantColumn asInstantColumn(ZoneId zone) {
+        Instant[] output = new Instant[data.size()];
+        for (int i = 0; i < data.size(); i++) {
+            LocalDateTime dateTime = PackedLocalDateTime.asLocalDateTime(data.getLong(i));
+            if (dateTime == null) {
+                output[i] = null;
+            } else {
+                output[i] = dateTime.atZone(zone).toInstant();
+            }
+        }
+        return InstantColumn.create(name(), output);
+    }
+
     @Override
     public DateTimeColumn append(Column<LocalDateTime> column) {
         Preconditions.checkArgument(column.type() == this.type());
diff --git a/core/src/main/java/tech/tablesaw/api/InstantColumn.java b/core/src/main/java/tech/tablesaw/api/InstantColumn.java
new file mode 100644
index 00000000..e73ec4ef
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/api/InstantColumn.java
@@ -0,0 +1,749 @@
+/*
+ * Licensed 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 tech.tablesaw.api;
+
+import java.nio.ByteBuffer;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import com.google.common.base.Preconditions;
+
+import it.unimi.dsi.fastutil.ints.IntComparator;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.longs.LongArrays;
+import it.unimi.dsi.fastutil.longs.LongComparator;
+import it.unimi.dsi.fastutil.longs.LongIterator;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import it.unimi.dsi.fastutil.longs.LongSet;
+import tech.tablesaw.columns.AbstractColumn;
+import tech.tablesaw.columns.AbstractColumnParser;
+import tech.tablesaw.columns.Column;
+import tech.tablesaw.columns.instant.InstantColumnFormatter;
+import tech.tablesaw.columns.instant.InstantColumnType;
+import tech.tablesaw.columns.instant.PackedInstant;
+import tech.tablesaw.columns.temporal.TemporalFillers;
+import tech.tablesaw.columns.temporal.TemporalFilters;
+import tech.tablesaw.columns.temporal.TemporalMapFunctions;
+import tech.tablesaw.selection.Selection;
+import tech.tablesaw.sorting.comparators.DescendingLongComparator;
+
+/**
+ * A column in a table that contains long-integer encoded (packed) local date-time values
+ */
+public class InstantColumn extends AbstractColumn<Instant>
+    implements TemporalMapFunctions<Instant>, TemporalFillers<Instant, InstantColumn>,
+        TemporalFilters<Instant>, CategoricalColumn<Instant> {
+
+    private final LongComparator reverseLongComparator = DescendingLongComparator.instance();
+
+    private LongArrayList data;
+
+    private final IntComparator comparator = (r1, r2) -> {
+        long f1 = getPackedDateTime(r1);
+        long f2 = getPackedDateTime(r2);
+        return Long.compare(f1, f2);
+    };
+
+    private InstantColumnFormatter printFormatter = new InstantColumnFormatter();
+
+    private InstantColumn(String name, LongArrayList data) {
+        super(InstantColumnType.instance(), name);
+        this.data = data;
+    }
+
+    public static InstantColumn create(String name) {
+        return new InstantColumn(name, new LongArrayList(DEFAULT_ARRAY_SIZE));
+    }
+
+    public static InstantColumn create(String name, int initialSize) {
+        InstantColumn column = new InstantColumn(name, new LongArrayList(initialSize));
+        for (int i = 0; i < initialSize; i++) {
+            column.appendMissing();
+        }
+        return column;
+    }
+
+    public static InstantColumn create(String name, List<Instant> data) {
+        InstantColumn column = new InstantColumn(name, new LongArrayList(data.size()));
+        for (Instant date : data) {
+            column.append(date);
+        }
+        return column;
+    }
+
+    public static InstantColumn create(String name, Instant[] data) {
+        InstantColumn column = new InstantColumn(name, new LongArrayList(data.length));
+        for (Instant date : data) {
+            column.append(date);
+        }
+        return column;
+    }
+
+    public static boolean valueIsMissing(long value) {
+        return InstantColumnType.missingValueIndicator() == value;
+    }
+
+    @Override
+    public boolean isMissing(int rowNumber) {
+        return valueIsMissing(getLongInternal(rowNumber));
+    }
+
+    @Override
+    public InstantColumn subset(final int[] rows) {
+        final InstantColumn c = this.emptyCopy();
+        for (final int row : rows) {
+            c.appendInternal(getLongInternal(row));
+        }
+        return c;
+    }
+
+    @Override
+    public InstantColumn removeMissing() {
+        InstantColumn noMissing = emptyCopy();
+        LongIterator iterator = longIterator();
+        while (iterator.hasNext()) {
+            long i = iterator.nextLong();
+            if (!valueIsMissing(i)) {
+                noMissing.appendInternal(i);
+            }
+        }
+        return noMissing;
+    }
+
+    public boolean contains(Instant dateTime) {
+        long dt = PackedInstant.pack(dateTime);
+        return data().contains(dt);
+    }
+
+    @Override
+    public Column<Instant> setMissing(int i) {
+        return set(i, InstantColumnType.missingValueIndicator());
+    }
+
+    public InstantColumn where(Selection selection) {
+        return subset(selection.toArray());
+    }
+
+    public void setPrintFormatter(InstantColumnFormatter formatter) {
+        Preconditions.checkNotNull(formatter);
+        this.printFormatter = formatter;
+    }
+
+    @Override
+    public InstantColumn lag(int n) {
+        int srcPos = n >= 0 ? 0 : 0 - n;
+        long[] dest = new long[size()];
+        int destPos = n <= 0 ? 0 : n;
+        int length = n >= 0 ? size() - n : size() + n;
+
+        for (int i = 0; i < size(); i++) {
+            dest[i] = InstantColumnType.missingValueIndicator();
+        }
+
+        System.arraycopy(data.toLongArray(), srcPos, dest, destPos, length);
+
+        InstantColumn copy = emptyCopy(size());
+        copy.data = new LongArrayList(dest);
+        copy.setName(name() + " lag(" + n + ")");
+        return copy;
+    }
+
+    @Override
+    public InstantColumn appendCell(String stringValue) {
+        return appendInternal(PackedInstant.pack(InstantColumnType.DEFAULT_PARSER.parse(stringValue)));
+    }
+
+    @Override
+    public InstantColumn appendCell(String stringValue, AbstractColumnParser<?> parser) {
+        return appendObj(parser.parse(stringValue));
+    }
+
+    public InstantColumn append(Instant dateTime) {
+        if (dateTime != null) {
+            final long dt = PackedInstant.pack(dateTime);
+            appendInternal(dt);
+        } else {
+            appendInternal(InstantColumnType.missingValueIndicator());
+        }
+        return this;
+    }
+
+    @Override
+    public InstantColumn appendObj(Object obj) {
+        if (obj == null) {
+            return appendMissing();
+        }
+        if (obj instanceof Instant) {
+            return append((Instant) obj);
+        }
+        if (obj instanceof Timestamp ){
+            Timestamp timestamp = (Timestamp) obj;
+            return append(timestamp.toInstant());
+        }
+        throw new IllegalArgumentException("Cannot append " + obj.getClass().getName() + " to DateTimeColumn");
+    }
+
+    public int size() {
+        return data.size();
+    }
+
+    public LongArrayList data() {
+        return data;
+    }
+
+    public InstantColumn appendInternal(long dateTime) {
+        data.add(dateTime);
+        return this;
+    }
+
+    @Override
+    public String getString(int row) {
+        return printFormatter.format(getPackedDateTime(row));
+    }
+
+    @Override
+    public String getUnformattedString(int row) {
+        return PackedInstant.toString(getPackedDateTime(row));
+    }
+
+    @Override
+    public InstantColumn emptyCopy() {
+        InstantColumn empty = create(name());
+        empty.printFormatter = printFormatter;
+        return empty;
+    }
+
+    @Override
+    public InstantColumn emptyCopy(int rowSize) {
+        InstantColumn column = create(name(), rowSize);
+        column.setPrintFormatter(printFormatter);
+        return column;
+    }
+
+    @Override
+    public InstantColumn copy() {
+        InstantColumn column = emptyCopy(data.size());
+        column.data = data.clone();
+        return column;
+    }
+
+    @Override
+    public void clear() {
+        data.clear();
+    }
+
+    @Override
+    public void sortAscending() {
+        Arrays.parallelSort(data.elements());
+    }
+
+    @Override
+    public void sortDescending() {
+        LongArrays.parallelQuickSort(data.elements(), reverseLongComparator);
+    }
+
+    @Override
+    public Table summary() {
+        Table table = Table.create("Column: " + name());
+        StringColumn measure = StringColumn.create("Measure");
+        StringColumn value = StringColumn.create("Value");
+        table.addColumns(measure);
+        table.addColumns(value);
+
+        measure.append("Count");
+        value.append(String.valueOf(size()));
+
+        measure.append("Missing");
+        value.append(String.valueOf(countMissing()));
+
+        measure.append("Earliest");
+        value.append(String.valueOf(min()));
+
+        measure.append("Latest");
+        value.append(String.valueOf(max()));
+
+        return table;
+    }
+
+    @Override
+    public int countUnique() {
+        LongSet ints = new LongOpenHashSet(data.size());
+        for (long i : data) {
+            ints.add(i);
+        }
+        return ints.size();
+    }
+
+    @Override
+    public InstantColumn unique() {
+        LongSet ints = new LongOpenHashSet(data.size());
+        for (long i : data) {
+            ints.add(i);
+        }
+        InstantColumn column = emptyCopy(ints.size());
+        column.setName(name() + " Unique values");
+        column.data = LongArrayList.wrap(ints.toLongArray());
+        return column;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return data.isEmpty();
+    }
+
+    public long getLongInternal(int index) {
+        return data.getLong(index);
+    }
+
+    protected long getPackedDateTime(int index) {
+        return getLongInternal(index);
+    }
+
+    public Instant get(int index) {
+        return PackedInstant.asInstant(getPackedDateTime(index));
+    }
+
+    @Override
+    public IntComparator rowComparator() {
+        return comparator;
+    }
+
+    /**
+     * Conditionally update this column, replacing current values with newValue for all rows where the current value
+     * matches the selection criteria
+     * <p>
+     * Example:
+     * myColumn.set(myColumn.valueIsMissing(), Instant.now()); // no more missing values
+     */
+    public InstantColumn set(Selection rowSelection, Instant newValue) {
+        for (int row : rowSelection) {
+            set(row, newValue);
+        }
+        return this;
+    }
+
+    /**
+     * Returns the count of missing values in this column
+     */
+    @Override
+    public int countMissing() {
+        int count = 0;
+        for (int i = 0; i < size(); i++) {
+            if (getPackedDateTime(i) == InstantColumnType.missingValueIndicator()) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Returns an array where each entry is the difference, measured in seconds,
+     * between the Instant and midnight, January 1, 1970 UTC.
+     *
+     * If a value is missing, InstantColumnType.missingValueIndicator() is used
+     */
+    public long[] asEpochSecondArray() {
+        return asEpochSecondArray(ZoneOffset.UTC);
+    }
+
+    /**
+     * Returns the seconds from epoch for each value as an array based on the given offset
+     *
+     * If a value is missing, InstantColumnType.missingValueIndicator() is used
+     */
+    public long[] asEpochSecondArray(ZoneOffset offset) {
+        long[] output = new long[data.size()];
+        for (int i = 0; i < data.size(); i++) {
+            Instant instant = PackedInstant.asInstant(data.getLong(i));
+            if (instant == null) {
+                output[i] = InstantColumnType.missingValueIndicator();
+            } else {
+                output[i] = instant.getEpochSecond();
+            }
+        }
+        return output;
+    }
+
+    /**
+     * Returns an array where each entry is the difference, measured in milliseconds,
+     * between the Instant and midnight, January 1, 1970 UTC.
+     *
+     * If a missing value is encountered, InstantColumnType.missingValueIndicator() is inserted in the array
+     */
+    public long[] asEpochMillisArray() {
+        return asEpochMillisArray(ZoneOffset.UTC);
+    }
+
+    /**
+     * Returns an array where each entry is the difference, measured in milliseconds,
+     * between the Instant and midnight, January 1, 1970 UTC.
+     *
+     * If a missing value is encountered, InstantColumnType.missingValueIndicator() is inserted in the array
+     */
+    public long[] asEpochMillisArray(ZoneOffset offset) {
+        long[] output = new long[data.size()];
+        for (int i = 0; i < data.size(); i++) {
+            Instant instant = PackedInstant.asInstant(data.getLong(i));
+            if (instant == null) {
+                output[i] = InstantColumnType.missingValueIndicator();
+            } else {
+                output[i] = instant.toEpochMilli();
+            }
+        }
+        return output;
+    }
+
+    public DateTimeColumn asLocalDateTimeColumn() {
+        return asLocalDateTimeColumn(ZoneOffset.UTC);
+    }
+
+    public DateTimeColumn asLocalDateTimeColumn(ZoneId zone) {
+        LocalDateTime[] output = new LocalDateTime[data.size()];
+        for (int i = 0; i < data.size(); i++) {
+            Instant instant = PackedInstant.asInstant(data.getLong(i));
+            if (instant == null) {
+                output[i] = null;
+            } else {
+                output[i] = LocalDateTime.ofInstant(instant, zone);
+            }
+        }
+        return DateTimeColumn.create(name(), output);
+    }
+
+    @Override
+    public InstantColumn append(Column<Instant> column) {
+        Preconditions.checkArgument(column.type() == this.type());
+        InstantColumn dateTimeColumn = (InstantColumn) column;
+        final int size = dateTimeColumn.size();
+        for (int i = 0; i < size; i++) {
+            append(dateTimeColumn.get(i));
+        }
+        return this;
+    }
+
+    @Override
+    public InstantColumn append(Column<Instant> column, int row) {
+        Preconditions.checkArgument(column.type() == this.type());
+        return appendInternal(((InstantColumn) column).getLongInternal(row));
+    }
+
+    @Override
+    public InstantColumn set(int row, Column<Instant> column, int sourceRow) {
+        Preconditions.checkArgument(column.type() == this.type());
+        return set(row, ((InstantColumn) column).getLongInternal(sourceRow));
+    }
+
+    public Instant max() {
+        long max;
+        if (!isEmpty()) {
+            max = getPackedDateTime(0);
+        } else {
+            return null;
+        }
+        for (long aData : data) {
+            if (InstantColumnType.missingValueIndicator() != aData) {
+                max = (max > aData) ? max : aData;
+            }
+        }
+
+        if (InstantColumnType.missingValueIndicator() == max) {
+            return null;
+        }
+        return PackedInstant.asInstant(max);
+    }
+
+    @Override
+    public InstantColumn appendMissing() {
+        appendInternal(InstantColumnType.missingValueIndicator());
+        return this;
+    }
+
+    @Override
+    public Instant min() {
+        long min;
+
+        if (!isEmpty()) {
+            min = getPackedDateTime(0);
+        } else {
+            return null;
+        }
+        for (long aData : data) {
+            if (InstantColumnType.missingValueIndicator() != aData) {
+                min = (min < aData) ? min : aData;
+            }
+        }
+        if (Integer.MIN_VALUE == min) {
+            return null;
+        }
+        return PackedInstant.asInstant(min);
+    }
+
+    public InstantColumn set(int index, long value) {
+        data.set(index, value);
+        return this;
+    }
+
+    @Override
+    public InstantColumn set(int index, Instant value) {
+        data.set(index, PackedInstant.pack(value));
+        return this;
+    }
+
+    /**
+     * Returns the largest ("top") n values in the column
+     *
+     * @param n The maximum number of records to return. The actual number will be smaller if n is greater than the
+     *          number of observations in the column
+     * @return A list, possibly empty, of the largest observations
+     */
+    public List<Instant> top(int n) {
+        List<Instant> top = new ArrayList<>();
+        long[] values = data.toLongArray();
+        LongArrays.parallelQuickSort(values, DescendingLongComparator.instance());
+        for (int i = 0; i < n && i < values.length; i++) {
+            top.add(PackedInstant.asInstant(values[i]));
+        }
+        return top;
+    }
+
+    /**
+     * Returns the smallest ("bottom") n values in the column
+     *
+     * @param n The maximum number of records to return. The actual number will be smaller if n is greater than the
+     *          number of observations in the column
+     * @return A list, possibly empty, of the smallest n observations
+     */
+    public List<Instant> bottom(int n) {
+        List<Instant> bottom = new ArrayList<>();
+        long[] values = data.toLongArray();
+        LongArrays.parallelQuickSort(values);
+        for (int i = 0; i < n && i < values.length; i++) {
+            bottom.add(PackedInstant.asInstant(values[i]));
+        }
+        return bottom;
+    }
+
+    public LongIterator longIterator() {
+        return data.iterator();
+    }
+
+    public Set<Instant> asSet() {
+        Set<Instant> times = new HashSet<>();
+        InstantColumn unique = unique();
+        for (Instant Instant : unique) {
+            times.add(Instant);
+        }
+        return times;
+    }
+
+    public int byteSize() {
+        return type().byteSize();
+    }
+
+    /**
+     * Returns the contents of the cell at rowNumber as a byte[]
+     */
+    @Override
+    public byte[] asBytes(int rowNumber) {
+        return ByteBuffer.allocate(byteSize()).putLong(getPackedDateTime(rowNumber)).array();
+    }
+
+    public double getDouble(int i) {
+        return getPackedDateTime(i);
+    }
+
+    public double[] asDoubleArray() {
+        double[] doubles = new double[size()];
+        long[] millis = asEpochSecondArray();
+        for (int i = 0; i < millis.length; i++) {
+            doubles[i] = millis[i];
+        }
+        return doubles;
+    }
+
+    public DoubleColumn asDoubleColumn() {
+	return DoubleColumn.create(name(), asEpochSecondArray());
+    }
+
+    /**
+     * Returns an iterator over elements of type {@code T}.
+     *
+     * @return an Iterator.
+     */
+    @Override
+    public Iterator<Instant> iterator() {
+
+        return new Iterator<Instant>() {
+
+            final LongIterator longIterator = longIterator();
+
+            @Override
+            public boolean hasNext() {
+                return longIterator.hasNext();
+            }
+
+            @Override
+            public Instant next() {
+                return PackedInstant.asInstant(longIterator.nextLong());
+            }
+        };
+    }
+
+    // fillWith methods
+
+    private InstantColumn fillWith(int count, Iterator<Instant> iterator, Consumer<Instant> acceptor) {
+        for (int r = 0; r < count; r++) {
+            if (!iterator.hasNext()) {
+                break;
+            }
+            acceptor.accept(iterator.next());
+        }
+        return this;
+    }
+
+    @Override
+    public InstantColumn fillWith(Iterator<Instant> iterator) {
+        int[] r = new int[1];
+        fillWith(size(), iterator, date -> set(r[0]++, date));
+        return this;
+    }
+
+    private InstantColumn fillWith(int count, Iterable<Instant> iterable, Consumer<Instant> acceptor) {
+        Iterator<Instant> iterator = iterable.iterator();
+        for (int r = 0; r < count; r++) {
+            if (!iterator.hasNext()) {
+                iterator = iterable.iterator();
+                if (!iterator.hasNext()) {
+                    break;
+                }
+            }
+            acceptor.accept(iterator.next());
+        }
+        return this;
+    }
+
+    @Override
+    public InstantColumn fillWith(Iterable<Instant> iterable) {
+        int[] r = new int[1];
+        fillWith(size(), iterable, date -> set(r[0]++, date));
+        return this;
+    }
+
+    private InstantColumn fillWith(int count, Supplier<Instant> supplier, Consumer<Instant> acceptor) {
+        for (int r = 0; r < count; r++) {
+            try {
+                acceptor.accept(supplier.get());
+            } catch (Exception e) {
+                break;
+            }
+        }
+        return this;
+    }
+
+    @Override
+    public InstantColumn fillWith(Supplier<Instant> supplier) {
+        int[] r = new int[1];
+        fillWith(size(), supplier, date -> set(r[0]++, date));
+        return this;
+    }
+
+    @Override
+    public Instant[] asObjectArray() {
+        final Instant[] output = new Instant[data.size()];
+        for (int i = 0; i < data.size(); i++) {
+            output[i] = get(i);
+        }
+        return output;
+    }
+
+    @Override
+    public int compare(Instant o1, Instant o2) {
+        return o1.compareTo(o2);
+    }
+
+    @Override
+    public InstantColumn setName(String name) {
+        return (InstantColumn) super.setName(name);
+    }
+
+    @Override
+    public InstantColumn filter(Predicate<? super Instant> test) {
+        return (InstantColumn) super.filter(test);
+    }
+
+    @Override
+    public InstantColumn sorted(Comparator<? super Instant> comp) {
+        return (InstantColumn) super.sorted(comp);
+    }
+
+    @Override
+    public InstantColumn map(Function<? super Instant, ? extends Instant> fun) {
+        return (InstantColumn) super.map(fun);
+    }
+
+    @Override
+    public InstantColumn min(Column<Instant> other) {
+        return (InstantColumn) super.min(other);
+    }
+
+    @Override
+    public InstantColumn max(Column<Instant> other) {
+        return (InstantColumn) super.max(other);
+    }
+
+    @Override
+    public InstantColumn set(Selection condition, Column<Instant> other) {
+        return (InstantColumn) super.set(condition, other);
+    }
+
+    @Override
+    public InstantColumn first(int numRows) {
+        return (InstantColumn) super.first(numRows);
+    }
+
+    @Override
+    public InstantColumn last(int numRows) {
+        return (InstantColumn) super.last(numRows);
+    }
+
+    @Override
+    public InstantColumn inRange(int start, int end) {
+        return (InstantColumn) super.inRange(start, end);
+    }
+
+    @Override
+    public InstantColumn sampleN(int n) {
+        return (InstantColumn) super.sampleN(n);
+    }
+
+    @Override
+    public InstantColumn sampleX(double proportion) {
+        return (InstantColumn) super.sampleX(proportion);
+    }
+}
diff --git a/core/src/main/java/tech/tablesaw/api/Table.java b/core/src/main/java/tech/tablesaw/api/Table.java
index d49180aa..70646547 100644
--- a/core/src/main/java/tech/tablesaw/api/Table.java
+++ b/core/src/main/java/tech/tablesaw/api/Table.java
@@ -651,6 +651,12 @@ public class Table extends Relation implements Iterable<Row> {
         }
     }
 
+    public Row row(int rowIndex) {
+	Row row = new Row(Table.this);
+	row.at(rowIndex);
+	return row;
+    }
+
     public Table rows(int... rowNumbers) {
         Preconditions.checkArgument(Ints.max(rowNumbers) <= rowCount());
         return where(Selection.with(rowNumbers));
diff --git a/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeFilters.java b/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeFilters.java
index 593b675e..95bc715b 100644
--- a/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeFilters.java
+++ b/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeFilters.java
@@ -1,37 +1,30 @@
 package tech.tablesaw.columns.datetimes;
 
-import static tech.tablesaw.columns.datetimes.DateTimePredicates.isEqualTo;
-import static tech.tablesaw.columns.datetimes.DateTimePredicates.isGreaterThan;
-import static tech.tablesaw.columns.datetimes.DateTimePredicates.isGreaterThanOrEqualTo;
 import static tech.tablesaw.columns.datetimes.DateTimePredicates.isInYear;
-import static tech.tablesaw.columns.datetimes.DateTimePredicates.isLessThan;
-import static tech.tablesaw.columns.datetimes.DateTimePredicates.isLessThanOrEqualTo;
-import static tech.tablesaw.columns.datetimes.DateTimePredicates.isMissing;
-import static tech.tablesaw.columns.datetimes.DateTimePredicates.isNotEqualTo;
-import static tech.tablesaw.columns.datetimes.DateTimePredicates.isNotMissing;
+import static tech.tablesaw.columns.temporal.TemporalPredicates.isEqualTo;
+import static tech.tablesaw.columns.temporal.TemporalPredicates.isGreaterThan;
+import static tech.tablesaw.columns.temporal.TemporalPredicates.isGreaterThanOrEqualTo;
+import static tech.tablesaw.columns.temporal.TemporalPredicates.isLessThan;
+import static tech.tablesaw.columns.temporal.TemporalPredicates.isLessThanOrEqualTo;
+import static tech.tablesaw.columns.temporal.TemporalPredicates.isNotEqualTo;
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
-import java.util.function.BiPredicate;
-import java.util.function.LongPredicate;
-import java.util.function.Predicate;
 
-import it.unimi.dsi.fastutil.longs.LongArrayList;
 import it.unimi.dsi.fastutil.longs.LongIterator;
 import tech.tablesaw.api.DateTimeColumn;
-import tech.tablesaw.columns.Column;
-import tech.tablesaw.filtering.predicates.LongBiPredicate;
+import tech.tablesaw.columns.temporal.TemporalFilters;
 import tech.tablesaw.selection.BitmapBackedSelection;
 import tech.tablesaw.selection.Selection;
 
-public interface DateTimeFilters extends Column<LocalDateTime> {
+public interface DateTimeFilters extends TemporalFilters<LocalDateTime> {
 
     default Selection isAfter(LocalDateTime value) {
         return eval(isGreaterThan, PackedLocalDateTime.pack(value));
     }
 
     default Selection isAfter(LocalDate value) {
-        return isOnOrAfter(value.plusDays(1).atStartOfDay());
+        return isAfter(value.atStartOfDay());
     }
 
     default Selection isOnOrAfter(LocalDate value) {
@@ -238,59 +231,6 @@ public interface DateTimeFilters extends Column<LocalDateTime> {
         return eval(PackedLocalDateTime::PM);
     }
 
-    default Selection eval(LongPredicate predicate) {
-        Selection bitmap = new BitmapBackedSelection();
-        for (int idx = 0; idx < size(); idx++) {
-            long next = data().getLong(idx);
-            if (predicate.test(next)) {
-                bitmap.add(idx);
-            }
-        }
-        return bitmap;
-    }
-
-    default Selection eval(LongBiPredicate predicate, long value) {
-        Selection bitmap = new BitmapBackedSelection();
-        for (int idx = 0; idx < size(); idx++) {
-            long next = data().getLong(idx);
-            if (predicate.test(next, value)) {
-                bitmap.add(idx);
-            }
-        }
-        return bitmap;
-    }
-
-    default Selection eval(LongBiPredicate predicate, DateTimeColumn otherColumn) {
-        Selection selection = new BitmapBackedSelection();
-        for (int idx = 0; idx < size(); idx++) {
-            if (predicate.test(this.getLongInternal(idx), otherColumn.getLongInternal(idx))) {
-                selection.add(idx);
-            }
-        }
-        return selection;
-    }
-
-    default Selection eval(BiPredicate<LocalDateTime, LocalDateTime> predicate, LocalDateTime valueToCompare) {
-        Selection selection = new BitmapBackedSelection();
-        for (int idx = 0; idx < size(); idx++) {
-            if (predicate.test(get(idx), valueToCompare)) {
-                selection.add(idx);
-            }
-        }
-        return selection;
-    }
-
-    default Selection eval(Predicate<LocalDateTime> predicate) {
-        Selection selection = new BitmapBackedSelection();
-        for (int idx = 0; idx < size(); idx++) {
-            if (predicate.test(get(idx))) {
-                selection.add(idx);
-            }
-        }
-        return selection;
-    }
-
-
     default Selection isBetweenExcluding(LocalDateTime lowValue, LocalDateTime highValue) {
         return isBetweenExcluding(PackedLocalDateTime.pack(lowValue), PackedLocalDateTime.pack(highValue));
     }
@@ -299,35 +239,8 @@ public interface DateTimeFilters extends Column<LocalDateTime> {
         return isBetweenIncluding(PackedLocalDateTime.pack(lowValue), PackedLocalDateTime.pack(highValue));
     }
 
-    default Selection isBetweenExcluding(long lowPackedDateTime, long highPackedDateTime) {
-        return eval(PackedLocalDateTime::isAfter, lowPackedDateTime)
-                .and(eval(PackedLocalDateTime::isBefore, highPackedDateTime));
-    }
-
-    default Selection isBetweenIncluding(long lowPackedDateTime, long highPackedDateTime) {
-        return eval(PackedLocalDateTime::isOnOrAfter, lowPackedDateTime)
-                .and(eval(PackedLocalDateTime::isOnOrBefore, highPackedDateTime));
-    }
-
     default Selection isInYear(int year) {
         return eval(isInYear, year);
     }
 
-    @Override
-    default Selection isMissing() {
-        return eval(isMissing);
-    }
-
-    @Override
-    default Selection isNotMissing() {
-        return eval(isNotMissing);
-    }
-
-    int size();
-
-    LongArrayList data();
-
-    long getLongInternal(int index);
-
-    LocalDateTime get(int index);
 }
diff --git a/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeMapFunctions.java b/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeMapFunctions.java
index 8324baf7..6eb770d8 100644
--- a/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeMapFunctions.java
+++ b/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeMapFunctions.java
@@ -14,29 +14,6 @@
 
 package tech.tablesaw.columns.datetimes;
 
-import com.google.common.base.Strings;
-import tech.tablesaw.api.BooleanColumn;
-import tech.tablesaw.api.DateColumn;
-import tech.tablesaw.api.DateTimeColumn;
-import tech.tablesaw.api.IntColumn;
-import tech.tablesaw.api.LongColumn;
-import tech.tablesaw.api.StringColumn;
-import tech.tablesaw.api.TimeColumn;
-import tech.tablesaw.columns.Column;
-import tech.tablesaw.columns.booleans.BooleanColumnType;
-import tech.tablesaw.columns.dates.PackedLocalDate;
-import tech.tablesaw.columns.numbers.NumberColumnFormatter;
-import tech.tablesaw.columns.strings.StringColumnType;
-import tech.tablesaw.columns.times.TimeColumnType;
-
-import java.time.LocalDateTime;
-import java.time.Month;
-import java.time.temporal.ChronoUnit;
-import java.time.temporal.TemporalUnit;
-import java.time.temporal.UnsupportedTemporalTypeException;
-
-import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.asLocalDateTime;
-import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.daysUntil;
 import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.getDayOfMonth;
 import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.getDayOfWeek;
 import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.getDayOfYear;
@@ -48,60 +25,34 @@ import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.getQuarter;
 import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.getSecondOfDay;
 import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.getWeekOfYear;
 import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.getYear;
-import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.hoursUntil;
-import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.minutesUntil;
 import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.monthsUntil;
 import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.pack;
-import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.weeksUntil;
 import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.yearsUntil;
+import static tech.tablesaw.columns.instant.PackedInstant.daysUntil;
+import static tech.tablesaw.columns.instant.PackedInstant.hoursUntil;
+import static tech.tablesaw.columns.instant.PackedInstant.minutesUntil;
+import static tech.tablesaw.columns.instant.PackedInstant.weeksUntil;
 
-public interface DateTimeMapFunctions extends Column<LocalDateTime> {
-
-    default LongColumn differenceInMilliseconds(DateTimeColumn column2) {
-        return difference(column2, ChronoUnit.MILLIS);
-    }
-
-    default LongColumn differenceInSeconds(DateTimeColumn column2) {
-        return difference(column2, ChronoUnit.SECONDS);
-    }
-
-    default LongColumn differenceInMinutes(DateTimeColumn column2) {
-        return difference(column2, ChronoUnit.MINUTES);
-    }
-
-    default LongColumn differenceInHours(DateTimeColumn column2) {
-        return difference(column2, ChronoUnit.HOURS);
-    }
-
-    default LongColumn differenceInDays(DateTimeColumn column2) {
-        return difference(column2, ChronoUnit.DAYS);
-    }
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.UnsupportedTemporalTypeException;
 
-    default LongColumn differenceInYears(DateTimeColumn column2) {
-        return difference(column2, ChronoUnit.YEARS);
-    }
+import com.google.common.base.Strings;
 
-    default LongColumn difference(DateTimeColumn column2, ChronoUnit unit) {
+import tech.tablesaw.api.DateColumn;
+import tech.tablesaw.api.DateTimeColumn;
+import tech.tablesaw.api.IntColumn;
+import tech.tablesaw.api.LongColumn;
+import tech.tablesaw.api.StringColumn;
+import tech.tablesaw.api.TimeColumn;
+import tech.tablesaw.columns.dates.PackedLocalDate;
+import tech.tablesaw.columns.numbers.NumberColumnFormatter;
+import tech.tablesaw.columns.strings.StringColumnType;
+import tech.tablesaw.columns.temporal.TemporalMapFunctions;
+import tech.tablesaw.columns.times.TimeColumnType;
 
-	    LongColumn newColumn = LongColumn.create(name() + " - " + column2.name() + "[" + unit.name() + "]");
-
-        for (int r = 0; r < size(); r++) {
-            if (this.isMissing(r) || column2.isMissing(r)) {
-                newColumn.appendMissing();
-            } else {
-                long c1 = this.getLongInternal(r);
-                long c2 = column2.getLongInternal(r);
-                LocalDateTime value1 = asLocalDateTime(c1);
-                LocalDateTime value2 = asLocalDateTime(c2);
-                if (value1 != null && value2 != null) {
-                    newColumn.append(unit.between(value1, value2));
-                } else {
-                    newColumn.appendMissing();
-                }
-            }
-        }
-        return newColumn;
-    }
+public interface DateTimeMapFunctions extends TemporalMapFunctions<LocalDateTime> {
 
     default IntColumn hour() {
 	IntColumn newColumn = IntColumn.create(name() + "[" + "hour" + "]");
@@ -315,58 +266,6 @@ public interface DateTimeMapFunctions extends Column<LocalDateTime> {
         return newColumn;
     }
 
-    default DateTimeColumn plus(long amountToAdd, ChronoUnit unit) {
-
-        DateTimeColumn newColumn = DateTimeColumn.create(dateTimeColumnName(this, amountToAdd, unit));
-        DateTimeColumn column1 = (DateTimeColumn) this;
-
-        for (int r = 0; r < column1.size(); r++) {
-            long packedDateTime = column1.getLongInternal(r);
-            if (packedDateTime == DateTimeColumnType.missingValueIndicator()) {
-                newColumn.appendMissing();
-            } else {
-                newColumn.appendInternal(PackedLocalDateTime.plus(packedDateTime, amountToAdd, unit));
-            }
-        }
-        return newColumn;
-    }
-
-    default DateTimeColumn plusYears(long amountToAdd) {
-        return plus(amountToAdd, ChronoUnit.YEARS);
-    }
-
-    default DateTimeColumn plusMonths(long amountToAdd) {
-        return plus(amountToAdd, ChronoUnit.MONTHS);
-    }
-
-    default DateTimeColumn plusWeeks(long amountToAdd) {
-        return plus(amountToAdd, ChronoUnit.WEEKS);
-    }
-
-    default DateTimeColumn plusDays(long amountToAdd) {
-        return plus(amountToAdd, ChronoUnit.DAYS);
-    }
-
-    default DateTimeColumn plusHours(long amountToAdd) {
-        return plus(amountToAdd, ChronoUnit.HOURS);
-    }
-
-    default DateTimeColumn plusMinutes(long amountToAdd) {
-        return plus(amountToAdd, ChronoUnit.MINUTES);
-    }
-
-    default DateTimeColumn plusSeconds(long amountToAdd) {
-        return plus(amountToAdd, ChronoUnit.SECONDS);
-    }
-
-    default DateTimeColumn plusMillis(long amountToAdd) {
-        return plus(amountToAdd, ChronoUnit.MILLIS);
-    }
-
-    default DateTimeColumn plusMicros(long amountToAdd) {
-        return plus(amountToAdd, ChronoUnit.MICROS);
-    }
-
     /**
      * Returns a DateColumn containing the date portion of each dateTime in this DateTimeColumn
      */
@@ -396,18 +295,6 @@ public interface DateTimeMapFunctions extends Column<LocalDateTime> {
         return newColumn;
     }
 
-    default BooleanColumn missingValues() {
-	BooleanColumn newColumn = BooleanColumn.create(this.name() + " missing?");
-        for (int r = 0; r < this.size(); r++) {
-            if (isMissing(r)) {
-                newColumn.append(BooleanColumnType.BYTE_TRUE);
-            } else {
-                newColumn.append(BooleanColumnType.BYTE_FALSE);
-            }
-        }
-        return newColumn;
-    }
-
     default StringColumn dayOfWeek() {
         StringColumn newColumn = StringColumn.create(this.name() + " day of week", this.size());
         for (int r = 0; r < this.size(); r++) {
@@ -512,13 +399,4 @@ public interface DateTimeMapFunctions extends Column<LocalDateTime> {
         return timeWindow(unit, n, min());
     }
 
-    LocalDateTime get(int r);
-
-    long getLongInternal(int r);
-
-    LocalDateTime min();
-
-    static String dateTimeColumnName(Column<LocalDateTime> column1, long value, TemporalUnit unit) {
-        return column1.name() + ": " + value + " " + unit.toString() + "(s)";
-    }
 }
diff --git a/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimePredicates.java b/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimePredicates.java
index fd8927eb..3a1bb4f1 100644
--- a/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimePredicates.java
+++ b/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimePredicates.java
@@ -14,28 +14,10 @@
 
 package tech.tablesaw.columns.datetimes;
 
+import tech.tablesaw.columns.temporal.TemporalPredicates;
 import tech.tablesaw.filtering.predicates.LongBiPredicate;
 
-import java.util.function.LongPredicate;
-
-public class DateTimePredicates {
-
-    public static final LongPredicate isMissing = i -> i == DateTimeColumnType.missingValueIndicator();
-
-    public static final LongPredicate isNotMissing = i -> i != DateTimeColumnType.missingValueIndicator();
-
-    public static final LongBiPredicate isGreaterThan = (valueToTest, valueToCompareAgainst) -> valueToTest > valueToCompareAgainst;
-
-    public static final LongBiPredicate isGreaterThanOrEqualTo = (valueToTest, valueToCompareAgainst) -> valueToTest >=
-            valueToCompareAgainst;
-
-    public static final LongBiPredicate isLessThan = (valueToTest, valueToCompareAgainst) -> valueToTest < valueToCompareAgainst;
-
-    public static final LongBiPredicate isLessThanOrEqualTo = (valueToTest, valueToCompareAgainst) -> valueToTest <= valueToCompareAgainst;
-
-    public static final LongBiPredicate isEqualTo = (long valueToTest, long valueToCompareAgainst) -> valueToTest == valueToCompareAgainst;
-
-    public static final LongBiPredicate isNotEqualTo = (long valueToTest, long valueToCompareAgainst) -> valueToTest != valueToCompareAgainst;
+public class DateTimePredicates extends TemporalPredicates {
 
     public static final LongBiPredicate isInYear = (long valueToTest, long year) -> {
         return PackedLocalDateTime.isInYear(valueToTest, (int) year);
diff --git a/core/src/main/java/tech/tablesaw/columns/datetimes/PackedLocalDateTime.java b/core/src/main/java/tech/tablesaw/columns/datetimes/PackedLocalDateTime.java
index 5ba0de8f..1dea542a 100644
--- a/core/src/main/java/tech/tablesaw/columns/datetimes/PackedLocalDateTime.java
+++ b/core/src/main/java/tech/tablesaw/columns/datetimes/PackedLocalDateTime.java
@@ -14,10 +14,7 @@
 
 package tech.tablesaw.columns.datetimes;
 
-import com.google.common.base.Strings;
-import com.google.common.primitives.Ints;
-import tech.tablesaw.columns.dates.PackedLocalDate;
-import tech.tablesaw.columns.times.PackedLocalTime;
+import static tech.tablesaw.columns.datetimes.DateTimeColumnType.missingValueIndicator;
 
 import java.time.DayOfWeek;
 import java.time.LocalDate;
@@ -26,13 +23,16 @@ import java.time.LocalTime;
 import java.time.Month;
 import java.time.chrono.IsoChronology;
 import java.time.temporal.ChronoField;
-import java.time.temporal.ChronoUnit;
 import java.time.temporal.TemporalField;
-import java.time.temporal.TemporalUnit;
 import java.time.temporal.WeekFields;
 import java.util.Locale;
 
-import static tech.tablesaw.columns.datetimes.DateTimeColumnType.missingValueIndicator;
+import com.google.common.base.Strings;
+import com.google.common.primitives.Ints;
+
+import tech.tablesaw.columns.dates.PackedLocalDate;
+import tech.tablesaw.columns.instant.PackedInstant;
+import tech.tablesaw.columns.times.PackedLocalTime;
 
 /*
  * TODO(lwhite): Extend missing-value handling on predicates to DateColumn and TimeColumn
@@ -58,7 +58,7 @@ import static tech.tablesaw.columns.datetimes.DateTimeColumnType.missingValueInd
  * Neither Java nor Guava provide unsigned short support so we use char, which is a 16-bit unsigned int to
  * store values of up to 60,000 milliseconds (60 secs * 1000)
  */
-public class PackedLocalDateTime {
+public class PackedLocalDateTime extends PackedInstant {
 
     private PackedLocalDateTime() {}
 
@@ -195,17 +195,6 @@ public class PackedLocalDateTime {
         return PackedLocalDate.getQuarter(date(packedDate));
     }
 
-    /**
-     * Returns the given packedDateTime with amtToAdd of temporal units added
-     *
-     * TODO(lwhite): Replace with a native implementation that doesn't convert everything to LocalDateTime
-     */
-    public static long plus(long packedDateTime, long amountToAdd, TemporalUnit unit) {
-
-        LocalDateTime dateTime = asLocalDateTime(packedDateTime);
-        return pack(dateTime.plus(amountToAdd, unit));
-    }
-
     public static boolean isInQ1(long packedDateTime) {
         if (packedDateTime == missingValueIndicator()) return false;
         Month month = getMonth(packedDateTime);
@@ -409,34 +398,10 @@ public class PackedLocalDateTime {
         return total;
     }
 
-    public static long create(int date, int time) {
-        return (((long) date) << 32) | (time & 0xffffffffL);
-    }
-
     public static int lengthOfYear(long packedDateTime) {
         return (isLeapYear(packedDateTime) ? 366 : 365);
     }
 
-    // TODO: packed support for minutesUntil and hoursUnit. These implementations are inefficient
-    public static long minutesUntil(long packedDateTimeEnd, long packedDateTimeStart) {
-
-        return ChronoUnit.MINUTES.between(asLocalDateTime(packedDateTimeStart),
-                        asLocalDateTime(packedDateTimeEnd));
-    }
-
-    public static long hoursUntil(long packedDateTimeEnd, long packedDateTimeStart) {
-        return ChronoUnit.HOURS.between(asLocalDateTime(packedDateTimeStart), asLocalDateTime(packedDateTimeEnd));
-    }
-
-    public static int daysUntil(long packedDateTimeEnd, long packedDateTimeStart) {
-        return (int) (PackedLocalDate.toEpochDay(date(packedDateTimeEnd))
-                - PackedLocalDate.toEpochDay(date(packedDateTimeStart)));
-    }
-
-    public static int weeksUntil(long packedDateTimeEnd, long packedDateStart) {
-        return daysUntil(packedDateTimeEnd, packedDateStart)/7;
-    }
-
     public static int monthsUntil(long packedDateTimeEnd, long packedDateStart) {
 
         int start = getMonthInternal(packedDateStart) * 32 + getDayOfMonth(packedDateStart);
@@ -452,16 +417,4 @@ public class PackedLocalDateTime {
         return (getYear(packedDateTime) * 12 + getMonthValue(packedDateTime) - 1);
     }
 
-    public static boolean isEqualTo(long packedDateTime, long value) {
-        return DateTimePredicates.isEqualTo.test(packedDateTime, value);
-    }
-
-    public static boolean isOnOrAfter(long valueToTest, long valueToTestAgainst) {
-        return valueToTest >= valueToTestAgainst;
-    }
-
-    public static boolean isOnOrBefore(long valueToTest, long valueToTestAgainst) {
-        return isBefore(valueToTest, valueToTestAgainst)
-                || isEqualTo(valueToTest, valueToTestAgainst);
-    }
 }
diff --git a/core/src/main/java/tech/tablesaw/columns/instant/InstantColumnFormatter.java b/core/src/main/java/tech/tablesaw/columns/instant/InstantColumnFormatter.java
new file mode 100644
index 00000000..0c27f0b9
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/columns/instant/InstantColumnFormatter.java
@@ -0,0 +1,72 @@
+package tech.tablesaw.columns.instant;
+
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import javax.annotation.concurrent.Immutable;
+
+import tech.tablesaw.columns.times.TimeColumnType;
+
+@Immutable
+public class InstantColumnFormatter {
+
+    private final DateTimeFormatter format;
+    private final ZoneId zoneId;
+    private String missingValueString = "";
+
+    public InstantColumnFormatter() {
+        this.format = null;
+        this.zoneId = ZoneOffset.UTC;
+    }
+
+    public InstantColumnFormatter(ZoneId zoneId) {
+        this.format = null;
+        this.zoneId = zoneId;
+    }
+
+    public InstantColumnFormatter(DateTimeFormatter format) {
+        this.format = format;
+        this.zoneId = ZoneOffset.UTC;
+    }
+
+    public InstantColumnFormatter(DateTimeFormatter format, ZoneId zoneId) {
+        this.format = format;
+        this.zoneId = zoneId;
+    }
+
+    public InstantColumnFormatter(DateTimeFormatter format, String missingValueString) {
+        this.format = format;
+        this.missingValueString = missingValueString;
+        this.zoneId = ZoneOffset.UTC;
+    }
+
+    public InstantColumnFormatter(DateTimeFormatter format, ZoneId zoneId, String missingValueString) {
+        this.format = format;
+        this.missingValueString = missingValueString;
+        this.zoneId = zoneId;
+    }
+
+    public String format(long value) {
+        if (value == TimeColumnType.missingValueIndicator()) {
+            return missingValueString;
+        }
+        if (format == null) {
+            return PackedInstant.toString(value);
+        }
+        ZonedDateTime time = PackedInstant.asInstant(value).atZone(zoneId);
+        if (time == null) {
+            return "";
+        }
+        return format.format(time);
+    }
+
+    @Override
+    public String toString() {
+        return "InstantColumnFormatter{" +
+                "format=" + format +
+                ", missingValueString='" + missingValueString + '\'' +
+                '}';
+    }
+}
diff --git a/core/src/main/java/tech/tablesaw/columns/instant/InstantColumnType.java b/core/src/main/java/tech/tablesaw/columns/instant/InstantColumnType.java
new file mode 100644
index 00000000..a7e233f2
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/columns/instant/InstantColumnType.java
@@ -0,0 +1,41 @@
+package tech.tablesaw.columns.instant;
+
+import tech.tablesaw.api.ColumnType;
+import tech.tablesaw.api.DateTimeColumn;
+import tech.tablesaw.columns.AbstractColumnType;
+import tech.tablesaw.io.ReadOptions;
+
+public class InstantColumnType extends AbstractColumnType {
+
+    public static int BYTE_SIZE = 8;
+
+    public static final InstantParser DEFAULT_PARSER = new InstantParser(ColumnType.LOCAL_DATE_TIME);
+
+    private static InstantColumnType INSTANCE =
+            new InstantColumnType(BYTE_SIZE, "INSTANT", "Instant");
+
+    private InstantColumnType(int byteSize, String name, String printerFriendlyName) {
+        super(byteSize, name, printerFriendlyName);
+    }
+
+    public static InstantColumnType instance() {
+        if (INSTANCE == null) {
+            INSTANCE = new InstantColumnType(BYTE_SIZE, "INSTANT", "Instant");
+        }
+        return INSTANCE;
+    }
+
+    @Override
+    public DateTimeColumn create(String name) {
+        return DateTimeColumn.create(name);
+    }
+
+    @Override
+    public InstantParser customParser(ReadOptions options) {
+        return new InstantParser(this);
+    }
+
+    public static long missingValueIndicator() {
+        return Long.MIN_VALUE;
+    }
+}
diff --git a/core/src/main/java/tech/tablesaw/columns/instant/InstantParser.java b/core/src/main/java/tech/tablesaw/columns/instant/InstantParser.java
new file mode 100644
index 00000000..9db01bc0
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/columns/instant/InstantParser.java
@@ -0,0 +1,27 @@
+package tech.tablesaw.columns.instant;
+
+import java.time.Instant;
+
+import tech.tablesaw.api.ColumnType;
+import tech.tablesaw.columns.AbstractColumnParser;
+
+/**
+ * Dummy implementation
+ * Create an InstantColumn using SqlReader which doesn't require string parsing or convert from another column type
+ */
+public class InstantParser extends AbstractColumnParser<Instant> {
+
+    public InstantParser(ColumnType columnType) {
+        super(columnType);
+    }
+
+    @Override
+    public boolean canParse(String s) {
+        return false;
+    }
+
+    @Override
+    public Instant parse(String value) {
+        throw new IllegalArgumentException("Cannot parse " + value);
+    }
+}
diff --git a/core/src/main/java/tech/tablesaw/columns/instant/PackedInstant.java b/core/src/main/java/tech/tablesaw/columns/instant/PackedInstant.java
new file mode 100644
index 00000000..99f2fce0
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/columns/instant/PackedInstant.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed 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 tech.tablesaw.columns.instant;
+
+import static tech.tablesaw.columns.datetimes.DateTimeColumnType.missingValueIndicator;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
+
+import com.google.common.base.Strings;
+
+import tech.tablesaw.columns.dates.PackedLocalDate;
+import tech.tablesaw.columns.datetimes.DateTimePredicates;
+import tech.tablesaw.columns.times.PackedLocalTime;
+
+/*
+ * TODO(lwhite): Extend missing-value handling on predicates to DateColumn and TimeColumn
+ *
+ * TODO(lwhite): Handle missing values on non-boolean (predicate) methods
+ */
+
+/**
+ * A short localdatetime packed into a single long value. The long is comprised of an int for the date and an int
+ * for the time
+ * <p>
+ * The bytes are packed into the date int as:
+ * First two bytes: short (year)
+ * next byte (month of year)
+ * last byte (day of month)
+ * <p>
+ * The bytes are packed into the time int as
+ * First byte: hourOfDay
+ * next byte: minuteOfHour
+ * last two bytes (short): millisecond of minute
+ * <p>
+ * Storing the millisecond of minute in an short requires that we treat the short as if it were unsigned. Unfortunately,
+ * Neither Java nor Guava provide unsigned short support so we use char, which is a 16-bit unsigned int to
+ * store values of up to 60,000 milliseconds (60 secs * 1000)
+ */
+public class PackedInstant {
+
+    protected PackedInstant() {}
+
+    public static Instant asInstant(long dateTime) {
+        if (dateTime == missingValueIndicator()) {
+            return null;
+        }
+        int date = date(dateTime);
+        int time = time(dateTime);
+        LocalDate d = PackedLocalDate.asLocalDate(date);
+        LocalTime t = PackedLocalTime.asLocalTime(time);
+        if (d == null || t == null) {
+            return null;
+        }
+        return LocalDateTime.of(d, t).toInstant(ZoneOffset.UTC);
+    }
+
+    protected static long pack(LocalDate date, LocalTime time) {
+        if (date == null || time == null) {
+            return missingValueIndicator();
+        }
+        int d = PackedLocalDate.pack(date);
+        int t = PackedLocalTime.pack(time);
+        return (((long) d) << 32) | (t & 0xffffffffL);
+    }
+
+    public static long pack(Instant instant) {
+        if (instant == null) {
+            return missingValueIndicator();
+        }
+        LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
+        LocalDate date = dateTime.toLocalDate();
+        LocalTime time = dateTime.toLocalTime();
+        return (pack(date, time));
+    }
+
+    public static int date(long packedDateTIme) {
+        return (int) (packedDateTIme >> 32);
+    }
+
+    public static int time(long packedDateTIme) {
+        return (int) packedDateTIme;
+    }
+
+    public static String toString(long dateTime) {
+        if (dateTime == Long.MIN_VALUE) {
+            return "";
+        }
+        int date = date(dateTime);
+        int time = time(dateTime);
+
+        return
+                "" + PackedLocalDate.getYear(date)
+                        + "-"
+                        + Strings.padStart(Byte.toString(PackedLocalDate.getMonthValue(date)), 2, '0')
+                        + "-"
+                        + Strings.padStart(Byte.toString(PackedLocalDate.getDayOfMonth(date)), 2, '0')
+                        + "T"
+                        + Strings.padStart(Byte.toString(PackedLocalTime.getHour(time)), 2, '0')
+                        + ":"
+                        + Strings.padStart(Byte.toString(PackedLocalTime.getMinute(time)), 2, '0')
+                        + ":"
+                        + Strings.padStart(Byte.toString(PackedLocalTime.getSecond(time)), 2, '0')
+                        + "."
+                        + Strings.padStart(String.valueOf(PackedLocalTime.getMilliseconds(time)), 3, '0');
+    }
+
+    /**
+     * Returns the given packedDateTime with amtToAdd of temporal units added
+     *
+     * TODO(lwhite): Replace with a native implementation that doesn't convert everything to LocalDateTime
+     */
+    public static long plus(long packedDateTime, long amountToAdd, TemporalUnit unit) {
+        Instant dateTime = asInstant(packedDateTime);
+        return pack(dateTime.plus(amountToAdd, unit));
+    }
+
+    public static boolean isAfter(long packedDateTime, long value) {
+        return (packedDateTime != missingValueIndicator()) && packedDateTime > value;
+    }
+
+    public static boolean isBefore(long packedDateTime, long value) {
+        return (packedDateTime != missingValueIndicator()) && packedDateTime < value;
+    }
+
+    public static long create(int date, int time) {
+        return (((long) date) << 32) | (time & 0xffffffffL);
+    }
+
+    // TODO: packed support for minutesUntil and hoursUnit. These implementations are inefficient
+    public static long minutesUntil(long packedDateTimeEnd, long packedDateTimeStart) {
+        return ChronoUnit.MINUTES.between(asInstant(packedDateTimeStart),
+        	asInstant(packedDateTimeEnd));
+    }
+
+    public static long hoursUntil(long packedDateTimeEnd, long packedDateTimeStart) {
+        return ChronoUnit.HOURS.between(asInstant(packedDateTimeStart), asInstant(packedDateTimeEnd));
+    }
+
+    public static int daysUntil(long packedDateTimeEnd, long packedDateTimeStart) {
+        return (int) (PackedLocalDate.toEpochDay(date(packedDateTimeEnd))
+                - PackedLocalDate.toEpochDay(date(packedDateTimeStart)));
+    }
+
+    public static int weeksUntil(long packedDateTimeEnd, long packedDateStart) {
+        return daysUntil(packedDateTimeEnd, packedDateStart)/7;
+    }
+
+    public static boolean isEqualTo(long packedDateTime, long value) {
+        return DateTimePredicates.isEqualTo.test(packedDateTime, value);
+    }
+
+    public static boolean isOnOrAfter(long valueToTest, long valueToTestAgainst) {
+        return valueToTest >= valueToTestAgainst;
+    }
+
+    public static boolean isOnOrBefore(long valueToTest, long valueToTestAgainst) {
+        return isBefore(valueToTest, valueToTestAgainst)
+                || isEqualTo(valueToTest, valueToTestAgainst);
+    }
+}
diff --git a/core/src/main/java/tech/tablesaw/columns/temporal/TemporalColumn.java b/core/src/main/java/tech/tablesaw/columns/temporal/TemporalColumn.java
new file mode 100644
index 00000000..093cd83e
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/columns/temporal/TemporalColumn.java
@@ -0,0 +1,15 @@
+package tech.tablesaw.columns.temporal;
+
+import java.time.temporal.Temporal;
+
+import tech.tablesaw.columns.Column;
+
+public interface TemporalColumn<T extends Temporal> extends Column<T> {
+
+    T get(int r);
+
+    long getLongInternal(int r);
+
+    TemporalColumn<T> appendInternal(long value);
+
+}
diff --git a/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeFillers.java b/core/src/main/java/tech/tablesaw/columns/temporal/TemporalFillers.java
similarity index 68%
rename from core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeFillers.java
rename to core/src/main/java/tech/tablesaw/columns/temporal/TemporalFillers.java
index 5f501c38..e91d0a7a 100644
--- a/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeFillers.java
+++ b/core/src/main/java/tech/tablesaw/columns/temporal/TemporalFillers.java
@@ -12,19 +12,21 @@
  * limitations under the License.
  */
 
-package tech.tablesaw.columns.datetimes;
+package tech.tablesaw.columns.temporal;
 
-import java.time.LocalDateTime;
+import java.time.temporal.Temporal;
 import java.util.Iterator;
 import java.util.function.Supplier;
 
-public interface DateTimeFillers<T> {
+import tech.tablesaw.columns.Column;
 
-    T fillWith(Iterator<LocalDateTime> iterator);
+public interface TemporalFillers<T extends Temporal, C extends Column<T>> {
 
-    default T fillWith(final Iterable<LocalDateTime> iterable) {
+    C fillWith(Iterator<T> iterator);
+
+    default C fillWith(final Iterable<T> iterable) {
         return fillWith(iterable.iterator());
     }
 
-    T fillWith(Supplier<LocalDateTime> supplier);
+    C fillWith(Supplier<T> supplier);
 }
diff --git a/core/src/main/java/tech/tablesaw/columns/temporal/TemporalFilters.java b/core/src/main/java/tech/tablesaw/columns/temporal/TemporalFilters.java
new file mode 100644
index 00000000..35eea8d3
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/columns/temporal/TemporalFilters.java
@@ -0,0 +1,100 @@
+package tech.tablesaw.columns.temporal;
+
+import static tech.tablesaw.columns.temporal.TemporalPredicates.isMissing;
+import static tech.tablesaw.columns.temporal.TemporalPredicates.isNotMissing;
+
+import java.time.temporal.Temporal;
+import java.util.function.BiPredicate;
+import java.util.function.LongPredicate;
+import java.util.function.Predicate;
+
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import tech.tablesaw.api.DateTimeColumn;
+import tech.tablesaw.columns.Column;
+import tech.tablesaw.columns.instant.PackedInstant;
+import tech.tablesaw.filtering.predicates.LongBiPredicate;
+import tech.tablesaw.selection.BitmapBackedSelection;
+import tech.tablesaw.selection.Selection;
+
+public interface TemporalFilters<T extends Temporal> extends Column<T> {
+
+    default Selection eval(LongPredicate predicate) {
+        Selection bitmap = new BitmapBackedSelection();
+        for (int idx = 0; idx < size(); idx++) {
+            long next = data().getLong(idx);
+            if (predicate.test(next)) {
+                bitmap.add(idx);
+            }
+        }
+        return bitmap;
+    }
+
+    default Selection eval(LongBiPredicate predicate, long value) {
+        Selection bitmap = new BitmapBackedSelection();
+        for (int idx = 0; idx < size(); idx++) {
+            long next = data().getLong(idx);
+            if (predicate.test(next, value)) {
+                bitmap.add(idx);
+            }
+        }
+        return bitmap;
+    }
+
+    default Selection eval(LongBiPredicate predicate, DateTimeColumn otherColumn) {
+        Selection selection = new BitmapBackedSelection();
+        for (int idx = 0; idx < size(); idx++) {
+            if (predicate.test(this.getLongInternal(idx), otherColumn.getLongInternal(idx))) {
+                selection.add(idx);
+            }
+        }
+        return selection;
+    }
+
+    default Selection eval(BiPredicate<T, T> predicate, T valueToCompare) {
+        Selection selection = new BitmapBackedSelection();
+        for (int idx = 0; idx < size(); idx++) {
+            if (predicate.test(get(idx), valueToCompare)) {
+                selection.add(idx);
+            }
+        }
+        return selection;
+    }
+
+    default Selection eval(Predicate<T> predicate) {
+        Selection selection = new BitmapBackedSelection();
+        for (int idx = 0; idx < size(); idx++) {
+            if (predicate.test(get(idx))) {
+                selection.add(idx);
+            }
+        }
+        return selection;
+    }
+
+    default Selection isBetweenExcluding(long lowPackedDateTime, long highPackedDateTime) {
+        return eval(PackedInstant::isAfter, lowPackedDateTime)
+                .and(eval(PackedInstant::isBefore, highPackedDateTime));
+    }
+
+    default Selection isBetweenIncluding(long lowPackedDateTime, long highPackedDateTime) {
+        return eval(PackedInstant::isOnOrAfter, lowPackedDateTime)
+                .and(eval(PackedInstant::isOnOrBefore, highPackedDateTime));
+    }
+
+    @Override
+    default Selection isMissing() {
+        return eval(isMissing);
+    }
+
+    @Override
+    default Selection isNotMissing() {
+        return eval(isNotMissing);
+    }
+
+    int size();
+
+    LongArrayList data();
+
+    long getLongInternal(int index);
+
+    T get(int index);
+}
diff --git a/core/src/main/java/tech/tablesaw/columns/temporal/TemporalMapFunctions.java b/core/src/main/java/tech/tablesaw/columns/temporal/TemporalMapFunctions.java
new file mode 100644
index 00000000..dcbd5a01
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/columns/temporal/TemporalMapFunctions.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed 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 tech.tablesaw.columns.temporal;
+
+import static tech.tablesaw.columns.datetimes.PackedLocalDateTime.asLocalDateTime;
+
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalUnit;
+
+import tech.tablesaw.api.BooleanColumn;
+import tech.tablesaw.api.LongColumn;
+import tech.tablesaw.columns.Column;
+import tech.tablesaw.columns.booleans.BooleanColumnType;
+import tech.tablesaw.columns.instant.InstantColumnType;
+import tech.tablesaw.columns.instant.PackedInstant;
+
+public interface TemporalMapFunctions<T extends Temporal> extends TemporalColumn<T> {
+
+    T min();
+
+    TemporalColumn<T> emptyCopy();
+
+    default LongColumn differenceInMilliseconds(TemporalColumn<T> column2) {
+        return difference(column2, ChronoUnit.MILLIS);
+    }
+
+    default LongColumn differenceInSeconds(TemporalColumn<T> column2) {
+        return difference(column2, ChronoUnit.SECONDS);
+    }
+
+    default LongColumn differenceInMinutes(TemporalColumn<T> column2) {
+        return difference(column2, ChronoUnit.MINUTES);
+    }
+
+    default LongColumn differenceInHours(TemporalColumn<T> column2) {
+        return difference(column2, ChronoUnit.HOURS);
+    }
+
+    default LongColumn differenceInDays(TemporalColumn<T> column2) {
+        return difference(column2, ChronoUnit.DAYS);
+    }
+
+    default LongColumn differenceInYears(TemporalColumn<T> column2) {
+        return difference(column2, ChronoUnit.YEARS);
+    }
+
+    default LongColumn difference(TemporalColumn<T> column2, ChronoUnit unit) {
+
+        LongColumn newColumn = LongColumn.create(name() + " - " + column2.name() + "[" + unit.name() + "]");
+
+        for (int r = 0; r < size(); r++) {
+            if (this.isMissing(r) || column2.isMissing(r)) {
+                newColumn.appendMissing();
+            } else {
+                long c1 = this.getLongInternal(r);
+                long c2 = column2.getLongInternal(r);
+                LocalDateTime value1 = asLocalDateTime(c1);
+                LocalDateTime value2 = asLocalDateTime(c2);
+                if (value1 != null && value2 != null) {
+                    newColumn.append(unit.between(value1, value2));
+                } else {
+                    newColumn.appendMissing();
+                }
+            }
+        }
+        return newColumn;
+    }
+    
+    default Column<T> plus(long amountToAdd, ChronoUnit unit) {
+	TemporalColumn<T> newColumn = emptyCopy();
+	newColumn.setName(temporalColumnName(this, amountToAdd, unit));
+	TemporalColumn<T> column1 = this;
+
+        for (int r = 0; r < column1.size(); r++) {
+            long packedDateTime = column1.getLongInternal(r);
+            if (packedDateTime == InstantColumnType.missingValueIndicator()) {
+                newColumn.appendMissing();
+            } else {
+                newColumn.appendInternal(PackedInstant.plus(packedDateTime, amountToAdd, unit));
+            }
+        }
+        return newColumn;
+    }
+
+    default Column<T> plusYears(long amountToAdd) {
+        return plus(amountToAdd, ChronoUnit.YEARS);
+    }
+
+    default Column<T> plusMonths(long amountToAdd) {
+        return plus(amountToAdd, ChronoUnit.MONTHS);
+    }
+
+    default Column<T> plusWeeks(long amountToAdd) {
+        return plus(amountToAdd, ChronoUnit.WEEKS);
+    }
+
+    default Column<T> plusDays(long amountToAdd) {
+        return plus(amountToAdd, ChronoUnit.DAYS);
+    }
+
+    default Column<T> plusHours(long amountToAdd) {
+        return plus(amountToAdd, ChronoUnit.HOURS);
+    }
+
+    default Column<T> plusMinutes(long amountToAdd) {
+        return plus(amountToAdd, ChronoUnit.MINUTES);
+    }
+
+    default Column<T> plusSeconds(long amountToAdd) {
+        return plus(amountToAdd, ChronoUnit.SECONDS);
+    }
+
+    default Column<T> plusMillis(long amountToAdd) {
+        return plus(amountToAdd, ChronoUnit.MILLIS);
+    }
+
+    default Column<T> plusMicros(long amountToAdd) {
+        return plus(amountToAdd, ChronoUnit.MICROS);
+    }
+
+    default BooleanColumn missingValues() {
+	BooleanColumn newColumn = BooleanColumn.create(this.name() + " missing?");
+        for (int r = 0; r < this.size(); r++) {
+            if (isMissing(r)) {
+                newColumn.append(BooleanColumnType.BYTE_TRUE);
+            } else {
+                newColumn.append(BooleanColumnType.BYTE_FALSE);
+            }
+        }
+        return newColumn;
+    }
+
+    default String temporalColumnName(Column<T> column1, long value, TemporalUnit unit) {
+        return column1.name() + ": " + value + " " + unit.toString() + "(s)";
+    }
+}
diff --git a/core/src/main/java/tech/tablesaw/columns/temporal/TemporalPredicates.java b/core/src/main/java/tech/tablesaw/columns/temporal/TemporalPredicates.java
new file mode 100644
index 00000000..7ff6329a
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/columns/temporal/TemporalPredicates.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed 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 tech.tablesaw.columns.temporal;
+
+import tech.tablesaw.columns.instant.InstantColumnType;
+import tech.tablesaw.filtering.predicates.LongBiPredicate;
+
+import java.util.function.LongPredicate;
+
+public class TemporalPredicates {
+
+    public static final LongPredicate isMissing = i -> i == InstantColumnType.missingValueIndicator();
+
+    public static final LongPredicate isNotMissing = i -> i != InstantColumnType.missingValueIndicator();
+
+    public static final LongBiPredicate isGreaterThan = (valueToTest, valueToCompareAgainst) -> valueToTest > valueToCompareAgainst;
+
+    public static final LongBiPredicate isGreaterThanOrEqualTo = (valueToTest, valueToCompareAgainst) ->
+            valueToTest >= valueToCompareAgainst;
+
+    public static final LongBiPredicate isLessThan = (valueToTest, valueToCompareAgainst) -> valueToTest < valueToCompareAgainst;
+
+    public static final LongBiPredicate isLessThanOrEqualTo = (valueToTest, valueToCompareAgainst) -> valueToTest <= valueToCompareAgainst;
+
+    public static final LongBiPredicate isEqualTo = (long valueToTest, long valueToCompareAgainst) -> valueToTest == valueToCompareAgainst;
+
+    public static final LongBiPredicate isNotEqualTo = (long valueToTest, long valueToCompareAgainst) -> valueToTest != valueToCompareAgainst;
+
+}
diff --git a/core/src/main/java/tech/tablesaw/columns/datetimes/fillers/TemporalRangeIterable.java b/core/src/main/java/tech/tablesaw/columns/temporal/fillers/TemporalRangeIterable.java
similarity index 97%
rename from core/src/main/java/tech/tablesaw/columns/datetimes/fillers/TemporalRangeIterable.java
rename to core/src/main/java/tech/tablesaw/columns/temporal/fillers/TemporalRangeIterable.java
index 2a286527..ef2042d8 100644
--- a/core/src/main/java/tech/tablesaw/columns/datetimes/fillers/TemporalRangeIterable.java
+++ b/core/src/main/java/tech/tablesaw/columns/temporal/fillers/TemporalRangeIterable.java
@@ -1,4 +1,4 @@
-package tech.tablesaw.columns.datetimes.fillers;
+package tech.tablesaw.columns.temporal.fillers;
 
 import java.time.temporal.Temporal;
 import java.time.temporal.TemporalUnit;
diff --git a/core/src/main/java/tech/tablesaw/index/LongIndex.java b/core/src/main/java/tech/tablesaw/index/LongIndex.java
index edce18b2..0ab2f862 100644
--- a/core/src/main/java/tech/tablesaw/index/LongIndex.java
+++ b/core/src/main/java/tech/tablesaw/index/LongIndex.java
@@ -14,19 +14,21 @@
 
 package tech.tablesaw.index;
 
+import java.time.Instant;
+import java.time.LocalDateTime;
+
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectSortedMap;
-import tech.tablesaw.api.DateTimeColumn;
 import tech.tablesaw.api.IntColumn;
 import tech.tablesaw.api.LongColumn;
 import tech.tablesaw.columns.datetimes.PackedLocalDateTime;
+import tech.tablesaw.columns.instant.PackedInstant;
+import tech.tablesaw.columns.temporal.TemporalColumn;
 import tech.tablesaw.selection.BitmapBackedSelection;
 import tech.tablesaw.selection.Selection;
 
-import java.time.LocalDateTime;
-
 /**
  * An index for eight-byte long and long backed columns (datetime)
  */
@@ -34,7 +36,7 @@ public class LongIndex implements Index {
 
     private final Long2ObjectAVLTreeMap<IntArrayList> index;
 
-    public LongIndex(DateTimeColumn column) {
+    public LongIndex(TemporalColumn<?> column) {
         int sizeEstimate = Integer.min(1_000_000, column.size() / 100);
         Long2ObjectOpenHashMap<IntArrayList> tempMap = new Long2ObjectOpenHashMap<>(sizeEstimate);
         for (int i = 0; i < column.size(); i++) {
@@ -108,6 +110,10 @@ public class LongIndex implements Index {
         return selection;
     }
 
+    public Selection get(Instant value) {
+        return get(PackedInstant.pack(value));
+    }
+
     public Selection get(LocalDateTime value) {
         return get(PackedLocalDateTime.pack(value));
     }
@@ -121,6 +127,10 @@ public class LongIndex implements Index {
         return selection;
     }
 
+    public Selection atLeast(Instant value) {
+        return atLeast(PackedInstant.pack(value));
+    }
+
     public Selection atLeast(LocalDateTime value) {
         return atLeast(PackedLocalDateTime.pack(value));
     }
@@ -134,6 +144,10 @@ public class LongIndex implements Index {
         return selection;
     }
 
+    public Selection greaterThan(Instant value) {
+        return greaterThan(PackedInstant.pack(value));
+    }
+
     public Selection greaterThan(LocalDateTime value) {
         return greaterThan(PackedLocalDateTime.pack(value));
     }
@@ -147,6 +161,10 @@ public class LongIndex implements Index {
         return selection;
     }
 
+    public Selection atMost(Instant value) {
+        return atMost(PackedInstant.pack(value));
+    }
+
     public Selection atMost(LocalDateTime value) {
         return atMost(PackedLocalDateTime.pack(value));
     }
@@ -160,6 +178,10 @@ public class LongIndex implements Index {
         return selection;
     }
 
+    public Selection lessThan(Instant value) {
+        return lessThan(PackedInstant.pack(value));
+    }
+
     public Selection lessThan(LocalDateTime value) {
         return lessThan(PackedLocalDateTime.pack(value));
     }
diff --git a/core/src/main/java/tech/tablesaw/io/ColumnTypeDetector.java b/core/src/main/java/tech/tablesaw/io/ColumnTypeDetector.java
index e88fcbbf..351425d6 100644
--- a/core/src/main/java/tech/tablesaw/io/ColumnTypeDetector.java
+++ b/core/src/main/java/tech/tablesaw/io/ColumnTypeDetector.java
@@ -1,9 +1,6 @@
 package tech.tablesaw.io;
 
-import com.google.common.collect.Lists;
-import tech.tablesaw.api.ColumnType;
-import tech.tablesaw.columns.AbstractColumnParser;
-import tech.tablesaw.columns.strings.StringColumnType;
+import static tech.tablesaw.api.ColumnType.TEXT;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -11,15 +8,9 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
-import static tech.tablesaw.api.ColumnType.BOOLEAN;
-import static tech.tablesaw.api.ColumnType.DOUBLE;
-import static tech.tablesaw.api.ColumnType.INTEGER;
-import static tech.tablesaw.api.ColumnType.LOCAL_DATE;
-import static tech.tablesaw.api.ColumnType.LOCAL_DATE_TIME;
-import static tech.tablesaw.api.ColumnType.LOCAL_TIME;
-import static tech.tablesaw.api.ColumnType.LONG;
-import static tech.tablesaw.api.ColumnType.STRING;
-import static tech.tablesaw.api.ColumnType.TEXT;
+import tech.tablesaw.api.ColumnType;
+import tech.tablesaw.columns.AbstractColumnParser;
+import tech.tablesaw.columns.strings.StringColumnType;
 
 public class ColumnTypeDetector {
 
@@ -47,29 +38,18 @@ public class ColumnTypeDetector {
      */
     private static final double STRING_COLUMN_CUTOFF = 0.50;
 
+    private final List<ColumnType> typeArray;
+    
     /**
-     * Types to choose from. When more than one would work, we pick the first of the options. The order these appear in
-     * is critical. The broadest must go last, which is why String is at the end of the list. Any String read from
-     * the input will match string. If it were first on the list, you would get nothing but strings in your table.
-     *
-     * As another example, an integer type, should go before double. Otherwise double would match integers so
-     * the integer test would never be evaluated and all the ints would be read as doubles.
+     * @param typeArray Types to choose from. When more than one would work, we pick the first of
+     * the options. The order these appear in is critical. The broadest must go last, so String
+     * must be at the end of the list. Any String read from the input will match string. If it
+     * were first on the list, you would get nothing but strings in your table. As another example,
+     * an integer type, should go before double. Otherwise double would match integers so the
+     * integer test would never be evaluated and all the ints would be read as doubles.
      */
-    private List<ColumnType> typeArray =
-            Lists.newArrayList(
-                    LOCAL_DATE_TIME,
-                    LOCAL_TIME,
-                    LOCAL_DATE,
-                    BOOLEAN,
-                    INTEGER,
-                    LONG,
-                    DOUBLE,
-                    STRING);
-
-    public ColumnTypeDetector() {}
-    
-    public ColumnTypeDetector(List<ColumnType> typeArrayOverrides) {
-	    typeArray = typeArrayOverrides;
+    public ColumnTypeDetector(List<ColumnType> typeArray) {
+        this.typeArray = typeArray;
     }
     
     /**
diff --git a/core/src/main/java/tech/tablesaw/io/FileReader.java b/core/src/main/java/tech/tablesaw/io/FileReader.java
index 949c96ff..49dce8c3 100644
--- a/core/src/main/java/tech/tablesaw/io/FileReader.java
+++ b/core/src/main/java/tech/tablesaw/io/FileReader.java
@@ -19,22 +19,11 @@ import static tech.tablesaw.api.ColumnType.SKIP;
 
 public abstract class FileReader {
 
-    private List<ColumnType> typeArrayOverrides = new ArrayList<>();
-
     /**
      * Constructs a CsvReader
      */
     public FileReader() {}
 
-    /**
-     * Constructs a CsvReader with the given list of ColumnTypes
-     * <p>
-     * These are the only types that the CsvReader can detect and parse
-     */
-    public FileReader(List<ColumnType> typeDetectionList) {
-        this.typeArrayOverrides = typeDetectionList;
-    }
-
     protected ColumnType[] getTypes(Reader reader, ReadOptions options, int linesToSkip, AbstractParser<?> parser) {
 
         parser.beginParsing(reader);
@@ -43,12 +32,7 @@ public abstract class FileReader {
             parser.parseNext();
         }
 
-        if (options.minimizeColumnSizes()) {
-            typeArrayOverrides = ReadOptions.EXTENDED_TYPE_ARRAY;
-        }
-
-        ColumnTypeDetector detector = typeArrayOverrides.isEmpty()
-                ? new ColumnTypeDetector() : new ColumnTypeDetector(typeArrayOverrides);
+        ColumnTypeDetector detector = new ColumnTypeDetector(options.columnTypesToDetect());
 
         return detector.detectColumnTypes(new Iterator<String[]>() {
 
diff --git a/core/src/main/java/tech/tablesaw/io/ReadOptions.java b/core/src/main/java/tech/tablesaw/io/ReadOptions.java
index 46d2a9db..a88f9e58 100644
--- a/core/src/main/java/tech/tablesaw/io/ReadOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/ReadOptions.java
@@ -42,12 +42,22 @@ import static tech.tablesaw.api.ColumnType.TEXT;
 
 public class ReadOptions {
 
+    private static final List<ColumnType> DEFAULT_TYPES = Lists.newArrayList(
+            LOCAL_DATE_TIME,
+            LOCAL_TIME,
+            LOCAL_DATE,
+            BOOLEAN,
+            INTEGER,
+            LONG,
+            DOUBLE,
+            STRING);
+
     /**
      * An extended list of types that are used if minimizeColumnSizes is true. By including extra types like Short
      * the resulting table size is reduced at the cost of some additional complexity for the programmer if, for example,
      * they will subsequently modify the data in a way that exceeds the range of the type.
      */
-    public static final List<ColumnType> EXTENDED_TYPE_ARRAY =
+    private static final List<ColumnType> EXTENDED_TYPES =
             Lists.newArrayList(
                     LOCAL_DATE_TIME,
                     LOCAL_TIME,
@@ -63,6 +73,7 @@ public class ReadOptions {
 
     protected final Source source;
     protected final String tableName;
+    protected final List<ColumnType> columnTypesToDetect;
     protected final boolean sample;
     protected final String dateFormat;
     protected final String dateTimeFormat;
@@ -81,6 +92,7 @@ public class ReadOptions {
     protected ReadOptions(ReadOptions.Builder builder) {
 	source = builder.source;
         tableName = builder.tableName;
+        columnTypesToDetect = builder.columnTypesToDetect;
         sample = builder.sample;
         dateFormat = builder.dateFormat;
         timeFormat = builder.timeFormat;
@@ -109,6 +121,10 @@ public class ReadOptions {
         return tableName;
     }
 
+    public List<ColumnType> columnTypesToDetect() {
+        return columnTypesToDetect;
+    }
+
     public boolean sample() {
         return sample;
     }
@@ -164,6 +180,7 @@ public class ReadOptions {
 
         protected final Source source;
         protected String tableName = "";
+        protected List<ColumnType> columnTypesToDetect = DEFAULT_TYPES;
         protected boolean sample = true;
         protected String dateFormat;
         protected DateTimeFormatter dateFormatter;
@@ -275,8 +292,20 @@ public class ReadOptions {
             return this;
         }
 
-        public Builder minimizeColumnSizes(boolean minimize) {
-            this.minimizeColumnSizes = minimize;
+        /**
+         * @see ColumnTypeDetector
+         */
+        public Builder columnTypesToDetect(List<ColumnType> columnTypesToDetect) {
+            this.columnTypesToDetect = columnTypesToDetect;
+            return this;
+        }
+
+        /**
+         * Allow the {@link ColumnTypeDetector} to choose shorter column types such as float
+         * instead of double when the data will fit in a smaller type
+         */
+        public Builder minimizeColumnSizes() {
+            this.columnTypesToDetect = EXTENDED_TYPES;
             return this;
         }
         
diff --git a/core/src/main/java/tech/tablesaw/io/TableBuildingUtils.java b/core/src/main/java/tech/tablesaw/io/TableBuildingUtils.java
index 4f437881..e8125f52 100644
--- a/core/src/main/java/tech/tablesaw/io/TableBuildingUtils.java
+++ b/core/src/main/java/tech/tablesaw/io/TableBuildingUtils.java
@@ -15,7 +15,7 @@ public class TableBuildingUtils {
             return table;
         }
 
-        ColumnTypeDetector detector = new ColumnTypeDetector();
+        ColumnTypeDetector detector = new ColumnTypeDetector(options.columnTypesToDetect());
         Iterator<String[]> iterator = dataRows.iterator();
         ColumnType[] types = detector.detectColumnTypes(iterator, options);
         for (int i = 0; i < columnNames.size(); i++) {
diff --git a/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java b/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
index d3b40d5c..844e718e 100644
--- a/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/csv/CsvReadOptions.java
@@ -268,8 +268,8 @@ public class CsvReadOptions extends ReadOptions {
         }
 
         @Override
-        public Builder minimizeColumnSizes(boolean minimizeColumnSizes) {
-            super.minimizeColumnSizes(minimizeColumnSizes);
+        public Builder minimizeColumnSizes() {
+            super.minimizeColumnSizes();
             return this;
         }
     }
diff --git a/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java b/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
index da696b74..cadfb8ff 100644
--- a/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
+++ b/core/src/main/java/tech/tablesaw/io/csv/CsvReader.java
@@ -30,7 +30,6 @@ import tech.tablesaw.io.Source;
 import javax.annotation.concurrent.Immutable;
 import java.io.IOException;
 import java.io.Reader;
-import java.util.List;
 
 @Immutable
 public class CsvReader extends FileReader implements DataReader<CsvReadOptions> {
@@ -54,15 +53,6 @@ public class CsvReader extends FileReader implements DataReader<CsvReadOptions>
         super();
     }
 
-    /**
-     * Constructs a CsvReader with the given list of ColumnTypes
-     * <p>
-     * These are the only types that the CsvReader can detect and parse
-     */
-    public CsvReader(List<ColumnType> typeDetectionList) {
-        super(typeDetectionList);
-    }
-
     /**
      * Determines column types if not provided by the user
      * Reads all input into memory unless File was provided
diff --git a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReadOptions.java b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReadOptions.java
index efbd98a1..c0a544f5 100644
--- a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReadOptions.java
+++ b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReadOptions.java
@@ -290,8 +290,8 @@ public class FixedWidthReadOptions extends ReadOptions {
         }
 
         @Override
-        public Builder minimizeColumnSizes(boolean minimizeColumnSizes) {
-            super.minimizeColumnSizes(minimizeColumnSizes);
+        public Builder minimizeColumnSizes() {
+            super.minimizeColumnSizes();
             return this;
         }
     }
diff --git a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReader.java b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReader.java
index 08d68b08..d2a486df 100644
--- a/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReader.java
+++ b/core/src/main/java/tech/tablesaw/io/fixed/FixedWidthReader.java
@@ -30,7 +30,6 @@ import tech.tablesaw.io.Source;
 import javax.annotation.concurrent.Immutable;
 import java.io.IOException;
 import java.io.Reader;
-import java.util.List;
 
 @Immutable
 public class FixedWidthReader extends FileReader implements DataReader<FixedWidthReadOptions> {
@@ -52,15 +51,6 @@ public class FixedWidthReader extends FileReader implements DataReader<FixedWidt
         super();
     }
 
-    /**
-     * Constructs a FixedWidthReader with the given list of ColumnTypes
-     * <p>
-     * These are the only types that the FixedWidthReader can detect and parse
-     */
-    public FixedWidthReader(List<ColumnType> typeDetectionList) {
-        super(typeDetectionList);
-    }
-
     /**
      * Determines column types if not provided by the user
      * Reads all input into memory unless File was provided
diff --git a/core/src/main/java/tech/tablesaw/io/jdbc/SqlResultSetReader.java b/core/src/main/java/tech/tablesaw/io/jdbc/SqlResultSetReader.java
index c8e95821..2e984b79 100644
--- a/core/src/main/java/tech/tablesaw/io/jdbc/SqlResultSetReader.java
+++ b/core/src/main/java/tech/tablesaw/io/jdbc/SqlResultSetReader.java
@@ -24,7 +24,6 @@ import tech.tablesaw.api.LongColumn;
 import tech.tablesaw.api.ShortColumn;
 import tech.tablesaw.api.Table;
 import tech.tablesaw.columns.Column;
-import tech.tablesaw.columns.numbers.ShortColumnType;
 
 import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
@@ -50,7 +49,8 @@ public class SqlResultSetReader {
 
                 .put(Types.DATE, ColumnType.LOCAL_DATE)
                 .put(Types.TIME, ColumnType.LOCAL_TIME)
-                .put(Types.TIMESTAMP, ColumnType.LOCAL_DATE_TIME)
+                 // Instant, LocalDateTime, OffsetDateTime and ZonedDateTime are often mapped to timestamp
+                .put(Types.TIMESTAMP, ColumnType.INSTANT)
 
                 .put(Types.DECIMAL, ColumnType.DOUBLE)
                 .put(Types.DOUBLE, ColumnType.DOUBLE)
@@ -94,48 +94,12 @@ public class SqlResultSetReader {
 
         // Setup the columns and add to the table
         for (int i = 1; i <= metaData.getColumnCount(); i++) {
-            String name = metaData.getColumnName(i);
-            int columnType = metaData.getColumnType(i);
-            ColumnType type = SQL_TYPE_TO_TABLESAW_TYPE.get(columnType);
-            // Try to improve on the initial type assigned to 'type' to minimize size/space of type needed.
-            // For all generic numeric columns inspect closer, checking the precision and 
-            // scale to more accurately determine the appropriate java type to use.
-            if (columnType == Types.NUMERIC || columnType == Types.DECIMAL) {
-                int s = metaData.getScale(i);
-                // When scale is 0 then column is a type of integer
-                if (s == 0) {
-                    int p = metaData.getPrecision(i);
-/* Mapping to java integer types based on integer precision defined:
+            ColumnType type = getColumnType(metaData.getColumnType(i), metaData.getScale(i), metaData.getPrecision(i));
 
-Java type           TypeMinVal              TypeMaxVal          p               MaxIntVal
------------------------------------------------------------------------------------------
-byte, Byte:         -128                    127                 NUMBER(2)       99
-short, Short:       -32768                  32767               NUMBER(4)       9_999
-int, Integer:       -2147483648             2147483647          NUMBER(9)       999_999_999 
-long, Long:         -9223372036854775808    9223372036854775807 NUMBER(18)      999_999_999_999_999_999
-
-*/
-                    // Start with SHORT (since ColumnType.BYTE isn't supported yet)
-                    // and find the smallest java integer type that fits
-                    if (p <= 4) {
-                        type = ShortColumnType.instance();
-                    } else if (p <= 9) {
-                        type = ColumnType.INTEGER;
-                    } else if (p <= 18) {
-                        type = ColumnType.LONG;
-                    }
-                } else { // s is not zero, so a decimal value is expected. First try float, then double
-                    if (s <= 7) {
-                        type = ColumnType.FLOAT;
-                    } else if (s <= 16) {
-                        type = ColumnType.DOUBLE;
-                    }
-                }
-            }
             Preconditions.checkState(type != null,
-                    "No column type found for %s as specified for column %s", metaData.getColumnType(i), name);
+                    "No column type found for %s as specified for column %s", metaData.getColumnType(i), metaData.getColumnName(i));
 
-            Column<?> newColumn = type.create(name);
+            Column<?> newColumn = type.create(metaData.getColumnName(i));
             table.addColumns(newColumn);
         }
 
@@ -160,4 +124,45 @@ long, Long:         -9223372036854775808    9223372036854775807 NUMBER(18)
         }
         return table;
     }
+    
+    protected static ColumnType getColumnType(int columnType, int scale, int precision) {
+        ColumnType type = SQL_TYPE_TO_TABLESAW_TYPE.get(columnType);
+        // Try to improve on the initial type assigned to 'type' to minimize size/space of type needed.
+        // For all generic numeric columns inspect closer, checking the precision and 
+        // scale to more accurately determine the appropriate java type to use.
+        if (columnType == Types.NUMERIC || columnType == Types.DECIMAL) {
+            // When scale is 0 then column is a type of integer
+            if (scale == 0) {
+/* Mapping to java integer types based on integer precision defined:
+
+Java type           TypeMinVal              TypeMaxVal          p               MaxIntVal
+-----------------------------------------------------------------------------------------
+byte, Byte:         -128                    127                 NUMBER(2)       99
+short, Short:       -32768                  32767               NUMBER(4)       9_999
+int, Integer:       -2147483648             2147483647          NUMBER(9)       999_999_999 
+long, Long:         -9223372036854775808    9223372036854775807 NUMBER(18)      999_999_999_999_999_999
+
+*/
+                if (precision > 0) {
+                    if (precision <= 4) {
+                        // Start with SHORT (since ColumnType.BYTE isn't supported yet)
+                        // and find the smallest java integer type that fits
+                        type = ColumnType.SHORT;
+                    } else if (precision <= 9) {
+                        type = ColumnType.INTEGER;
+                    } else if (precision <= 18) {
+                        type = ColumnType.LONG;
+                    }
+                }
+            } else { // s is not zero, so a decimal value is expected. First try float, then double
+                if (scale <= 7) {
+                    type = ColumnType.FLOAT;
+                } else if (scale <= 16) {
+                    type = ColumnType.DOUBLE;
+                }
+            }
+        }
+        return type;
+    }
+
 }
\ No newline at end of file
diff --git a/core/src/main/java/tech/tablesaw/joining/DataFrameJoiner.java b/core/src/main/java/tech/tablesaw/joining/DataFrameJoiner.java
index 5600067e..cd21e0bf 100644
--- a/core/src/main/java/tech/tablesaw/joining/DataFrameJoiner.java
+++ b/core/src/main/java/tech/tablesaw/joining/DataFrameJoiner.java
@@ -7,6 +7,7 @@ import tech.tablesaw.api.DateColumn;
 import tech.tablesaw.api.DateTimeColumn;
 import tech.tablesaw.api.DoubleColumn;
 import tech.tablesaw.api.FloatColumn;
+import tech.tablesaw.api.InstantColumn;
 import tech.tablesaw.api.IntColumn;
 import tech.tablesaw.api.LongColumn;
 import tech.tablesaw.api.Row;
@@ -18,6 +19,7 @@ import tech.tablesaw.columns.Column;
 import tech.tablesaw.columns.booleans.BooleanColumnType;
 import tech.tablesaw.columns.dates.DateColumnType;
 import tech.tablesaw.columns.datetimes.DateTimeColumnType;
+import tech.tablesaw.columns.instant.InstantColumnType;
 import tech.tablesaw.columns.numbers.DoubleColumnType;
 import tech.tablesaw.columns.numbers.FloatColumnType;
 import tech.tablesaw.columns.numbers.IntColumnType;
@@ -209,15 +211,20 @@ public class DataFrameJoiner {
                     DateColumn col1 = (DateColumn) table1Column;
                     int value = col1.getIntInternal(ri);
                     rowBitMapOneCol = index.get(value);
+                } else if (type instanceof TimeColumnType) {
+                    IntIndex index = (IntIndex) columnIndexMap.get(column);
+                    TimeColumn col1 = (TimeColumn) table1Column;
+                    int value = col1.getIntInternal(ri);
+                    rowBitMapOneCol = index.get(value);
                 } else if (type instanceof DateTimeColumnType) {
                     LongIndex index = (LongIndex) columnIndexMap.get(column);
                     DateTimeColumn col1 = (DateTimeColumn) table1Column;
                     long value = col1.getLongInternal(ri);
                     rowBitMapOneCol = index.get(value);
-                } else if (type instanceof TimeColumnType) {
-                    IntIndex index = (IntIndex) columnIndexMap.get(column);
-                    TimeColumn col1 = (TimeColumn) table1Column;
-                    int value = col1.getIntInternal(ri);
+                } else if (type instanceof InstantColumnType) {
+                    LongIndex index = (LongIndex) columnIndexMap.get(column);
+                    InstantColumn col1 = (InstantColumn) table1Column;
+                    long value = col1.getLongInternal(ri);
                     rowBitMapOneCol = index.get(value);
                 } else if (type instanceof StringColumnType || type instanceof TextColumnType) {
                     StringIndex index = (StringIndex) columnIndexMap.get(column);
diff --git a/excel/src/main/java/tech/tablesaw/io/xlsx/XlsxReadOptions.java b/excel/src/main/java/tech/tablesaw/io/xlsx/XlsxReadOptions.java
index 8ecbf98a..e39ad065 100644
--- a/excel/src/main/java/tech/tablesaw/io/xlsx/XlsxReadOptions.java
+++ b/excel/src/main/java/tech/tablesaw/io/xlsx/XlsxReadOptions.java
@@ -140,8 +140,8 @@ public class XlsxReadOptions extends ReadOptions {
         }
 
         @Override
-        public Builder minimizeColumnSizes(boolean minimizeColumnSizes) {
-            super.minimizeColumnSizes(minimizeColumnSizes);
+        public Builder minimizeColumnSizes() {
+            super.minimizeColumnSizes();
             return this;
         }
     }
diff --git a/html/src/main/java/tech/tablesaw/io/html/HtmlReadOptions.java b/html/src/main/java/tech/tablesaw/io/html/HtmlReadOptions.java
index 11e7f266..f0724f06 100644
--- a/html/src/main/java/tech/tablesaw/io/html/HtmlReadOptions.java
+++ b/html/src/main/java/tech/tablesaw/io/html/HtmlReadOptions.java
@@ -170,8 +170,8 @@ public class HtmlReadOptions extends ReadOptions {
         }
 
         @Override
-        public Builder minimizeColumnSizes(boolean minimizeColumnSizes) {
-            super.minimizeColumnSizes(minimizeColumnSizes);
+        public Builder minimizeColumnSizes() {
+            super.minimizeColumnSizes();
             return this;
         }    
     }
diff --git a/json/src/main/java/tech/tablesaw/io/json/JsonReadOptions.java b/json/src/main/java/tech/tablesaw/io/json/JsonReadOptions.java
index 762c8b42..2d02ec84 100644
--- a/json/src/main/java/tech/tablesaw/io/json/JsonReadOptions.java
+++ b/json/src/main/java/tech/tablesaw/io/json/JsonReadOptions.java
@@ -169,8 +169,8 @@ public class JsonReadOptions extends ReadOptions {
         }
 
         @Override
-        public Builder minimizeColumnSizes(boolean minimizeColumnSizes) {
-            super.minimizeColumnSizes(minimizeColumnSizes);
+        public Builder minimizeColumnSizes() {
+            super.minimizeColumnSizes();
             return this;
         }
     }


Test patch:
diff --git a/core/src/test/java/tech/tablesaw/TableFilteringTest.java b/core/src/test/java/tech/tablesaw/TableFilteringTest.java
index 2ec48f3a..dc775ab9 100644
--- a/core/src/test/java/tech/tablesaw/TableFilteringTest.java
+++ b/core/src/test/java/tech/tablesaw/TableFilteringTest.java
@@ -41,7 +41,7 @@ public class TableFilteringTest {
 
     @BeforeEach
     public void setUp() throws Exception {
-        table = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes(true));
+        table = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes());
     }
 
     @Test
diff --git a/core/src/test/java/tech/tablesaw/api/RowTest.java b/core/src/test/java/tech/tablesaw/api/RowTest.java
index 1d458e87..37df598d 100644
--- a/core/src/test/java/tech/tablesaw/api/RowTest.java
+++ b/core/src/test/java/tech/tablesaw/api/RowTest.java
@@ -92,7 +92,7 @@ public class RowTest {
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/rev_tornadoes_1950-2014.csv"))
                         .columnTypes(types)
-                        .minimizeColumnSizes(true));
+                        .minimizeColumnSizes());
         DateTimeColumn dateTimeCol = table.dateColumn("Date").atTime(table.timeColumn("Time"));
         dateTimeCol.setName("DateTime");
         table.addColumns(dateTimeCol);
@@ -154,7 +154,7 @@ public class RowTest {
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/bush.csv"))
                         .columnTypes(types)
-                        .minimizeColumnSizes(true));
+                        .minimizeColumnSizes());
         Row row = new Row(table);
         while (row.hasNext()) {
             row.next();
@@ -172,7 +172,7 @@ public class RowTest {
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/bush.csv"))
                         .columnTypes(types)
-                        .minimizeColumnSizes(true));
+                        .minimizeColumnSizes());
         Row row = new Row(table);
         while (row.hasNext()) {
             row.next();
@@ -205,7 +205,7 @@ public class RowTest {
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/rev_tornadoes_1950-2014.csv"))
                         .columnTypes(types)
-                        .minimizeColumnSizes(true));
+                        .minimizeColumnSizes());
         DateTimeColumn dateTimeCol = table.dateColumn("Date").atTime(table.timeColumn("Time"));
         dateTimeCol.setName("DateTime");
         table.addColumns(dateTimeCol);
@@ -227,7 +227,7 @@ public class RowTest {
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/rev_tornadoes_1950-2014.csv"))
                         .columnTypes(types)
-                        .minimizeColumnSizes(true));
+                        .minimizeColumnSizes());
         Row row = new Row(table);
         while (row.hasNext()) {
             row.next();
@@ -244,7 +244,7 @@ public class RowTest {
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/bush.csv"))
                         .columnTypes(types)
-                        .minimizeColumnSizes(true));
+                        .minimizeColumnSizes());
         Row row = new Row(table);
         while (row.hasNext()) {
             row.next();
@@ -260,7 +260,7 @@ public class RowTest {
         ColumnType[] types = {LOCAL_DATE, SHORT, STRING};
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/bush.csv"))
-                        .minimizeColumnSizes(true)
+                        .minimizeColumnSizes()
                         .columnTypes(types));
         Row row = new Row(table);
         while (row.hasNext()) {
@@ -277,7 +277,7 @@ public class RowTest {
         ColumnType[] types = {LOCAL_DATE, SHORT, TEXT};
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/bush.csv"))
-                        .minimizeColumnSizes(true)
+                        .minimizeColumnSizes()
                         .columnTypes(types));
         Row row = new Row(table);
         while (row.hasNext()) {
@@ -297,7 +297,7 @@ public class RowTest {
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/rev_tornadoes_1950-2014.csv"))
                         .columnTypes(types)
-                        .minimizeColumnSizes(true));
+                        .minimizeColumnSizes());
         Row row = new Row(table);
         while (row.hasNext()) {
             row.next();
@@ -360,7 +360,7 @@ public class RowTest {
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/rev_tornadoes_1950-2014.csv"))
                         .columnTypes(types)
-                        .minimizeColumnSizes(true));
+                        .minimizeColumnSizes());
         DateTimeColumn dateTimeCol = table.dateColumn("Date").atTime(table.timeColumn("Time"));
         dateTimeCol.setName("DateTime");
         table.addColumns(dateTimeCol);
@@ -467,7 +467,7 @@ public class RowTest {
         ColumnType[] types = {LOCAL_DATE, LONG, STRING};
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/bush.csv"))
-                        .minimizeColumnSizes(true)
+                        .minimizeColumnSizes()
                         .columnTypes(types));
         Row row = new Row(table);
         while (row.hasNext()) {
@@ -491,7 +491,7 @@ public class RowTest {
         ColumnType[] types = {LOCAL_DATE, SHORT, STRING};
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/bush.csv"))
-                        .minimizeColumnSizes(true)
+                        .minimizeColumnSizes()
                         .columnTypes(types));
         Row row = new Row(table);
         while (row.hasNext()) {
@@ -537,7 +537,7 @@ public class RowTest {
         ColumnType[] types = {LOCAL_DATE, SHORT, TEXT};
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/bush.csv"))
-                        .minimizeColumnSizes(true)
+                        .minimizeColumnSizes()
                         .columnTypes(types));
         Row row = new Row(table);
         while (row.hasNext()) {
@@ -566,7 +566,7 @@ public class RowTest {
         Table table = Table.read().csv(
                 CsvReadOptions.builder(new File("../data/rev_tornadoes_1950-2014.csv"))
                         .columnTypes(types)
-                        .minimizeColumnSizes(true));
+                        .minimizeColumnSizes());
         Row row = new Row(table);
         while (row.hasNext()) {
             row.next();
diff --git a/core/src/test/java/tech/tablesaw/api/TableTest.java b/core/src/test/java/tech/tablesaw/api/TableTest.java
index bec00611..0e88b89f 100644
--- a/core/src/test/java/tech/tablesaw/api/TableTest.java
+++ b/core/src/test/java/tech/tablesaw/api/TableTest.java
@@ -217,7 +217,7 @@ public class TableTest {
 
     @Test
     public void testDoWithEachRow() throws Exception {
-        Table t = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes(true)).first(10);
+        Table t = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes()).first(10);
         Short[] ratingsArray = {53, 58};
         List<Short> ratings = Lists.asList((short) 52, ratingsArray);
 
@@ -231,7 +231,7 @@ public class TableTest {
 
     @Test
     public void testDoWithEachRow2() throws Exception {
-        Table t = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes(true));
+        Table t = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes());
         int dateTarget = PackedLocalDate.pack(LocalDate.of(2002, 1, 1));
         double ratingTarget = 75;
         AtomicInteger count = new AtomicInteger(0);
@@ -247,7 +247,7 @@ public class TableTest {
 
     @Test
     public void testDetect() throws Exception {
-        Table t = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes(true));
+        Table t = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes());
         int dateTarget = PackedLocalDate.pack(LocalDate.of(2002, 1, 1));
         double ratingTarget = 75;
         Predicate<Row> doable = row ->
@@ -269,14 +269,14 @@ public class TableTest {
 
     @Test
     public void testPairs() throws Exception {
-        Table t = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes(true));
+        Table t = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes());
         PairChild pairs = new PairChild();
         t.doWithRows(pairs);
     }
 
     @Test
     public void testPairs2() throws Exception {
-        Table t = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes(true));
+        Table t = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes());
 
         Table.Pairs runningAvg =  new Table.Pairs() {
 
@@ -301,7 +301,7 @@ public class TableTest {
     @Test
     public void stepWithRows() throws Exception {
         Table t = Table.read()
-                .csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes(true))
+                .csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes())
                 .first(6);
 
         final int sum1 = (int) t.shortColumn("approval").sum();
@@ -330,7 +330,7 @@ public class TableTest {
 
     @Test
     public void testRollWithNrows2() throws Exception {
-        Table t = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes(true)).first(4);
+        Table t = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes()).first(4);
         ShortColumn approval = t.shortColumn("approval");
 
         List<Integer> sums = new ArrayList<>();
@@ -515,7 +515,7 @@ public class TableTest {
 
     @Test
     public void testRowSort() throws Exception {
-        Table bush = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes(true));
+        Table bush = Table.read().csv(CsvReadOptions.builder("../data/bush.csv").minimizeColumnSizes());
 
         Comparator<Row> rowComparator = Comparator.comparingDouble(o -> o.getShort("approval"));
 
diff --git a/core/src/test/java/tech/tablesaw/columns/dates/DateFillersTest.java b/core/src/test/java/tech/tablesaw/columns/dates/DateFillersTest.java
index 31ac40a5..6388b419 100644
--- a/core/src/test/java/tech/tablesaw/columns/dates/DateFillersTest.java
+++ b/core/src/test/java/tech/tablesaw/columns/dates/DateFillersTest.java
@@ -7,7 +7,7 @@ import java.time.temporal.ChronoUnit;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static tech.tablesaw.api.DateColumn.create;
-import static tech.tablesaw.columns.datetimes.fillers.TemporalRangeIterable.range;
+import static tech.tablesaw.columns.temporal.fillers.TemporalRangeIterable.range;
 
 public class DateFillersTest {
 
diff --git a/core/src/test/java/tech/tablesaw/columns/datetimes/DateTimeFillersTest.java b/core/src/test/java/tech/tablesaw/columns/datetimes/DateTimeFillersTest.java
index 77af5f79..151f0127 100644
--- a/core/src/test/java/tech/tablesaw/columns/datetimes/DateTimeFillersTest.java
+++ b/core/src/test/java/tech/tablesaw/columns/datetimes/DateTimeFillersTest.java
@@ -7,7 +7,7 @@ import java.time.temporal.ChronoUnit;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static tech.tablesaw.api.DateTimeColumn.create;
-import static tech.tablesaw.columns.datetimes.fillers.TemporalRangeIterable.range;
+import static tech.tablesaw.columns.temporal.fillers.TemporalRangeIterable.range;
 
 public class DateTimeFillersTest {
 
diff --git a/core/src/test/java/tech/tablesaw/columns/times/TimeFillersTest.java b/core/src/test/java/tech/tablesaw/columns/times/TimeFillersTest.java
index 8b9e19de..e15749ee 100644
--- a/core/src/test/java/tech/tablesaw/columns/times/TimeFillersTest.java
+++ b/core/src/test/java/tech/tablesaw/columns/times/TimeFillersTest.java
@@ -7,7 +7,7 @@ import java.time.temporal.ChronoUnit;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static tech.tablesaw.api.TimeColumn.create;
-import static tech.tablesaw.columns.datetimes.fillers.TemporalRangeIterable.range;
+import static tech.tablesaw.columns.temporal.fillers.TemporalRangeIterable.range;
 
 public class TimeFillersTest {
 
diff --git a/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java b/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
index 873642e9..f19480a4 100644
--- a/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
+++ b/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
@@ -164,7 +164,7 @@ public class CsvReaderTest {
         Reader reader = new FileReader("../data/bus_stop_test.csv");
         CsvReadOptions options = CsvReadOptions.builder(reader)
                 .header(true)
-                .minimizeColumnSizes(true)
+                .minimizeColumnSizes()
                 .separator(',')
                 .sample(false)
                 .locale(Locale.getDefault())
@@ -590,7 +590,7 @@ public class CsvReaderTest {
     public void testReadFailure() throws IOException {
         // TODO (lwhite): These tests don't fail. What was their intent?
         Table table1 = Table.read().csv(CsvReadOptions.builder("../data/read_failure_test.csv")
-                .minimizeColumnSizes(true));
+                .minimizeColumnSizes());
         table1.structure(); // just make sure the import completed
         ShortColumn test = table1.shortColumn("Test");
         //TODO(lwhite): Better tests
@@ -601,7 +601,7 @@ public class CsvReaderTest {
     public void testReadFailure2() throws IOException {
         Table table1 = Table.read().csv(
                 CsvReadOptions.builder("../data/read_failure_test2.csv")
-                .minimizeColumnSizes(true));
+                .minimizeColumnSizes());
         table1.structure(); // just make sure the import completed
         ShortColumn test = table1.shortColumn("Test");
 
diff --git a/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthReaderTest.java b/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthReaderTest.java
index 2ac3330b..5f3b2792 100644
--- a/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthReaderTest.java
+++ b/core/src/test/java/tech/tablesaw/io/fixed/FixedWidthReaderTest.java
@@ -113,7 +113,7 @@ public class FixedWidthReaderTest {
                 .lineEnding("\n")
                 .sample(false)
                 .locale(Locale.getDefault())
-                .minimizeColumnSizes(true)
+                .minimizeColumnSizes()
                 .build();
 
         Reader reader = new FileReader("../data/fixed_width_missing_values.txt");
@@ -131,7 +131,7 @@ public class FixedWidthReaderTest {
                 .padding('_')
                 .lineEnding("\n")
                 .missingValueIndicator("null")
-                .minimizeColumnSizes(true)
+                .minimizeColumnSizes()
                 .sample(false)
                 .build();
 
diff --git a/core/src/test/java/tech/tablesaw/io/jdbc/SqlResultSetReaderTest.java b/core/src/test/java/tech/tablesaw/io/jdbc/SqlResultSetReaderTest.java
index 62257d68..495a3c38 100644
--- a/core/src/test/java/tech/tablesaw/io/jdbc/SqlResultSetReaderTest.java
+++ b/core/src/test/java/tech/tablesaw/io/jdbc/SqlResultSetReaderTest.java
@@ -21,9 +21,11 @@ import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.ResultSet;
 import java.sql.Statement;
+import java.sql.Types;
 
 import org.junit.jupiter.api.Test;
 
+import tech.tablesaw.api.ColumnType;
 import tech.tablesaw.api.Table;
 import tech.tablesaw.columns.numbers.DoubleColumnType;
 import tech.tablesaw.columns.numbers.FloatColumnType;
@@ -108,4 +110,9 @@ public class SqlResultSetReaderTest {
 
         }
     }
+
+    @Test
+    public void invalidPrecision() {
+	assertEquals(ColumnType.DOUBLE, SqlResultSetReader.getColumnType(Types.NUMERIC, 0, 0));
+    }
 }
\ No newline at end of file