Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pybind11
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
open
pybind11
Commits
2f597687
Commit
2f597687
authored
Sep 13, 2016
by
Trent Houliston
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Changed non system clocks to be time deltas
Allowed durations and non system clocks to be set from floats.
parent
207d0da3
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
85 additions
and
32 deletions
+85
-32
docs/advanced.rst
+10
-4
include/pybind11/chrono.h
+49
-20
tests/test_chrono.cpp
+6
-0
tests/test_chrono.py
+20
-8
No files found.
docs/advanced.rst
View file @
2f597687
...
@@ -764,19 +764,25 @@ When including the additional header file :file:`pybind11/chrono.h` conversions
...
@@ -764,19 +764,25 @@ When including the additional header file :file:`pybind11/chrono.h` conversions
to corresponding python datetime objects are automatically enabled.
to corresponding python datetime objects are automatically enabled.
The following rules describe how the conversions are applied.
The following rules describe how the conversions are applied.
O
bjects of type ``std::chrono::system_clock::time_point`` are converted into datetime.datetime objects.
When passed to python o
bjects of type ``std::chrono::system_clock::time_point`` are converted into datetime.datetime objects.
These objects are those that specifically come from the system_clock as this is the only clock that measures wall time.
These objects are those that specifically come from the system_clock as this is the only clock that measures wall time.
Objects of type ``std::chrono::[other_clock]::time_point`` are converted into datetime.time
objects.
When passed to python of type ``std::chrono::[other_clock]::time_point`` are converted into datetime.timedelta
objects.
These objects are those that come from all clocks that are not the system_clock (e.g. steady_clock).
These objects are those that come from all clocks that are not the system_clock (e.g. steady_clock).
Clocks other than the system_clock are not measured from wall date/time and instead have any start time
Clocks other than the system_clock are not measured from wall date/time and instead have any start time
(often when the computer was turned on).
(often when the computer was turned on).
Therefore as these clocks can only measure time from an arbitrary start point they are represented as time
without date
.
Therefore as these clocks can only measure time from an arbitrary start point they are represented as time
delta from this start point
.
Objects of type ``std::chrono::duration`` are converted into datetime.timedelta objects.
When passed to python of type ``std::chrono::duration`` are converted into datetime.timedelta objects.
When python objects are passed to c++ for the case of non system clocks and durations instances of both datetime.timedelta
and float are accepted. The float arguments are interpreted as a number of seconds since the epoch.
.. note::
.. note::
Other clocks may be the same as system_clock. For example on many platforms std::high_resolution_clock is the same as system_clock.
Because of this if you are converting a timepoint from one of these clocks they may appear to python as a datetime.datetime object.
Pythons datetime implementation is limited to microsecond precision.
Pythons datetime implementation is limited to microsecond precision.
The extra precision that c++11 clocks can have have (nanoseconds) will be lost upon conversion.
The extra precision that c++11 clocks can have have (nanoseconds) will be lost upon conversion.
The rounding policy from c++ to python is via ``std::chrono::duration_cast<>`` (rounding towards 0 in microseconds).
The rounding policy from c++ to python is via ``std::chrono::duration_cast<>`` (rounding towards 0 in microseconds).
...
...
include/pybind11/chrono.h
View file @
2f597687
...
@@ -24,9 +24,12 @@ public:
...
@@ -24,9 +24,12 @@ public:
bool
load
(
handle
src
,
bool
)
{
bool
load
(
handle
src
,
bool
)
{
using
namespace
std
::
chrono
;
using
namespace
std
::
chrono
;
// Lazy initialise the PyDateTime import
if
(
!
PyDateTimeAPI
)
{
PyDateTime_IMPORT
;
}
if
(
!
PyDateTimeAPI
)
{
PyDateTime_IMPORT
;
}
if
(
!
src
)
return
false
;
if
(
!
src
)
return
false
;
// If they have passed us a datetime.delta object
if
(
PyDelta_Check
(
src
.
ptr
()))
{
if
(
PyDelta_Check
(
src
.
ptr
()))
{
// The accessor macros for timedelta exist in some versions of python but not others (e.g. Mac OSX default python)
// The accessor macros for timedelta exist in some versions of python but not others (e.g. Mac OSX default python)
// Therefore we are just doing what the macros do explicitly
// Therefore we are just doing what the macros do explicitly
...
@@ -37,6 +40,13 @@ public:
...
@@ -37,6 +40,13 @@ public:
+
microseconds
(
delta
->
microseconds
));
+
microseconds
(
delta
->
microseconds
));
return
true
;
return
true
;
}
}
// If they have passed us a float we can assume it is seconds and convert
else
if
(
PyFloat_Check
(
src
.
ptr
()))
{
double
val
=
PyFloat_AsDouble
(
src
.
ptr
());
// Multiply by the reciprocal of the ratio and round
value
=
type
(
std
::
lround
(
val
*
type
::
period
::
den
/
type
::
period
::
num
));
return
true
;
}
else
return
false
;
else
return
false
;
}
}
...
@@ -45,9 +55,9 @@ public:
...
@@ -45,9 +55,9 @@ public:
if
(
!
PyDateTimeAPI
)
{
PyDateTime_IMPORT
;
}
if
(
!
PyDateTimeAPI
)
{
PyDateTime_IMPORT
;
}
// Declare these special duration types so the conversions happen with the correct primitive types (int)
// Declare these special duration types so the conversions happen with the correct primitive types (int)
typedef
duration
<
int
,
std
::
ratio
<
86400
>>
dd_t
;
using
dd_t
=
duration
<
int
,
std
::
ratio
<
86400
>>
;
typedef
duration
<
int
,
std
::
ratio
<
1
>>
ss_t
;
using
ss_t
=
duration
<
int
,
std
::
ratio
<
1
>>
;
typedef
duration
<
int
,
std
::
micro
>
us_t
;
using
us_t
=
duration
<
int
,
std
::
micro
>
;
return
PyDelta_FromDSU
(
return
PyDelta_FromDSU
(
duration_cast
<
dd_t
>
(
src
).
count
()
duration_cast
<
dd_t
>
(
src
).
count
()
...
@@ -57,11 +67,14 @@ public:
...
@@ -57,11 +67,14 @@ public:
PYBIND11_TYPE_CASTER
(
type
,
_
(
"datetime.timedelta"
));
PYBIND11_TYPE_CASTER
(
type
,
_
(
"datetime.timedelta"
));
};
};
// This is for casting times on the system clock into datetime.datetime instances
template
<
typename
Duration
>
class
type_caster
<
std
::
chrono
::
time_point
<
std
::
chrono
::
system_clock
,
Duration
>>
{
template
<
typename
Duration
>
class
type_caster
<
std
::
chrono
::
time_point
<
std
::
chrono
::
system_clock
,
Duration
>>
{
public
:
public
:
typedef
std
::
chrono
::
time_point
<
std
::
chrono
::
system_clock
,
Duration
>
type
;
typedef
std
::
chrono
::
time_point
<
std
::
chrono
::
system_clock
,
Duration
>
type
;
bool
load
(
handle
src
,
bool
)
{
bool
load
(
handle
src
,
bool
)
{
using
namespace
std
::
chrono
;
using
namespace
std
::
chrono
;
// Lazy initialise the PyDateTime import
if
(
!
PyDateTimeAPI
)
{
PyDateTime_IMPORT
;
}
if
(
!
PyDateTimeAPI
)
{
PyDateTime_IMPORT
;
}
if
(
!
src
)
return
false
;
if
(
!
src
)
return
false
;
...
@@ -83,6 +96,8 @@ public:
...
@@ -83,6 +96,8 @@ public:
static
handle
cast
(
const
std
::
chrono
::
time_point
<
std
::
chrono
::
system_clock
,
Duration
>
&
src
,
return_value_policy
/* policy */
,
handle
/* parent */
)
{
static
handle
cast
(
const
std
::
chrono
::
time_point
<
std
::
chrono
::
system_clock
,
Duration
>
&
src
,
return_value_policy
/* policy */
,
handle
/* parent */
)
{
using
namespace
std
::
chrono
;
using
namespace
std
::
chrono
;
// Lazy initialise the PyDateTime import
if
(
!
PyDateTimeAPI
)
{
PyDateTime_IMPORT
;
}
if
(
!
PyDateTimeAPI
)
{
PyDateTime_IMPORT
;
}
time_t
tt
=
system_clock
::
to_time_t
(
src
);
time_t
tt
=
system_clock
::
to_time_t
(
src
);
...
@@ -104,21 +119,33 @@ public:
...
@@ -104,21 +119,33 @@ public:
PYBIND11_TYPE_CASTER
(
type
,
_
(
"datetime.datetime"
));
PYBIND11_TYPE_CASTER
(
type
,
_
(
"datetime.datetime"
));
};
};
// Other clocks that are not the system clock are not measured as datetime.datetime objects
// since they are not measured on calendar time. So instead we just make them timedeltas
// Or if they have passed us a time as a float we convert that
template
<
typename
Clock
,
typename
Duration
>
class
type_caster
<
std
::
chrono
::
time_point
<
Clock
,
Duration
>>
{
template
<
typename
Clock
,
typename
Duration
>
class
type_caster
<
std
::
chrono
::
time_point
<
Clock
,
Duration
>>
{
public
:
public
:
typedef
std
::
chrono
::
time_point
<
Clock
,
Duration
>
type
;
typedef
std
::
chrono
::
time_point
<
Clock
,
Duration
>
type
;
typedef
std
::
chrono
::
duration
<
std
::
chrono
::
hours
::
rep
,
std
::
ratio
<
86400
>>
days
;
bool
load
(
handle
src
,
bool
)
{
bool
load
(
handle
src
,
bool
)
{
using
namespace
std
::
chrono
;
using
namespace
std
::
chrono
;
if
(
!
PyDateTimeAPI
)
{
PyDateTime_IMPORT
;
}
if
(
!
PyDateTimeAPI
)
{
PyDateTime_IMPORT
;
}
if
(
!
src
)
return
false
;
// If they have passed us a datetime.delta object
if
(
PyTime_Check
(
src
.
ptr
()))
{
if
(
PyDelta_Check
(
src
.
ptr
()))
{
value
=
type
(
duration_cast
<
Duration
>
(
// The accessor macros for timedelta exist in some versions of python but not others (e.g. Mac OSX default python)
hours
(
PyDateTime_TIME_GET_HOUR
(
src
.
ptr
()))
// Therefore we are just doing what the macros do explicitly
+
minutes
(
PyDateTime_TIME_GET_MINUTE
(
src
.
ptr
()))
const
PyDateTime_Delta
*
delta
=
reinterpret_cast
<
PyDateTime_Delta
*>
(
src
.
ptr
());
+
seconds
(
PyDateTime_TIME_GET_SECOND
(
src
.
ptr
()))
value
=
time_point
<
Clock
,
Duration
>
(
+
microseconds
(
PyDateTime_TIME_GET_MICROSECOND
(
src
.
ptr
()))
days
(
delta
->
days
)
));
+
seconds
(
delta
->
seconds
)
+
microseconds
(
delta
->
microseconds
));
return
true
;
}
// If they have passed us a float we can assume it is seconds and convert
else
if
(
PyFloat_Check
(
src
.
ptr
()))
{
double
val
=
PyFloat_AsDouble
(
src
.
ptr
());
value
=
time_point
<
Clock
,
Duration
>
(
Duration
(
std
::
lround
((
val
/
Clock
::
period
::
num
)
*
Clock
::
period
::
den
)));
return
true
;
return
true
;
}
}
else
return
false
;
else
return
false
;
...
@@ -126,21 +153,23 @@ public:
...
@@ -126,21 +153,23 @@ public:
static
handle
cast
(
const
std
::
chrono
::
time_point
<
Clock
,
Duration
>
&
src
,
return_value_policy
/* policy */
,
handle
/* parent */
)
{
static
handle
cast
(
const
std
::
chrono
::
time_point
<
Clock
,
Duration
>
&
src
,
return_value_policy
/* policy */
,
handle
/* parent */
)
{
using
namespace
std
::
chrono
;
using
namespace
std
::
chrono
;
// Lazy initialise the PyDateTime import
if
(
!
PyDateTimeAPI
)
{
PyDateTime_IMPORT
;
}
if
(
!
PyDateTimeAPI
)
{
PyDateTime_IMPORT
;
}
// Declare these special duration types so the conversions happen with the correct primitive types (int)
// Declare these special duration types so the conversions happen with the correct primitive types (int)
typedef
duration
<
int
,
std
::
ratio
<
3600
>>
hh_t
;
using
dd_t
=
duration
<
int
,
std
::
ratio
<
86400
>>
;
typedef
duration
<
int
,
std
::
ratio
<
60
>>
mm_t
;
using
ss_t
=
duration
<
int
,
std
::
ratio
<
1
>>
;
typedef
duration
<
int
,
std
::
ratio
<
1
>>
ss_t
;
using
us_t
=
duration
<
int
,
std
::
micro
>
;
typedef
duration
<
int
,
std
::
micro
>
us_t
;
Duration
d
=
src
.
time_since_epoch
();
Duration
d
=
src
.
time_since_epoch
();
return
PyTime_FromTime
(
duration_cast
<
hh_t
>
(
d
).
count
()
,
duration_cast
<
mm_t
>
(
d
%
hours
(
1
)).
count
()
return
PyDelta_FromDSU
(
,
duration_cast
<
ss_t
>
(
d
%
minutes
(
1
)).
count
()
duration_cast
<
dd_t
>
(
d
).
count
()
,
duration_cast
<
us_t
>
(
d
%
seconds
(
1
)).
count
());
,
duration_cast
<
ss_t
>
(
d
%
days
(
1
)).
count
()
,
duration_cast
<
us_t
>
(
d
%
seconds
(
1
)).
count
());
}
}
PYBIND11_TYPE_CASTER
(
type
,
_
(
"datetime.time"
));
PYBIND11_TYPE_CASTER
(
type
,
_
(
"datetime.time
delta
"
));
};
};
NAMESPACE_END
(
detail
)
NAMESPACE_END
(
detail
)
...
...
tests/test_chrono.cpp
View file @
2f597687
...
@@ -43,6 +43,11 @@ std::chrono::steady_clock::time_point test_chrono6(std::chrono::steady_clock::ti
...
@@ -43,6 +43,11 @@ std::chrono::steady_clock::time_point test_chrono6(std::chrono::steady_clock::ti
return
t
;
return
t
;
}
}
// Roundtrip a duration in microseconds from a float argument
std
::
chrono
::
microseconds
test_chrono7
(
std
::
chrono
::
microseconds
t
)
{
return
t
;
}
test_initializer
chrono
([]
(
py
::
module
&
m
)
{
test_initializer
chrono
([]
(
py
::
module
&
m
)
{
m
.
def
(
"test_chrono1"
,
&
test_chrono1
);
m
.
def
(
"test_chrono1"
,
&
test_chrono1
);
m
.
def
(
"test_chrono2"
,
&
test_chrono2
);
m
.
def
(
"test_chrono2"
,
&
test_chrono2
);
...
@@ -50,4 +55,5 @@ test_initializer chrono([] (py::module &m) {
...
@@ -50,4 +55,5 @@ test_initializer chrono([] (py::module &m) {
m
.
def
(
"test_chrono4"
,
&
test_chrono4
);
m
.
def
(
"test_chrono4"
,
&
test_chrono4
);
m
.
def
(
"test_chrono5"
,
&
test_chrono5
);
m
.
def
(
"test_chrono5"
,
&
test_chrono5
);
m
.
def
(
"test_chrono6"
,
&
test_chrono6
);
m
.
def
(
"test_chrono6"
,
&
test_chrono6
);
m
.
def
(
"test_chrono7"
,
&
test_chrono7
);
});
});
tests/test_chrono.py
View file @
2f597687
...
@@ -82,21 +82,33 @@ def test_chrono_steady_clock():
...
@@ -82,21 +82,33 @@ def test_chrono_steady_clock():
time1
=
test_chrono5
()
time1
=
test_chrono5
()
time2
=
test_chrono5
()
time2
=
test_chrono5
()
assert
isinstance
(
time1
,
datetime
.
time
)
assert
isinstance
(
time1
,
datetime
.
time
delta
)
assert
isinstance
(
time2
,
datetime
.
time
)
assert
isinstance
(
time2
,
datetime
.
time
delta
)
def
test_chrono_steady_clock_roundtrip
():
def
test_chrono_steady_clock_roundtrip
():
from
pybind11_tests
import
test_chrono6
from
pybind11_tests
import
test_chrono6
import
datetime
import
datetime
time1
=
datetime
.
time
(
second
=
10
,
microsecond
=
100
)
time1
=
datetime
.
time
delta
(
days
=
10
,
seconds
=
10
,
microseconds
=
100
)
time2
=
test_chrono6
(
time1
)
time2
=
test_chrono6
(
time1
)
assert
isinstance
(
time2
,
datetime
.
time
)
assert
isinstance
(
time2
,
datetime
.
time
delta
)
# They should be identical (no information lost on roundtrip)
# They should be identical (no information lost on roundtrip)
assert
time1
.
hour
==
time2
.
hour
assert
time1
.
days
==
time2
.
days
assert
time1
.
minute
==
time2
.
minute
assert
time1
.
seconds
==
time2
.
seconds
assert
time1
.
second
==
time2
.
second
assert
time1
.
microseconds
==
time2
.
microseconds
assert
time1
.
microsecond
==
time2
.
microsecond
def
test_floating_point_duration
():
from
pybind11_tests
import
test_chrono7
import
datetime
# Test using 35.525123 seconds as an example floating point number in seconds
time
=
test_chrono7
(
35.525123
)
assert
isinstance
(
time
,
datetime
.
timedelta
)
assert
time
.
seconds
==
35
assert
time
.
microseconds
==
525123
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment