diff --git a/cassandra/cqlengine/columns.py b/cassandra/cqlengine/columns.py index 4adb88476..08961ccd5 100644 --- a/cassandra/cqlengine/columns.py +++ b/cassandra/cqlengine/columns.py @@ -13,7 +13,7 @@ # limitations under the License. from copy import deepcopy, copy -from datetime import date, datetime, timedelta +from datetime import date, datetime, timedelta, timezone import logging from uuid import UUID as _UUID @@ -21,7 +21,7 @@ from cassandra.cqltypes import SimpleDateType, _cqltypes, UserType from cassandra.cqlengine import ValidationError from cassandra.cqlengine.functions import get_total_seconds -from cassandra.util import Duration as _Duration +from cassandra.util import Duration as _Duration, utcfromtimestamp log = logging.getLogger(__name__) @@ -551,7 +551,7 @@ def to_python(self, value): elif isinstance(value, date): return datetime(*(value.timetuple()[:6])) - return datetime.utcfromtimestamp(value) + return utcfromtimestamp(value) def to_database(self, value): value = super(DateTime, self).to_database(value) diff --git a/cassandra/query.py b/cassandra/query.py index f3922849a..9968cceb2 100644 --- a/cassandra/query.py +++ b/cassandra/query.py @@ -26,7 +26,7 @@ import warnings from cassandra import ConsistencyLevel, OperationTimedOut -from cassandra.util import unix_time_from_uuid1, maybe_add_timeout_to_query +from cassandra.util import unix_time_from_uuid1, maybe_add_timeout_to_query, utcfromtimestamp from cassandra.encoder import Encoder import cassandra.encoder from cassandra.policies import ColDesc @@ -1100,7 +1100,7 @@ class TraceEvent(object): def __init__(self, description, timeuuid, source, source_elapsed, thread_name): self.description = description - self.datetime = datetime.fromtimestamp(unix_time_from_uuid1(timeuuid), tz=timezone.utc) + self.datetime = utcfromtimestamp(unix_time_from_uuid1(timeuuid)) self.source = source if source_elapsed is not None: self.source_elapsed = timedelta(microseconds=source_elapsed) diff --git a/cassandra/util.py b/cassandra/util.py index c6e2f0eda..4a0ecd5bc 100644 --- a/cassandra/util.py +++ b/cassandra/util.py @@ -40,8 +40,17 @@ from cassandra import DriverException + +def utcfromtimestamp(timestamp): + """ + It replicates behavior of datetime.utcfromtimestamp + """ + dt = datetime.datetime.fromtimestamp(timestamp, datetime.timezone.utc) + return datetime.datetime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, fold=dt.fold) + + DATETIME_EPOC = datetime.datetime(1970, 1, 1) -UTC_DATETIME_EPOC = datetime.datetime.utcfromtimestamp(0) +UTC_DATETIME_EPOC = utcfromtimestamp(0) _nan = float('nan') diff --git a/tests/integration/cqlengine/columns/test_validation.py b/tests/integration/cqlengine/columns/test_validation.py index 21fe1581f..33acb0df1 100644 --- a/tests/integration/cqlengine/columns/test_validation.py +++ b/tests/integration/cqlengine/columns/test_validation.py @@ -15,7 +15,7 @@ import unittest import sys -from datetime import datetime, timedelta, date, tzinfo, time +from datetime import datetime, timedelta, date, tzinfo, time, timezone from decimal import Decimal as D from uuid import uuid4, uuid1 from packaging.version import Version @@ -30,6 +30,7 @@ from cassandra.cqlengine.models import Model, ValidationError from cassandra.cqlengine.usertype import UserType from cassandra import util +from cassandra.util import utcfromtimestamp from tests.integration import PROTOCOL_VERSION, CASSANDRA_VERSION, greaterthanorequalcass30, greaterthanorequalcass3_11 from tests.integration.cqlengine.base import BaseCassEngTestCase @@ -97,7 +98,7 @@ def test_datetime_timestamp(self): dt_value = 1454520554 self.DatetimeTest.objects.create(test_id=5, created_at=dt_value) dt2 = self.DatetimeTest.objects(test_id=5).first() - self.assertEqual(dt2.created_at, datetime.utcfromtimestamp(dt_value)) + self.assertEqual(dt2.created_at, utcfromtimestamp(dt_value)) def test_datetime_large(self): dt_value = datetime(2038, 12, 31, 10, 10, 10, 123000) @@ -809,7 +810,7 @@ def test_conversion_specific_date(self): assert isinstance(uuid, UUID) ts = (uuid.time - 0x01b21dd213814000) / 1e7 # back to a timestamp - new_dt = datetime.utcfromtimestamp(ts) + new_dt = utcfromtimestamp(ts) # checks that we created a UUID1 with the proper timestamp assert new_dt == dt diff --git a/tests/integration/cqlengine/model/test_model_io.py b/tests/integration/cqlengine/model/test_model_io.py index 7195d685f..ad0a86df5 100644 --- a/tests/integration/cqlengine/model/test_model_io.py +++ b/tests/integration/cqlengine/model/test_model_io.py @@ -15,7 +15,7 @@ from uuid import uuid4, UUID import random -from datetime import datetime, date, time +from datetime import datetime, date, time, timezone from decimal import Decimal from operator import itemgetter @@ -26,7 +26,7 @@ from cassandra.cqlengine.management import drop_table from cassandra.cqlengine.models import Model from cassandra.query import SimpleStatement -from cassandra.util import Date, Time, Duration +from cassandra.util import Date, Time, Duration, utcfromtimestamp from cassandra.cqlengine.statements import SelectStatement, DeleteStatement, WhereClause from cassandra.cqlengine.operators import EqualsOperator @@ -200,13 +200,13 @@ class AllDatatypesModel(Model): sync_table(AllDatatypesModel) - input = ['ascii', 2 ** 63 - 1, bytearray(b'hello world'), True, datetime.utcfromtimestamp(872835240), + input = ['ascii', 2 ** 63 - 1, bytearray(b'hello world'), True, utcfromtimestamp(872835240), Decimal('12.3E+7'), 2.39, 3.4028234663852886e+38, '123.123.123.123', 2147483647, 'text', UUID('FE2B4360-28C6-11E2-81C1-0800200C9A66'), UUID('067e6162-3b6f-4ae2-a171-2470b63dff00'), int(str(2147483647) + '000')] AllDatatypesModel.create(id=0, a='ascii', b=2 ** 63 - 1, c=bytearray(b'hello world'), d=True, - e=datetime.utcfromtimestamp(872835240), f=Decimal('12.3E+7'), g=2.39, + e=utcfromtimestamp(872835240), f=Decimal('12.3E+7'), g=2.39, h=3.4028234663852886e+38, i='123.123.123.123', j=2147483647, k='text', l=UUID('FE2B4360-28C6-11E2-81C1-0800200C9A66'), m=UUID('067e6162-3b6f-4ae2-a171-2470b63dff00'), n=int(str(2147483647) + '000'), diff --git a/tests/integration/cqlengine/model/test_udts.py b/tests/integration/cqlengine/model/test_udts.py index 1e3adf9a7..721cf5ea7 100644 --- a/tests/integration/cqlengine/model/test_udts.py +++ b/tests/integration/cqlengine/model/test_udts.py @@ -13,7 +13,7 @@ # limitations under the License. import unittest -from datetime import datetime, date, time +from datetime import datetime, date, time, timezone from decimal import Decimal from mock import Mock from uuid import UUID, uuid4 @@ -23,7 +23,7 @@ from cassandra.cqlengine import columns, connection from cassandra.cqlengine.management import sync_table, drop_table, sync_type, create_keyspace_simple, drop_keyspace from cassandra.cqlengine import ValidationError -from cassandra.util import Date, Time +from cassandra.util import Date, Time, utcfromtimestamp from tests.integration import PROTOCOL_VERSION from tests.integration.cqlengine.base import BaseCassEngTestCase @@ -272,7 +272,7 @@ def test_can_insert_udts_with_all_datatypes(self): self.addCleanup(drop_table, AllDatatypesModel) input = AllDatatypes(a='ascii', b=2 ** 63 - 1, c=bytearray(b'hello world'), d=True, - e=datetime.utcfromtimestamp(872835240), f=Decimal('12.3E+7'), g=2.39, + e=utcfromtimestamp(872835240), f=Decimal('12.3E+7'), g=2.39, h=3.4028234663852886e+38, i='123.123.123.123', j=2147483647, k='text', l=UUID('FE2B4360-28C6-11E2-81C1-0800200C9A66'), m=UUID('067e6162-3b6f-4ae2-a171-2470b63dff00'), n=int(str(2147483647) + '000')) diff --git a/tests/unit/test_types.py b/tests/unit/test_types.py index a06bbd452..b9d8c5c8f 100644 --- a/tests/unit/test_types.py +++ b/tests/unit/test_types.py @@ -17,6 +17,9 @@ import tempfile import time from binascii import unhexlify +from datetime import tzinfo + +from dateutil.tz import tzlocal import cassandra from cassandra import util @@ -41,7 +44,7 @@ from cassandra.util import ( OPEN_BOUND, Date, DateRange, DateRangeBound, DateRangePrecision, Time, ms_timestamp_from_datetime, - datetime_from_timestamp + datetime_from_timestamp, utcfromtimestamp ) from tests.unit.util import check_sequence_consistency @@ -200,7 +203,7 @@ def test_empty_value(self): def test_datetype(self): now_time_seconds = time.time() - now_datetime = datetime.datetime.utcfromtimestamp(now_time_seconds) + now_datetime = utcfromtimestamp(now_time_seconds) # Cassandra timestamps in millis now_timestamp = now_time_seconds * 1e3 @@ -211,7 +214,7 @@ def test_datetype(self): # deserialize # epoc expected = 0 - self.assertEqual(DateType.deserialize(int64_pack(1000 * expected), 0), datetime.datetime.utcfromtimestamp(expected)) + self.assertEqual(DateType.deserialize(int64_pack(1000 * expected), 0), utcfromtimestamp(expected)) # beyond 32b expected = 2 ** 33 @@ -738,7 +741,7 @@ def get_upper_bound(seconds): The way to do this is to add one month and leave the date at YEAR-MONTH-01 00:00:00 000000. Then substract one millisecond. """ - dt = datetime.datetime.fromtimestamp(seconds / 1000.0, tz=utc_timezone) + dt = utcfromtimestamp(seconds / 1000.0) dt = dt + datetime.timedelta(days=32) dt = dt.replace(day=1) - datetime.timedelta(microseconds=1) return int((dt - self.epoch).total_seconds() * 1000) @@ -765,7 +768,7 @@ def get_upper_bound(seconds): The way to do this is to add one year and leave the date at YEAR-01-01 00:00:00 000000. Then substract one millisecond. """ - dt = datetime.datetime.fromtimestamp(seconds / 1000.0, tz=utc_timezone) + dt = utcfromtimestamp(seconds / 1000.0) dt = dt + datetime.timedelta(days=370) dt = dt.replace(day=1) - datetime.timedelta(microseconds=1) @@ -812,7 +815,7 @@ def truncate_date(number): For example if truncate_kwargs = {"hour": 0, "minute": 0, "second": 0, "microsecond": 0} the returned value will be the original given date but with the hours, minutes, seconds and microseconds set to 0 """ - dt = datetime.datetime.fromtimestamp(number / 1000.0, tz=utc_timezone) + dt = utcfromtimestamp(number / 1000.0) dt = dt.replace(**truncate_kwargs) return round((dt - self.epoch).total_seconds() * 1000.0)