Improved handling of Time type.

still needs test, doc update
This commit is contained in:
Adam Holmberg
2015-01-30 17:58:07 -06:00
parent 0cf8b8ec23
commit 5acb8cd5bd
3 changed files with 104 additions and 44 deletions

View File

@@ -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):

View File

@@ -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,

View File

@@ -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)