Improved handling of Time type.
still needs test, doc update
This commit is contained in:
@@ -48,15 +48,13 @@ from cassandra.marshal import (int8_pack, int8_unpack,
|
||||
int32_pack, int32_unpack, int64_pack, int64_unpack,
|
||||
float_pack, float_unpack, double_pack, double_unpack,
|
||||
varint_pack, varint_unpack)
|
||||
from cassandra.util import OrderedMap, sortedset
|
||||
from cassandra.util import OrderedMap, sortedset, Time
|
||||
|
||||
apache_cassandra_type_prefix = 'org.apache.cassandra.db.marshal.'
|
||||
|
||||
|
||||
if six.PY3:
|
||||
_number_types = frozenset((int, float))
|
||||
_time_types = frozenset((int,))
|
||||
_date_types = frozenset((int,))
|
||||
long = int
|
||||
|
||||
def _name_from_hex_string(encoded_name):
|
||||
@@ -64,8 +62,6 @@ if six.PY3:
|
||||
return bin_str.decode('ascii')
|
||||
else:
|
||||
_number_types = frozenset((int, long, float))
|
||||
_time_types = frozenset((int, long))
|
||||
_date_types = frozenset((int, long))
|
||||
_name_from_hex_string = unhexlify
|
||||
|
||||
|
||||
@@ -633,7 +629,7 @@ class SimpleDateType(_CassandraType):
|
||||
def validate(cls, val):
|
||||
if isinstance(val, six.string_types):
|
||||
val = cls.interpret_simpledate_string(val)
|
||||
elif (not isinstance(val, datetime.date)) and (type(val) not in _date_types):
|
||||
elif (not isinstance(val, datetime.date)) and not isinstance(val, six.integer_types):
|
||||
raise TypeError('SimpleDateType arg must be a datetime.date, unsigned integer, or string in the format YYYY-MM-DD')
|
||||
return val
|
||||
|
||||
@@ -662,55 +658,24 @@ class SimpleDateType(_CassandraType):
|
||||
|
||||
class TimeType(_CassandraType):
|
||||
typename = 'time'
|
||||
ONE_MICRO = 1000
|
||||
ONE_MILLI = 1000 * ONE_MICRO
|
||||
ONE_SECOND = 1000 * ONE_MILLI
|
||||
ONE_MINUTE = 60 * ONE_SECOND
|
||||
ONE_HOUR = 60 * ONE_MINUTE
|
||||
|
||||
@classmethod
|
||||
def validate(cls, val):
|
||||
if isinstance(val, six.string_types):
|
||||
val = cls.interpret_timestring(val)
|
||||
elif (not isinstance(val, datetime.time)) and (type(val) not in _time_types):
|
||||
raise TypeError('TimeType arguments must be a string or whole number')
|
||||
if not isinstance(val, Time):
|
||||
val = Time(val)
|
||||
return val
|
||||
|
||||
@staticmethod
|
||||
def interpret_timestring(val):
|
||||
try:
|
||||
nano = 0
|
||||
parts = val.split('.')
|
||||
base_time = time.strptime(parts[0], "%H:%M:%S")
|
||||
nano = (base_time.tm_hour * TimeType.ONE_HOUR +
|
||||
base_time.tm_min * TimeType.ONE_MINUTE +
|
||||
base_time.tm_sec * TimeType.ONE_SECOND)
|
||||
|
||||
if len(parts) > 1:
|
||||
# right pad to 9 digits
|
||||
nano_time_str = parts[1] + "0" * (9 - len(parts[1]))
|
||||
nano += int(nano_time_str)
|
||||
|
||||
return nano
|
||||
except ValueError:
|
||||
raise ValueError("can't interpret %r as a time" % (val,))
|
||||
|
||||
@staticmethod
|
||||
def serialize(val, protocol_version):
|
||||
# Values of the @time@ type are encoded as 64-bit signed integers
|
||||
# representing the number of nanoseconds since midnight.
|
||||
try:
|
||||
nano = (val.hour * TimeType.ONE_HOUR +
|
||||
val.minute * TimeType.ONE_MINUTE +
|
||||
val.second * TimeType.ONE_SECOND +
|
||||
val.microsecond * TimeType.ONE_MICRO)
|
||||
nano = val.nanosecond_time
|
||||
except AttributeError:
|
||||
nano = val
|
||||
nano = Time(val).nanosecond_time
|
||||
return int64_pack(nano)
|
||||
|
||||
@staticmethod
|
||||
def deserialize(byts, protocol_version):
|
||||
return int64_unpack(byts)
|
||||
return Time(int64_unpack(byts))
|
||||
|
||||
|
||||
class UTF8Type(_CassandraType):
|
||||
|
||||
@@ -28,7 +28,7 @@ import types
|
||||
from uuid import UUID
|
||||
import six
|
||||
|
||||
from cassandra.util import OrderedDict, OrderedMap, sortedset
|
||||
from cassandra.util import OrderedDict, OrderedMap, sortedset, Time
|
||||
|
||||
if six.PY3:
|
||||
long = int
|
||||
@@ -75,6 +75,7 @@ class Encoder(object):
|
||||
datetime.datetime: self.cql_encode_datetime,
|
||||
datetime.date: self.cql_encode_date,
|
||||
datetime.time: self.cql_encode_time,
|
||||
Time: self.cql_encode_time,
|
||||
dict: self.cql_encode_map_collection,
|
||||
OrderedDict: self.cql_encode_map_collection,
|
||||
OrderedMap: self.cql_encode_map_collection,
|
||||
|
||||
@@ -563,7 +563,7 @@ from six.moves import cPickle
|
||||
|
||||
class OrderedMap(Mapping):
|
||||
'''
|
||||
An ordered map that accepts non-hashable types for keys. It also maintains the
|
||||
An ordered map that accepts non-hashable types for keys. It also maintains the
|
||||
insertion order of items, behaving as OrderedDict in that regard. These maps
|
||||
are constructed and read just as normal mapping types, exept that they may
|
||||
contain arbitrary collections and other non-hashable items as keys::
|
||||
@@ -653,3 +653,97 @@ class OrderedMap(Mapping):
|
||||
@staticmethod
|
||||
def _serialize_key(key):
|
||||
return cPickle.dumps(key)
|
||||
|
||||
|
||||
import datetime
|
||||
import time
|
||||
|
||||
if six.PY3:
|
||||
long = int
|
||||
|
||||
|
||||
class Time(object):
|
||||
'''
|
||||
Idealized time, independent of day.
|
||||
|
||||
Up to nanosecond resolution
|
||||
'''
|
||||
|
||||
MICRO = 1000
|
||||
MILLI = 1000 * MICRO
|
||||
SECOND = 1000 * MILLI
|
||||
MINUTE = 60 * SECOND
|
||||
HOUR = 60 * MINUTE
|
||||
DAY = 24 * HOUR
|
||||
|
||||
nanosecond_time = 0
|
||||
|
||||
def __init__(self, value):
|
||||
if isinstance(value, six.integer_types):
|
||||
self._from_timestamp(value)
|
||||
elif isinstance(value, datetime.time):
|
||||
self._from_time(value)
|
||||
elif isinstance(value, six.string_types):
|
||||
self._from_timestring(value)
|
||||
else:
|
||||
raise TypeError('Time arguments must be a whole number, datetime.time, or string')
|
||||
|
||||
@property
|
||||
def hour(self):
|
||||
return self.nanosecond_time // Time.HOUR
|
||||
|
||||
@property
|
||||
def minute(self):
|
||||
minutes = self.nanosecond_time // Time.MINUTE
|
||||
return minutes % 60
|
||||
|
||||
@property
|
||||
def second(self):
|
||||
seconds = self.nanosecond_time // Time.SECOND
|
||||
return seconds % 60
|
||||
|
||||
@property
|
||||
def nanosecond(self):
|
||||
return self.nanosecond_time % Time.SECOND
|
||||
|
||||
def _from_timestamp(self, t):
|
||||
if t >= Time.DAY:
|
||||
raise ValueError("value must be less than number of nanoseconds in a day (%d)" % Time.DAY)
|
||||
self.nanosecond_time = t
|
||||
|
||||
def _from_timestring(self, s):
|
||||
try:
|
||||
parts = s.split('.')
|
||||
base_time = time.strptime(parts[0], "%H:%M:%S")
|
||||
self.nanosecond_time = (base_time.tm_hour * Time.HOUR +
|
||||
base_time.tm_min * Time.MINUTE +
|
||||
base_time.tm_sec * Time.SECOND)
|
||||
|
||||
if len(parts) > 1:
|
||||
# right pad to 9 digits
|
||||
nano_time_str = parts[1] + "0" * (9 - len(parts[1]))
|
||||
self.nanosecond_time += int(nano_time_str)
|
||||
|
||||
except ValueError:
|
||||
raise ValueError("can't interpret %r as a time" % (s,))
|
||||
|
||||
def _from_time(self, t):
|
||||
self.nanosecond_time = (t.hour * Time.HOUR +
|
||||
t.minute * Time.MINUTE +
|
||||
t.second * Time.SECOND +
|
||||
t.microsecond * Time.MICRO)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Time):
|
||||
return self.nanosecond_time == other.nanosecond_time
|
||||
|
||||
return self.nanosecond_time % Time.MICRO == 0 and \
|
||||
datetime.time(hour=self.hour, minute=self.minute, second=self.second,
|
||||
microsecond=self.nanosecond // Time.MICRO) == other
|
||||
|
||||
def __repr__(self):
|
||||
return "Time(%s)" % self.nanosecond_time
|
||||
|
||||
def __str__(self):
|
||||
return "%02d:%02d:%02d.%09d" % (self.hour, self.minute,
|
||||
self.second, self.nanosecond)
|
||||
|
||||
Reference in New Issue
Block a user