pitrou commented on a change in pull request #12522:
URL: https://github.com/apache/arrow/pull/12522#discussion_r834409885
##########
File path: cpp/src/arrow/python/datetime.cc
##########
@@ -391,62 +391,165 @@ Result<std::string> PyTZInfo_utcoffset_hhmm(PyObject*
pytzinfo) {
Result<PyObject*> StringToTzinfo(const std::string& tz) {
util::string_view sign_str, hour_str, minute_str;
OwnedRef pytz;
- RETURN_NOT_OK(internal::ImportModule("pytz", &pytz));
+ OwnedRef zoneinfo;
+ OwnedRef datetime;
+ if (internal::ImportModule("pytz", &pytz).ok()) {
+ RETURN_NOT_OK(internal::ImportModule("pytz", &pytz));
+
+ 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;
+ }
+
+ // catch fixed offset if pytz is not present
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 fixed_offset;
- RETURN_NOT_OK(internal::ImportFromModule(pytz.obj(), "FixedOffset",
&fixed_offset));
+
+ // import timezone and timedelta module to create a tzinfo object
+ 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));
+
+ // check input
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);
}
+
+ // save offset as a signed integer
OwnedRef total_minutes(PyLong_FromLong(
sign * ((static_cast<int>(hours) * 60) + static_cast<int>(minutes))));
+ // create zero integers for empty arguments in datetime.timedelta
+ OwnedRef zero(PyLong_FromLong(static_cast<int>(0)));
+
+ // call datetime.timedelta to get correct offset object for
datetime.timezone
+ auto offset =
+ PyObject_CallFunctionObjArgs(class_timedelta.obj(), zero.obj(),
zero.obj(),
+ zero.obj(), zero.obj(),
total_minutes.obj(), NULL);
RETURN_IF_PYERROR();
+ // call datetime.timezone
+ auto tzinfo = PyObject_CallFunctionObjArgs(class_timezone.obj(), offset,
NULL);
+ RETURN_IF_PYERROR();
+ return tzinfo;
+ }
+
+ // fallback on zoneinfo if tz is string and pytz is not present
+ if (internal::ImportModule("zoneinfo", &zoneinfo).ok()) {
+ RETURN_NOT_OK(internal::ImportModule("zoneinfo", &zoneinfo));
+
+ 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(fixed_offset.obj(), total_minutes.obj(),
NULL);
+ PyObject_CallFunctionObjArgs(class_zoneinfo.obj(), py_tz_string.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;
+ return Status::Invalid("Pytz package must be installed.");
}
Result<std::string> TzinfoToString(PyObject* tzinfo) {
OwnedRef module_pytz; // import pytz
OwnedRef module_datetime; // import datetime
+ OwnedRef module_zoneinfo; // import zoneinfo
+ OwnedRef module_dateutil; // import dateutil
OwnedRef class_timezone; // from datetime import timezone
OwnedRef class_fixedoffset; // from pytz import _FixedOffset
+ OwnedRef class_basetzinfo; // from pytz import BaseTzInfo
+ OwnedRef class_zoneinfo; // from zoneinfo import ZoneInfo
+ OwnedRef class_tzfile; // from zoneinfo import tzfile
// import necessary modules
- RETURN_NOT_OK(internal::ImportModule("pytz", &module_pytz));
RETURN_NOT_OK(internal::ImportModule("datetime", &module_datetime));
// import necessary classes
- RETURN_NOT_OK(
- internal::ImportFromModule(module_pytz.obj(), "_FixedOffset",
&class_fixedoffset));
RETURN_NOT_OK(
internal::ImportFromModule(module_datetime.obj(), "timezone",
&class_timezone));
+ // Try to import pytz if it is available
Review comment:
You can handle `datetime.timezone` before this, and so on for other
possibilities. This will avoid trying to import those modules when not
necessary.
##########
File path: cpp/src/arrow/python/datetime.cc
##########
@@ -482,7 +586,9 @@ Result<std::string> TzinfoToString(PyObject* tzinfo) {
// try to look up _filename attribute
// in case of dateutil.tz object
- if (PyObject_HasAttrString(tzinfo, "_filename")) {
+ // if dateutil is installed and tzinfo is and instance of dateutil.tz.tzfile
Review comment:
```suggestion
// if dateutil is installed and tzinfo is an instance of dateutil.tz.tzfile
```
##########
File path: cpp/src/arrow/python/datetime.cc
##########
@@ -461,18 +564,19 @@ Result<std::string> TzinfoToString(PyObject* tzinfo) {
return PyTZInfo_utcoffset_hhmm(tzinfo);
}
- // try to look up zone attribute
- if (PyObject_HasAttrString(tzinfo, "zone")) {
+ // if pytz is installed and tzinfo is and instance of pytz.BaseTzInfo
+ 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;
}
- // try to look up key attribute
- // in case of zoneinfo object
- if (PyObject_HasAttrString(tzinfo, "key")) {
+ // if zoneinfo is installed and tzinfo is and instance of zoneinfo.ZoneInfo
Review comment:
```suggestion
// if zoneinfo is installed and tzinfo is an instance of zoneinfo.ZoneInfo
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]