Skip to content

Commit a1a2919

Browse files
committed
Merge pull request #254 from datastax/PYTHON-99
PYTHON-99 - Time/TimeUUID Utility Functions
2 parents fae9abd + dd1b0ed commit a1a2919

File tree

3 files changed

+148
-23
lines changed

3 files changed

+148
-23
lines changed

cassandra/cqltypes.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,27 @@
3434
import datetime
3535
from decimal import Decimal
3636
import io
37+
import logging
3738
import re
3839
import socket
3940
import time
41+
import six
42+
from six.moves import range
4043
import sys
4144
from uuid import UUID
45+
import warnings
4246

43-
import six
44-
from six.moves import range
4547

4648
from cassandra.marshal import (int8_pack, int8_unpack,
4749
uint16_pack, uint16_unpack, uint32_pack, uint32_unpack,
4850
int32_pack, int32_unpack, int64_pack, int64_unpack,
4951
float_pack, float_unpack, double_pack, double_unpack,
5052
varint_pack, varint_unpack)
51-
from cassandra.util import OrderedMap, sortedset, Time
53+
from cassandra import util
5254

5355
apache_cassandra_type_prefix = 'org.apache.cassandra.db.marshal.'
5456

57+
log = logging.getLogger(__name__)
5558

5659
if six.PY3:
5760
_number_types = frozenset((int, float))
@@ -72,16 +75,17 @@ def trim_if_startswith(s, prefix):
7275

7376

7477
def unix_time_from_uuid1(u):
75-
return (u.time - 0x01B21DD213814000) / 10000000.0
76-
77-
78-
DATETIME_EPOC = datetime.datetime(1970, 1, 1)
78+
msg = "'cassandra.cqltypes.unix_time_from_uuid1' has moved to 'cassandra.util'. This entry point will be removed in the next major version."
79+
warnings.warn(msg, DeprecationWarning)
80+
log.warn(msg)
81+
return util.unix_time_from_uuid1(u)
7982

8083

8184
def datetime_from_timestamp(timestamp):
82-
# PYTHON-119: workaround for Windows
83-
dt = DATETIME_EPOC + datetime.timedelta(seconds=timestamp)
84-
return dt
85+
msg = "'cassandra.cqltypes.datetime_from_timestamp' has moved to 'cassandra.util'. This entry point will be removed in the next major version."
86+
warnings.warn(msg, DeprecationWarning)
87+
log.warn(msg)
88+
return util.datetime_from_timestamp(timestamp)
8589

8690

8791
_casstypes = {}
@@ -581,7 +585,7 @@ def my_timestamp(self):
581585
@staticmethod
582586
def deserialize(byts, protocol_version):
583587
timestamp = int64_unpack(byts) / 1000.0
584-
return datetime_from_timestamp(timestamp)
588+
return util.datetime_from_timestamp(timestamp)
585589

586590
@staticmethod
587591
def serialize(v, protocol_version):
@@ -652,7 +656,7 @@ def serialize(val, protocol_version):
652656
@staticmethod
653657
def deserialize(byts, protocol_version):
654658
timestamp = SimpleDateType.seconds_per_day * (uint32_unpack(byts) - 2 ** 31)
655-
dt = datetime_from_timestamp(timestamp)
659+
dt = util.datetime_from_timestamp(timestamp)
656660
return datetime.date(dt.year, dt.month, dt.day)
657661

658662

@@ -661,21 +665,21 @@ class TimeType(_CassandraType):
661665

662666
@classmethod
663667
def validate(cls, val):
664-
if not isinstance(val, Time):
665-
val = Time(val)
668+
if not isinstance(val, util.Time):
669+
val = util.Time(val)
666670
return val
667671

668672
@staticmethod
669673
def serialize(val, protocol_version):
670674
try:
671675
nano = val.nanosecond_time
672676
except AttributeError:
673-
nano = Time(val).nanosecond_time
677+
nano = util.Time(val).nanosecond_time
674678
return int64_pack(nano)
675679

676680
@staticmethod
677681
def deserialize(byts, protocol_version):
678-
return Time(int64_unpack(byts))
682+
return util.Time(int64_unpack(byts))
679683

680684

681685
class UTF8Type(_CassandraType):
@@ -771,7 +775,7 @@ class ListType(_SimpleParameterizedType):
771775
class SetType(_SimpleParameterizedType):
772776
typename = 'set'
773777
num_subtypes = 1
774-
adapter = sortedset
778+
adapter = util.sortedset
775779

776780

777781
class MapType(_ParameterizedType):
@@ -794,7 +798,7 @@ def deserialize_safe(cls, byts, protocol_version):
794798
length = 2
795799
numelements = unpack(byts[:length])
796800
p = length
797-
themap = OrderedMap()
801+
themap = util.OrderedMap()
798802
for _ in range(numelements):
799803
key_len = unpack(byts[p:p + length])
800804
p += length

