Skip to content

Commit e49b705

Browse files
committed
Support for TIME_NS type
1 parent 77e4b02 commit e49b705

13 files changed

Lines changed: 134 additions & 11 deletions

File tree

_duckdb-stubs/_sqltypes.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ INTERVAL: DuckDBPyType # value = INTERVAL
5959
SMALLINT: DuckDBPyType # value = SMALLINT
6060
SQLNULL: DuckDBPyType # value = "NULL"
6161
TIME: DuckDBPyType # value = TIME
62+
TIME_NS: DuckDBPyType # value = TIME_NS
6263
TIMESTAMP: DuckDBPyType # value = TIMESTAMP
6364
TIMESTAMP_MS: DuckDBPyType # value = TIMESTAMP_MS
6465
TIMESTAMP_NS: DuckDBPyType # value = TIMESTAMP_NS

duckdb/experimental/spark/sql/type_utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
StringType,
2424
StructField,
2525
StructType,
26+
TimeNSType,
2627
TimeNTZType,
2728
TimestampMillisecondNTZType,
2829
TimestampNanosecondNTZType,
@@ -56,6 +57,7 @@
5657
"uuid": UUIDType,
5758
"date": DateType,
5859
"time": TimeNTZType,
60+
"time_ns": TimeNSType,
5961
"time with time zone": TimeType,
6062
"timestamp": TimestampNTZType,
6163
"timestamp with time zone": TimestampType,

duckdb/experimental/spark/sql/types.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"StringType",
5252
"StructField",
5353
"StructType",
54+
"TimeNSType",
5455
"TimeNTZType",
5556
"TimeType",
5657
"TimestampMillisecondNTZType",
@@ -505,6 +506,16 @@ def simpleString(self) -> str: # noqa: D102
505506
return "time"
506507

507508

509+
class TimeNSType(IntegralType):
510+
"""Time NS (datetime.time) data type without timezone information."""
511+
512+
def __init__(self) -> None: # noqa: D107
513+
super().__init__(DuckDBPyType("TIME_NS"))
514+
515+
def simpleString(self) -> str: # noqa: D102
516+
return "time_ns"
517+
518+
508519
class DayTimeIntervalType(AtomicType):
509520
"""DayTimeIntervalType (datetime.timedelta)."""
510521

duckdb/sqltypes/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
SMALLINT,
1515
SQLNULL,
1616
TIME,
17+
TIME_NS,
1718
TIME_TZ,
1819
TIMESTAMP,
1920
TIMESTAMP_MS,
@@ -50,6 +51,7 @@
5051
"TIMESTAMP_NS",
5152
"TIMESTAMP_S",
5253
"TIMESTAMP_TZ",
54+
"TIME_NS",
5355
"TIME_TZ",
5456
"TINYINT",
5557
"UBIGINT",

external/duckdb

Submodule duckdb updated 203 files

src/duckdb_py/native/python_objects.cpp

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,9 @@ Value PyTime::ToDuckValue() {
225225
auto seconds = PyTimezone::GetUTCOffsetSeconds(this->timezone_obj);
226226
return Value::TIMETZ(dtime_tz_t(duckdb_time, seconds));
227227
}
228-
return Value::TIME(duckdb_time);
228+
// For non-timezoned time's we default to TIME_NS
229+
const auto nanos = Time::ToNanoTime(hour, minute, second, microsecond * 1000);
230+
return Value::TIME_NS(dtime_ns_t(nanos));
229231
}
230232

231233
int32_t PyTime::GetHours(py::handle &obj) {
@@ -585,13 +587,20 @@ py::object PythonObject::FromValue(const Value &val, const LogicalType &type,
585587
auto tmp_datetime_with_tz = import_cache.datetime.datetime.combine()(tmp_datetime, py_time, timezone_offset);
586588
return tmp_datetime_with_tz.attr("timetz")();
587589
}
588-
case LogicalTypeId::TIME: {
590+
case LogicalTypeId::TIME:
591+
case LogicalTypeId::TIME_NS: {
589592
D_ASSERT(type.InternalType() == PhysicalType::INT64);
590-
int32_t hour, min, sec, microsec;
591-
auto time = val.GetValueUnsafe<dtime_t>();
592-
duckdb::Time::Convert(time, hour, min, sec, microsec);
593+
int32_t hour, min, sec, usec;
594+
dtime_t time;
595+
if (type.id() == LogicalTypeId::TIME) {
596+
time = val.GetValueUnsafe<dtime_t>();
597+
} else {
598+
// Python's datetime doesn't support nanoseconds, we convert to micros.
599+
time = val.GetValueUnsafe<dtime_ns_t>().time();
600+
}
601+
duckdb::Time::Convert(time, hour, min, sec, usec);
593602
try {
594-
auto pytime = PyTime_FromTime(hour, min, sec, microsec);
603+
auto pytime = PyTime_FromTime(hour, min, sec, usec);
595604
if (!pytime) {
596605
throw py::error_already_set();
597606
}

src/duckdb_py/numpy/array_wrapper.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,24 @@ struct TimeConvert {
140140
}
141141
};
142142

143+
struct TimeNSConvert {
144+
template <class DUCKDB_T, class NUMPY_T>
145+
static PyObject *ConvertValue(dtime_ns_t val, NumpyAppendData &append_data) {
146+
auto &client_properties = append_data.client_properties;
147+
auto value = Value::TIME_NS(val);
148+
auto py_obj = PythonObject::FromValue(value, LogicalType::TIME_NS, client_properties);
149+
// Release ownership of the PyObject* without decreasing refcount
150+
// this returns a handle, of which we take the ptr to get the PyObject*
151+
return py_obj.release().ptr();
152+
}
153+
154+
template <class NUMPY_T, bool PANDAS>
155+
static NUMPY_T NullValue(bool &set_mask) {
156+
set_mask = true;
157+
return nullptr;
158+
}
159+
};
160+
143161
struct StringConvert {
144162
template <class DUCKDB_T, class NUMPY_T>
145163
static PyObject *ConvertValue(string_t val, NumpyAppendData &append_data) {
@@ -639,6 +657,9 @@ void ArrayWrapper::Append(idx_t current_offset, Vector &input, idx_t source_size
639657
case LogicalTypeId::TIME:
640658
may_have_null = ConvertColumn<dtime_t, PyObject *, duckdb_py_convert::TimeConvert>(append_data);
641659
break;
660+
case LogicalTypeId::TIME_NS:
661+
may_have_null = ConvertColumn<dtime_ns_t, PyObject *, duckdb_py_convert::TimeNSConvert>(append_data);
662+
break;
642663
case LogicalTypeId::INTERVAL:
643664
may_have_null = ConvertColumn<interval_t, int64_t, duckdb_py_convert::IntervalConvert>(append_data);
644665
break;

src/duckdb_py/numpy/raw_array_wrapper.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ static idx_t GetNumpyTypeWidth(const LogicalType &type) {
4848
case LogicalTypeId::TIMESTAMP_TZ:
4949
return sizeof(int64_t);
5050
case LogicalTypeId::TIME:
51+
case LogicalTypeId::TIME_NS:
5152
case LogicalTypeId::TIME_TZ:
5253
case LogicalTypeId::VARCHAR:
5354
case LogicalTypeId::BIT:
@@ -110,6 +111,7 @@ string RawArrayWrapper::DuckDBToNumpyDtype(const LogicalType &type) {
110111
case LogicalTypeId::INTERVAL:
111112
return "timedelta64[ns]";
112113
case LogicalTypeId::TIME:
114+
case LogicalTypeId::TIME_NS:
113115
case LogicalTypeId::TIME_TZ:
114116
case LogicalTypeId::VARCHAR:
115117
case LogicalTypeId::BIT:

src/duckdb_py/pandas/analyzer.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,8 @@ LogicalType PandasAnalyzer::GetItemType(py::object ele, bool &can_convert) {
395395
if (!py::none().is(tzinfo)) {
396396
return LogicalType::TIME_TZ;
397397
}
398-
return LogicalType::TIME;
398+
// Convert to the most precise type
399+
return LogicalType::TIME_NS;
399400
}
400401
case PythonObjectType::Date:
401402
return LogicalType::DATE;

src/duckdb_py/typing/typing.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ static void DefineBaseTypes(py::handle &m) {
2727
m.attr("TIMESTAMP_S") = make_shared_ptr<DuckDBPyType>(LogicalType::TIMESTAMP_S);
2828

2929
m.attr("TIME") = make_shared_ptr<DuckDBPyType>(LogicalType::TIME);
30+
m.attr("TIME_NS") = make_shared_ptr<DuckDBPyType>(LogicalType::TIME_NS);
3031

3132
m.attr("TIME_TZ") = make_shared_ptr<DuckDBPyType>(LogicalType::TIME_TZ);
3233
m.attr("TIMESTAMP_TZ") = make_shared_ptr<DuckDBPyType>(LogicalType::TIMESTAMP_TZ);

0 commit comments

Comments
 (0)