Welcome, guest | Sign In | My Account | Store | Cart

This recipe allows to transparently convert python's datetime.datetime objects to and from boost's boost::posix_time::ptime objects.

Python, 176 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include <boost/python.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <datetime.h> // compile with -I/path/to/python/include

/**
 * Convert boost::posix_ptime objects (ptime and time_duration)
 * to/from python datetime objects (datetime and timedelta).
 *
 * Credits:
 * http://libtorrent.svn.sourceforge.net/viewvc/libtorrent/trunk/bindings/python/src/datetime.cpp
 * http://www.nabble.com/boost::posix_time::ptime-conversion-td16857866.html
 */

static long get_usecs(boost::posix_time::time_duration const& d)
{
  static long resolution
    = boost::posix_time::time_duration::ticks_per_second();
  long fracsecs = d.fractional_seconds();
  if (resolution > 1000000)
    return fracsecs / (resolution / 1000000);
  else
    return fracsecs * (1000000 / resolution);
}


/* Convert ptime to/from python */
struct ptime_to_python_datetime
{
    static PyObject* convert(boost::posix_time::ptime const& pt)
    {
        boost::gregorian::date date = pt.date();
        boost::posix_time::time_duration td = pt.time_of_day();
        return PyDateTime_FromDateAndTime((int)date.year(),
					  (int)date.month(),
					  (int)date.day(),
					  td.hours(),
					  td.minutes(),
					  td.seconds(),
					  get_usecs(td));
    }
};


struct ptime_from_python_datetime
{
     ptime_from_python_datetime()
     {
         boost::python::converter::registry::push_back(
             &convertible,
             &construct,
             boost::python::type_id<boost::posix_time::ptime > ());
     }

     static void* convertible(PyObject * obj_ptr)
     {
       if ( ! PyDateTime_Check(obj_ptr))
	 return 0;
       return obj_ptr;
     }

     static void construct(
         PyObject* obj_ptr,
         boost::python::converter::rvalue_from_python_stage1_data * data)
     {
       PyDateTime_DateTime const* pydate
	 = reinterpret_cast<PyDateTime_DateTime*>(obj_ptr);

       // Create date object
       boost::gregorian::date _date(PyDateTime_GET_YEAR(pydate),
				    PyDateTime_GET_MONTH(pydate),
				    PyDateTime_GET_DAY(pydate));

       // Create time duration object
       boost::posix_time::time_duration
	 _duration(PyDateTime_DATE_GET_HOUR(pydate),
		   PyDateTime_DATE_GET_MINUTE(pydate),
		   PyDateTime_DATE_GET_SECOND(pydate),
		   0);
       // Set the usecs value
       _duration += boost::posix_time::microseconds(PyDateTime_DATE_GET_MICROSECOND(pydate));

       // Create posix time object
       void* storage = (
			(boost::python::converter::rvalue_from_python_storage<boost::posix_time::ptime>*)
			data)->storage.bytes;
       new (storage)
	 boost::posix_time::ptime(_date, _duration);
       data->convertible = storage;
     }
};


/* Convert time_duration to/from python */
struct tduration_to_python_delta
{
    static PyObject* convert(boost::posix_time::time_duration d)
    {
      long days = d.hours() / 24;
      if (days < 0)
	days --;
      long seconds = d.total_seconds() - days*(24*3600);
      long usecs = get_usecs(d);
      if (days < 0)
	usecs = 1000000-1 - usecs;
      return PyDelta_FromDSU(days, seconds, usecs);
    }
};


/* Should support the negative values, but not the special boost time
   durations */
struct tduration_from_python_delta
{
     tduration_from_python_delta()
     {
         boost::python::converter::registry::push_back(
             &convertible,
             &construct,
             boost::python::type_id<boost::posix_time::time_duration>());
     }

     static void* convertible(PyObject * obj_ptr)
     {
       if ( ! PyDelta_Check(obj_ptr))
	 return 0;
       return obj_ptr;
     }

     static void construct(
         PyObject* obj_ptr,
         boost::python::converter::rvalue_from_python_stage1_data * data)
     {
       PyDateTime_Delta const* pydelta
	 = reinterpret_cast<PyDateTime_Delta*>(obj_ptr);

       long days = pydelta->days;
       bool is_negative = (days < 0);
       if (is_negative)
	 days = -days;

       // Create time duration object
       boost::posix_time::time_duration
	 duration = boost::posix_time::hours(24)*days
	            + boost::posix_time::seconds(pydelta->seconds)
	            + boost::posix_time::microseconds(pydelta->microseconds);
       if (is_negative)
	 duration = duration.invert_sign();

       void* storage = (
			(boost::python::converter::rvalue_from_python_storage<boost::posix_time::time_duration>*)
			data)->storage.bytes;
       new (storage)
	 boost::posix_time::time_duration(duration);
       data->convertible = storage;
     }
};

void bind_datetime()
{
    PyDateTime_IMPORT;

    ptime_from_python_datetime();

    to_python_converter<
        const boost::posix_time::ptime
      , ptime_to_python_datetime
    >();

    tduration_from_python_delta();

    to_python_converter<
        const boost::posix_time::time_duration
      , tduration_to_python_delta
    >();
}

This recipe would work for both datetime/ptime and timedelta/time_duration objects. The discussion below illustrates the datetime/ptime case.

Imagine you want to export this C++ function to the python world:

 void take_time(boost::posix_time::ptime const& t);

And this one:

 boost::posix_time::ptime const& give_time();

With the code above, you just call bind_datetime() from inside a BOOST_PYTHON_MODULE(MyModule), and define your bindings as usual. The result is that you can do this from python:

 MyModule.take_time(datetime.datetime.utcnow())

And if you do this:

 d = MyModule.give_time()
 print d, repr(d), type(d)

then python tells you:

 2008-07-29 22:04:32.202591 datetime.datetime(2008, 7, 29, 22, 4, 32, 202591) <type 'datetime.datetime'>

which means that it sees a genuine datetime.datetime object.

This recipe is based on the libtorrent and HÃ¥kon Groven's implementations. It mainly adds the support for the "from python" conversion without the need to transit trhough the string representation, and it should make the "to python" implementation more low-level (faster ?) and with correct support for microseconds.

The code above is C++, but I think it is more a python recipe. To compile, don't forget to add both the Boost.Python and python compilation flags (-I...../include/python).

2 comments

david decotigny (author) 15 years, 7 months ago  # | flag

Meta: For some reason, I cannot update the "discussion" section... Is it a bug in the web app ?

david decotigny (author) 15 years, 6 months ago  # | flag

Just updated (rev 10) with the timedelta/time_duration conversion.

Created by david decotigny on Tue, 29 Jul 2008 (MIT)
Python recipes (4591)
david decotigny's recipes (8)

Required Modules

  • (none specified)

Other Information and Tasks