cassandra/util.py

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,128 @@
11
from __future__ import with_statement
2+
import calendar
3+
import datetime
4+
import random
5+
import six
6+
import uuid
7+
8+
DATETIME_EPOC = datetime.datetime(1970, 1, 1)
9+
10+
11+
def datetime_from_timestamp(timestamp):
12+
"""
13+
Creates a timezone-agnostic datetime from timestamp (in seconds) in a consistent manner.
14+
Works around a Windows issue with large negative timestamps (PYTHON-119),
15+
and rounding differences in Python 3.4 (PYTHON-340).
16+
17+
:param timestamp: a unix timestamp, in seconds
18+
19+
:rtype: datetime
20+
"""
21+
dt = DATETIME_EPOC + datetime.timedelta(seconds=timestamp)
22+
return dt
23+
24+
25+
def unix_time_from_uuid1(uuid_arg):
26+
"""
27+
Converts a version 1 :class:`uuid.UUID` to a timestamp with the same precision
28+
as :meth:`time.time()` returns. This is useful for examining the
29+
results of queries returning a v1 :class:`~uuid.UUID`.
30+
31+
:param uuid_arg: a version 1 :class:`~uuid.UUID`
32+
33+
:rtype: timestamp
34+
35+
"""
36+
return (uuid_arg.time - 0x01B21DD213814000) / 1e7
37+
38+
39+
def datetime_from_uuid1(uuid_arg):
40+
"""
41+
Creates a timezone-agnostic datetime from the timestamp in the
42+
specified type-1 UUID.
43+
44+
:param uuid_arg: a version 1 :class:`~uuid.UUID`
45+
46+
:rtype: timestamp
47+
48+
"""
49+
return datetime_from_timestamp(unix_time_from_uuid1(uuid_arg))
50+
51+
52+
def min_uuid_from_time(timestamp):
53+
"""
54+
Generates the minimum TimeUUID (type 1) for a given timestamp, as compared by Cassandra.
55+
56+
See :func:`uuid_from_time` for argument and return types.
57+
"""
58+
return uuid_from_time(timestamp, 0x80, 0x808080808080) # Cassandra does byte-wise comparison; fill with min signed bytes (0x80 = -128)
59+
60+
61+
def max_uuid_from_time(timestamp):
62+
"""
63+
Generates the maximum TimeUUID (type 1) for a given timestamp, as compared by Cassandra.
64+
65+
See :func:`uuid_from_time` for argument and return types.
66+
"""
67+
return uuid_from_time(timestamp, 0x3f7f, 0x7f7f7f7f7f7f) # Max signed bytes (0x7f = 127)
68+
69+
70+
def uuid_from_time(time_arg, clock_seq=None, node=None):
71+
"""
72+
Converts a datetime or timestamp to a type 1 :class:`uuid.UUID`.
73+
74+
:param time_arg:
75+
The time to use for the timestamp portion of the UUID.
76+
This can either be a :class:`datetime` object or a timestamp
77+
in seconds (as returned from :meth:`time.time()`).
78+
:type datetime: :class:`datetime` or timestamp
79+
80+
:param clock_seq:
81+
Clock sequence field for the UUID (up to 14 bits). If not specified,
82+
a random sequence is generated.
83+
:type clock_seq: int
84+
85+
:param node:
86+
None integer for the UUID (up to 48 bits). If not specified, this
87+
field is randomized.
88+
:type node: long
89+
90+
:rtype: :class:`uuid.UUID`
91+
92+
"""
93+
if hasattr(time_arg, 'utctimetuple'):
94+
seconds = int(calendar.timegm(time_arg.utctimetuple()))
95+
microseconds = (seconds * 1e6) + time_arg.time().microsecond
96+
else:
97+
microseconds = int(time_arg * 1e6)
98+
99+
# 0x01b21dd213814000 is the number of 100-ns intervals between the
100+
# UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
101+
intervals = int(microseconds * 10) + 0x01b21dd213814000
102+
103+
time_low = intervals & 0xffffffff
104+
time_mid = (intervals >> 32) & 0xffff
105+
time_hi_version = (intervals >> 48) & 0x0fff
106+
107+
if clock_seq is None:
108+
clock_seq = random.getrandbits(14)
109+
110+
clock_seq_low = clock_seq & 0xff
111+
clock_seq_hi_variant = 0x80 | ((clock_seq >> 8) & 0x3f)
112+
113+
if node is None:
114+
node = random.getrandbits(48)
115+
node &= 0xffffffffff
116+
117+
return uuid.UUID(fields=(time_low, time_mid, time_hi_version,
118+
clock_seq_hi_variant, clock_seq_low, node), version=1)
119+
120+
LOWEST_TIME_UUID = uuid.UUID('00000000-0000-1000-8080-808080808080')
121+
""" The lowest possible TimeUUID, as sorted by Cassandra. """
122+
123+
HIGHEST_TIME_UUID = uuid.UUID('ffffffff-ffff-1fff-bf7f-7f7f7f7f7f7f')
124+
""" The highest possible TimeUUID, as sorted by Cassandra. """
125+
2126

3127
try:
4128
from collections import OrderedDict
@@ -557,7 +681,6 @@ def _intersect(self, other):
557681
return isect
558682

559683
from collections import Mapping
560-
import six
561684
from six.moves import cPickle
562685

563686

docs/api/cassandra/util.rst

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
``cassandra.util`` - Utilities
22
===================================
33

4-
.. module:: cassandra.util
5-
6-
.. autoclass:: OrderedMap
7-
:members:
4+
.. automodule:: cassandra.util
5+
:members:

0 commit comments

Comments
 (0)