Skip to content

Commit 5118514

Browse files
authored
fix the astimezone the replace methods (#189)
1 parent 46a738d commit 5118514

File tree

3 files changed

+107
-19
lines changed

3 files changed

+107
-19
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## [Unreleased]
44

5+
- Fix the `astimezone()` and `replace()` methods of datetime objects. ([#188](https://github.com/sdispater/tomlkit/issues/178))
6+
57
## [0.10.1] - 2022-03-27
68

79
### Fixed

tests/test_items.py

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@
88

99
import pytest
1010

11-
from tomlkit import inline_table
11+
from tomlkit import api
1212
from tomlkit import parse
13-
from tomlkit.api import array
14-
from tomlkit.api import ws
1513
from tomlkit.exceptions import NonExistentKey
1614
from tomlkit.items import Bool
1715
from tomlkit.items import Comment
@@ -27,6 +25,50 @@
2725
from tomlkit.parser import Parser
2826

2927

28+
@pytest.fixture()
29+
def tz_pst():
30+
try:
31+
from datetime import timezone
32+
33+
return timezone(timedelta(hours=-8), "PST")
34+
except ImportError:
35+
from datetime import tzinfo
36+
37+
class PST(tzinfo):
38+
def utcoffset(self, dt):
39+
return timedelta(hours=-8)
40+
41+
def tzname(self, dt):
42+
return "PST"
43+
44+
def dst(self, dt):
45+
return timedelta(0)
46+
47+
return PST()
48+
49+
50+
@pytest.fixture()
51+
def tz_utc():
52+
try:
53+
from datetime import timezone
54+
55+
return timezone.utc
56+
except ImportError:
57+
from datetime import tzinfo
58+
59+
class UTC(tzinfo):
60+
def utcoffset(self, dt):
61+
return timedelta(hours=0)
62+
63+
def tzname(self, dt):
64+
return "UTC"
65+
66+
def dst(self, dt):
67+
return timedelta(0)
68+
69+
return UTC()
70+
71+
3072
def test_key_comparison():
3173
k = Key("foo")
3274

@@ -320,10 +362,10 @@ def test_dicts_are_converted_to_tables():
320362

321363

322364
def test_array_add_line():
323-
t = array()
365+
t = api.array()
324366
t.add_line(1, 2, 3, comment="Line 1")
325367
t.add_line(4, 5, 6, comment="Line 2")
326-
t.add_line(7, ws(","), ws(" "), 8, add_comma=False)
368+
t.add_line(7, api.ws(","), api.ws(" "), 8, add_comma=False)
327369
t.add_line(indent="")
328370
assert len(t) == 8
329371
assert list(t) == [1, 2, 3, 4, 5, 6, 7, 8]
@@ -338,9 +380,9 @@ def test_array_add_line():
338380

339381

340382
def test_array_add_line_invalid_value():
341-
t = array()
383+
t = api.array()
342384
with pytest.raises(ValueError, match="is not allowed"):
343-
t.add_line(1, ws(" "))
385+
t.add_line(1, api.ws(" "))
344386
with pytest.raises(ValueError, match="is not allowed"):
345387
t.add_line(Comment(Trivia(" ", comment="test")))
346388
assert len(t) == 0
@@ -465,7 +507,7 @@ def test_floats_behave_like_floats():
465507
assert doc.as_string() == "float = +35.12"
466508

467509

468-
def test_datetimes_behave_like_datetimes():
510+
def test_datetimes_behave_like_datetimes(tz_utc, tz_pst):
469511
i = item(datetime(2018, 7, 22, 12, 34, 56))
470512

471513
assert i == datetime(2018, 7, 22, 12, 34, 56)
@@ -479,6 +521,14 @@ def test_datetimes_behave_like_datetimes():
479521
assert i == datetime(2018, 7, 21, 12, 34, 56)
480522
assert i.as_string() == "2018-07-21T12:34:56"
481523

524+
i = i.replace(year=2019, tzinfo=tz_utc)
525+
assert i == datetime(2019, 7, 21, 12, 34, 56, tzinfo=tz_utc)
526+
assert i.as_string() == "2019-07-21T12:34:56+00:00"
527+
528+
i = i.astimezone(tz_pst)
529+
assert i == datetime(2019, 7, 21, 4, 34, 56, tzinfo=tz_pst)
530+
assert i.as_string() == "2019-07-21T04:34:56-08:00"
531+
482532
doc = parse("dt = 2018-07-22T12:34:56-05:00")
483533
doc["dt"] += timedelta(days=1)
484534

@@ -499,6 +549,10 @@ def test_dates_behave_like_dates():
499549
assert i == date(2018, 7, 21)
500550
assert i.as_string() == "2018-07-21"
501551

552+
i = i.replace(year=2019)
553+
assert i == datetime(2019, 7, 21)
554+
assert i.as_string() == "2019-07-21"
555+
502556
doc = parse("dt = 2018-07-22 # Comment")
503557
doc["dt"] += timedelta(days=1)
504558

@@ -511,6 +565,10 @@ def test_times_behave_like_times():
511565
assert i == time(12, 34, 56)
512566
assert i.as_string() == "12:34:56"
513567

568+
i = i.replace(hour=13)
569+
assert i == time(13, 34, 56)
570+
assert i.as_string() == "13:34:56"
571+
514572

515573
def test_strings_behave_like_strs():
516574
i = item("foo")
@@ -611,7 +669,7 @@ def test_items_are_pickable():
611669
s = pickle.dumps(n)
612670
assert pickle.loads(s).as_string() == 'foo = "bar"\n'
613671

614-
n = inline_table()
672+
n = api.inline_table()
615673
n["foo"] = "bar"
616674

617675
s = pickle.dumps(n)
@@ -629,7 +687,7 @@ def test_items_are_pickable():
629687

630688

631689
def test_trim_comments_when_building_inline_table():
632-
table = inline_table()
690+
table = api.inline_table()
633691
row = parse('foo = "bar" # Comment')
634692
table.update(row)
635693
assert table.as_string() == '{foo = "bar"}'
@@ -641,7 +699,7 @@ def test_trim_comments_when_building_inline_table():
641699

642700

643701
def test_deleting_inline_table_elemeent_does_not_leave_trailing_separator():
644-
table = inline_table()
702+
table = api.inline_table()
645703
table["foo"] = "bar"
646704
table["baz"] = "boom"
647705

@@ -651,7 +709,7 @@ def test_deleting_inline_table_elemeent_does_not_leave_trailing_separator():
651709

652710
assert '{foo = "bar"}' == table.as_string()
653711

654-
table = inline_table()
712+
table = api.inline_table()
655713
table["foo"] = "bar"
656714

657715
del table["foo"]

tomlkit/items.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -721,8 +721,7 @@ def __new__(
721721
second: int,
722722
microsecond: int,
723723
tzinfo: Optional[tzinfo],
724-
trivia: Trivia,
725-
raw: str,
724+
*_: Any,
726725
**kwargs: Any,
727726
) -> datetime:
728727
return datetime.__new__(
@@ -748,12 +747,13 @@ def __init__(
748747
second: int,
749748
microsecond: int,
750749
tzinfo: Optional[tzinfo],
751-
trivia: Trivia,
752-
raw: str,
750+
trivia: Optional[Trivia] = None,
751+
raw: Optional[str] = None,
752+
**kwargs: Any,
753753
) -> None:
754-
super().__init__(trivia)
754+
super().__init__(trivia or Trivia())
755755

756-
self._raw = raw
756+
self._raw = raw or self.isoformat()
757757

758758
@property
759759
def discriminant(self) -> int:
@@ -803,7 +803,16 @@ def __sub__(self, other):
803803

804804
return result
805805

806-
def _new(self, result):
806+
def replace(self, *args: Any, **kwargs: Any) -> datetime:
807+
return self._new(super().replace(*args, **kwargs))
808+
809+
def astimezone(self, tz: tzinfo) -> datetime:
810+
result = super().astimezone(tz)
811+
if PY38:
812+
return result
813+
return self._new(result)
814+
815+
def _new(self, result) -> "DateTime":
807816
raw = result.isoformat()
808817

809818
return DateTime(
@@ -879,6 +888,9 @@ def __sub__(self, other):
879888

880889
return result
881890

891+
def replace(self, *args: Any, **kwargs: Any) -> date:
892+
return self._new(super().replace(*args, **kwargs))
893+
882894
def _new(self, result):
883895
raw = result.isoformat()
884896

@@ -929,6 +941,22 @@ def value(self) -> time:
929941
def as_string(self) -> str:
930942
return self._raw
931943

944+
def replace(self, *args: Any, **kwargs: Any) -> time:
945+
return self._new(super().replace(*args, **kwargs))
946+
947+
def _new(self, result):
948+
raw = result.isoformat()
949+
950+
return Time(
951+
result.hour,
952+
result.minute,
953+
result.second,
954+
result.microsecond,
955+
result.tzinfo,
956+
self._trivia,
957+
raw,
958+
)
959+
932960
def _getstate(self, protocol: int = 3) -> tuple:
933961
return (
934962
self.hour,

0 commit comments

Comments
 (0)