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