Repository: innobi/pantab (language: python)

Issue:
`Date32` dates are off when written to hyper file
**Bug Description**
When a `Date32` column is included in a `pyarrow` table, the the dates seem to skew (at least for dates after 2024-01-31) upon hyper creation. See examples:
- `2024-02-01` **triggers issue** (writes 2024-03-03 instead)
- `2024-03-16` **triggers issue** (writes 2024-05-15 instead)
- `2024-03-01` **triggers issue** (writes 2024-04-30 instead)
- `2024-01-01` successfully writes 2024-01-01 (does not trigger issue)
- `2024-01-31` successfully writes 2024-01-31 (does not trigger issue)

**Versions Tested On**
- Windows 10 machine
  - Python 3.11.8
  - pantab 4.0
- Databricks enviornment
  - Python 3.9.5
  - pantab 4.0

**To Reproduce**
Use following script to reproduce (same dates referenced earlier):
```python
import datetime
import pyarrow as pa
import pantab as pt

def basic_arrow_table():
    schema = pa.schema(
        [
            ("date32_test_col", pa.date32())
        ]
    )

    tbl = pa.Table.from_arrays(
        [
            pa.array(
                [
                    datetime.date(2024,2,1), # TRIGGERS ISSUE: (writes 2024-03-03 instead)
                    datetime.date(2024, 3, 16), # TRIGGERS ISSUE: (writes 2024-05-15 instead)
                    datetime.date(2024, 3, 1), # TRIGGERS ISSUE: (writes 2024-04-30 instead)
                    datetime.date(2024, 1, 1), # DOES NOT TRIGGER ISSUE
                    datetime.date(2024, 1, 31) # DOES NOT TRIGGER ISSUE
                ]
            )
        ],
        schema=schema
    )

    return tbl

tbl = basic_arrow_table()

pt.frame_to_hyper(tbl, "pantab_hyper_defect_test.hyper", table="extract")
```

Resulting dates written:
<img width="633" alt="image" src="https://github.com/innobi/pantab/assets/97246612/75f785af-8fba-47d6-a1f6-399730dc6899">


Discussion / hints:

Fix patch:
diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst
index 37978c63..7752585f 100644
--- a/doc/source/changelog.rst
+++ b/doc/source/changelog.rst
@@ -1,6 +1,14 @@
 Changelog
 ^^^^^^^^^
 
+Pantab 4.0.1 (2024-03-21)
+=========================
+
+Bugfixes
+--------
+
+- Fixed issue where dates were not properly written to Hyper (#282)
+
 Pantab 4.0.0 (2024-03-15)
 =========================
 
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 475b0906..91fcf2d7 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -5,7 +5,7 @@
 project = "pantab"
 copyright = "2019-2024, Will Ayd, innobi, LLC"
 author = "Will Ayd, innobi, LLC"
-release = "4.0.0"
+release = "4.0.1"
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/pyproject.toml b/pyproject.toml
index 38b19ccb..6c2e55f2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,7 +8,7 @@ build-backend = "scikit_build_core.build"
 
 [project]
 name = "pantab"
-version = "4.0.0"
+version = "4.0.1"
 description = "Converts pandas DataFrames into Tableau Hyper Extracts and back"
 license = {file = "LICENSE.txt"}
 readme = "README.md"
diff --git a/src/pantab/__init__.py b/src/pantab/__init__.py
index 1032bf87..ea7aa6ad 100644
--- a/src/pantab/__init__.py
+++ b/src/pantab/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "4.0.0"
+__version__ = "4.0.1"
 
 
 from pantab._reader import frame_from_hyper, frame_from_hyper_query, frames_from_hyper
diff --git a/src/pantab/writer.cpp b/src/pantab/writer.cpp
index 4caec111..c88b2124 100644
--- a/src/pantab/writer.cpp
+++ b/src/pantab/writer.cpp
@@ -218,7 +218,7 @@ class Date32InsertHelper : public InsertHelper {
     const struct tm utc_tm = *std::gmtime(&tt);
     const hyperapi::Date dt{1900 + utc_tm.tm_year,
                             static_cast<int16_t>(1 + utc_tm.tm_mon),
-                            static_cast<int16_t>(1 + utc_tm.tm_yday)};
+                            static_cast<int16_t>(utc_tm.tm_mday)};
 
     hyperapi::internal::ValueInserter{*inserter_}.addValue(dt);
   }


Test patch:
diff --git a/tests/test_writer.py b/tests/test_writer.py
index 35f58db5..221eebd2 100644
--- a/tests/test_writer.py
+++ b/tests/test_writer.py
@@ -1,7 +1,8 @@
+import datetime
 import re
-from datetime import datetime, timezone
 
 import pandas as pd
+import pyarrow as pa
 import pytest
 import tableauhyperapi as tab_api
 
@@ -186,7 +187,12 @@ def test_utc_bug(tmp_hyper):
     Red-Green for UTC bug
     """
     frame = pd.DataFrame(
-        {"utc_time": [datetime.now(timezone.utc), pd.Timestamp("today", tz="UTC")]}
+        {
+            "utc_time": [
+                datetime.datetime.now(datetime.timezone.utc),
+                pd.Timestamp("today", tz="UTC"),
+            ]
+        }
     )
     pt.frame_to_hyper(frame, tmp_hyper, table="exp")
     with tab_api.HyperProcess(
@@ -305,3 +311,40 @@ def test_can_write_chunked_frames(chunked_frame, tmp_hyper):
             data = connection.execute_list_query("select * from test")
 
     assert data == [[1], [2], [3], [4], [5], [6]]
+
+
+def test_write_date_bug(tmp_hyper):
+    # GH282
+    schema = pa.schema([("date32_test_col", pa.date32())])
+
+    tbl = pa.Table.from_arrays(
+        [
+            pa.array(
+                [
+                    datetime.date(2024, 2, 1),
+                    datetime.date(2024, 3, 16),
+                    datetime.date(2024, 3, 1),
+                    datetime.date(2024, 1, 1),
+                    datetime.date(2024, 1, 31),
+                ]
+            )
+        ],
+        schema=schema,
+    )
+
+    pt.frame_to_hyper(tbl, tmp_hyper, table="test")
+    with tab_api.HyperProcess(
+        tab_api.Telemetry.DO_NOT_SEND_USAGE_DATA_TO_TABLEAU
+    ) as hyper:
+        with tab_api.Connection(
+            hyper.endpoint, tmp_hyper, tab_api.CreateMode.CREATE_IF_NOT_EXISTS
+        ) as connection:
+            data = connection.execute_list_query("select * from test")
+
+    assert data == [
+        [tab_api.Date(2024, 2, 1)],
+        [tab_api.Date(2024, 3, 16)],
+        [tab_api.Date(2024, 3, 1)],
+        [tab_api.Date(2024, 1, 1)],
+        [tab_api.Date(2024, 1, 31)],
+    ]