Repository: mathesar-foundation/mathesar

Issue:
Handle `DATE` data type in the backend
This issue is to ensure that Mathesar can handle the Postgres `DATE` data type.

As part of this issue, we need to ensure that:
- Users can use the API to change a column to `DATE` if it's possible to do so.
- Automatic type inference during file import suggests `DATE` when it makes sense to do so. 
  - We could use something like https://dateparser.readthedocs.io/

**Additional Context** 
- #250 (parent issue)
- See PR #368 for code that calculates which types are allowed for a column.
- This issue was previously specced out in #128


Discussion:

Resolving patch:
diff --git a/db/columns.py b/db/columns.py
--- a/db/columns.py
+++ b/db/columns.py
@@ -160,8 +160,6 @@ def column_index(self):
 
     @property
     def default_value(self):
-        print(self.table_)
-        print(self.original_table)
         if self.table_ is not None:
             table_oid = tables.get_oid_from_table(
                 self.table_.name, self.table_.schema, self.engine
diff --git a/db/types/alteration.py b/db/types/alteration.py
--- a/db/types/alteration.py
+++ b/db/types/alteration.py
@@ -171,6 +171,7 @@ def install_all_casts(engine):
     create_integer_casts(engine)
     create_decimal_number_casts(engine)
     create_interval_casts(engine)
+    create_date_casts(engine)
     create_varchar_casts(engine)
 
 
@@ -203,6 +204,11 @@ def create_decimal_number_casts(engine):
         create_cast_functions(type_str, type_body_map, engine)
 
 
+def create_date_casts(engine):
+    type_body_map = _get_date_type_body_map()
+    create_cast_functions(DATE, type_body_map, engine)
+
+
 def create_varchar_casts(engine):
     type_body_map = _get_varchar_type_body_map(engine)
     create_cast_functions(VARCHAR, type_body_map, engine)
@@ -237,6 +243,7 @@ def get_defined_source_target_cast_tuples(engine):
         NUMERIC: _get_decimal_number_type_body_map(target_type_str=NUMERIC),
         REAL: _get_decimal_number_type_body_map(target_type_str=REAL),
         SMALLINT: _get_integer_type_body_map(target_type_str=SMALLINT),
+        DATE: _get_date_type_body_map(),
         VARCHAR: _get_varchar_type_body_map(engine),
     }
     return {
@@ -473,6 +480,15 @@ def _get_varchar_type_body_map(engine):
     return _get_default_type_body_map(supported_types, VARCHAR)
 
 
+def _get_date_type_body_map():
+    # Note that default postgres conversion for dates depends on the `DateStyle` option
+    # set on the server, which can be one of DMY, MDY, or YMD. Defaults to MDY.
+    default_behavior_source_types = [DATE, VARCHAR]
+    return _get_default_type_body_map(
+        default_behavior_source_types, DATE,
+    )
+
+
 def _get_default_type_body_map(source_types, target_type_str):
     default_cast_str = f"""
         BEGIN
diff --git a/db/types/inference.py b/db/types/inference.py
--- a/db/types/inference.py
+++ b/db/types/inference.py
@@ -23,6 +23,7 @@
     base.STRING: [
         base.PostgresType.BOOLEAN.value,
         base.PostgresType.NUMERIC.value,
+        base.PostgresType.DATE.value,
         base.PostgresType.INTERVAL.value,
         base.MathesarCustomType.EMAIL.value,
     ],


Test patch:
diff --git a/db/tests/types/test_alteration.py b/db/tests/types/test_alteration.py
--- a/db/tests/types/test_alteration.py
+++ b/db/tests/types/test_alteration.py
@@ -1,4 +1,4 @@
-from datetime import timedelta
+from datetime import timedelta, date
 from decimal import Decimal
 
 import pytest
@@ -31,6 +31,7 @@
 NUMERIC = PostgresType.NUMERIC.value.upper()
 REAL = PostgresType.REAL.value.upper()
 SMALLINT = PostgresType.SMALLINT.value.upper()
+DATE = PostgresType.DATE.value.upper()
 VARCHAR = "VARCHAR"
 
 
@@ -268,6 +269,14 @@
             VARCHAR: {VALID: [(3, "3")]},
         }
     },
+    DATE: {
+        ISCHEMA_NAME: PostgresType.DATE.value,
+        REFLECTED_NAME: DATE,
+        TARGET_DICT: {
+            DATE: {VALID: [(date(1999, 1, 18), date(1999, 1, 18))]},
+            VARCHAR: {VALID: [(date(1999, 1, 18), "1999-01-18")]},
+        },
+    },
     VARCHAR: {
         ISCHEMA_NAME: PostgresType.CHARACTER_VARYING.value,
         SUPPORTED_MAP_NAME: "varchar",
@@ -327,6 +336,19 @@
                 VALID: [("432", 432)],
                 INVALID: ["1.2234"]
             },
+            DATE: {
+                VALID: [
+                    ("1999-01-18", date(1999, 1, 18)),
+                    ("1/18/1999", date(1999, 1, 18)),
+                    ("jan-1999-18", date(1999, 1, 18)),
+                    ("19990118", date(1999, 1, 18)),
+                ],
+                INVALID: [
+                    "18/1/1999",
+                    "not a date",
+                    "1234",
+                ]
+            },
             VARCHAR: {VALID: [("a string", "a string")]},
         }
     }
diff --git a/db/tests/types/test_inference.py b/db/tests/types/test_inference.py
--- a/db/tests/types/test_inference.py
+++ b/db/tests/types/test_inference.py
@@ -1,7 +1,7 @@
 import pytest
 from unittest.mock import patch
 from sqlalchemy import Column, MetaData, Table, select
-from sqlalchemy import BOOLEAN, Numeric, NUMERIC, String, VARCHAR
+from sqlalchemy import BOOLEAN, Numeric, NUMERIC, String, VARCHAR, DATE
 from db.tests.types import fixtures
 from db.types import inference
 
@@ -21,6 +21,7 @@
     (String, ["t", "false", "2", "0"], VARCHAR),
     (String, ["a", "cat", "mat", "bat"], VARCHAR),
     (String, ["2", "1", "0", "0"], NUMERIC),
+    (String, ["2000-01-12", "6/23/2004", "May-2007-29", "20200909"], DATE),
 ]
 
 
diff --git a/mathesar/tests/views/api/test_column_api.py b/mathesar/tests/views/api/test_column_api.py
--- a/mathesar/tests/views/api/test_column_api.py
+++ b/mathesar/tests/views/api/test_column_api.py
@@ -86,7 +86,7 @@ def test_column_list(column_test_table, client):
             'nullable': True,
             'primary_key': False,
             'valid_target_types': [
-                'BIGINT', 'BOOLEAN', 'DECIMAL', 'DOUBLE PRECISION', 'FLOAT',
+                'BIGINT', 'BOOLEAN', 'DATE', 'DECIMAL', 'DOUBLE PRECISION', 'FLOAT',
                 'INTEGER', 'INTERVAL', 'MATHESAR_TYPES.EMAIL', 'NUMERIC',
                 'REAL', 'SMALLINT', 'VARCHAR',
             ],