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

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