Commit 1b851a73 authored by CharlieC's avatar CharlieC
Browse files

Merge branch 'topic/3.0/tzinfo' into 'branch/3.0'

raise exception for aware datetime/time

See merge request !403

--HG--
branch : 3.0
Pipeline #19588 passed with stage
in 2 minutes and 36 seconds
......@@ -15,9 +15,14 @@ deliberately refuses to recognize and display such dates. Consequently,
it is not advised to use `openpyxl` for such purposes either, especially
when exchanging files with others.
The date and time representations in Excel do not support timezones.
Timezone information attached to Python datetimes is therefore lost when
datetimes are stored in XLSX files.
Timezones
---------
The date and time representations in Excel do not support timezones,
therefore `openpyxl` can only deal with naive datetime/time objects.
Any timezone information attached to Python datetimes must be stripped
off by the user before datetimes can be stored in XLSX files.
Using the ISO 8601 format
......
......@@ -3,7 +3,7 @@
from openpyxl.compat import safe_string
from openpyxl.xml.functions import Element, SubElement, whitespace, XML_NS, REL_NS
from openpyxl import LXML
from openpyxl.utils.datetime import to_excel, days_to_time, to_ISO8601
from openpyxl.utils.datetime import to_excel, to_ISO8601
from datetime import timedelta
......@@ -24,6 +24,10 @@ def _set_attributes(cell, styled=None):
value = cell._value
if cell.data_type == "d":
if hasattr(value, "tzinfo") and value.tzinfo is not None:
raise TypeError("Excel does not support timezones in datetimes. "
"The tzinfo in the datetime/time object must be set to None.")
if cell.parent.parent.iso_dates and not isinstance(value, timedelta):
value = to_ISO8601(value)
else:
......
......@@ -98,6 +98,28 @@ def test_write_date(worksheet, write_cell_implementation, value, expected, iso_d
assert diff is None, diff
@pytest.mark.parametrize("value, iso_dates",
[
(datetime.datetime(2021, 3, 19, 23, tzinfo=datetime.timezone.utc), True),
(datetime.datetime(2021, 3, 19, 23, tzinfo=datetime.timezone.utc), False),
(datetime.time(23, 58, tzinfo=datetime.timezone.utc), True),
(datetime.time(23, 58, tzinfo=datetime.timezone.utc), False),
]
)
def test_write_invalid_date(worksheet, write_cell_implementation, value, iso_dates):
write_cell = write_cell_implementation
ws = worksheet
cell = ws['A1']
cell.value = value
cell.parent.parent.iso_dates = iso_dates
out = BytesIO()
with pytest.raises(TypeError):
with xmlfile(out) as xf:
write_cell(xf, ws, cell, cell.has_style)
@pytest.mark.parametrize("value, expected, epoch",
[
(datetime.date(2011, 12, 25), """<c r="A1" t="n" s="1"><v>40902</v></c>""",
......
from __future__ import division
# Copyright (c) 2010-2021 openpyxl
"""Manage Excel date weirdness."""
# Python stdlib imports
import datetime
from datetime import timedelta, timezone
from math import isnan
import re
......@@ -19,7 +17,6 @@ CALENDAR_WINDOWS_1900 = WINDOWS_EPOCH
CALENDAR_MAC_1904 = MAC_EPOCH
SECS_PER_DAY = 86400
EPOCH = datetime.datetime.utcfromtimestamp(0)
ISO_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
ISO_REGEX = re.compile(r'''
(?P<date>(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2}))?T?
......@@ -122,13 +119,8 @@ def from_excel(value, epoch=WINDOWS_EPOCH, timedelta=False):
return epoch + datetime.timedelta(days=day) + diff
UTC = timezone(timedelta(0))
def time_to_days(value):
"""Convert a time value to fractions of day"""
if value.tzinfo is not None:
value = value.astimezone(UTC)
return (
(value.hour * 3600)
+ (value.minute * 60)
......
......@@ -6,7 +6,6 @@ from datetime import (
date,
timedelta,
time,
tzinfo
)
import pytest
......@@ -176,13 +175,17 @@ def test_from_excel_mac(value, expected):
assert FUT(value, CALENDAR_MAC_1904) == expected
def test_time_to_days():
@pytest.mark.parametrize("value, expected",
[
(time(13, 55, 12, 36), 0.5800000004166667),
(time(3, 0, 0), 0.125),
(datetime(2021, 3, 19, 13, 55, 12, 36), 0.5800000004166667),
(datetime(1536, 12, 24, 3, 0, 0), 0.125),
])
def test_time_to_days(value, expected):
from ..datetime import time_to_days
FUT = time_to_days
t1 = time(13, 55, 12, 36)
assert FUT(t1) == 0.5800000004166667
t2 = time(3, 0, 0)
assert FUT(t2) == 0.125
assert FUT(value) == expected
def test_timedelta_to_days():
......@@ -197,38 +200,3 @@ def test_days_to_time():
td = timedelta(0, 51320, 1600)
FUT = days_to_time
assert FUT(td) == time(14, 15, 20, 1600)
class CET(tzinfo):
def utcoffset(self, dt):
return timedelta(hours=1) + self.dst(dt)
def dst(self, dt):
# DST starts last Sunday in March
d = datetime(dt.year, 4, 1) # ends last Sunday in October
self.dston = d - timedelta(days=d.weekday() + 1)
d = datetime(dt.year, 11, 1)
self.dstoff = d - timedelta(days=d.weekday() + 1)
if self.dston <= dt.replace(tzinfo=None) < self.dstoff:
return timedelta(hours=1)
else:
return timedelta(0)
def tzname(self,dt):
return "CET +1"
def test_localised_time():
from ..datetime import time_to_days, UTC
dt1 = datetime(2015, 7, 24, tzinfo=UTC)
dt2 = datetime(2015, 7, 24, 2, tzinfo=CET())
assert dt2 - dt2 == timedelta(0)
e1 = time_to_days(dt1)
e2 = time_to_days(dt2)
assert e1 == 0
assert e2 == 0
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment