diff --git a/ietf/meeting/migrations/0001_initial.py b/ietf/meeting/migrations/0001_initial.py
index e742baae9..65d2dde5b 100644
--- a/ietf/meeting/migrations/0001_initial.py
+++ b/ietf/meeting/migrations/0001_initial.py
@@ -3,8 +3,6 @@ from __future__ import unicode_literals
from django.db import models, migrations
import datetime
-import ietf.meeting.timedeltafield
-
class Migration(migrations.Migration):
@@ -112,7 +110,7 @@ class Migration(migrations.Migration):
('attendees', models.IntegerField(null=True, blank=True)),
('agenda_note', models.CharField(max_length=255, blank=True)),
('requested', models.DateTimeField(default=datetime.datetime.now)),
- ('requested_duration', ietf.meeting.timedeltafield.TimedeltaField(default=0)),
+ ('requested_duration', models.IntegerField(default=0)),
('comments', models.TextField(blank=True)),
('scheduled', models.DateTimeField(null=True, blank=True)),
('modified', models.DateTimeField(default=datetime.datetime.now)),
@@ -141,7 +139,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=255)),
('time', models.DateTimeField()),
- ('duration', ietf.meeting.timedeltafield.TimedeltaField()),
+ ('duration', models.IntegerField()),
('show_location', models.BooleanField(default=True, help_text=b'Show location in agenda')),
('modified', models.DateTimeField(default=datetime.datetime.now)),
('location', models.ForeignKey(blank=True, to='meeting.Room', null=True)),
diff --git a/ietf/meeting/migrations/0002_auto_20150221_0947.py b/ietf/meeting/migrations/0002_auto_20150221_0947.py
index 54d94cf0c..7d50996cd 100644
--- a/ietf/meeting/migrations/0002_auto_20150221_0947.py
+++ b/ietf/meeting/migrations/0002_auto_20150221_0947.py
@@ -2,8 +2,6 @@
from __future__ import unicode_literals
from django.db import models, migrations
-import timedelta.fields
-
class Migration(migrations.Migration):
@@ -27,13 +25,13 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='meeting',
name='idsubmit_cutoff_time_utc',
- field=timedelta.fields.TimedeltaField(default=86399.0, help_text=b'The time of day (UTC) after which submission will be closed. Use for example 23 hours, 59 minutes, 59 seconds.'),
+ field=models.CharField(max_length=20, default='86399.0', help_text=b'The time of day (UTC) after which submission will be closed. Use for example 23 hours, 59 minutes, 59 seconds.'),
preserve_default=True,
),
migrations.AddField(
model_name='meeting',
name='idsubmit_cutoff_warning_days',
- field=timedelta.fields.TimedeltaField(default=1814400.0, help_text=b'How long before the 00 cutoff to start showing cutoff warnings. Use for example 21 days or 3 weeks.'),
+ field=models.CharField(max_length=20, default='1814400.0', help_text=b'How long before the 00 cutoff to start showing cutoff warnings. Use for example 21 days or 3 weeks.'),
preserve_default=True,
),
]
diff --git a/ietf/meeting/migrations/0003_auto_20150304_0738.py b/ietf/meeting/migrations/0003_auto_20150304_0738.py
index afc8fe39a..ff5b4962e 100644
--- a/ietf/meeting/migrations/0003_auto_20150304_0738.py
+++ b/ietf/meeting/migrations/0003_auto_20150304_0738.py
@@ -2,8 +2,6 @@
from __future__ import unicode_literals
from django.db import models, migrations
-import timedelta.fields
-
class Migration(migrations.Migration):
@@ -27,13 +25,13 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='meeting',
name='idsubmit_cutoff_time_utc',
- field=timedelta.fields.TimedeltaField(default=86399.0, help_text=b'The time of day (UTC) after which submission will be closed. Use for example 23 hours, 59 minutes, 59 seconds.', blank=True),
+ field=models.CharField(max_length=20, default='86399.0', help_text=b'The time of day (UTC) after which submission will be closed. Use for example 23 hours, 59 minutes, 59 seconds.', blank=True),
preserve_default=True,
),
migrations.AlterField(
model_name='meeting',
name='idsubmit_cutoff_warning_days',
- field=timedelta.fields.TimedeltaField(default=1814400.0, help_text=b'How long before the 00 cutoff to start showing cutoff warnings. Use for example 21 days or 3 weeks.', blank=True),
+ field=models.CharField(max_length=20, default='1814400.0', help_text=b'How long before the 00 cutoff to start showing cutoff warnings. Use for example 21 days or 3 weeks.', blank=True),
preserve_default=True,
),
]
diff --git a/ietf/meeting/timedeltafield.py b/ietf/meeting/timedeltafield.py
deleted file mode 100644
index a03a29fe4..000000000
--- a/ietf/meeting/timedeltafield.py
+++ /dev/null
@@ -1,188 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-# $Id: TimedeltaField.py 1787 2011-04-20 07:09:57Z tguettler $
-# $HeadURL: svn+ssh://svnserver/svn/djangotools/trunk/dbfields/TimedeltaField.py $
-
-# from http://djangosnippets.org/snippets/1060/ with some fixes
-
-# Python
-import datetime
-
-# Django
-import django
-from django import forms
-from django.db import models
-from django.utils.safestring import mark_safe
-from django.utils.translation import ugettext_lazy as _
-
-
-SECS_PER_DAY=3600*24
-
-class TimedeltaField(models.Field):
- u'''
- Store Python's datetime.timedelta in an integer column.
- Most database systems only support 32 bit integers by default.
- '''
- __metaclass__ = models.SubfieldBase
- empty_strings_allowed = False
-
- def __init__(self, *args, **kwargs):
- super(TimedeltaField, self).__init__(*args, **kwargs)
-
- def to_python(self, value):
- if (value is None) or isinstance(value, datetime.timedelta):
- return value
-
- try:
- # else try to convert to int (e.g. from string)
- value = int(value)
- except (TypeError, ValueError):
- raise django.core.exceptions.ValidationError(
- _("This value must be an integer or a datetime.timedelta."))
-
- return datetime.timedelta(seconds=value)
-
- def get_internal_type(self):
- return 'IntegerField'
-
- def get_db_prep_lookup(self, lookup_type, value, connection=None, prepared=False):
- raise NotImplementedError() # SQL WHERE
-
- def get_db_prep_save(self, value, connection=None, prepared=False):
- if (value is None) or isinstance(value, int):
- return value
- return SECS_PER_DAY*value.days+value.seconds
-
- def formfield(self, *args, **kwargs):
- defaults={'form_class': TimedeltaFormField}
- defaults.update(kwargs)
- return super(TimedeltaField, self).formfield(*args, **defaults)
-
- def value_to_string(self, obj):
- value = self._get_val_from_obj(obj)
- return self.get_db_prep_value(value) # pylint: disable=no-value-for-parameter
-
-
-class TimedeltaFormField(forms.Field):
- default_error_messages = {
- 'invalid': _(u'Enter a whole number.'),
- }
-
- def __init__(self, *args, **kwargs):
- defaults={'widget': TimedeltaWidget}
- defaults.update(kwargs)
- super(TimedeltaFormField, self).__init__(*args, **defaults)
-
- def clean(self, value):
- # value comes from Timedelta.Widget.value_from_datadict(): tuple of strings
- super(TimedeltaFormField, self).clean(value)
- assert len(value)==len(self.widget.inputs), (value, self.widget.inputs)
- i=0
- for value, multiply in zip(value, self.widget.multiply):
- try:
- i+=int(value)*multiply
- except (ValueError, TypeError):
- raise forms.ValidationError(self.error_messages['invalid'])
- return i
-
-class TimedeltaWidget(forms.Widget):
- INPUTS=['days', 'hours', 'minutes', 'seconds']
- MULTIPLY=[60*60*24, 60*60, 60, 1]
- def __init__(self, attrs=None):
- self.widgets=[]
- if not attrs:
- attrs={}
- inputs=attrs.get('inputs', self.INPUTS)
- multiply=[]
- for input in inputs:
- assert input in self.INPUTS, (input, self.INPUT)
- self.widgets.append(forms.TextInput(attrs=attrs))
- multiply.append(self.MULTIPLY[self.INPUTS.index(input)])
- self.inputs=inputs
- self.multiply=multiply
- super(TimedeltaWidget, self).__init__(attrs)
-
- def render(self, name, value, attrs):
- if value is None:
- values=[0 for i in self.inputs]
- elif isinstance(value, datetime.timedelta):
- values=split_seconds(value.days*SECS_PER_DAY+value.seconds, self.inputs, self.multiply)
- elif isinstance(value, int):
- # initial data from model
- values=split_seconds(value, self.inputs, self.multiply)
- else:
- assert isinstance(value, tuple), (value, type(value))
- assert len(value)==len(self.inputs), (value, self.inputs)
- values=value
- id=attrs.pop('id')
- assert not attrs, attrs
- rendered=[]
- for input, widget, val in zip(self.inputs, self.widgets, values):
- rendered.append(u'%s %s' % (_(input), widget.render('%s_%s' % (name, input), val)))
- return mark_safe('
%s
' % (id, ' '.join(rendered)))
-
- def value_from_datadict(self, data, files, name):
- # Don't throw ValidationError here, just return a tuple of strings.
- ret=[]
- for input, multi in zip(self.inputs, self.multiply):
- ret.append(data.get('%s_%s' % (name, input), 0))
- return tuple(ret)
-
- def _has_changed(self, initial_value, data_value):
- # data_value comes from value_from_datadict(): A tuple of strings.
- if initial_value is None:
- return bool(set(data_value)!=set([u'0']))
- assert isinstance(initial_value, datetime.timedelta), initial_value
- initial=tuple([unicode(i) for i in split_seconds(initial_value.days*SECS_PER_DAY+initial_value.seconds, self.inputs, self.multiply)])
- assert len(initial)==len(data_value), (initial, data_value)
- return bool(initial!=data_value)
-
-def main():
- assert split_seconds(1000000)==[11, 13, 46, 40]
-
- field=TimedeltaField()
-
- td=datetime.timedelta(days=10, seconds=11)
- s=field.get_db_prep_save(td)
- assert isinstance(s, int), (s, type(s))
- td_again=field.to_python(s)
- assert td==td_again, (td, td_again)
-
- td=datetime.timedelta(seconds=11)
- s=field.get_db_prep_save(td)
- td_again=field.to_python(s)
- assert td==td_again, (td, td_again)
-
- field=TimedeltaFormField()
- assert field.widget._has_changed(datetime.timedelta(seconds=0), (u'0', u'0', u'0', u'0',)) is False
- assert field.widget._has_changed(None, (u'0', u'0', u'0', u'0',)) is False
- assert field.widget._has_changed(None, (u'0', u'0')) is False
- assert field.widget._has_changed(datetime.timedelta(days=1, hours=2, minutes=3, seconds=4), (u'1', u'2', u'3', u'4',)) is False
-
- for secs, soll, kwargs in [
- (100, [0, 0, 1, 40], dict()),
- (100, ['0days', '0hours', '1minutes', '40seconds'], dict(with_unit=True)),
- (100, ['1minutes', '40seconds'], dict(with_unit=True, remove_leading_zeros=True)),
- (100000, ['1days', '3hours'], dict(inputs=['days', 'hours'], with_unit=True, remove_leading_zeros=True)),
- ]:
- ist=split_seconds(secs, **kwargs)
- if ist!=soll:
- raise Exception('geg=%s soll=%s ist=%s kwargs=%s' % (secs, soll, ist, kwargs))
-
- print "unittest OK"
-
-def split_seconds(secs, inputs=TimedeltaWidget.INPUTS, multiply=TimedeltaWidget.MULTIPLY,
- with_unit=False, remove_leading_zeros=False):
- ret=[]
- assert len(inputs)<=len(multiply), (inputs, multiply)
- for input, multi in zip(inputs, multiply):
- count, secs = divmod(secs, multi)
- if remove_leading_zeros and not ret and not count:
- continue
- if with_unit:
- ret.append('%s%s' % (count, input))
- else:
- ret.append(count)
- return ret
-
-if __name__=='__main__':
- main()
diff --git a/timedelta/VERSION b/timedelta/VERSION
deleted file mode 100644
index f38fc5393..000000000
--- a/timedelta/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-0.7.3
diff --git a/timedelta/__init__.py b/timedelta/__init__.py
deleted file mode 100644
index e7b26d09c..000000000
--- a/timedelta/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import os
-
-__version__ = open(os.path.join(os.path.dirname(__file__), "VERSION")).read().strip()
-
-try:
- from django.core.exceptions import ImproperlyConfigured
-except ImportError:
- ImproperlyConfigured = ImportError
-
-try:
- from .fields import TimedeltaField
- from .helpers import (
- divide, multiply, modulo,
- parse, nice_repr,
- percentage, decimal_percentage,
- total_seconds
- )
-except (ImportError, ImproperlyConfigured):
- pass
\ No newline at end of file
diff --git a/timedelta/fields.py b/timedelta/fields.py
deleted file mode 100644
index fd9e0c36b..000000000
--- a/timedelta/fields.py
+++ /dev/null
@@ -1,120 +0,0 @@
-from django.db import models
-from django.core.exceptions import ValidationError
-from django.utils import six
-
-from collections import defaultdict
-import datetime
-import six
-
-from .helpers import parse
-from .forms import TimedeltaFormField
-
-# TODO: Figure out why django admin thinks fields of this type have changed every time an object is saved.
-
-# Define the different column types that different databases can use.
-COLUMN_TYPES = defaultdict(lambda:"char(20)")
-COLUMN_TYPES["django.db.backends.postgresql_psycopg2"] = "interval"
-COLUMN_TYPES["django.contrib.gis.db.backends.postgis"] = "interval"
-
-class TimedeltaField(six.with_metaclass(models.SubfieldBase, models.Field)):
- """
- Store a datetime.timedelta as an INTERVAL in postgres, or a
- CHAR(20) in other database backends.
- """
- _south_introspects = True
-
- description = "A datetime.timedelta object"
-
- def __init__(self, *args, **kwargs):
- self._min_value = kwargs.pop('min_value', None)
-
- if isinstance(self._min_value, (int, float)):
- self._min_value = datetime.timedelta(seconds=self._min_value)
-
- self._max_value = kwargs.pop('max_value', None)
-
- if isinstance(self._max_value, (int, float)):
- self._max_value = datetime.timedelta(seconds=self._max_value)
-
- super(TimedeltaField, self).__init__(*args, **kwargs)
-
- def to_python(self, value):
- if (value is None) or isinstance(value, datetime.timedelta):
- return value
- if isinstance(value, (int, float)):
- return datetime.timedelta(seconds=value)
- if isinstance(value, six.string_types) and value.replace('.','0').isdigit():
- return datetime.timedelta(seconds=float(value))
- if value == "":
- if self.null:
- return None
- else:
- return datetime.timedelta(0)
- return parse(value)
-
- def get_prep_value(self, value):
- if self.null and value == "":
- return None
- if (value is None) or isinstance(value, six.string_types):
- return value
- return str(value).replace(',', '')
-
- def get_db_prep_value(self, value, connection=None, prepared=None):
- return self.get_prep_value(value)
-
- def formfield(self, *args, **kwargs):
- defaults = {'form_class':TimedeltaFormField}
- defaults.update(kwargs)
- return super(TimedeltaField, self).formfield(*args, **defaults)
-
- def validate(self, value, model_instance):
- super(TimedeltaField, self).validate(value, model_instance)
- if self._min_value is not None:
- if self._min_value > value:
- raise ValidationError('Less than minimum allowed value')
- if self._max_value is not None:
- if self._max_value < value:
- raise ValidationError('More than maximum allowed value')
-
- def value_to_string(self, obj):
- value = self._get_val_from_obj(obj)
- return unicode(value)
-
- def get_default(self):
- """
- Needed to rewrite this, as the parent class turns this value into a
- unicode string. That sux pretty deep.
- """
- if self.has_default():
- if callable(self.default):
- return self.default()
- return self.get_prep_value(self.default)
- if not self.empty_strings_allowed or (self.null):
- return None
- return ""
-
- def db_type(self, connection):
- return COLUMN_TYPES[connection.settings_dict['ENGINE']]
-
- def deconstruct(self):
- """
- Break down this field into arguments that can be used to reproduce it
- with Django migrations.
-
- The thing to to note here is that currently the migration file writer
- can't serialize timedelta objects so we convert them to a float
- representation (in seconds) that we can later interpret as a timedelta.
- """
-
- name, path, args, kwargs = super(TimedeltaField, self).deconstruct()
-
- if isinstance(self._min_value, datetime.timedelta):
- kwargs['min_value'] = self._min_value.total_seconds()
-
- if isinstance(self._max_value, datetime.timedelta):
- kwargs['max_value'] = self._max_value.total_seconds()
-
- if isinstance(kwargs.get('default'), datetime.timedelta):
- kwargs['default'] = kwargs['default'].total_seconds()
-
- return name, path, args, kwargs
diff --git a/timedelta/forms.py b/timedelta/forms.py
deleted file mode 100644
index 3ec1f11c1..000000000
--- a/timedelta/forms.py
+++ /dev/null
@@ -1,99 +0,0 @@
-from django import forms
-from django.utils.translation import ugettext_lazy as _
-from django.utils import six
-
-import datetime
-from collections import defaultdict
-
-from .widgets import TimedeltaWidget
-from .helpers import parse
-
-class TimedeltaFormField(forms.Field):
-
- default_error_messages = {
- 'invalid':_('Enter a valid time span: e.g. "3 days, 4 hours, 2 minutes"')
- }
-
- def __init__(self, *args, **kwargs):
- defaults = {'widget':TimedeltaWidget}
- defaults.update(kwargs)
- super(TimedeltaFormField, self).__init__(*args, **defaults)
-
- def clean(self, value):
- """
- This doesn't really need to be here: it should be tested in
- parse()...
-
- >>> t = TimedeltaFormField()
- >>> t.clean('1 day')
- datetime.timedelta(1)
- >>> t.clean('1 day, 0:00:00')
- datetime.timedelta(1)
- >>> t.clean('1 day, 8:42:42.342')
- datetime.timedelta(1, 31362, 342000)
- >>> t.clean('3 days, 8:42:42.342161')
- datetime.timedelta(3, 31362, 342161)
- >>> try:
- ... t.clean('3 days, 8:42:42.3.42161')
- ... except forms.ValidationError as arg:
- ... six.print_(arg.messages[0])
- Enter a valid time span: e.g. "3 days, 4 hours, 2 minutes"
- >>> t.clean('5 day, 8:42:42')
- datetime.timedelta(5, 31362)
- >>> t.clean('1 days')
- datetime.timedelta(1)
- >>> t.clean('1 second')
- datetime.timedelta(0, 1)
- >>> t.clean('1 sec')
- datetime.timedelta(0, 1)
- >>> t.clean('10 seconds')
- datetime.timedelta(0, 10)
- >>> t.clean('30 seconds')
- datetime.timedelta(0, 30)
- >>> t.clean('1 minute, 30 seconds')
- datetime.timedelta(0, 90)
- >>> t.clean('2.5 minutes')
- datetime.timedelta(0, 150)
- >>> t.clean('2 minutes, 30 seconds')
- datetime.timedelta(0, 150)
- >>> t.clean('.5 hours')
- datetime.timedelta(0, 1800)
- >>> t.clean('30 minutes')
- datetime.timedelta(0, 1800)
- >>> t.clean('1 hour')
- datetime.timedelta(0, 3600)
- >>> t.clean('5.5 hours')
- datetime.timedelta(0, 19800)
- >>> t.clean('1 day, 1 hour, 30 mins')
- datetime.timedelta(1, 5400)
- >>> t.clean('8 min')
- datetime.timedelta(0, 480)
- >>> t.clean('3 days, 12 hours')
- datetime.timedelta(3, 43200)
- >>> t.clean('3.5 day')
- datetime.timedelta(3, 43200)
- >>> t.clean('1 week')
- datetime.timedelta(7)
- >>> t.clean('2 weeks, 2 days')
- datetime.timedelta(16)
- >>> try:
- ... t.clean(six.u('2 we\xe8k, 2 days'))
- ... except forms.ValidationError as arg:
- ... six.print_(arg.messages[0])
- Enter a valid time span: e.g. "3 days, 4 hours, 2 minutes"
- """
-
- super(TimedeltaFormField, self).clean(value)
- if value == '' and not self.required:
- return ''
- try:
- return parse(value)
- except TypeError:
- raise forms.ValidationError(self.error_messages['invalid'])
-
-class TimedeltaChoicesField(TimedeltaFormField):
- def __init__(self, *args, **kwargs):
- choices = kwargs.pop('choices')
- defaults = {'widget':forms.Select(choices=choices)}
- defaults.update(kwargs)
- super(TimedeltaChoicesField, self).__init__(*args, **defaults)
diff --git a/timedelta/helpers.py b/timedelta/helpers.py
deleted file mode 100644
index dc3a40d5c..000000000
--- a/timedelta/helpers.py
+++ /dev/null
@@ -1,554 +0,0 @@
-from __future__ import division
-
-import re
-import datetime
-from decimal import Decimal
-
-from django.utils import six
-
-STRFDATETIME = re.compile('([dgGhHis])')
-STRFDATETIME_REPL = lambda x: '%%(%s)s' % x.group()
-
-def nice_repr(timedelta, display="long", sep=", "):
- """
- Turns a datetime.timedelta object into a nice string repr.
-
- display can be "sql", "iso8601", "minimal", "short" or "long" [default].
-
- >>> from datetime import timedelta as td
- >>> nice_repr(td(days=1, hours=2, minutes=3, seconds=4))
- '1 day, 2 hours, 3 minutes, 4 seconds'
- >>> nice_repr(td(days=1, seconds=1), "minimal")
- '1d, 1s'
- >>> nice_repr(datetime.timedelta(days=1))
- '1 day'
- >>> nice_repr(datetime.timedelta(days=0))
- '0 seconds'
- >>> nice_repr(datetime.timedelta(seconds=1))
- '1 second'
- >>> nice_repr(datetime.timedelta(seconds=10))
- '10 seconds'
- >>> nice_repr(datetime.timedelta(seconds=30))
- '30 seconds'
- >>> nice_repr(datetime.timedelta(seconds=60))
- '1 minute'
- >>> nice_repr(datetime.timedelta(seconds=150))
- '2 minutes, 30 seconds'
- >>> nice_repr(datetime.timedelta(seconds=1800))
- '30 minutes'
- >>> nice_repr(datetime.timedelta(seconds=3600))
- '1 hour'
- >>> nice_repr(datetime.timedelta(seconds=3601))
- '1 hour, 1 second'
- >>> nice_repr(datetime.timedelta(seconds=19800))
- '5 hours, 30 minutes'
- >>> nice_repr(datetime.timedelta(seconds=91800))
- '1 day, 1 hour, 30 minutes'
- >>> nice_repr(datetime.timedelta(seconds=302400))
- '3 days, 12 hours'
-
- Tests for handling zero:
- >>> nice_repr(td(seconds=0), 'minimal')
- '0s'
- >>> nice_repr(td(seconds=0), 'short')
- '0 sec'
- >>> nice_repr(td(seconds=0), 'long')
- '0 seconds'
- """
-
- assert isinstance(timedelta, datetime.timedelta), "First argument must be a timedelta."
-
- result = []
-
- weeks = int(timedelta.days / 7)
- days = timedelta.days % 7
- hours = int(timedelta.seconds / 3600)
- minutes = int((timedelta.seconds % 3600) / 60)
- seconds = timedelta.seconds % 60
-
- if display == "sql":
- days += weeks * 7
- return "%i %02i:%02i:%02i" % (days, hours, minutes, seconds)
- elif display == "iso8601":
- return iso8601_repr(timedelta)
- elif display == 'minimal':
- words = ["w", "d", "h", "m", "s"]
- elif display == 'short':
- words = [" wks", " days", " hrs", " min", " sec"]
- elif display == 'long':
- words = [" weeks", " days", " hours", " minutes", " seconds"]
- else:
- # Use django template-style formatting.
- # Valid values are:
- # d,g,G,h,H,i,s
- return STRFDATETIME.sub(STRFDATETIME_REPL, display) % {
- 'd': days,
- 'g': hours,
- 'G': hours if hours > 9 else '0%s' % hours,
- 'h': hours,
- 'H': hours if hours > 9 else '0%s' % hours,
- 'i': minutes if minutes > 9 else '0%s' % minutes,
- 's': seconds if seconds > 9 else '0%s' % seconds
- }
-
- values = [weeks, days, hours, minutes, seconds]
-
- for i in range(len(values)):
- if values[i]:
- if values[i] == 1 and len(words[i]) > 1:
- result.append("%i%s" % (values[i], words[i].rstrip('s')))
- else:
- result.append("%i%s" % (values[i], words[i]))
-
- # values with less than one second, which are considered zeroes
- if len(result) == 0:
- # display as 0 of the smallest unit
- result.append('0%s' % (words[-1]))
-
- return sep.join(result)
-
-
-def iso8601_repr(timedelta, format=None):
- """
- Represent a timedelta as an ISO8601 duration.
- http://en.wikipedia.org/wiki/ISO_8601#Durations
-
- >>> from datetime import timedelta as td
- >>> iso8601_repr(td(days=1, hours=2, minutes=3, seconds=4))
- 'P1DT2H3M4S'
-
- >>> iso8601_repr(td(hours=1, minutes=10, seconds=20), 'alt')
- 'PT01:10:20'
- """
- years = int(timedelta.days / 365)
- weeks = int((timedelta.days % 365) / 7)
- days = timedelta.days % 7
-
- hours = int(timedelta.seconds / 3600)
- minutes = int((timedelta.seconds % 3600) / 60)
- seconds = timedelta.seconds % 60
-
- if format == 'alt':
- if years or weeks or days:
- raise ValueError('Does not support alt format for durations > 1 day')
- return 'PT{0:02d}:{1:02d}:{2:02d}'.format(hours, minutes, seconds)
-
- formatting = (
- ('P', (
- ('Y', years),
- ('W', weeks),
- ('D', days),
- )),
- ('T', (
- ('H', hours),
- ('M', minutes),
- ('S', seconds),
- )),
- )
-
- result = []
- for category, subcats in formatting:
- result += category
- for format, value in subcats:
- if value:
- result.append('%d%c' % (value, format))
- if result[-1] == 'T':
- result = result[:-1]
-
- return "".join(result)
-
-def parse(string):
- """
- Parse a string into a timedelta object.
-
- >>> parse("1 day")
- datetime.timedelta(1)
- >>> parse("2 days")
- datetime.timedelta(2)
- >>> parse("1 d")
- datetime.timedelta(1)
- >>> parse("1 hour")
- datetime.timedelta(0, 3600)
- >>> parse("1 hours")
- datetime.timedelta(0, 3600)
- >>> parse("1 hr")
- datetime.timedelta(0, 3600)
- >>> parse("1 hrs")
- datetime.timedelta(0, 3600)
- >>> parse("1h")
- datetime.timedelta(0, 3600)
- >>> parse("1wk")
- datetime.timedelta(7)
- >>> parse("1 week")
- datetime.timedelta(7)
- >>> parse("1 weeks")
- datetime.timedelta(7)
- >>> parse("2 wks")
- datetime.timedelta(14)
- >>> parse("1 sec")
- datetime.timedelta(0, 1)
- >>> parse("1 secs")
- datetime.timedelta(0, 1)
- >>> parse("1 s")
- datetime.timedelta(0, 1)
- >>> parse("1 second")
- datetime.timedelta(0, 1)
- >>> parse("1 seconds")
- datetime.timedelta(0, 1)
- >>> parse("1 minute")
- datetime.timedelta(0, 60)
- >>> parse("1 min")
- datetime.timedelta(0, 60)
- >>> parse("1 m")
- datetime.timedelta(0, 60)
- >>> parse("1 minutes")
- datetime.timedelta(0, 60)
- >>> parse("1 mins")
- datetime.timedelta(0, 60)
- >>> parse("2 ws")
- Traceback (most recent call last):
- ...
- TypeError: '2 ws' is not a valid time interval
- >>> parse("2 ds")
- Traceback (most recent call last):
- ...
- TypeError: '2 ds' is not a valid time interval
- >>> parse("2 hs")
- Traceback (most recent call last):
- ...
- TypeError: '2 hs' is not a valid time interval
- >>> parse("2 ms")
- Traceback (most recent call last):
- ...
- TypeError: '2 ms' is not a valid time interval
- >>> parse("2 ss")
- Traceback (most recent call last):
- ...
- TypeError: '2 ss' is not a valid time interval
- >>> parse("")
- Traceback (most recent call last):
- ...
- TypeError: '' is not a valid time interval
- >>> parse("1.5 days")
- datetime.timedelta(1, 43200)
- >>> parse("3 weeks")
- datetime.timedelta(21)
- >>> parse("4.2 hours")
- datetime.timedelta(0, 15120)
- >>> parse(".5 hours")
- datetime.timedelta(0, 1800)
- >>> parse(" hours")
- Traceback (most recent call last):
- ...
- TypeError: 'hours' is not a valid time interval
- >>> parse("1 hour, 5 mins")
- datetime.timedelta(0, 3900)
-
- >>> parse("-2 days")
- datetime.timedelta(-2)
- >>> parse("-1 day 0:00:01")
- datetime.timedelta(-1, 1)
- >>> parse("-1 day, -1:01:01")
- datetime.timedelta(-2, 82739)
- >>> parse("-1 weeks, 2 days, -3 hours, 4 minutes, -5 seconds")
- datetime.timedelta(-5, 11045)
-
- >>> parse("0 seconds")
- datetime.timedelta(0)
- >>> parse("0 days")
- datetime.timedelta(0)
- >>> parse("0 weeks")
- datetime.timedelta(0)
-
- >>> zero = datetime.timedelta(0)
- >>> parse(nice_repr(zero))
- datetime.timedelta(0)
- >>> parse(nice_repr(zero, 'minimal'))
- datetime.timedelta(0)
- >>> parse(nice_repr(zero, 'short'))
- datetime.timedelta(0)
- >>> parse(' 50 days 00:00:00 ')
- datetime.timedelta(50)
- """
- string = string.strip()
-
- if string == "":
- raise TypeError("'%s' is not a valid time interval" % string)
- # This is the format we get from sometimes Postgres, sqlite,
- # and from serialization
- d = re.match(r'^((?P[-+]?\d+) days?,? )?(?P[-+]?)(?P\d+):'
- r'(?P\d+)(:(?P\d+(\.\d+)?))?$',
- six.text_type(string))
- if d:
- d = d.groupdict(0)
- if d['sign'] == '-':
- for k in 'hours', 'minutes', 'seconds':
- d[k] = '-' + d[k]
- d.pop('sign', None)
- else:
- # This is the more flexible format
- d = re.match(
- r'^((?P-?((\d*\.\d+)|\d+))\W*w((ee)?(k(s)?)?)(,)?\W*)?'
- r'((?P-?((\d*\.\d+)|\d+))\W*d(ay(s)?)?(,)?\W*)?'
- r'((?P-?((\d*\.\d+)|\d+))\W*h(ou)?(r(s)?)?(,)?\W*)?'
- r'((?P-?((\d*\.\d+)|\d+))\W*m(in(ute)?(s)?)?(,)?\W*)?'
- r'((?P-?((\d*\.\d+)|\d+))\W*s(ec(ond)?(s)?)?)?\W*$',
- six.text_type(string))
- if not d:
- raise TypeError("'%s' is not a valid time interval" % string)
- d = d.groupdict(0)
-
- return datetime.timedelta(**dict(( (k, float(v)) for k,v in d.items())))
-
-
-def divide(obj1, obj2, as_float=False):
- """
- Allows for the division of timedeltas by other timedeltas, or by
- floats/Decimals
-
- >>> from datetime import timedelta as td
- >>> divide(td(1), td(1))
- 1
- >>> divide(td(2), td(1))
- 2
- >>> divide(td(32), 16)
- datetime.timedelta(2)
- >>> divide(datetime.timedelta(1), datetime.timedelta(hours=6))
- 4
- >>> divide(datetime.timedelta(2), datetime.timedelta(3))
- 0
- >>> divide(datetime.timedelta(8), datetime.timedelta(3), as_float=True)
- 2.6666666666666665
- >>> divide(datetime.timedelta(8), 2.0)
- datetime.timedelta(4)
- >>> divide(datetime.timedelta(8), 2, as_float=True)
- Traceback (most recent call last):
- ...
- AssertionError: as_float=True is inappropriate when dividing timedelta by a number.
-
- """
- assert isinstance(obj1, datetime.timedelta), "First argument must be a timedelta."
- assert isinstance(obj2, (datetime.timedelta, int, float, Decimal)), "Second argument must be a timedelta or number"
-
- sec1 = obj1.days * 86400 + obj1.seconds
- if isinstance(obj2, datetime.timedelta):
- sec2 = obj2.days * 86400 + obj2.seconds
- value = sec1 / sec2
- if as_float:
- return value
- return int(value)
- else:
- if as_float:
- assert None, "as_float=True is inappropriate when dividing timedelta by a number."
- secs = sec1 / obj2
- if isinstance(secs, Decimal):
- secs = float(secs)
- return datetime.timedelta(seconds=secs)
-
-def modulo(obj1, obj2):
- """
- Allows for remainder division of timedelta by timedelta or integer.
-
- >>> from datetime import timedelta as td
- >>> modulo(td(5), td(2))
- datetime.timedelta(1)
- >>> modulo(td(6), td(3))
- datetime.timedelta(0)
- >>> modulo(td(15), 4 * 3600 * 24)
- datetime.timedelta(3)
-
- >>> modulo(5, td(1))
- Traceback (most recent call last):
- ...
- AssertionError: First argument must be a timedelta.
- >>> modulo(td(1), 2.8)
- Traceback (most recent call last):
- ...
- AssertionError: Second argument must be a timedelta or int.
- """
- assert isinstance(obj1, datetime.timedelta), "First argument must be a timedelta."
- assert isinstance(obj2, (datetime.timedelta, int)), "Second argument must be a timedelta or int."
-
- sec1 = obj1.days * 86400 + obj1.seconds
- if isinstance(obj2, datetime.timedelta):
- sec2 = obj2.days * 86400 + obj2.seconds
- return datetime.timedelta(seconds=sec1 % sec2)
- else:
- return datetime.timedelta(seconds=(sec1 % obj2))
-
-def percentage(obj1, obj2):
- """
- What percentage of obj2 is obj1? We want the answer as a float.
- >>> percentage(datetime.timedelta(4), datetime.timedelta(2))
- 200.0
- >>> percentage(datetime.timedelta(2), datetime.timedelta(4))
- 50.0
- """
- assert isinstance(obj1, datetime.timedelta), "First argument must be a timedelta."
- assert isinstance(obj2, datetime.timedelta), "Second argument must be a timedelta."
-
- return divide(obj1 * 100, obj2, as_float=True)
-
-def decimal_percentage(obj1, obj2):
- """
- >>> decimal_percentage(datetime.timedelta(4), datetime.timedelta(2))
- Decimal('200.0')
- >>> decimal_percentage(datetime.timedelta(2), datetime.timedelta(4))
- Decimal('50.0')
- """
- return Decimal(str(percentage(obj1, obj2)))
-
-
-def multiply(obj, val):
- """
- Allows for the multiplication of timedeltas by float values.
- >>> multiply(datetime.timedelta(seconds=20), 1.5)
- datetime.timedelta(0, 30)
- >>> multiply(datetime.timedelta(1), 2.5)
- datetime.timedelta(2, 43200)
- >>> multiply(datetime.timedelta(1), 3)
- datetime.timedelta(3)
- >>> multiply(datetime.timedelta(1), Decimal("5.5"))
- datetime.timedelta(5, 43200)
- >>> multiply(datetime.date.today(), 2.5)
- Traceback (most recent call last):
- ...
- AssertionError: First argument must be a timedelta.
- >>> multiply(datetime.timedelta(1), "2")
- Traceback (most recent call last):
- ...
- AssertionError: Second argument must be a number.
- """
-
- assert isinstance(obj, datetime.timedelta), "First argument must be a timedelta."
- assert isinstance(val, (int, float, Decimal)), "Second argument must be a number."
-
- sec = obj.days * 86400 + obj.seconds
- sec *= val
- if isinstance(sec, Decimal):
- sec = float(sec)
- return datetime.timedelta(seconds=sec)
-
-
-def round_to_nearest(obj, timedelta):
- """
- The obj is rounded to the nearest whole number of timedeltas.
-
- obj can be a timedelta, datetime or time object.
-
- >>> round_to_nearest(datetime.datetime(2012, 1, 1, 9, 43), datetime.timedelta(1))
- datetime.datetime(2012, 1, 1, 0, 0)
- >>> round_to_nearest(datetime.datetime(2012, 1, 1, 9, 43), datetime.timedelta(hours=1))
- datetime.datetime(2012, 1, 1, 10, 0)
- >>> round_to_nearest(datetime.datetime(2012, 1, 1, 9, 43), datetime.timedelta(minutes=15))
- datetime.datetime(2012, 1, 1, 9, 45)
- >>> round_to_nearest(datetime.datetime(2012, 1, 1, 9, 43), datetime.timedelta(minutes=1))
- datetime.datetime(2012, 1, 1, 9, 43)
-
- >>> td = datetime.timedelta(minutes=30)
- >>> round_to_nearest(datetime.timedelta(minutes=0), td)
- datetime.timedelta(0)
- >>> round_to_nearest(datetime.timedelta(minutes=14), td)
- datetime.timedelta(0)
- >>> round_to_nearest(datetime.timedelta(minutes=15), td)
- datetime.timedelta(0, 1800)
- >>> round_to_nearest(datetime.timedelta(minutes=29), td)
- datetime.timedelta(0, 1800)
- >>> round_to_nearest(datetime.timedelta(minutes=30), td)
- datetime.timedelta(0, 1800)
- >>> round_to_nearest(datetime.timedelta(minutes=42), td)
- datetime.timedelta(0, 1800)
- >>> round_to_nearest(datetime.timedelta(hours=7, minutes=22), td)
- datetime.timedelta(0, 27000)
-
- >>> td = datetime.timedelta(minutes=15)
- >>> round_to_nearest(datetime.timedelta(minutes=0), td)
- datetime.timedelta(0)
- >>> round_to_nearest(datetime.timedelta(minutes=14), td)
- datetime.timedelta(0, 900)
- >>> round_to_nearest(datetime.timedelta(minutes=15), td)
- datetime.timedelta(0, 900)
- >>> round_to_nearest(datetime.timedelta(minutes=29), td)
- datetime.timedelta(0, 1800)
- >>> round_to_nearest(datetime.timedelta(minutes=30), td)
- datetime.timedelta(0, 1800)
- >>> round_to_nearest(datetime.timedelta(minutes=42), td)
- datetime.timedelta(0, 2700)
- >>> round_to_nearest(datetime.timedelta(hours=7, minutes=22), td)
- datetime.timedelta(0, 26100)
-
- >>> td = datetime.timedelta(minutes=30)
- >>> round_to_nearest(datetime.datetime(2010,1,1,9,22), td)
- datetime.datetime(2010, 1, 1, 9, 30)
- >>> round_to_nearest(datetime.datetime(2010,1,1,9,32), td)
- datetime.datetime(2010, 1, 1, 9, 30)
- >>> round_to_nearest(datetime.datetime(2010,1,1,9,42), td)
- datetime.datetime(2010, 1, 1, 9, 30)
-
- >>> round_to_nearest(datetime.time(0,20), td)
- datetime.time(0, 30)
-
- TODO: test with tzinfo (non-naive) datetimes/times.
- """
-
- assert isinstance(obj, (datetime.datetime, datetime.timedelta, datetime.time)), "First argument must be datetime, time or timedelta."
- assert isinstance(timedelta, datetime.timedelta), "Second argument must be a timedelta."
-
- time_only = False
- if isinstance(obj, datetime.timedelta):
- counter = datetime.timedelta(0)
- elif isinstance(obj, datetime.datetime):
- counter = datetime.datetime.combine(obj.date(), datetime.time(0, tzinfo=obj.tzinfo))
- elif isinstance(obj, datetime.time):
- counter = datetime.datetime.combine(datetime.date.today(), datetime.time(0, tzinfo=obj.tzinfo))
- obj = datetime.datetime.combine(datetime.date.today(), obj)
- time_only = True
-
- diff = abs(obj - counter)
- while counter < obj:
- old_diff = diff
- counter += timedelta
- diff = abs(obj - counter)
-
- if counter == obj:
- result = obj
- elif diff <= old_diff:
- result = counter
- else:
- result = counter - timedelta
-
- if time_only:
- return result.time()
- else:
- return result
-
-def decimal_hours(timedelta, decimal_places=None):
- """
- Return a decimal value of the number of hours that this timedelta
- object refers to.
- """
- hours = Decimal(timedelta.days*24) + Decimal(timedelta.seconds) / 3600
- if decimal_places:
- return hours.quantize(Decimal(str(10**-decimal_places)))
- return hours
-
-def week_containing(date):
- if date.weekday():
- date -= datetime.timedelta(date.weekday())
-
- return date, date + datetime.timedelta(6)
-
-try:
- datetime.timedelta().total_seconds
- def total_seconds(timedelta):
- return timedelta.total_seconds()
-except AttributeError:
- def total_seconds(timedelta):
- """
- Python < 2.7 does not have datetime.timedelta.total_seconds
- """
- return timedelta.days * 86400 + timedelta.seconds
-
-if __name__ == "__main__":
- import doctest
- doctest.testmod()
diff --git a/timedelta/models.py b/timedelta/models.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/timedelta/templatetags/__init__.py b/timedelta/templatetags/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/timedelta/templatetags/decimal_hours.py b/timedelta/templatetags/decimal_hours.py
deleted file mode 100644
index 0a7a9196a..000000000
--- a/timedelta/templatetags/decimal_hours.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django import template
-register = template.Library()
-
-from ..helpers import decimal_hours as dh
-
-@register.filter(name='decimal_hours')
-def decimal_hours(value, decimal_places=None):
- if value is None:
- return value
- return dh(value, decimal_places)
diff --git a/timedelta/templatetags/timedelta.py b/timedelta/templatetags/timedelta.py
deleted file mode 100644
index 54684cfdd..000000000
--- a/timedelta/templatetags/timedelta.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from django import template
-register = template.Library()
-
-# Don't really like using relative imports, but no choice here!
-from ..helpers import parse, nice_repr, iso8601_repr, total_seconds as _total_seconds
-
-
-@register.filter(name='timedelta')
-def timedelta(value, display="long"):
- if value is None:
- return value
- if isinstance(value, basestring):
- value = parse(value)
- return nice_repr(value, display)
-
-
-@register.filter(name='iso8601')
-def iso8601(value):
- return timedelta(value, display='iso8601')
-
-
-@register.filter(name='total_seconds')
-def total_seconds(value):
- if value is None:
- return value
- return _total_seconds(value)
-
-
-@register.filter(name='total_seconds_sort')
-def total_seconds(value, places=10):
- if value is None:
- return value
- return ("%0" + str(places) + "i") % _total_seconds(value)
-
diff --git a/timedelta/tests.py b/timedelta/tests.py
deleted file mode 100644
index 02c6af85d..000000000
--- a/timedelta/tests.py
+++ /dev/null
@@ -1,128 +0,0 @@
-from unittest import TestCase
-import datetime
-import doctest
-
-from django.core.exceptions import ValidationError
-from django.db import models
-from django.utils import six
-
-from .fields import TimedeltaField
-import timedelta.helpers
-import timedelta.forms
-import timedelta.widgets
-
-class MinMaxTestModel(models.Model):
- min = TimedeltaField(min_value=datetime.timedelta(1))
- max = TimedeltaField(max_value=datetime.timedelta(1))
- minmax = TimedeltaField(min_value=datetime.timedelta(1), max_value=datetime.timedelta(7))
-
-class IntTestModel(models.Model):
- field = TimedeltaField(min_value=1, max_value=86400)
-
-class FloatTestModel(models.Model):
- field = TimedeltaField(min_value=1.0, max_value=86400.0)
-
-class TimedeltaModelFieldTest(TestCase):
- def test_validate(self):
- test = MinMaxTestModel(
- min=datetime.timedelta(1),
- max=datetime.timedelta(1),
- minmax=datetime.timedelta(1)
- )
- test.full_clean() # This should have met validation requirements.
-
- test.min = datetime.timedelta(hours=23)
- self.assertRaises(ValidationError, test.full_clean)
-
- test.min = datetime.timedelta(hours=25)
- test.full_clean()
-
- test.max = datetime.timedelta(11)
- self.assertRaises(ValidationError, test.full_clean)
-
- test.max = datetime.timedelta(hours=20)
- test.full_clean()
-
- test.minmax = datetime.timedelta(0)
- self.assertRaises(ValidationError, test.full_clean)
- test.minmax = datetime.timedelta(22)
- self.assertRaises(ValidationError, test.full_clean)
- test.minmax = datetime.timedelta(6, hours=23, minutes=59, seconds=59)
- test.full_clean()
-
- def test_from_int(self):
- """
- Check that integers can be used to define the min_value and max_value
- arguments, and that when assigned an integer, TimedeltaField converts
- to timedelta.
- """
-
- test = IntTestModel()
-
- # valid
- test.field = 3600
- self.assertEquals(test.field, datetime.timedelta(seconds=3600))
- test.full_clean()
-
- # invalid
- test.field = 0
- self.assertRaises(ValidationError, test.full_clean)
-
- # also invalid
- test.field = 86401
- self.assertRaises(ValidationError, test.full_clean)
-
- def test_from_float(self):
- """
- Check that floats can be used to define the min_value and max_value
- arguments, and that when assigned a float, TimedeltaField converts
- to timedelta.
- """
-
- test = FloatTestModel()
-
- # valid
- test.field = 3600.0
- self.assertEquals(test.field, datetime.timedelta(seconds=3600))
- test.full_clean()
-
- # invalid
- test.field = 0.0
- self.assertRaises(ValidationError, test.full_clean)
-
- # also invalid
- test.field = 86401.0
- self.assertRaises(ValidationError, test.full_clean)
-
- def test_deconstruct(self):
- """
- Check that the deconstruct() method of TimedeltaField is returning the
- min_value, max_value and default kwargs as floats.
- """
-
- field = TimedeltaField(
- min_value=datetime.timedelta(minutes=5),
- max_value=datetime.timedelta(minutes=15),
- default=datetime.timedelta(minutes=30),
- )
-
- kwargs = field.deconstruct()[3]
- self.assertEqual(kwargs['default'], 1800.0)
- self.assertEqual(kwargs['max_value'], 900.0)
- self.assertEqual(kwargs['min_value'], 300.0)
-
- def test_load_from_db(self):
- obj = MinMaxTestModel.objects.create(min='2 days', max='2 minutes', minmax='3 days')
- self.assertEquals(datetime.timedelta(2), obj.min)
- self.assertEquals(datetime.timedelta(0, 120), obj.max)
- self.assertEquals(datetime.timedelta(3), obj.minmax)
-
- obj = MinMaxTestModel.objects.get()
- self.assertEquals(datetime.timedelta(2), obj.min)
- self.assertEquals(datetime.timedelta(0, 120), obj.max)
- self.assertEquals(datetime.timedelta(3), obj.minmax)
-
-def load_tests(loader, tests, ignore):
- tests.addTests(doctest.DocTestSuite(timedelta.helpers))
- tests.addTests(doctest.DocTestSuite(timedelta.forms))
- return tests
diff --git a/timedelta/widgets.py b/timedelta/widgets.py
deleted file mode 100644
index 233c00f40..000000000
--- a/timedelta/widgets.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import datetime
-
-from django import forms
-from django.utils import six
-
-from .helpers import nice_repr, parse
-
-class TimedeltaWidget(forms.TextInput):
- def __init__(self, *args, **kwargs):
- return super(TimedeltaWidget, self).__init__(*args, **kwargs)
-
- def render(self, name, value, attrs=None):
- if value is None:
- value = ""
- elif isinstance(value, six.string_types):
- pass
- else:
- if isinstance(value, int):
- value = datetime.timedelta(seconds=value)
- value = nice_repr(value)
- return super(TimedeltaWidget, self).render(name, value, attrs)
-
- def _has_changed(self, initial, data):
- """
- We need to make sure the objects are of the canonical form, as a
- string comparison may needlessly fail.
- """
- if initial in ["", None] and data in ["", None]:
- return False
-
- if initial in ["", None] or data in ["", None]:
- return True
-
- if initial:
- if not isinstance(initial, datetime.timedelta):
- initial = parse(initial)
-
- if not isinstance(data, datetime.timedelta):
- try:
- data = parse(data)
- except TypeError:
- # initial didn't throw a TypeError, so this must be different
- # from initial
- return True
-
- return initial != data