Skip to content

Commit aa511e3

Browse files
committed
add timestamp_tz_ns support
1 parent 6b23153 commit aa511e3

5 files changed

Lines changed: 22 additions & 12 deletions

File tree

src/duckdb_py/arrow/pyarrow_filter_pushdown.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ py::object MakePyArrowScalar(const Value &constant, const string &timezone_confi
112112
py::handle date_type = import_cache.pyarrow.timestamp();
113113
return dataset_scalar(scalar(converted_value, date_type(time_unit_string, py::arg("tz") = timezone_config)));
114114
}
115+
case LogicalTypeId::TIMESTAMP_TZ_NS: {
116+
py::handle date_type = import_cache.pyarrow.timestamp();
117+
auto converted_value = Timestamp::GetEpochNanoSeconds(timestamp_t(constant.GetValue<int64_t>()));
118+
return dataset_scalar(scalar(converted_value, date_type("ns", py::arg("tz") = timezone_config)));
119+
}
115120
case LogicalTypeId::UTINYINT: {
116121
py::handle integer_type = import_cache.pyarrow.uint8();
117122
return dataset_scalar(scalar(constant.GetValue<uint8_t>(), integer_type()));

src/duckdb_py/native/python_objects.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ static bool KeyIsHashable(const LogicalType &type) {
437437
case LogicalTypeId::TIMESTAMP_NS:
438438
case LogicalTypeId::TIMESTAMP_SEC:
439439
case LogicalTypeId::TIMESTAMP_TZ:
440+
case LogicalTypeId::TIMESTAMP_TZ_NS:
440441
case LogicalTypeId::TIME_TZ:
441442
case LogicalTypeId::TIME:
442443
case LogicalTypeId::DATE:
@@ -518,7 +519,8 @@ py::object PythonObject::FromValue(const Value &val, const LogicalType &type,
518519
case LogicalTypeId::TIMESTAMP_MS:
519520
case LogicalTypeId::TIMESTAMP_NS:
520521
case LogicalTypeId::TIMESTAMP_SEC:
521-
case LogicalTypeId::TIMESTAMP_TZ: {
522+
case LogicalTypeId::TIMESTAMP_TZ:
523+
case LogicalTypeId::TIMESTAMP_TZ_NS: {
522524
D_ASSERT(type.InternalType() == PhysicalType::INT64);
523525
auto timestamp = val.GetValueUnsafe<timestamp_t>();
524526

@@ -532,7 +534,7 @@ py::object PythonObject::FromValue(const Value &val, const LogicalType &type,
532534

533535
if (type.id() == LogicalTypeId::TIMESTAMP_MS) {
534536
timestamp = Timestamp::FromEpochMs(timestamp.value);
535-
} else if (type.id() == LogicalTypeId::TIMESTAMP_NS) {
537+
} else if (type.id() == LogicalTypeId::TIMESTAMP_NS || type.id() == LogicalTypeId::TIMESTAMP_TZ_NS) {
536538
timestamp = Timestamp::FromEpochNanoSeconds(timestamp.value);
537539
} else if (type.id() == LogicalTypeId::TIMESTAMP_SEC) {
538540
timestamp = Timestamp::FromEpochSeconds(timestamp.value);
@@ -555,7 +557,7 @@ py::object PythonObject::FromValue(const Value &val, const LogicalType &type,
555557
// Failed to convert, fall back to str
556558
return py::str(val.ToString());
557559
}
558-
if (type.id() == LogicalTypeId::TIMESTAMP_TZ) {
560+
if (type.id() == LogicalTypeId::TIMESTAMP_TZ || type.id() == LogicalTypeId::TIMESTAMP_TZ_NS) {
559561
// We have to add the timezone info
560562
auto tz_utc = import_cache.pytz.timezone()("UTC");
561563
auto timestamp_utc = tz_utc.attr("localize")(py_timestamp);

src/duckdb_py/numpy/array_wrapper.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,7 @@ void ArrayWrapper::Append(idx_t current_offset, Vector &input, idx_t source_size
654654
break;
655655
case LogicalTypeId::TIMESTAMP:
656656
case LogicalTypeId::TIMESTAMP_TZ:
657+
case LogicalTypeId::TIMESTAMP_TZ_NS:
657658
case LogicalTypeId::TIMESTAMP_SEC:
658659
case LogicalTypeId::TIMESTAMP_MS:
659660
case LogicalTypeId::TIMESTAMP_NS:

src/duckdb_py/numpy/raw_array_wrapper.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ static idx_t GetNumpyTypeWidth(const LogicalType &type) {
4646
case LogicalTypeId::DATE:
4747
case LogicalTypeId::INTERVAL:
4848
case LogicalTypeId::TIMESTAMP_TZ:
49+
case LogicalTypeId::TIMESTAMP_TZ_NS:
4950
return sizeof(int64_t);
5051
case LogicalTypeId::TIME:
5152
case LogicalTypeId::TIME_NS:
@@ -102,6 +103,7 @@ string RawArrayWrapper::DuckDBToNumpyDtype(const LogicalType &type) {
102103
return "datetime64[us]";
103104
case LogicalTypeId::TIMESTAMP_TZ:
104105
return "datetime64[us]";
106+
case LogicalTypeId::TIMESTAMP_TZ_NS:
105107
case LogicalTypeId::TIMESTAMP_NS:
106108
return "datetime64[ns]";
107109
case LogicalTypeId::TIMESTAMP_MS:

tests/fast/arrow/test_timestamp_timezone.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,17 @@ def test_timestamp_timezone_overflow(self, duckdb_cursor):
3737
with pytest.raises(duckdb.ConversionException, match="Could not convert"):
3838
duckdb.from_arrow(arrow_table).execute().fetchall()
3939

40-
def test_timestamp_tz_to_arrow(self, duckdb_cursor):
41-
precisions = ["us", "s", "ns", "ms"]
40+
@pytest.mark.parametrize("precision", ["us", "s", "ns", "ms"])
41+
def test_timestamp_tz_to_arrow(self, duckdb_cursor, precision):
4242
current_time = datetime.datetime(2017, 11, 28, 23, 55, 59)
4343
con = duckdb.connect()
44-
for precision in precisions:
45-
for timezone in timezones:
46-
con.execute("SET TimeZone = '" + timezone + "'")
47-
arrow_table = generate_table(current_time, precision, timezone)
48-
res = con.from_arrow(arrow_table).to_arrow_table()
49-
assert res[0].type == pa.timestamp("us", tz=timezone)
50-
assert res == generate_table(current_time, "us", timezone)
44+
expected_precision = "ns" if precision == "ns" else "us"
45+
for timezone in timezones:
46+
con.execute("SET TimeZone = '" + timezone + "'")
47+
arrow_table = generate_table(current_time, precision, timezone)
48+
res = con.from_arrow(arrow_table).to_arrow_table()
49+
assert res[0].type == pa.timestamp(expected_precision, tz=timezone)
50+
assert res == generate_table(current_time, expected_precision, timezone)
5151

5252
def test_timestamp_tz_with_null(self, duckdb_cursor):
5353
con = duckdb.connect()

0 commit comments

Comments
 (0)