<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>blog.freelan.org &#187; &#187; time</title>
	<atom:link href="https://blog.freelan.org/tag/time/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.freelan.org</link>
	<description>De l&#039;informatique, des octets et des poneys.</description>
	<lastBuildDate>Fri, 04 Apr 2014 17:34:59 +0000</lastBuildDate>
	<language>fr-FR</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.1.42</generator>
	<item>
		<title>Boost::Python, dates et conversions</title>
		<link>https://blog.freelan.org/2011/08/09/boostpython-dates-et-conversions/</link>
		<comments>https://blog.freelan.org/2011/08/09/boostpython-dates-et-conversions/#comments</comments>
		<pubDate>Tue, 09 Aug 2011 17:54:20 +0000</pubDate>
		<dc:creator><![CDATA[Julien Kauffmann]]></dc:creator>
				<category><![CDATA[Boost]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Développement]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[boost]]></category>
		<category><![CDATA[boost::posix_time]]></category>
		<category><![CDATA[boost::python]]></category>
		<category><![CDATA[c++]]></category>
		<category><![CDATA[conversion]]></category>
		<category><![CDATA[date]]></category>
		<category><![CDATA[datetime]]></category>
		<category><![CDATA[heure]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[time]]></category>

		<guid isPermaLink="false">http://blog.freelan.org/?p=354</guid>
		<description><![CDATA[Ceux qui ont déjà joué avec Boost::Python le savent : cette API est très complète mais malheureusement trop peu documentée. Si elle permet de rendre accessible du code C++ depuis un environnement Python avec parfois une déconcertante simplicité, certaines tâches triviales sont en revanche plus difficiles à réaliser qu'on aurait pu l'imaginer.
Si vous êtes sur cette page, c'est très probablement parce que vous aussi devez jouer avec des dates et des durées, de C++ vers Python, inversement ou bien les deux et que vous vous êtes vous aussi heurté à la complexité de Boost::Python.
L'article qui suit est le résultat de mes recherches en la matière, et la synthèse de la solution à laquelle je suis parvenu.]]></description>
				<content:encoded><![CDATA[<p>Ceux qui ont déjà joué avec Boost::Python le savent : cette API est très complète mais malheureusement trop peu documentée. Si elle permet de rendre accessible du code C++ depuis un environnement Python avec parfois une déconcertante simplicité, certaines tâches triviales sont en revanche plus difficiles à réaliser qu&#8217;on aurait pu l&#8217;imaginer.</p>
<p>Si vous êtes sur cette page, c&#8217;est très probablement parce que vous aussi devez jouer avec des dates et des durées, de C++ vers Python, inversement ou bien les deux et que vous vous êtes vous aussi heurté à la complexité de Boost::Python.</p>
<p>L&#8217;article qui suit est le résultat de mes recherches en la matière, et la synthèse de la solution à laquelle je suis parvenu.</p>
<h1>Le code de base</h1>
<p>Pour l&#8217;ensemble de l&#8217;article, nous partirons du principe que vous utilisez la classe <code>boost::posix_time::ptime</code> côté C++, et <code>datetime.datetime</code> côté Python.</p>
<p><em>Remarque : Si jamais vous utilisiez une autre classe pour vos dates (quelle soit &#8220;maison&#8221; ou issue d&#8217;une autre bibliothèque), vous devriez pouvoir adapter le principe sans trop de problèmes.</em></p>
<p>Le code minimal pour un module Python avec Boost::Python dont nous partirons est le suivant :</p>
<p></p><pre class="crayon-plain-tag">/**
* \file module.cpp
* \author Julien Kauffmann
* \brief The Python module file.
*/

#include &lt;boost/python/module.hpp&gt;

BOOST_PYTHON_MODULE(module)
{
}</pre><p></p>
<p>Rien de fou donc, pour l&#8217;instant.</p>
<p>Ajoutons une fonction qui prend en paramètre et retourne un <code>boost::posix_time::ptime</code> :</p><pre class="crayon-plain-tag">/**
 * \file module.cpp
 * \author Julien Kauffmann
 * \brief The Python module file.
 */

#include &lt;boost/python/module.hpp&gt;
#include &lt;boost/python/def.hpp&gt;

#include &lt;boost/date_time/posix_time/posix_time.hpp&gt;

boost::posix_time::ptime add_five_seconds(boost::posix_time::ptime ptime)
{
  if (!ptime.is_special())
  {
    return ptime + boost::posix_time::seconds(5);
  }

  return ptime;
}

BOOST_PYTHON_MODULE(module)
{
  boost::python::def(&quot;add_five_seconds&quot;, &amp;add_five_seconds, boost::python::args(&quot;ptime&quot;), &quot;Add five seconds to a datetime then return the result&quot;);
}</pre><p>Cette fonction ajoute bêtement 5 secondes à toute date qui lui est passée, sauf si celle-ci n&#8217;est pas une date valide.</p>
<p>Si le code actuel compile et donne une bibliothèque Python valide, l&#8217;appel de <code>add_five_seconds()</code> depuis l&#8217;interpréteur Python provoque la levée d&#8217;une exception :</p><pre class="crayon-plain-tag">Python 2.4.3 (#1, Apr 14 2011, 20:41:59)
Type &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.

In [1]: import datetime, module

In [2]: module.add_five_seconds(datetime.datetime.today())
---------------------------------------------------------------------------
ArgumentError                             Traceback (most recent call last)

/home/ereon/workbench/pythondate/

ArgumentError: Python argument types in
    module.add_five_seconds(datetime.datetime)
did not match C++ signature:
    add_five_seconds(boost::posix_time::ptime ptime)</pre><p>En effet : à aucun moment nous n&#8217;avons indiqué à Boost::Python comment convertir une date Python en date C++, ni même que cette conversion était possible.</p>
<p>Pour que ceci fonctionne, il faut ajouter deux &#8220;converters&#8221; : un de C++ vers Python, et l&#8217;autre de Python vers C++.</p>
<h2>Conversion de C++ vers Python</h2>
<p>Si Boost::Python ne prend pas nativement en charge la conversion de date, il fournit néanmoins des outils puissants pour nous permettre d&#8217;y arriver.</p>
<p>Ajoutons le code de conversion à notre exemple précédent :</p><pre class="crayon-plain-tag">/**
 * \file module.cpp
 * \author Julien Kauffmann &lt;julien.kauffmann@freelan.org&gt;
 * \brief The Python module file.
 */

#include &lt;boost/python/module.hpp&gt;
#include &lt;boost/python/def.hpp&gt;
#include &lt;boost/python/class.hpp&gt;

#include &lt;boost/date_time/posix_time/posix_time.hpp&gt;

#include &lt;datetime.h&gt;

boost::posix_time::ptime add_five_seconds(boost::posix_time::ptime ptime)
{
  if (!ptime.is_special())
  {
    return ptime + boost::posix_time::seconds(5);
  }

  return ptime;
}

struct date_to_python_converter
{
  static PyObject* convert(boost::posix_time::ptime value)
  {
    if (value.is_not_a_date_time())
      return Py_None;

    PyDateTime_IMPORT;

    return PyDateTime_FromDateAndTime(
      static_cast&lt;int&gt;(value.date().year()),
      static_cast&lt;int&gt;(value.date().month()),
      static_cast&lt;int&gt;(value.date().day()),
      static_cast&lt;int&gt;(value.time_of_day().hours()),
      static_cast&lt;int&gt;(value.time_of_day().minutes()),
      static_cast&lt;int&gt;(value.time_of_day().seconds()),
      static_cast&lt;int&gt;(value.time_of_day().total_microseconds() - value.time_of_day().total_seconds() * 1000000L)
      );
  }
};

BOOST_PYTHON_MODULE(module)
{
  boost::python::to_python_converter&lt;boost::posix_time::ptime, date_to_python_converter&gt;();

  boost::python::def(&quot;add_five_seconds&quot;, &amp;add_five_seconds, boost::python::args(&quot;ptime&quot;), &quot;Add five seconds to a datetime then return the result&quot;);
}</pre><p></p>
<h3>Analyse</h3>
<p>Regardons ligne par ligne les changements apportés.</p><pre class="crayon-plain-tag">#include &lt;boost/python/class.hpp&gt;
#include &lt;datetime.h&gt;</pre><p>Le premier include est nécessaire pour utiliser <code>boost::python::to_python_converter</code>; le second pour rendre disponibles les types Python natifs, tels que <code>PyObject</code> ou <code>PyDateTime</code>.</p><pre class="crayon-plain-tag">struct date_to_python_converter
{
  static PyObject* convert(boost::posix_time::ptime value)
  {
    if (value.is_not_a_date_time())
      return Py_None;

    PyDateTime_IMPORT;

    return PyDateTime_FromDateAndTime(
      static_cast&lt;int&gt;(value.date().year()),
      static_cast&lt;int&gt;(value.date().month()),
      static_cast&lt;int&gt;(value.date().day()),
      static_cast&lt;int&gt;(value.time_of_day().hours()),
      static_cast&lt;int&gt;(value.time_of_day().minutes()),
      static_cast&lt;int&gt;(value.time_of_day().seconds()),
      static_cast&lt;int&gt;(value.time_of_day().total_microseconds() - value.time_of_day().total_seconds() * 1000000L)
      );
  }
};</pre><p>Ici nous déclarons un &#8220;converter&#8221;. Au sens de Boost::Python, un &#8220;converter&#8221; est une simple structure ou classe qui contient une méthode statique nommée <code>convert()</code> qui prend un paramètre le type natif C++ à convertir, et qui retourne un <code>PyObject*</code>.</p>
<p>Dans le cas où notre date n&#8217;est pas une date valide, nous choisissons ici de renvoyer <code>None</code>. Libre à vous de modifier ce comportement pour satisfaire vos propres besoins.</p>
<p>Remarque : La valeur retournée <strong>doit</strong> avoir un compteur de référence <strong>strictement positif</strong>.</p><pre class="crayon-plain-tag">boost::python::to_python_converter&lt;boost::posix_time::ptime, date_to_python_converter&gt;();</pre><p>Enfin nous déclarons notre &#8220;converter&#8221; en spécifiant le type natif et la structure/classe à utiliser pour la conversion.</p>
<p>À partir de ce moment là, notre module Python sera capable de convertir implicitement tout <code>boost::posix_time::ptime</code> en <code>datetime.datetime</code> Python.</p>
<h2>Conversion de Python vers C++</h2>
<p>La conversion dans l&#8217;autre sens demande un peu plus de travail :</p><pre class="crayon-plain-tag">/**
 * \file module.cpp
 * \author Julien Kauffmann &lt;julien.kauffmann@freelan.org&gt;
 * \brief The Python module file.
 */

#include &lt;boost/python/module.hpp&gt;
#include &lt;boost/python/def.hpp&gt;
#include &lt;boost/python/class.hpp&gt;

#include &lt;boost/date_time/posix_time/posix_time.hpp&gt;

#include &lt;datetime.h&gt;

boost::posix_time::ptime add_five_seconds(boost::posix_time::ptime ptime)
{
  if (!ptime.is_special())
  {
    return ptime + boost::posix_time::seconds(5);
  }

  return ptime;
}

struct date_to_python_converter
{
  static PyObject* convert(boost::posix_time::ptime value)
  {
    if (value.is_not_a_date_time())
      return Py_None;

    PyDateTime_IMPORT;

    return PyDateTime_FromDateAndTime(
        static_cast&lt;int&gt;(value.date().year()),
        static_cast&lt;int&gt;(value.date().month()),
        static_cast&lt;int&gt;(value.date().day()),
        static_cast&lt;int&gt;(value.time_of_day().hours()),
        static_cast&lt;int&gt;(value.time_of_day().minutes()),
        static_cast&lt;int&gt;(value.time_of_day().seconds()),
        static_cast&lt;int&gt;(value.time_of_day().total_microseconds() - value.time_of_day().total_seconds() * 1000000L)
        );
  }
};

struct date_from_python_converter
{
  static void* is_convertible(PyObject* obj_ptr)
  {
    assert(obj_ptr);

    if (obj_ptr == Py_None)
      return obj_ptr;

    PyDateTime_IMPORT;

    if (PyDateTime_Check(obj_ptr))
      return obj_ptr;

    return NULL;
  }

  static void convert(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data)
  {
    assert(obj_ptr);

    void* const storage = reinterpret_cast&lt;boost::python::converter::rvalue_from_python_storage&lt;boost::posix_time::ptime&gt;*&gt;(data)-&gt;storage.bytes;

    if (obj_ptr == Py_None)
    {
      new (storage) boost::posix_time::ptime();
    } else
    {
      PyDateTime_IMPORT;
      PyDateTime_DateTime* dt_ptr = reinterpret_cast&lt;PyDateTime_DateTime*&gt;(obj_ptr);

      const int year = PyDateTime_GET_YEAR(dt_ptr);
      const int month = PyDateTime_GET_MONTH(dt_ptr);
      const int day = PyDateTime_GET_DAY(dt_ptr);
      const int hour  = PyDateTime_DATE_GET_HOUR(dt_ptr);
      const int minute  = PyDateTime_DATE_GET_MINUTE(dt_ptr);
      const int second = PyDateTime_DATE_GET_SECOND(dt_ptr);
      const int microsecond = PyDateTime_DATE_GET_MICROSECOND(dt_ptr);

      new (storage) boost::posix_time::ptime(boost::gregorian::date(year, month, day), boost::posix_time::time_duration(hour, minute, second, 0) + boost::posix_time::microseconds(microsecond));
    }

    data-&gt;convertible = storage;
  }
};

BOOST_PYTHON_MODULE(module)
{
  boost::python::to_python_converter&lt;boost::posix_time::ptime, date_to_python_converter&gt;();

  boost::python::converter::registry::push_back(&amp;date_from_python_converter::is_convertible, &amp;date_from_python_converter::convert, boost::python::type_id&lt;boost::posix_time::ptime&gt;());  

  boost::python::def(&quot;add_five_seconds&quot;, &amp;add_five_seconds, boost::python::args(&quot;ptime&quot;), &quot;Add five seconds to a datetime then return the result&quot;);
}</pre><p></p>
<h3>Analyse</h3>
<p>Regardons encore une fois, ligne par ligne les modifications apportées :</p><pre class="crayon-plain-tag">struct date_from_python_converter</pre><p>Nous ajoutons une structure qui va contenir les routines de conversions. Contrairement à tout à l&#8217;heure, et comme nous le verrons plus tard, ceci n&#8217;est pas indispensable. Pour effectuer des conversions de Python vers C++, Boost::Python a juste besoin de deux fonctions. Que celles-ci soient statiques au sein d&#8217;une classe ou libres n&#8217;a aucune incidence.</p><pre class="crayon-plain-tag">static void* is_convertible(PyObject* obj_ptr)
  {
    assert(obj_ptr);

    if (obj_ptr == Py_None)
      return obj_ptr;

    PyDateTime_IMPORT;

    if (PyDateTime_Check(obj_ptr))
      return obj_ptr;

    return NULL;
  }</pre><p>La fonction <code>is_convertible()</code> sera utilisée par Boost::Python pour déterminer si l&#8217;instance Python à convertir peut l&#8217;être.</p>
<p>Dans notre cas nous acceptons tout d&#8217;abord <code>None</code> comme valeur &#8220;valide&#8221; pour respecter la symétrie, puis nous testons si l&#8217;instance est de type <code>datetime.datetime</code> grâce à la fonction <code>PyDateTime_Check()</code>.</p>
<p>En cas de succès, nous renvoyons la valeur passée en paramètre telle quelle. En cas d&#8217;erreur, nous renvoyons <code>NULL</code>.</p><pre class="crayon-plain-tag">static void convert(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data)
  {
    assert(obj_ptr);

    void* const storage = reinterpret_cast&lt;boost::python::converter::rvalue_from_python_storage&lt;boost::posix_time::ptime&gt;*&gt;(data)-&gt;storage.bytes;

    if (obj_ptr == Py_None)
    {
      new (storage) boost::posix_time::ptime();
    } else
    {
      PyDateTime_IMPORT;
      PyDateTime_DateTime* dt_ptr = reinterpret_cast&lt;PyDateTime_DateTime*&gt;(obj_ptr);

      const int year = PyDateTime_GET_YEAR(dt_ptr);
      const int month = PyDateTime_GET_MONTH(dt_ptr);
      const int day = PyDateTime_GET_DAY(dt_ptr);
      const int hour  = PyDateTime_DATE_GET_HOUR(dt_ptr);
      const int minute  = PyDateTime_DATE_GET_MINUTE(dt_ptr);
      const int second = PyDateTime_DATE_GET_SECOND(dt_ptr);
      const int microsecond = PyDateTime_DATE_GET_MICROSECOND(dt_ptr);

      new (storage) boost::posix_time::ptime(boost::gregorian::date(year, month, day), boost::posix_time::time_duration(hour, minute, second, 0) + boost::posix_time::microseconds(microsecond));
    }

    data-&gt;convertible = storage;
  }</pre><p>C&#8217;est ici que se passe le gros du travail : lorsque cette fonction est appelée, cela signifie que l&#8217;instance <code>obj_ptr</code> a passé l&#8217;appel à <code>is_convertible()</code> et est prête à être convertie.</p>
<p>Le paramètre <code>data</code> lui contient entre autres l&#8217;adresse de la zone mémoire où nous devons instancier notre résultat de conversion. Notez que pour ce faire, nous utilisons le &#8220;placement new&#8221; qui permet de construire une instance à un emplacement mémoire donné. Le <code>delete</code> correspondant sera automatiquement appelé par Boost::Python au besoin.</p>
<p>Pour finir, nous renseignons le champ <code>convertible</code> du paramètre <code>data</code> pour y indiquer où nous avons alloué notre résultat.</p><pre class="crayon-plain-tag">boost::python::converter::registry::push_back(&amp;date_from_python_converter::is_convertible, &amp;date_from_python_converter::convert, boost::python::type_id&lt;boost::posix_time::ptime&gt;());</pre><p>Comme auparavant, la dernière étape consiste à enregistrer le &#8220;converter&#8221; pour le faire connaître de Boost::python.<br />
On remarque ici que comme énoncé précédemment, l&#8217;appel prend en paramètre deux fonctions qui n&#8217;ont pas nécessairement besoin de faire partie d&#8217;une classe ou d&#8217;une structure.</p>
<h1>Résultat</h1>
<p>Compilez le code ci-dessus (voir le script <em>SConstruct</em> en annexe), puis chargez votre module au sein de l&#8217;interpreteur Python :</p><pre class="crayon-plain-tag">Python 2.4.3 (#1, Apr 14 2011, 20:41:59)
Type &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.

In [1]: import datetime, module

In [2]: d = datetime.datetime.now()

In [3]: print d
2011-08-09 11:30:43.239460

In [4]: print module.add_five_seconds(d)
2011-08-09 11:30:48.239460</pre><p>Ça y est ! La conversion <code>boost::posix_time::ptime</code> &lt;=&gt; <code>datetime.datetime</code> fonctionne parfaitement. <img src="https://blog.freelan.org/wp-includes/images/smilies/icon_smile.gif" alt=":)" class="wp-smiley" /></p>
<h1>Conclusion</h1>
<p>Boost::Python est définitivement une bibliothèque très puissante. On peut certes regretter la qualité de sa documentation, mais fort heureusement les ressources à son sujet sur l&#8217;Internet ne manquent pas. Son extensibilité la rend utilisable dans toutes les situations et facilite grandement la vie du développeur.</p>
<p>J&#8217;espère que cet article vous aura aidé et/ou donné envie de découvrir/utiliser Boost::Python. Comme toujours, n&#8217;hésitez pas à me signaler toute coquille, erreur ou optimisation qui m&#8217;aurait échappé.</p>
<h1>Annexe</h1>
<p>Voici le script SConstruct que j&#8217;ai utilisé pour la compilation :</p><pre class="crayon-plain-tag">import os

env = Environment(ENV = os.environ.copy())
env['CPPPATH'] = ['/usr/include/python2.4']

module = env.SharedLibrary(source = Glob('*.cpp'), target = 'python/module.so', SHLIBPREFIX='', LIBS = ['boost_python', 'python2.4'])</pre><p></p>
<h1>Sources</h1>
<p>Ces pages m&#8217;ont été très utiles lors de la rédaction de cet article :</p>
<ul>
<li>L&#8217;API datetime sur <a href="http://docs.python.org/c-api/datetime.htmlhttp://docs.python.org/c-api/datetime.html">python.org</a> (en Anglais);</li>
<li>l&#8217;API Boost::Posix Time sur <a href="http://www.boost.org/doc/libs/1_47_0/doc/html/date_time/posix_time.html">boost.org</a> (en Anglais);</li>
<li>l&#8217;API de Boost::Python sur <a href="http://www.boost.org/doc/libs/1_46_0/libs/python/doc/index.html">boost.org</a> (en Anglais);</li>
<li>cet <a href="http://misspent.wordpress.com/2009/09/27/how-to-write-boost-python-converters/">article</a> de misspent (en Anglais).</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>https://blog.freelan.org/2011/08/09/boostpython-dates-et-conversions/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>
