Skip to content

Instantly share code, notes, and snippets.

@buriy
Created May 25, 2009 00:13
Show Gist options
  • Save buriy/117307 to your computer and use it in GitHub Desktop.
Save buriy/117307 to your computer and use it in GitHub Desktop.
Django DurationField
from widgets.duration import DurationField as FDurationField
from widgets.duration import TimeDelta
from django.db.models.fields import Field
from django.core.exceptions import ValidationError
from django.db import connection
class DurationProxy(object):
def __init__(self, field):
self.field_name = field.name
def __get__(self, instance=None, owner=None):
if instance is None:
raise AttributeError, "%s can only be accessed from %s instances." % (self.field_name, owner.__name__)
if self.field_name not in instance.__dict__:
return None
return instance.__dict__[self.field_name]
def __set__(self, instance, value):
if value and not isinstance(value, TimeDelta):
value = TimeDelta(value=value)
instance.__dict__[self.field_name] = value
class DurationField(Field):
def __init__(self, *args, **kwargs):
super(DurationField, self).__init__(*args, **kwargs)
self.max_digits, self.decimal_places = 20, 6
def get_internal_type(self):
return "DecimalField"
def contribute_to_class(self, cls, name):
super(DurationField, self).contribute_to_class(cls, name)
setattr(cls, name, DurationProxy(self))
def get_db_prep_save(self, value):
if value is None:
return None
if not isinstance(value, TimeDelta):
value = TimeDelta(value=value)
t = value.days * 24 * 3600 * 1000000 + value.seconds * 1000000 + value.microseconds
return connection.ops.value_to_db_decimal(t, 20, 0)
# max value 86399999999999999999 microseconds
def to_python(self, value):
if isinstance(value, TimeDelta):
return value
try:
return TimeDelta(value=float(value))
except TypeError:
raise ValidationError('The value must be an integer.')
except OverflowError:
raise ValidationError('The maximum allowed value is %s' % TimeDelta.max)
def formfield(self, form_class=FDurationField, **kwargs):
return super(DurationField, self).formfield(form_class, **kwargs)
from django.forms import Field
from django.utils.translation import ugettext as _
from django.utils.datastructures import SortedDict
from django.forms.util import ValidationError
import datetime
class TimeDelta(datetime.timedelta):
values_in_microseconds = SortedDict((
('y', 31556925993600), # 52.177457 * (7*24*60*60*1000*1000)
('m', 2629743832800), # 4.34812141 * (7*24*60*60*1000*1000)
('w', 604800000000), # 7*24*60*60*1000*1000
('d', 86400000000), # 24*60*60*1000*1000
('h', 3600000000), # 60*60*1000*1000
('min', 60000000), # 60*1000*1000
('s', 1000000), # 1000*1000
('ms', 1000),
('us', 1),
))
def from_string(cls, value):
if value == '0':
return datetime.timedelta.__new__(TimeDelta)
pairs = []
for b in value.lower().split():
for index, char in enumerate(b):
if not char.isdigit():
pairs.append((b[:index], b[index:])) #digits, letters
break
if not pairs:
raise ValueError("Incorrect TimeDelta value")
microseconds = 0
for digits, chars in pairs:
if not digits or not chars:
raise ValidationError("Incorrect TimeDelta pair")
microseconds += int(digits) * TimeDelta.values_in_microseconds[chars]
return datetime.timedelta.__new__(TimeDelta, microseconds=microseconds)
from_string = classmethod(from_string)
def __new__(self, days=0, seconds=0, microseconds=0, value=None):
"""
Creates TimeDelta
Use value to cast from other type, or
days, seconds, and microseconds for timedelta-like construction
"""
if value != None and (days or seconds or microseconds):
raise ValueError("Using value argument with other arguments is prohibited.")
if value:
if isinstance(value, basestring):
return TimeDelta.from_string(value)
# if isinstance(value, datetime.timedelta):
# microseconds = float(value)
microseconds = float(value)
return datetime.timedelta.__new__(TimeDelta, days, seconds, microseconds)
def __unicode__(self):
if not self:
return u"0"
vals = []
mis = self.days * 24 * 3600 * 1000000 + self.seconds * 1000000 + self.microseconds
for k in self.values_in_microseconds:
if mis >= self.values_in_microseconds[k]:
diff, mis = divmod(mis, self.values_in_microseconds[k])
vals.append("%d%s" % (diff, k))
return u" ".join(vals)
class DurationField(Field):
default_error_messages = {
'invalid': _(u'Enter a valid duration.'),
'min_value': _(u'Ensure this self is greater than or equal to %(min)s.'),
'max_value': _(u'Ensure this self is less than or equal to %(max)s.'),
}
def __init__(self, min_value=None, max_value=None, *args, **kwargs):
super(DurationField, self).__init__(*args, **kwargs)
self.min_value, self.max_value = min_value, max_value
def to_timedelta(self, value):
"""
Takes an Unicode self and converts it to a datetime.timedelta object.
1y 7m 6w 3d 18h 30min 23s 10ms 150mis =>
1 year 7 months 6 weeks 3 days 18 hours 30 minutes 23 seconds 10 milliseconds 150 microseconds
=> datetime.timedelta(624, 6155, 805126)
"""
try:
return TimeDelta(value=value)
except ValueError:
raise ValidationError(self.error_messages['invalid'])
def from_timedelta(self, value):
if not value:
return u"0"
return unicode(value)
def clean(self, value):
"Validates max_value and min_value. Returns a datetime.timedelta object."
value = self.to_timedelta(value)
if self.max_value is not None and value > self.max_value:
raise ValidationError(self.error_messages['max_value'] % {'max': self.max_value})
if self.min_value is not None and value < self.min_value:
raise ValidationError(self.error_messages['min_value'] % {'min': self.min_value})
return value
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment