|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "datetime.h" |
|
|
|
#include <algorithm> |
|
#include <chrono> |
|
#include <iomanip> |
|
#include <regex> |
|
#include <string_view> |
|
|
|
#include "arrow/array.h" |
|
#include "arrow/python/arrow_to_python_internal.h" |
|
#include "arrow/python/common.h" |
|
#include "arrow/python/helpers.h" |
|
#include "arrow/python/platform.h" |
|
#include "arrow/scalar.h" |
|
#include "arrow/status.h" |
|
#include "arrow/type.h" |
|
#include "arrow/util/logging.h" |
|
#include "arrow/util/regex.h" |
|
#include "arrow/util/value_parsing.h" |
|
|
|
namespace arrow { |
|
|
|
using internal::RegexMatch; |
|
|
|
namespace py { |
|
namespace internal { |
|
|
|
namespace { |
|
|
|
bool MatchFixedOffset(const std::string& tz, std::string_view* sign, |
|
std::string_view* hour, std::string_view* minute) { |
|
static const std::regex regex("^([+-])(0[0-9]|1[0-9]|2[0-3]):([0-5][0-9])$"); |
|
if (tz.size() < 5) { |
|
return false; |
|
} |
|
return RegexMatch(regex, tz, {sign, hour, minute}); |
|
} |
|
|
|
constexpr char* NonConst(const char* st) { |
|
|
|
|
|
return const_cast<char*>(st); |
|
} |
|
|
|
static PyTypeObject MonthDayNanoTupleType = {}; |
|
|
|
static PyStructSequence_Field MonthDayNanoField[] = { |
|
{NonConst("months"), NonConst("The number of months in the interval")}, |
|
{NonConst("days"), NonConst("The number days in the interval")}, |
|
{NonConst("nanoseconds"), NonConst("The number of nanoseconds in the interval")}, |
|
{nullptr, nullptr}}; |
|
|
|
static PyStructSequence_Desc MonthDayNanoTupleDesc = { |
|
NonConst("MonthDayNano"), |
|
NonConst("A calendar interval consisting of months, days and nanoseconds."), |
|
MonthDayNanoField, |
|
3}; |
|
|
|
} |
|
|
|
#ifndef PYPY_VERSION |
|
PyDateTime_CAPI* datetime_api = nullptr; |
|
|
|
void InitDatetime() { |
|
PyAcquireGIL lock; |
|
datetime_api = |
|
reinterpret_cast<PyDateTime_CAPI*>(PyCapsule_Import(PyDateTime_CAPSULE_NAME, 0)); |
|
if (datetime_api == nullptr) { |
|
Py_FatalError("Could not import datetime C API"); |
|
} |
|
} |
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
static int64_t _days_per_month_table[2][12] = { |
|
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, |
|
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; |
|
|
|
static bool is_leapyear(int64_t year) { |
|
return (year & 0x3) == 0 && |
|
((year % 100) != 0 || (year % 400) == 0); |
|
} |
|
|
|
|
|
static int64_t get_days_from_date(int64_t date_year, int64_t date_month, |
|
int64_t date_day) { |
|
int64_t i, month; |
|
int64_t year, days = 0; |
|
int64_t* month_lengths; |
|
|
|
year = date_year - 1970; |
|
days = year * 365; |
|
|
|
|
|
if (days >= 0) { |
|
|
|
|
|
year += 1; |
|
|
|
days += year / 4; |
|
|
|
year += 68; |
|
|
|
days -= year / 100; |
|
|
|
year += 300; |
|
|
|
days += year / 400; |
|
} else { |
|
|
|
|
|
year -= 2; |
|
|
|
days += year / 4; |
|
|
|
year -= 28; |
|
|
|
days -= year / 100; |
|
|
|
|
|
days += year / 400; |
|
} |
|
|
|
month_lengths = _days_per_month_table[is_leapyear(date_year)]; |
|
month = date_month - 1; |
|
|
|
|
|
for (i = 0; i < month; ++i) { |
|
days += month_lengths[i]; |
|
} |
|
|
|
|
|
days += date_day - 1; |
|
|
|
return days; |
|
} |
|
|
|
|
|
|
|
static int64_t days_to_yearsdays(int64_t* days_) { |
|
const int64_t days_per_400years = (400 * 365 + 100 - 4 + 1); |
|
|
|
int64_t days = (*days_) - (365 * 30 + 7); |
|
int64_t year; |
|
|
|
|
|
if (days >= 0) { |
|
year = 400 * (days / days_per_400years); |
|
days = days % days_per_400years; |
|
} else { |
|
year = 400 * ((days - (days_per_400years - 1)) / days_per_400years); |
|
days = days % days_per_400years; |
|
if (days < 0) { |
|
days += days_per_400years; |
|
} |
|
} |
|
|
|
|
|
if (days >= 366) { |
|
year += 100 * ((days - 1) / (100 * 365 + 25 - 1)); |
|
days = (days - 1) % (100 * 365 + 25 - 1); |
|
if (days >= 365) { |
|
year += 4 * ((days + 1) / (4 * 365 + 1)); |
|
days = (days + 1) % (4 * 365 + 1); |
|
if (days >= 366) { |
|
year += (days - 1) / 365; |
|
days = (days - 1) % 365; |
|
} |
|
} |
|
} |
|
|
|
*days_ = days; |
|
return year + 2000; |
|
} |
|
|
|
|
|
static void get_date_from_days(int64_t days, int64_t* date_year, int64_t* date_month, |
|
int64_t* date_day) { |
|
int64_t *month_lengths, i; |
|
|
|
*date_year = days_to_yearsdays(&days); |
|
month_lengths = _days_per_month_table[is_leapyear(*date_year)]; |
|
|
|
for (i = 0; i < 12; ++i) { |
|
if (days < month_lengths[i]) { |
|
*date_month = i + 1; |
|
*date_day = days + 1; |
|
return; |
|
} else { |
|
days -= month_lengths[i]; |
|
} |
|
} |
|
|
|
|
|
return; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static inline int64_t split_time(int64_t total, int64_t quotient, int64_t* next) { |
|
int64_t r = total % quotient; |
|
if (r < 0) { |
|
*next = total / quotient - 1; |
|
return r + quotient; |
|
} else { |
|
*next = total / quotient; |
|
return r; |
|
} |
|
} |
|
|
|
static inline Status PyTime_convert_int(int64_t val, const TimeUnit::type unit, |
|
int64_t* hour, int64_t* minute, int64_t* second, |
|
int64_t* microsecond) { |
|
switch (unit) { |
|
case TimeUnit::NANO: |
|
if (val % 1000 != 0) { |
|
return Status::Invalid("Value ", val, " has non-zero nanoseconds"); |
|
} |
|
val /= 1000; |
|
|
|
case TimeUnit::MICRO: |
|
*microsecond = split_time(val, 1000000LL, &val); |
|
*second = split_time(val, 60, &val); |
|
*minute = split_time(val, 60, hour); |
|
break; |
|
case TimeUnit::MILLI: |
|
*microsecond = split_time(val, 1000, &val) * 1000; |
|
|
|
case TimeUnit::SECOND: |
|
*second = split_time(val, 60, &val); |
|
*minute = split_time(val, 60, hour); |
|
break; |
|
default: |
|
break; |
|
} |
|
return Status::OK(); |
|
} |
|
|
|
static inline Status PyDate_convert_int(int64_t val, const DateUnit unit, int64_t* year, |
|
int64_t* month, int64_t* day) { |
|
switch (unit) { |
|
case DateUnit::MILLI: |
|
val /= 86400000LL; |
|
case DateUnit::DAY: |
|
get_date_from_days(val, year, month, day); |
|
default: |
|
break; |
|
} |
|
return Status::OK(); |
|
} |
|
|
|
PyObject* NewMonthDayNanoTupleType() { |
|
if (MonthDayNanoTupleType.tp_name == nullptr) { |
|
if (PyStructSequence_InitType2(&MonthDayNanoTupleType, &MonthDayNanoTupleDesc) != 0) { |
|
Py_FatalError("Could not initialize MonthDayNanoTuple"); |
|
} |
|
} |
|
Py_INCREF(&MonthDayNanoTupleType); |
|
return (PyObject*)&MonthDayNanoTupleType; |
|
} |
|
|
|
Status PyTime_from_int(int64_t val, const TimeUnit::type unit, PyObject** out) { |
|
int64_t hour = 0, minute = 0, second = 0, microsecond = 0; |
|
RETURN_NOT_OK(PyTime_convert_int(val, unit, &hour, &minute, &second, µsecond)); |
|
*out = PyTime_FromTime(static_cast<int32_t>(hour), static_cast<int32_t>(minute), |
|
static_cast<int32_t>(second), static_cast<int32_t>(microsecond)); |
|
return Status::OK(); |
|
} |
|
|
|
Status PyDate_from_int(int64_t val, const DateUnit unit, PyObject** out) { |
|
int64_t year = 0, month = 0, day = 0; |
|
RETURN_NOT_OK(PyDate_convert_int(val, unit, &year, &month, &day)); |
|
*out = PyDate_FromDate(static_cast<int32_t>(year), static_cast<int32_t>(month), |
|
static_cast<int32_t>(day)); |
|
return Status::OK(); |
|
} |
|
|
|
Status PyDateTime_from_int(int64_t val, const TimeUnit::type unit, PyObject** out) { |
|
int64_t hour = 0, minute = 0, second = 0, microsecond = 0; |
|
RETURN_NOT_OK(PyTime_convert_int(val, unit, &hour, &minute, &second, µsecond)); |
|
int64_t total_days = 0; |
|
hour = split_time(hour, 24, &total_days); |
|
int64_t year = 0, month = 0, day = 0; |
|
get_date_from_days(total_days, &year, &month, &day); |
|
*out = PyDateTime_FromDateAndTime( |
|
static_cast<int32_t>(year), static_cast<int32_t>(month), static_cast<int32_t>(day), |
|
static_cast<int32_t>(hour), static_cast<int32_t>(minute), |
|
static_cast<int32_t>(second), static_cast<int32_t>(microsecond)); |
|
return Status::OK(); |
|
} |
|
|
|
int64_t PyDate_to_days(PyDateTime_Date* pydate) { |
|
return get_days_from_date(PyDateTime_GET_YEAR(pydate), PyDateTime_GET_MONTH(pydate), |
|
PyDateTime_GET_DAY(pydate)); |
|
} |
|
|
|
Result<int64_t> PyDateTime_utcoffset_s(PyObject* obj) { |
|
|
|
|
|
OwnedRef pyoffset(PyObject_CallMethod(obj, "utcoffset", NULL)); |
|
RETURN_IF_PYERROR(); |
|
if (pyoffset.obj() != nullptr && pyoffset.obj() != Py_None) { |
|
auto delta = reinterpret_cast<PyDateTime_Delta*>(pyoffset.obj()); |
|
return internal::PyDelta_to_s(delta); |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
Result<std::string> PyTZInfo_utcoffset_hhmm(PyObject* pytzinfo) { |
|
|
|
OwnedRef pydelta_object(PyObject_CallMethod(pytzinfo, "utcoffset", "O", Py_None)); |
|
RETURN_IF_PYERROR(); |
|
|
|
if (!PyDelta_Check(pydelta_object.obj())) { |
|
return Status::Invalid( |
|
"Object returned by tzinfo.utcoffset(None) is not an instance of " |
|
"datetime.timedelta"); |
|
} |
|
auto pydelta = reinterpret_cast<PyDateTime_Delta*>(pydelta_object.obj()); |
|
|
|
|
|
auto total_seconds = internal::PyDelta_to_s(pydelta); |
|
|
|
|
|
auto sign = (total_seconds < 0) ? "-" : "+"; |
|
total_seconds = abs(total_seconds); |
|
|
|
|
|
int64_t hours, minutes, seconds; |
|
seconds = split_time(total_seconds, 60, &minutes); |
|
minutes = split_time(minutes, 60, &hours); |
|
if (seconds > 0) { |
|
|
|
return Status::Invalid("Offset must represent whole number of minutes"); |
|
} |
|
|
|
|
|
std::stringstream stream; |
|
stream << sign << std::setfill('0') << std::setw(2) << hours << ":" << std::setfill('0') |
|
<< std::setw(2) << minutes; |
|
return stream.str(); |
|
} |
|
|
|
|
|
|
|
Result<PyObject*> StringToTzinfo(const std::string& tz) { |
|
std::string_view sign_str, hour_str, minute_str; |
|
OwnedRef pytz; |
|
OwnedRef zoneinfo; |
|
OwnedRef datetime; |
|
|
|
if (internal::ImportModule("pytz", &pytz).ok()) { |
|
if (MatchFixedOffset(tz, &sign_str, &hour_str, &minute_str)) { |
|
int sign = -1; |
|
if (sign_str == "+") { |
|
sign = 1; |
|
} |
|
OwnedRef fixed_offset; |
|
RETURN_NOT_OK(internal::ImportFromModule(pytz.obj(), "FixedOffset", &fixed_offset)); |
|
uint32_t minutes, hours; |
|
if (!::arrow::internal::ParseUnsigned(hour_str.data(), hour_str.size(), &hours) || |
|
!::arrow::internal::ParseUnsigned(minute_str.data(), minute_str.size(), |
|
&minutes)) { |
|
return Status::Invalid("Invalid timezone: ", tz); |
|
} |
|
OwnedRef total_minutes(PyLong_FromLong( |
|
sign * ((static_cast<int>(hours) * 60) + static_cast<int>(minutes)))); |
|
RETURN_IF_PYERROR(); |
|
auto tzinfo = |
|
PyObject_CallFunctionObjArgs(fixed_offset.obj(), total_minutes.obj(), NULL); |
|
RETURN_IF_PYERROR(); |
|
return tzinfo; |
|
} |
|
|
|
OwnedRef timezone; |
|
RETURN_NOT_OK(internal::ImportFromModule(pytz.obj(), "timezone", &timezone)); |
|
OwnedRef py_tz_string( |
|
PyUnicode_FromStringAndSize(tz.c_str(), static_cast<Py_ssize_t>(tz.size()))); |
|
auto tzinfo = PyObject_CallFunctionObjArgs(timezone.obj(), py_tz_string.obj(), NULL); |
|
RETURN_IF_PYERROR(); |
|
return tzinfo; |
|
} |
|
|
|
|
|
if (MatchFixedOffset(tz, &sign_str, &hour_str, &minute_str)) { |
|
RETURN_NOT_OK(internal::ImportModule("datetime", &datetime)); |
|
int sign = -1; |
|
if (sign_str == "+") { |
|
sign = 1; |
|
} |
|
|
|
|
|
OwnedRef class_timezone; |
|
OwnedRef class_timedelta; |
|
RETURN_NOT_OK( |
|
internal::ImportFromModule(datetime.obj(), "timezone", &class_timezone)); |
|
RETURN_NOT_OK( |
|
internal::ImportFromModule(datetime.obj(), "timedelta", &class_timedelta)); |
|
|
|
|
|
uint32_t minutes, hours; |
|
if (!::arrow::internal::ParseUnsigned(hour_str.data(), hour_str.size(), &hours) || |
|
!::arrow::internal::ParseUnsigned(minute_str.data(), minute_str.size(), |
|
&minutes)) { |
|
return Status::Invalid("Invalid timezone: ", tz); |
|
} |
|
|
|
|
|
OwnedRef total_minutes(PyLong_FromLong( |
|
sign * ((static_cast<int>(hours) * 60) + static_cast<int>(minutes)))); |
|
|
|
OwnedRef zero(PyLong_FromLong(static_cast<int>(0))); |
|
|
|
|
|
auto offset = |
|
PyObject_CallFunctionObjArgs(class_timedelta.obj(), zero.obj(), zero.obj(), |
|
zero.obj(), zero.obj(), total_minutes.obj(), NULL); |
|
RETURN_IF_PYERROR(); |
|
|
|
auto tzinfo = PyObject_CallFunctionObjArgs(class_timezone.obj(), offset, NULL); |
|
RETURN_IF_PYERROR(); |
|
return tzinfo; |
|
} |
|
|
|
|
|
if (internal::ImportModule("zoneinfo", &zoneinfo).ok()) { |
|
OwnedRef class_zoneinfo; |
|
RETURN_NOT_OK( |
|
internal::ImportFromModule(zoneinfo.obj(), "ZoneInfo", &class_zoneinfo)); |
|
OwnedRef py_tz_string( |
|
PyUnicode_FromStringAndSize(tz.c_str(), static_cast<Py_ssize_t>(tz.size()))); |
|
auto tzinfo = |
|
PyObject_CallFunctionObjArgs(class_zoneinfo.obj(), py_tz_string.obj(), NULL); |
|
RETURN_IF_PYERROR(); |
|
return tzinfo; |
|
} |
|
|
|
return Status::Invalid( |
|
"Pytz package or Python>=3.8 for zoneinfo module must be installed."); |
|
} |
|
|
|
Result<std::string> TzinfoToString(PyObject* tzinfo) { |
|
OwnedRef module_pytz; |
|
OwnedRef module_datetime; |
|
OwnedRef module_zoneinfo; |
|
OwnedRef module_dateutil; |
|
OwnedRef class_timezone; |
|
OwnedRef class_fixedoffset; |
|
OwnedRef class_basetzinfo; |
|
OwnedRef class_zoneinfo; |
|
OwnedRef class_tzfile; |
|
|
|
|
|
RETURN_NOT_OK(internal::ImportModule("datetime", &module_datetime)); |
|
|
|
RETURN_NOT_OK( |
|
internal::ImportFromModule(module_datetime.obj(), "timezone", &class_timezone)); |
|
|
|
|
|
if (!PyTZInfo_Check(tzinfo)) { |
|
return Status::TypeError("Not an instance of datetime.tzinfo"); |
|
} |
|
|
|
|
|
|
|
if (PyObject_IsInstance(tzinfo, class_timezone.obj())) { |
|
|
|
OwnedRef tzname_object(PyObject_CallMethod(tzinfo, "tzname", "O", Py_None)); |
|
RETURN_IF_PYERROR(); |
|
if (PyUnicode_Check(tzname_object.obj())) { |
|
std::string result; |
|
RETURN_NOT_OK(internal::PyUnicode_AsStdString(tzname_object.obj(), &result)); |
|
if (result == "UTC") { |
|
return result; |
|
} |
|
} |
|
return PyTZInfo_utcoffset_hhmm(tzinfo); |
|
} |
|
|
|
|
|
if (internal::ImportModule("pytz", &module_pytz).ok()) { |
|
RETURN_NOT_OK(internal::ImportFromModule(module_pytz.obj(), "_FixedOffset", |
|
&class_fixedoffset)); |
|
RETURN_NOT_OK( |
|
internal::ImportFromModule(module_pytz.obj(), "BaseTzInfo", &class_basetzinfo)); |
|
} |
|
|
|
|
|
|
|
if (module_pytz.obj() != nullptr && |
|
PyObject_IsInstance(tzinfo, class_fixedoffset.obj())) { |
|
OwnedRef tzname_object(PyObject_CallMethod(tzinfo, "tzname", "O", Py_None)); |
|
RETURN_IF_PYERROR(); |
|
return PyTZInfo_utcoffset_hhmm(tzinfo); |
|
} |
|
|
|
|
|
if (module_pytz.obj() != nullptr && |
|
PyObject_IsInstance(tzinfo, class_basetzinfo.obj())) { |
|
OwnedRef zone(PyObject_GetAttrString(tzinfo, "zone")); |
|
RETURN_IF_PYERROR(); |
|
std::string result; |
|
RETURN_NOT_OK(internal::PyUnicode_AsStdString(zone.obj(), &result)); |
|
return result; |
|
} |
|
|
|
|
|
if (internal::ImportModule("zoneinfo", &module_zoneinfo).ok()) { |
|
RETURN_NOT_OK( |
|
internal::ImportFromModule(module_zoneinfo.obj(), "ZoneInfo", &class_zoneinfo)); |
|
} |
|
|
|
|
|
if (module_zoneinfo.obj() != nullptr && |
|
PyObject_IsInstance(tzinfo, class_zoneinfo.obj())) { |
|
OwnedRef key(PyObject_GetAttrString(tzinfo, "key")); |
|
RETURN_IF_PYERROR(); |
|
std::string result; |
|
RETURN_NOT_OK(internal::PyUnicode_AsStdString(key.obj(), &result)); |
|
return result; |
|
} |
|
|
|
|
|
if (internal::ImportModule("dateutil.tz", &module_dateutil).ok()) { |
|
RETURN_NOT_OK( |
|
internal::ImportFromModule(module_dateutil.obj(), "tzfile", &class_tzfile)); |
|
} |
|
|
|
|
|
if (module_dateutil.obj() != nullptr && |
|
PyObject_IsInstance(tzinfo, class_tzfile.obj())) { |
|
OwnedRef _filename(PyObject_GetAttrString(tzinfo, "_filename")); |
|
RETURN_IF_PYERROR(); |
|
std::string result; |
|
RETURN_NOT_OK(internal::PyUnicode_AsStdString(_filename.obj(), &result)); |
|
|
|
|
|
std::size_t pos = result.find("zoneinfo/"); |
|
if (pos != std::string::npos) { |
|
return result.substr(pos + 9); |
|
} |
|
return result; |
|
} |
|
|
|
|
|
OwnedRef tzname_object(PyObject_CallMethod(tzinfo, "tzname", "O", Py_None)); |
|
RETURN_IF_PYERROR(); |
|
if (PyUnicode_Check(tzname_object.obj())) { |
|
std::string result; |
|
RETURN_NOT_OK(internal::PyUnicode_AsStdString(tzname_object.obj(), &result)); |
|
return result; |
|
} |
|
|
|
|
|
return PyTZInfo_utcoffset_hhmm(tzinfo); |
|
} |
|
|
|
PyObject* MonthDayNanoIntervalToNamedTuple( |
|
const MonthDayNanoIntervalType::MonthDayNanos& interval) { |
|
OwnedRef tuple(PyStructSequence_New(&MonthDayNanoTupleType)); |
|
if (ARROW_PREDICT_FALSE(tuple.obj() == nullptr)) { |
|
return nullptr; |
|
} |
|
PyStructSequence_SetItem(tuple.obj(), 0, PyLong_FromLong(interval.months)); |
|
PyStructSequence_SetItem(tuple.obj(), 1, PyLong_FromLong(interval.days)); |
|
PyStructSequence_SetItem(tuple.obj(), 2, |
|
PyLong_FromLongLong(interval.nanoseconds)); |
|
return tuple.detach(); |
|
} |
|
|
|
namespace { |
|
|
|
|
|
|
|
struct PyListAssigner { |
|
public: |
|
explicit PyListAssigner(PyObject* list) : list_(list) { DCHECK(PyList_Check(list_)); } |
|
|
|
PyListAssigner& operator*() { return *this; } |
|
|
|
void operator=(PyObject* obj) { |
|
if (ARROW_PREDICT_FALSE(PyList_SetItem(list_, current_index_, obj) == -1)) { |
|
Py_FatalError("list did not have the correct preallocated size."); |
|
} |
|
} |
|
|
|
PyListAssigner& operator++() { |
|
current_index_++; |
|
return *this; |
|
} |
|
|
|
PyListAssigner& operator+=(int64_t offset) { |
|
current_index_ += offset; |
|
return *this; |
|
} |
|
|
|
private: |
|
PyObject* list_; |
|
int64_t current_index_ = 0; |
|
}; |
|
|
|
} |
|
|
|
Result<PyObject*> MonthDayNanoIntervalArrayToPyList( |
|
const MonthDayNanoIntervalArray& array) { |
|
OwnedRef out_list(PyList_New(array.length())); |
|
RETURN_IF_PYERROR(); |
|
PyListAssigner out_objects(out_list.obj()); |
|
auto& interval_array = |
|
arrow::internal::checked_cast<const MonthDayNanoIntervalArray&>(array); |
|
RETURN_NOT_OK(internal::WriteArrayObjects( |
|
interval_array, |
|
[&](const MonthDayNanoIntervalType::MonthDayNanos& interval, PyListAssigner& out) { |
|
PyObject* tuple = internal::MonthDayNanoIntervalToNamedTuple(interval); |
|
if (ARROW_PREDICT_FALSE(tuple == nullptr)) { |
|
RETURN_IF_PYERROR(); |
|
} |
|
|
|
*out = tuple; |
|
return Status::OK(); |
|
}, |
|
out_objects)); |
|
return out_list.detach(); |
|
} |
|
|
|
Result<PyObject*> MonthDayNanoIntervalScalarToPyObject( |
|
const MonthDayNanoIntervalScalar& scalar) { |
|
if (scalar.is_valid) { |
|
return internal::MonthDayNanoIntervalToNamedTuple(scalar.value); |
|
} else { |
|
Py_INCREF(Py_None); |
|
return Py_None; |
|
} |
|
} |
|
|
|
} |
|
} |
|
} |
|
|