Skip to content
Open
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

### Bug fixes

- Fix error in `write_dataframe` with `use_arrow=False` when writing an object-type
column with datetimes without any time zone offsets or with a mix of offsets and
no offsets (#634).
- Fix writing non-string object columns with arrow (#630).
- Fix writing empty string category columns with arrow fails (#621).
- Fix Time type columns being skipped with `use_arrow=False` (#617).
Expand Down
2 changes: 2 additions & 0 deletions pyogrio/_io.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -3070,6 +3070,8 @@ def ogr_write(
tz_array = gdal_tz_offsets.get(fields[field_idx], None)
if tz_array is None:
gdal_tz = 0
elif np.isnan(tz_array[i]):
gdal_tz = 0
else:
gdal_tz = tz_array[i]
OGR_F_SetFieldDateTimeEx(
Expand Down
4 changes: 3 additions & 1 deletion pyogrio/geopandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,9 @@ def write_dataframe(
elif col.dtype == "object":
# Column of Timestamp/datetime objects, split in naive datetime and tz.
if pd.api.types.infer_dtype(df[name]) == "datetime":
tz_offset = col.map(lambda x: x.utcoffset(), na_action="ignore")
tz_offset = col.map(lambda x: x.utcoffset(), na_action="ignore").astype(
"timedelta64[us]" if PANDAS_GE_30 else "timedelta64[ns]"
)
gdal_offset_repr = tz_offset // pd.Timedelta("15m") + 100
gdal_tz_offsets[name] = gdal_offset_repr.values
naive = col.map(lambda x: x.replace(tzinfo=None), na_action="ignore")
Expand Down
50 changes: 49 additions & 1 deletion pyogrio/tests/test_geopandas_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
import re
import warnings
from datetime import date, datetime, time, timezone
from datetime import date, datetime, time, timedelta, timezone
from decimal import Decimal
from io import BytesIO
from pathlib import Path
Expand Down Expand Up @@ -1005,6 +1005,54 @@ def test_write_read_datetime_tz_mixed_offsets(
assert_geodataframe_equal(result, df)


@pytest.mark.parametrize(
"dates",
[
[
datetime(2023, 1, 1, 11, 0, 1, 111000),
datetime(2023, 6, 1, 10, 0, 1, 111000),
np.nan,
],
[
datetime(2023, 1, 1, 11, 0, 1, 111000, tzinfo=timezone(timedelta(hours=1))),
datetime(2023, 6, 1, 10, 0, 1, 111000),
np.nan,
],
],
)
@pytest.mark.requires_arrow_write_api
@pytest.mark.skipif(
not GDAL_GE_311,
reason="before GDAL 3.11, datetimes weren't handled as well",
)
def test_write_read_datetime_tz_offsets_None(tmp_path, dates, use_arrow):
"""Test writing a column with datetimes with and without time zone offsets."""
df = gp.GeoDataFrame(
{"dates": dates, "geometry": [Point(1, 1)] * len(dates)},
crs="EPSG:4326",
dtype=object,
)
fpath = tmp_path / "test.gpkg"
write_dataframe(df, fpath, use_arrow=use_arrow)
result = read_dataframe(
fpath,
use_arrow=use_arrow,
datetime_as_string=False,
mixed_offsets_as_utc=False,
)

exp_df = df.copy()
if dates[0].tzinfo is None:
exp_df.dates = pd.to_datetime(exp_df.dates, utc=False)
if PANDAS_GE_20:
exp_df.dates = exp_df.dates.dt.as_unit("ms")

if not PANDAS_GE_30:
exp_df.loc[2, "dates"] = None

assert_geodataframe_equal(result, exp_df)


@pytest.mark.parametrize("ext", [ext for ext in ALL_EXTS if ext != ".shp"])
@pytest.mark.parametrize(
"dates_raw",
Expand Down
Loading