| 
5 | 5 | 
 
  | 
6 | 6 | # flake8: noqa  | 
7 | 7 | from __future__ import unicode_literals  | 
 | 8 | +import datetime  | 
 | 9 | +from datetime import timedelta  | 
 | 10 | +import re  | 
8 | 11 | from django.core.exceptions import ImproperlyConfigured  | 
9 | 12 | from django.conf import settings  | 
10 | 13 | from django.utils.encoding import force_text  | 
@@ -258,3 +261,78 @@ def apply_markdown(text):  | 
258 | 261 |     SHORT_SEPARATORS = (b',', b':')  | 
259 | 262 |     LONG_SEPARATORS = (b', ', b': ')  | 
260 | 263 |     INDENT_SEPARATORS = (b',', b': ')  | 
 | 264 | + | 
 | 265 | + | 
 | 266 | +if django.VERSION >= (1, 8):  | 
 | 267 | +    from django.utils.dateparse import parse_duration  | 
 | 268 | +    from django.utils.duration import duration_string  | 
 | 269 | +    from django.db.models import DurationField  | 
 | 270 | +else:  | 
 | 271 | +    from django.db.models import BigIntegerField  | 
 | 272 | + | 
 | 273 | +    class DurationField(BigIntegerField):  | 
 | 274 | +        def get_db_prep_value(self, value, connection, prepared=False):  | 
 | 275 | +            if value is None:  | 
 | 276 | +                return None  | 
 | 277 | +            return total_seconds(value) * 1000000  | 
 | 278 | + | 
 | 279 | + | 
 | 280 | +    # Backported from django 1.8  | 
 | 281 | +    standard_duration_re = re.compile(  | 
 | 282 | +        r'^'  | 
 | 283 | +        r'(?:(?P<days>-?\d+) )?'  | 
 | 284 | +        r'((?:(?P<hours>\d+):)(?=\d+:\d+))?'  | 
 | 285 | +        r'(?:(?P<minutes>\d+):)?'  | 
 | 286 | +        r'(?P<seconds>\d+)'  | 
 | 287 | +        r'(?:\.(?P<microseconds>\d{1,6})\d{0,6})?'  | 
 | 288 | +        r'$'  | 
 | 289 | +    )  | 
 | 290 | + | 
 | 291 | +    # Support the sections of ISO 8601 date representation that are accepted by  | 
 | 292 | +    # timedelta  | 
 | 293 | +    iso8601_duration_re = re.compile(  | 
 | 294 | +        r'^P'  | 
 | 295 | +        r'(?:(?P<days>\d+(.\d+)?)D)?'  | 
 | 296 | +        r'(?:T'  | 
 | 297 | +        r'(?:(?P<hours>\d+(.\d+)?)H)?'  | 
 | 298 | +        r'(?:(?P<minutes>\d+(.\d+)?)M)?'  | 
 | 299 | +        r'(?:(?P<seconds>\d+(.\d+)?)S)?'  | 
 | 300 | +        r')?'  | 
 | 301 | +        r'$'  | 
 | 302 | +    )  | 
 | 303 | + | 
 | 304 | +    def parse_duration(value):  | 
 | 305 | +        """Parses a duration string and returns a datetime.timedelta.  | 
 | 306 | +
  | 
 | 307 | +        The preferred format for durations in Django is '%d %H:%M:%S.%f'.  | 
 | 308 | +
  | 
 | 309 | +        Also supports ISO 8601 representation.  | 
 | 310 | +        """  | 
 | 311 | +        match = standard_duration_re.match(value)  | 
 | 312 | +        if not match:  | 
 | 313 | +            match = iso8601_duration_re.match(value)  | 
 | 314 | +        if match:  | 
 | 315 | +            kw = match.groupdict()  | 
 | 316 | +            if kw.get('microseconds'):  | 
 | 317 | +                kw['microseconds'] = kw['microseconds'].ljust(6, unicode_to_repr('0'))  | 
 | 318 | +            kw = dict((k, float(v)) for k, v in six.iteritems(kw) if v is not None)  | 
 | 319 | +            return datetime.timedelta(**kw)  | 
 | 320 | + | 
 | 321 | +    def duration_string(duration):  | 
 | 322 | +        days = duration.days  | 
 | 323 | +        seconds = duration.seconds  | 
 | 324 | +        microseconds = duration.microseconds  | 
 | 325 | + | 
 | 326 | +        minutes = seconds // 60  | 
 | 327 | +        seconds = seconds % 60  | 
 | 328 | + | 
 | 329 | +        hours = minutes // 60  | 
 | 330 | +        minutes = minutes % 60  | 
 | 331 | + | 
 | 332 | +        string = '{0:02d}:{1:02d}:{2:02d}'.format(hours, minutes, seconds)  | 
 | 333 | +        if days:  | 
 | 334 | +            string = '{0} '.format(days) + string  | 
 | 335 | +        if microseconds:  | 
 | 336 | +            string += '.{0:06d}'.format(microseconds)  | 
 | 337 | + | 
 | 338 | +        return string  | 
0 commit comments