Swig pass Python datetime object to C++

Question:

I have a function in C++ that is passed a uint64_t as nanoseconds from epoch. I have wrapped this number in an object DateTime as in

struct DateTime {
    uint64_t epochns;
};
void print( DateTime ts );

Obviously the function is not exactly print as I could use Python’s own for this purpose.

It is important that I use it seamlessly with Python datetime objects as in

print( datetime(1985,7,1) )

I’m unsure how to write a typemap that achieves this requirement.

Any hints?

Asked By: Henrique Bucher

||

Answers:

Call the method datetime.timestamp on the object to return the epoch in seconds then multiply by 1E6 to get to microseconds and round to integer. Then multiply again by 1000 to get to nanoseconds.

%typemap(in)  DateTime
{
    PyObject* str = PyString_FromString( "timestamp" );
    PyObject* obj = PyObject_CallMethodObjArgs( $input, str, nullptr );
    $1.epochns = int64_t(PyFloat_AsDouble( obj )*1000000)*1000LL;
    Py_XDECREF( obj );
    Py_XDECREF( str );
}

The intermediate multiplication is necessary as if you multiply straight by 1E9 you will have unrounded garbage in the nanoseconds part.

UPDATE 9/24/2022
I ended up with this extended code that takes datetime, time and timedelta as well. Also the above code will take everything as local timezone, which was something I did not want. I wanted the time to be all in UTC with no timezone meddling.

In my swig file:

%typemap(in)  hb::DateTime
{
    $1.epochns = hb::getEpochNanos( $input );
}

Somewhere in my c++ sources:

int64_t getEpochNanos(PyObject* obj) {
    if (PyDateTime_CheckExact(obj) || PyDate_CheckExact(obj)) {
        int64_t usecs = PyDateTime_DATE_GET_MICROSECOND(obj);
        struct tm tms;
        memset(&tms, 0, sizeof(tms));
        tms.tm_wday = -1;
        tms.tm_year = PyDateTime_GET_YEAR(obj) - 1900;
        tms.tm_mon = PyDateTime_GET_MONTH(obj) - 1;
        tms.tm_mday = PyDateTime_GET_DAY(obj);
        tms.tm_hour = PyDateTime_DATE_GET_HOUR(obj);
        tms.tm_min = PyDateTime_DATE_GET_MINUTE(obj);
        tms.tm_sec = PyDateTime_DATE_GET_SECOND(obj);
        time_t tt = timegm(&tms);
        if (tms.tm_wday == -1) return 0;
        return int64_t(tt) * 1000000000LL + usecs * 1000;
    } else if (PyTime_Check(obj)) {
        int64_t usecs = PyDateTime_TIME_GET_MICROSECOND(obj);
        int64_t hour = PyDateTime_TIME_GET_HOUR(obj);
        int64_t min = PyDateTime_TIME_GET_MINUTE(obj);
        int64_t sec = PyDateTime_TIME_GET_SECOND(obj);
        return int64_t(((hour * 60) + min) * 60 + sec) * 1000000000LL + usecs * 1000;
    } else if (PyDelta_Check(obj)) {
        int64_t usecs = PyDateTime_DELTA_GET_MICROSECONDS(obj);
        int64_t days = PyDateTime_DELTA_GET_DAYS(obj);
        int64_t sec = PyDateTime_DELTA_GET_SECONDS(obj);
        return int64_t(days * 86400 + sec) * 1000000000LL + usecs * 1000;
    }
    return -1;
}

Notice I set -1 as a sentinel in wday. The fact is that -1 is a valid epoch (1 day before epoch) so the only way to check if the call failed was to set the wday to minus one and check it afterwards. If the call is successful, wday would be set to the week day (0 to 6) accordingly but not if it fails.

Answered By: Henrique Bucher
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.