Skip to content

Commit 6d9c5c3

Browse files
committed
Handle datetime in non-inlined functions
1 parent ebb98fb commit 6d9c5c3

3 files changed

Lines changed: 238 additions & 209 deletions

File tree

src/datetime.rs

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
use crate::typeref::*;
2+
use smallvec::SmallVec;
3+
4+
const NAIVE_UTC: u8 = 1 << 1;
5+
6+
const HYPHEN: u8 = 45; // "-"
7+
const PLUS: u8 = 43; // "+"
8+
const ZERO: u8 = 48; // "0"
9+
const T: u8 = 84; // "T"
10+
const COLON: u8 = 58; // ":"
11+
const PERIOD: u8 = 46; // ":"
12+
13+
macro_rules! write_double_digit {
14+
($dt:ident, $value:ident) => {
15+
if $value < 10 {
16+
$dt.push(ZERO);
17+
}
18+
$dt.extend(itoa::Buffer::new().format($value).bytes());
19+
};
20+
}
21+
22+
macro_rules! write_microsecond {
23+
($dt:ident, $microsecond:ident) => {
24+
if $microsecond != 0 {
25+
$dt.push(PERIOD);
26+
let mut buf = itoa::Buffer::new();
27+
let formatted = buf.format($microsecond);
28+
let mut to_pad = 6 - formatted.len();
29+
while to_pad != 0 {
30+
$dt.push(ZERO);
31+
to_pad -= 1;
32+
}
33+
$dt.extend(formatted.bytes());
34+
}
35+
};
36+
}
37+
38+
pub enum DatetimeError {
39+
Offset,
40+
Library,
41+
}
42+
43+
#[inline(never)]
44+
pub fn write_datetime(
45+
ptr: *mut pyo3::ffi::PyObject,
46+
opts: u8,
47+
dt: &mut SmallVec<[u8; 32]>,
48+
) -> Result<(), DatetimeError> {
49+
let has_tz = unsafe { (*(ptr as *mut pyo3::ffi::PyDateTime_DateTime)).hastzinfo == 1 };
50+
let offset_day: i32;
51+
let mut offset_second: i32;
52+
if !has_tz {
53+
offset_second = 0;
54+
offset_day = 0;
55+
} else {
56+
let tzinfo = ffi!(PyDateTime_DATE_GET_TZINFO(ptr));
57+
if unsafe { (*(ptr as *mut pyo3::ffi::PyDateTime_DateTime)).hastzinfo == 1 } {
58+
if ffi!(PyObject_HasAttr(tzinfo, CONVERT_METHOD_STR)) == 1 {
59+
// pendulum
60+
let offset = unsafe {
61+
pyo3::ffi::PyObject_CallMethodObjArgs(
62+
ptr,
63+
UTCOFFSET_METHOD_STR,
64+
std::ptr::null_mut() as *mut pyo3::ffi::PyObject,
65+
)
66+
};
67+
// test_datetime_partial_second_pendulum_not_supported
68+
if offset.is_null() {
69+
return Err(DatetimeError::Offset);
70+
}
71+
offset_second = ffi!(PyDateTime_DELTA_GET_SECONDS(offset)) as i32;
72+
offset_day = ffi!(PyDateTime_DELTA_GET_DAYS(offset));
73+
} else if unsafe { pyo3::ffi::PyObject_HasAttr(tzinfo, NORMALIZE_METHOD_STR) == 1 } {
74+
// pytz
75+
let offset = unsafe {
76+
pyo3::ffi::PyObject_CallMethodObjArgs(
77+
pyo3::ffi::PyObject_CallMethodObjArgs(
78+
tzinfo,
79+
NORMALIZE_METHOD_STR,
80+
ptr,
81+
std::ptr::null_mut() as *mut pyo3::ffi::PyObject,
82+
),
83+
UTCOFFSET_METHOD_STR,
84+
std::ptr::null_mut() as *mut pyo3::ffi::PyObject,
85+
)
86+
};
87+
offset_second = ffi!(PyDateTime_DELTA_GET_SECONDS(offset)) as i32;
88+
offset_day = ffi!(PyDateTime_DELTA_GET_DAYS(offset));
89+
} else if ffi!(PyObject_HasAttr(tzinfo, DST_STR)) == 1 {
90+
// dateutil/arrow, datetime.timezone.utc
91+
let offset = unsafe {
92+
pyo3::ffi::PyObject_CallMethodObjArgs(
93+
tzinfo,
94+
UTCOFFSET_METHOD_STR,
95+
ptr,
96+
std::ptr::null_mut() as *mut pyo3::ffi::PyObject,
97+
)
98+
};
99+
offset_second = ffi!(PyDateTime_DELTA_GET_SECONDS(offset)) as i32;
100+
offset_day = ffi!(PyDateTime_DELTA_GET_DAYS(offset));
101+
} else {
102+
return Err(DatetimeError::Library);
103+
}
104+
} else {
105+
offset_second = 0;
106+
offset_day = 0;
107+
}
108+
};
109+
110+
dt.extend(
111+
itoa::Buffer::new()
112+
.format(ffi!(PyDateTime_GET_YEAR(ptr)) as i32)
113+
.bytes(),
114+
);
115+
dt.push(HYPHEN);
116+
{
117+
let month = ffi!(PyDateTime_GET_MONTH(ptr)) as u8;
118+
write_double_digit!(dt, month);
119+
}
120+
dt.push(HYPHEN);
121+
{
122+
let day = ffi!(PyDateTime_GET_DAY(ptr)) as u8;
123+
write_double_digit!(dt, day);
124+
}
125+
dt.push(T);
126+
{
127+
let hour = ffi!(PyDateTime_DATE_GET_HOUR(ptr)) as u8;
128+
write_double_digit!(dt, hour);
129+
}
130+
dt.push(COLON);
131+
{
132+
let minute = ffi!(PyDateTime_DATE_GET_MINUTE(ptr)) as u8;
133+
write_double_digit!(dt, minute);
134+
}
135+
dt.push(COLON);
136+
{
137+
let second = ffi!(PyDateTime_DATE_GET_SECOND(ptr)) as u8;
138+
write_double_digit!(dt, second);
139+
}
140+
{
141+
let microsecond = ffi!(PyDateTime_DATE_GET_MICROSECOND(ptr)) as u32;
142+
write_microsecond!(dt, microsecond);
143+
}
144+
if has_tz || opts & NAIVE_UTC == NAIVE_UTC {
145+
if offset_second == 0 {
146+
dt.extend([PLUS, ZERO, ZERO, COLON, ZERO, ZERO].iter().cloned());
147+
} else {
148+
if offset_day == -1 {
149+
// datetime.timedelta(days=-1, seconds=68400) -> -05:00
150+
dt.push(HYPHEN);
151+
offset_second = 86400 - offset_second
152+
} else {
153+
// datetime.timedelta(seconds=37800) -> +10:30
154+
dt.push(PLUS);
155+
}
156+
{
157+
let offset_minute = offset_second / 60;
158+
let offset_hour = offset_minute / 60;
159+
if offset_hour < 10 {
160+
dt.push(ZERO);
161+
}
162+
dt.extend(itoa::Buffer::new().format(offset_hour).bytes());
163+
dt.push(COLON);
164+
165+
let mut offset_minute_print = offset_minute % 60;
166+
167+
{
168+
// https://tools.ietf.org/html/rfc3339#section-5.8
169+
// "exactly 19 minutes and 32.13 seconds ahead of UTC"
170+
// "closest representable UTC offset"
171+
// "+20:00"
172+
let offset_excess_second =
173+
offset_second - (offset_minute_print * 60 + offset_hour * 3600);
174+
if offset_excess_second >= 30 {
175+
offset_minute_print += 1;
176+
}
177+
}
178+
179+
if offset_minute_print < 10 {
180+
dt.push(ZERO);
181+
}
182+
dt.extend(itoa::Buffer::new().format(offset_minute_print).bytes());
183+
}
184+
}
185+
}
186+
Ok(())
187+
}
188+
189+
#[inline(never)]
190+
pub fn write_date(ptr: *mut pyo3::ffi::PyObject, dt: &mut SmallVec<[u8; 32]>) {
191+
{
192+
let year = ffi!(PyDateTime_GET_YEAR(ptr)) as i32;
193+
dt.extend(itoa::Buffer::new().format(year).bytes());
194+
}
195+
dt.push(HYPHEN);
196+
{
197+
let month = ffi!(PyDateTime_GET_MONTH(ptr)) as u32;
198+
write_double_digit!(dt, month);
199+
}
200+
dt.push(HYPHEN);
201+
{
202+
let day = ffi!(PyDateTime_GET_DAY(ptr)) as u32;
203+
write_double_digit!(dt, day);
204+
}
205+
}
206+
207+
#[inline(never)]
208+
pub fn write_time(ptr: *mut pyo3::ffi::PyObject, dt: &mut SmallVec<[u8; 32]>) {
209+
{
210+
let hour = ffi!(PyDateTime_TIME_GET_HOUR(ptr)) as u8;
211+
write_double_digit!(dt, hour);
212+
}
213+
dt.push(COLON);
214+
{
215+
let minute = ffi!(PyDateTime_TIME_GET_MINUTE(ptr)) as u8;
216+
write_double_digit!(dt, minute);
217+
}
218+
dt.push(COLON);
219+
{
220+
let second = ffi!(PyDateTime_TIME_GET_SECOND(ptr)) as u8;
221+
write_double_digit!(dt, second);
222+
}
223+
{
224+
let microsecond = ffi!(PyDateTime_TIME_GET_MICROSECOND(ptr)) as u32;
225+
write_microsecond!(dt, microsecond);
226+
}
227+
}

0 commit comments

Comments
 (0)