Repository: jtablesaw/tablesaw
Pull request #1197 (2023-03-27 09:11:41)

Name to lowercase in renameDuplicateColumnHeaders
- [x] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](https://developercertificate.org)

renameDuplicateColumnHeaders when allowDuplicateColumnNames is true is case sensitive which causes validateColumn(final Column<?> newColumn) to fail and throw IllegalArgumentException. 

What was changed

Used toLowerCase while using HashMap.
Also thought of using treeMap, but get() in treeMap has timecomplexity of O(log(n))

Did you add a unit test?
No


Patch:
diff --git a/core/src/main/java/tech/tablesaw/columns/dates/DateParser.java b/core/src/main/java/tech/tablesaw/columns/dates/DateParser.java
index 0012b50f..b2d99f31 100644
--- a/core/src/main/java/tech/tablesaw/columns/dates/DateParser.java
+++ b/core/src/main/java/tech/tablesaw/columns/dates/DateParser.java
@@ -8,6 +8,7 @@ import java.time.format.DateTimeParseException;
 import java.util.Locale;
 import tech.tablesaw.api.ColumnType;
 import tech.tablesaw.columns.AbstractColumnParser;
+import tech.tablesaw.columns.datetimes.DateTimeParser;
 import tech.tablesaw.io.ReadOptions;
 
 public class DateParser extends AbstractColumnParser<LocalDate> {
@@ -19,19 +20,19 @@ public class DateParser extends AbstractColumnParser<LocalDate> {
   private static final DateTimeFormatter dtf4 = DateTimeFormatter.ofPattern("MM.dd.yyyy");
   private static final DateTimeFormatter dtf5 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
   private static final DateTimeFormatter dtf6 = DateTimeFormatter.ofPattern("yyyy/MM/dd");
-  private static final DateTimeFormatter dtf7 = DateTimeFormatter.ofPattern("dd/MMM/yyyy");
-  private static final DateTimeFormatter dtf8 = DateTimeFormatter.ofPattern("dd-MMM-yyyy");
+  private static final DateTimeFormatter dtf7 = DateTimeParser.caseInsensitiveFormatter("dd/MMM/yyyy");
+  private static final DateTimeFormatter dtf8 = DateTimeParser.caseInsensitiveFormatter("dd-MMM-yyyy");
   private static final DateTimeFormatter dtf9 = DateTimeFormatter.ofPattern("M/d/yyyy");
   private static final DateTimeFormatter dtf10 = DateTimeFormatter.ofPattern("M/d/yy");
-  private static final DateTimeFormatter dtf11 = DateTimeFormatter.ofPattern("MMM/dd/yyyy");
-  private static final DateTimeFormatter dtf12 = DateTimeFormatter.ofPattern("MMM-dd-yyyy");
-  private static final DateTimeFormatter dtf13 = DateTimeFormatter.ofPattern("MMM/dd/yy");
-  private static final DateTimeFormatter dtf14 = DateTimeFormatter.ofPattern("MMM-dd-yy");
-  private static final DateTimeFormatter dtf15 = DateTimeFormatter.ofPattern("MMM/dd/yyyy");
-  private static final DateTimeFormatter dtf16 = DateTimeFormatter.ofPattern("MMM/d/yyyy");
-  private static final DateTimeFormatter dtf17 = DateTimeFormatter.ofPattern("MMM-dd-yy");
-  private static final DateTimeFormatter dtf18 = DateTimeFormatter.ofPattern("MMM dd, yyyy");
-  private static final DateTimeFormatter dtf19 = DateTimeFormatter.ofPattern("MMM d, yyyy");
+  private static final DateTimeFormatter dtf11 = DateTimeParser.caseInsensitiveFormatter("MMM/dd/yyyy");
+  private static final DateTimeFormatter dtf12 = DateTimeParser.caseInsensitiveFormatter("MMM-dd-yyyy");
+  private static final DateTimeFormatter dtf13 = DateTimeParser.caseInsensitiveFormatter("MMM/dd/yy");
+  private static final DateTimeFormatter dtf14 = DateTimeParser.caseInsensitiveFormatter("MMM-dd-yy");
+  private static final DateTimeFormatter dtf15 = DateTimeParser.caseInsensitiveFormatter("MMM/dd/yyyy");
+  private static final DateTimeFormatter dtf16 = DateTimeParser.caseInsensitiveFormatter("MMM/d/yyyy");
+  private static final DateTimeFormatter dtf17 = DateTimeParser.caseInsensitiveFormatter("MMM-dd-yy");
+  private static final DateTimeFormatter dtf18 = DateTimeParser.caseInsensitiveFormatter("MMM dd, yyyy");
+  private static final DateTimeFormatter dtf19 = DateTimeParser.caseInsensitiveFormatter("MMM d, yyyy");
 
   // A formatter that handles all the date formats defined above
   public static final DateTimeFormatter DEFAULT_FORMATTER =
diff --git a/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeParser.java b/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeParser.java
index aa0dfd4e..cc6dd8f3 100644
--- a/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeParser.java
+++ b/core/src/main/java/tech/tablesaw/columns/datetimes/DateTimeParser.java
@@ -19,14 +19,26 @@ public class DateTimeParser extends AbstractColumnParser<LocalDateTime> {
       DateTimeFormatter.ofPattern(
           "yyyy-MM-dd HH:mm:ss.S"); // 2014-07-09 13:03:44.7 (as above, but without leading 0 in
   // millis)
-  private static final DateTimeFormatter dtTimef4 =
-      DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm"); // 09-Jul-2014 13:03
+  private static final DateTimeFormatter dtTimef4 = caseInsensitiveFormatter("dd-MMM-yyyy HH:mm"); // 09-Jul-2014 13:03
   private static final DateTimeFormatter dtTimef5 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
   private static final DateTimeFormatter dtTimef6; // ISO, with millis appended
   private static final DateTimeFormatter dtTimef7 = //  7/9/14 9:04
       DateTimeFormatter.ofPattern("M/d/yy H:mm");
-  private static final DateTimeFormatter dtTimef8 =
-      DateTimeFormatter.ofPattern("M/d/yyyy h:mm:ss a"); //  7/9/2014 9:04:55 PM
+  private static final DateTimeFormatter dtTimef8 = caseInsensitiveFormatter("M/d/yyyy h:mm:ss a"); //  7/9/2014 9:04:55 PM
+
+  /**
+   * Creates a Case-insensitive formatter using the specified pattern.
+   * This method will create a formatter based on a simple pattern of letters and symbols as described in the class documentation.
+   * For example, d MMM yyyy will format 2011-12-03 as '3 Dec 2011'. The formatter will use the default FORMAT locale.
+   * This function can handle cases like am/AM, pm/PM, Jan/JAN, Feb/FEB etc
+   *
+   * @param pattern the pattern to use, not null
+   * @return the formatter based on the pattern, not null
+   * @throws IllegalArgumentException if the pattern is invalid
+   */
+  public static DateTimeFormatter caseInsensitiveFormatter(String pattern) {
+    return new DateTimeFormatterBuilder().parseCaseInsensitive().appendPattern(pattern).toFormatter();
+  }
 
   static {
     dtTimef6 =
diff --git a/core/src/main/java/tech/tablesaw/columns/times/TimeParser.java b/core/src/main/java/tech/tablesaw/columns/times/TimeParser.java
index 35ee4107..242ed97f 100644
--- a/core/src/main/java/tech/tablesaw/columns/times/TimeParser.java
+++ b/core/src/main/java/tech/tablesaw/columns/times/TimeParser.java
@@ -9,16 +9,17 @@ import java.time.format.DateTimeParseException;
 import java.util.Locale;
 import tech.tablesaw.api.ColumnType;
 import tech.tablesaw.columns.AbstractColumnParser;
+import tech.tablesaw.columns.datetimes.DateTimeParser;
 import tech.tablesaw.io.ReadOptions;
 
 public class TimeParser extends AbstractColumnParser<LocalTime> {
 
   private static final DateTimeFormatter timef1 = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
-  private static final DateTimeFormatter timef2 = DateTimeFormatter.ofPattern("hh:mm:ss a");
-  private static final DateTimeFormatter timef3 = DateTimeFormatter.ofPattern("h:mm:ss a");
+  private static final DateTimeFormatter timef2 = DateTimeParser.caseInsensitiveFormatter("hh:mm:ss a");
+  private static final DateTimeFormatter timef3 = DateTimeParser.caseInsensitiveFormatter("h:mm:ss a");
   private static final DateTimeFormatter timef4 = DateTimeFormatter.ISO_LOCAL_TIME;
-  private static final DateTimeFormatter timef5 = DateTimeFormatter.ofPattern("hh:mm a");
-  private static final DateTimeFormatter timef6 = DateTimeFormatter.ofPattern("h:mm a");
+  private static final DateTimeFormatter timef5 = DateTimeParser.caseInsensitiveFormatter("hh:mm a");
+  private static final DateTimeFormatter timef6 = DateTimeParser.caseInsensitiveFormatter("h:mm a");
 
   // only for parsing:
   private static final DateTimeFormatter timef7 = DateTimeFormatter.ofPattern("HHmm");
diff --git a/core/src/main/java/tech/tablesaw/io/FileReader.java b/core/src/main/java/tech/tablesaw/io/FileReader.java
index bec89c04..4c8f96e5 100644
--- a/core/src/main/java/tech/tablesaw/io/FileReader.java
+++ b/core/src/main/java/tech/tablesaw/io/FileReader.java
@@ -132,12 +132,12 @@ public abstract class FileReader {
     Map<String, Integer> nameCounter = new HashMap<>();
     for (int i = 0; i < headerNames.length; i++) {
       String name = headerNames[i];
-      Integer count = nameCounter.get(name);
+      Integer count = nameCounter.get(name.toLowerCase());
       if (count == null) {
-        nameCounter.put(name, 1);
+        nameCounter.put(name.toLowerCase(), 1);
       } else {
         count++;
-        nameCounter.put(name, count);
+        nameCounter.put(name.toLowerCase(), count);
         headerNames[i] = name + "-" + count;
       }
     }
diff --git a/pom.xml b/pom.xml
index 91722105..0406ba12 100644
--- a/pom.xml
+++ b/pom.xml
@@ -274,7 +274,7 @@
             <dependency>
                 <groupId>com.fasterxml.jackson.core</groupId>
                 <artifactId>jackson-databind</artifactId>
-                <version>2.13.4.1</version>
+                <version>2.13.4.2</version>
             </dependency>
             <dependency>
                 <groupId>com.fasterxml.jackson.datatype</groupId>


Test patch:
diff --git a/core/src/test/java/tech/tablesaw/api/DateColumnTest.java b/core/src/test/java/tech/tablesaw/api/DateColumnTest.java
index f3d6b0ef..adbe8cb1 100644
--- a/core/src/test/java/tech/tablesaw/api/DateColumnTest.java
+++ b/core/src/test/java/tech/tablesaw/api/DateColumnTest.java
@@ -12,6 +12,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import tech.tablesaw.columns.dates.DateColumnType;
 import tech.tablesaw.columns.dates.DateParser;
+import tech.tablesaw.columns.datetimes.DateTimeParser;
 
 public class DateColumnTest {
   private DateColumn column1;
@@ -61,7 +62,7 @@ public class DateColumnTest {
     column1.appendCell("12/23/1924");
     column1.appendCell("12-May-2015");
     column1.appendCell("12-Jan-2015");
-    column1.setPrintFormatter(DateTimeFormatter.ofPattern("MMM~dd~yyyy"), "");
+    column1.setPrintFormatter(DateTimeParser.caseInsensitiveFormatter("MMM~dd~yyyy"), "");
     assertEquals(
         "Column: Game date"
             + System.lineSeparator()
@@ -82,7 +83,7 @@ public class DateColumnTest {
     column1.appendCell("12/23/1924");
     column1.appendCell("12-May-2015");
     column1.appendCell("12-Jan-2015");
-    column1.setPrintFormatter(DateTimeFormatter.ofPattern("MMM~dd~yyyy"));
+    column1.setPrintFormatter(DateTimeParser.caseInsensitiveFormatter("MMM~dd~yyyy"));
     assertEquals(
         "Column: Game date"
             + System.lineSeparator()
diff --git a/core/src/test/java/tech/tablesaw/api/DateTimeColumnTest.java b/core/src/test/java/tech/tablesaw/api/DateTimeColumnTest.java
index aa160e87..12b23a07 100644
--- a/core/src/test/java/tech/tablesaw/api/DateTimeColumnTest.java
+++ b/core/src/test/java/tech/tablesaw/api/DateTimeColumnTest.java
@@ -22,7 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
-import java.util.Arrays;
+import java.util.List;
+
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import tech.tablesaw.columns.datetimes.DateTimeParser;
@@ -63,7 +64,7 @@ public class DateTimeColumnTest {
   public void testCustomParser() {
     // Just do enough to ensure the parser is wired up correctly
     DateTimeParser customParser = new DateTimeParser(ColumnType.LOCAL_DATE_TIME);
-    customParser.setMissingValueStrings(Arrays.asList("not here"));
+    customParser.setMissingValueStrings(List.of("not here"));
     column1.setParser(customParser);
 
     column1.appendCell("not here");
diff --git a/core/src/test/java/tech/tablesaw/io/TypeUtilsTest.java b/core/src/test/java/tech/tablesaw/io/TypeUtilsTest.java
index 720b740b..b06567a1 100644
--- a/core/src/test/java/tech/tablesaw/io/TypeUtilsTest.java
+++ b/core/src/test/java/tech/tablesaw/io/TypeUtilsTest.java
@@ -27,7 +27,7 @@ import tech.tablesaw.columns.datetimes.DateTimeParser;
 public class TypeUtilsTest {
 
   @Test
-  public void testDateFormaterWithLocaleEN() {
+  public void testDateFormatterWithLocaleEN() {
     String anotherDate = "12-May-2015";
     LocalDate result =
         LocalDate.parse(anotherDate, DateParser.DEFAULT_FORMATTER.withLocale(Locale.ENGLISH));
@@ -35,8 +35,8 @@ public class TypeUtilsTest {
   }
 
   @Test
-  public void testDateFormater() {
-    final DateTimeFormatter dtTimef8 = DateTimeFormatter.ofPattern("M/d/yyyy h:mm:ss a");
+  public void testDateFormatter() {
+    final DateTimeFormatter dtTimef8 = DateTimeParser.caseInsensitiveFormatter("M/d/yyyy h:mm:ss a");
 
     String anotherDate = "10/2/2016 8:18:03 AM";
     dtTimef8.parse(anotherDate);
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 4e96df96..e9b418c6 100644
--- a/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
+++ b/core/src/test/java/tech/tablesaw/io/csv/CsvReaderTest.java
@@ -55,6 +55,7 @@ import tech.tablesaw.api.LongColumn;
 import tech.tablesaw.api.ShortColumn;
 import tech.tablesaw.api.StringColumn;
 import tech.tablesaw.api.Table;
+import tech.tablesaw.columns.datetimes.DateTimeParser;
 import tech.tablesaw.columns.numbers.DoubleColumnType;
 import tech.tablesaw.columns.numbers.NumberColumnFormatter;
 import tech.tablesaw.io.AddCellToColumnException;
@@ -302,7 +303,7 @@ public class CsvReaderTest {
     CsvReadOptions options =
         CsvReadOptions.builder(reader)
             .header(header)
-            .dateTimeFormat(DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm:ss"))
+            .dateTimeFormat(DateTimeParser.caseInsensitiveFormatter("dd-MMM-yyyy HH:mm:ss"))
             .build();
 
     final List<ColumnType> actual = asList(new CsvReader().detectColumnTypes(reader, options));
@@ -327,7 +328,7 @@ public class CsvReaderTest {
     CsvReadOptions options =
         CsvReadOptions.builder(reader)
             .header(header)
-            .dateTimeFormat(DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm:ss"))
+            .dateTimeFormat(DateTimeParser.caseInsensitiveFormatter("dd-MMM-yyyy HH:mm:ss"))
             .build();
 
     final List<ColumnType> actual = asList(new CsvReader().detectColumnTypes(reader, options));
diff --git a/core/src/test/java/tech/tablesaw/io/csv/CsvWriterTest.java b/core/src/test/java/tech/tablesaw/io/csv/CsvWriterTest.java
index 2f3488ac..1d0db44d 100644
--- a/core/src/test/java/tech/tablesaw/io/csv/CsvWriterTest.java
+++ b/core/src/test/java/tech/tablesaw/io/csv/CsvWriterTest.java
@@ -15,6 +15,7 @@ import java.util.Map;
 import org.junit.jupiter.api.Test;
 import tech.tablesaw.api.*;
 import tech.tablesaw.columns.booleans.BooleanFormatter;
+import tech.tablesaw.columns.datetimes.DateTimeParser;
 import tech.tablesaw.columns.instant.InstantColumnFormatter;
 import tech.tablesaw.columns.numbers.NumberColumnFormatter;
 import tech.tablesaw.columns.strings.StringColumnFormatter;
@@ -46,7 +47,7 @@ public class CsvWriterTest {
   @Test
   void dateFormatter() throws IOException {
     Table table = Table.read().csv("../data/bush.csv").rows(1);
-    table.dateColumn("date").setPrintFormatter(DateTimeFormatter.ofPattern("MMM dd, yyyy"));
+    table.dateColumn("date").setPrintFormatter(DateTimeParser.caseInsensitiveFormatter("MMM dd, yyyy"));
     StringWriter writer = new StringWriter();
     table.write().usingOptions(CsvWriteOptions.builder(writer).usePrintFormatters(true).build());
     assertEquals(
@@ -84,7 +85,7 @@ public class CsvWriterTest {
   @Test
   void printFormatter_date() throws IOException {
     Table table = Table.create("", DateColumn.create("dates"));
-    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-dd-MMM");
+    DateTimeFormatter formatter = DateTimeParser.caseInsensitiveFormatter("yyyy-dd-MMM");
     table.dateColumn("dates").setPrintFormatter(formatter, "WHAT?");
     table
         .dateColumn("dates")
@@ -154,7 +155,7 @@ public class CsvWriterTest {
   @Test
   void printFormatter_datetime() throws IOException {
     Table table = Table.create("", DateTimeColumn.create("dates"));
-    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM d, yyyy - hh:mm");
+    DateTimeFormatter formatter = DateTimeParser.caseInsensitiveFormatter("MMM d, yyyy - hh:mm");
     table.dateTimeColumn("dates").setPrintFormatter(formatter, "WHAT?");
     table.dateTimeColumn("dates").append(LocalDateTime.of(2011, 1, 1, 4, 30));
     StringWriter writer = new StringWriter();
@@ -216,7 +217,7 @@ public class CsvWriterTest {
   @Test
   void printFormatter_instant() throws IOException {
     Table table = Table.create("", InstantColumn.create("dates"));
-    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM d, yyyy - hh:mm");
+    DateTimeFormatter formatter = DateTimeParser.caseInsensitiveFormatter("MMM d, yyyy - hh:mm");
     table.instantColumn("dates").setPrintFormatter(new InstantColumnFormatter(formatter, "WHAT?"));
     table.instantColumn("dates").append(Instant.parse("2007-12-03T10:15:30.00Z")).appendMissing();
     StringWriter writer = new StringWriter();
@@ -253,7 +254,7 @@ public class CsvWriterTest {
     table.dateTimeColumn(0).append(LocalDateTime.of(2011, 1, 1, 4, 30));
     table
         .dateTimeColumn("dt")
-        .setPrintFormatter(DateTimeFormatter.ofPattern("MMM d, yyyy - hh:mm"));
+        .setPrintFormatter(DateTimeParser.caseInsensitiveFormatter("MMM d, yyyy - hh:mm"));
     StringWriter writer = new StringWriter();
     table.write().usingOptions(CsvWriteOptions.builder(writer).usePrintFormatters(true).build());
     assertEquals(