Skip to content

Commit 25849ef

Browse files
committed
OPT_UTC_Z
1 parent dee7b51 commit 25849ef

7 files changed

Lines changed: 77 additions & 3 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ is raised.
122122
on `datetime.datetime` and `datetime.time` instances.
123123
- `orjson.OPT_STRICT_INTEGER` for enforcing a 53-bit limit on integers. The
124124
limit is otherwise 64 bits, the same as the Python standard library.
125+
- `orjson.OPT_UTC_Z` to serialize a UTC timezone on `datetime.datetime`
126+
instances as `Z` instead of `+00:00`.
125127

126128
To specify multiple options, mask them together, e.g.,
127129
`option=orjson.OPT_STRICT_INTEGER | orjson.OPT_NAIVE_UTC`.

orjson.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ class JSONEncodeError(TypeError): ...
1515
OPT_NAIVE_UTC: int
1616
OPT_OMIT_MICROSECONDS: int
1717
OPT_STRICT_INTEGER: int
18+
OPT_UTC_Z: int

src/datetime.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ use smallvec::SmallVec;
33

44
pub const NAIVE_UTC: u8 = 1 << 1;
55
pub const OMIT_MICROSECONDS: u8 = 1 << 2;
6+
pub const UTC_Z: u8 = 1 << 3;
67

78
const HYPHEN: u8 = 45; // "-"
89
const PLUS: u8 = 43; // "+"
910
const ZERO: u8 = 48; // "0"
1011
const T: u8 = 84; // "T"
1112
const COLON: u8 = 58; // ":"
1213
const PERIOD: u8 = 46; // ":"
14+
const Z: u8 = 90; // "Z"
1315

1416
macro_rules! write_double_digit {
1517
($dt:ident, $value:ident) => {
@@ -144,7 +146,11 @@ pub fn write_datetime(
144146
}
145147
if has_tz || opts & NAIVE_UTC == NAIVE_UTC {
146148
if offset_second == 0 {
147-
dt.extend([PLUS, ZERO, ZERO, COLON, ZERO, ZERO].iter().cloned());
149+
if opts & UTC_Z == UTC_Z {
150+
dt.push(Z);
151+
} else {
152+
dt.extend([PLUS, ZERO, ZERO, COLON, ZERO, ZERO].iter().cloned());
153+
}
148154
} else {
149155
if offset_day == -1 {
150156
// datetime.timedelta(days=-1, seconds=68400) -> -05:00

src/encode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const STRICT_INT_MAX: i64 = 9007199254740991;
1717

1818
pub const STRICT_INTEGER: u8 = 1;
1919

20-
pub const MAX_OPT: i8 = (STRICT_INTEGER | NAIVE_UTC | OMIT_MICROSECONDS) as i8;
20+
pub const MAX_OPT: i8 = (STRICT_INTEGER | NAIVE_UTC | OMIT_MICROSECONDS | UTC_Z) as i8;
2121

2222
macro_rules! obj_name {
2323
($obj:ident) => {

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ fn orjson(py: Python, m: &PyModule) -> PyResult<()> {
4848
m.add("OPT_NAIVE_UTC", datetime::NAIVE_UTC)?;
4949
m.add("OPT_OMIT_MICROSECONDS", datetime::OMIT_MICROSECONDS)?;
5050
m.add("OPT_STRICT_INTEGER", encode::STRICT_INTEGER)?;
51+
m.add("OPT_UTC_Z", datetime::UTC_Z)?;
5152

5253
Ok(())
5354
}

test/test_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def test_option_range_high(self):
8888
dumps() option out of range high
8989
"""
9090
with self.assertRaises(orjson.JSONEncodeError):
91-
orjson.dumps(True, option=1 << 3)
91+
orjson.dumps(True, option=1 << 4)
9292

9393
def test_opts_multiple(self):
9494
"""

test/test_datetime.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,70 @@ def test_time_omit_microseconds(self):
435435
b'["02:03:04"]',
436436
)
437437

438+
def test_datetime_utc_z_naive_omit(self):
439+
"""
440+
datetime.datetime naive OPT_UTC_Z
441+
"""
442+
self.assertEqual(
443+
orjson.dumps(
444+
[datetime.datetime(2000, 1, 1, 2, 3, 4, 123)],
445+
option=orjson.OPT_NAIVE_UTC
446+
| orjson.OPT_UTC_Z
447+
| orjson.OPT_OMIT_MICROSECONDS,
448+
),
449+
b'["2000-01-01T02:03:04Z"]',
450+
)
451+
452+
def test_datetime_utc_z_naive(self):
453+
"""
454+
datetime.datetime naive OPT_UTC_Z
455+
"""
456+
self.assertEqual(
457+
orjson.dumps(
458+
[datetime.datetime(2000, 1, 1, 2, 3, 4, 123)],
459+
option=orjson.OPT_NAIVE_UTC | orjson.OPT_UTC_Z,
460+
),
461+
b'["2000-01-01T02:03:04.000123Z"]',
462+
)
463+
464+
def test_datetime_utc_z_without_tz(self):
465+
"""
466+
datetime.datetime naive OPT_UTC_Z
467+
"""
468+
self.assertEqual(
469+
orjson.dumps(
470+
[datetime.datetime(2000, 1, 1, 2, 3, 4, 123)], option=orjson.OPT_UTC_Z
471+
),
472+
b'["2000-01-01T02:03:04.000123"]',
473+
)
474+
475+
def test_datetime_utc_z_with_tz(self):
476+
"""
477+
datetime.datetime naive OPT_UTC_Z
478+
"""
479+
self.assertEqual(
480+
orjson.dumps(
481+
[
482+
datetime.datetime(
483+
2000, 1, 1, 0, 0, 0, 1, tzinfo=datetime.timezone.utc
484+
)
485+
],
486+
option=orjson.OPT_UTC_Z,
487+
),
488+
b'["2000-01-01T00:00:00.000001Z"]',
489+
)
490+
self.assertEqual(
491+
orjson.dumps(
492+
[
493+
datetime.datetime(
494+
1937, 1, 1, 12, 0, 27, 87, tzinfo=tz.gettz("Europe/Amsterdam")
495+
)
496+
],
497+
option=orjson.OPT_UTC_Z,
498+
),
499+
b'["1937-01-01T12:00:27.000087+00:20"]',
500+
)
501+
438502
@pytest.mark.skipif(pendulum is None, reason="pendulum install broken on win")
439503
def test_datetime_roundtrip(self):
440504
"""

0 commit comments

Comments
 (0)