Ticket #1443: datetime_safe.diff

File datetime_safe.diff, 13.6 KB (added by Chris Beaven, 16 years ago)

New and improved!

  • django/db/models/manipulators.py

     
    99from django.utils.text import capfirst
    1010from django.utils.encoding import smart_str
    1111from django.utils.translation import ugettext as _
     12from django.utils import datetime_safe
    1213
    1314def add_manipulators(sender):
    1415    cls = sender
     
    332333            pass
    333334        else:
    334335            format_string = (lookup_type == 'date') and '%B %d, %Y' or '%B %Y'
     336            date_val = datetime_safe.new_datetime(date_val)
    335337            raise validators.ValidationError, "Please enter a different %s. The one you entered is already being used for %s." % \
    336338                (from_field.verbose_name, date_val.strftime(format_string))
  • django/db/models/fields/__init__.py

     
    2020from django.utils.translation import ugettext_lazy, ugettext as _
    2121from django.utils.encoding import smart_unicode, force_unicode, smart_str
    2222from django.utils.maxlength import LegacyMaxlength
     23from django.utils import datetime_safe
    2324
    2425class NOT_PROVIDED:
    2526    pass
     
    530531        if lookup_type == 'range':
    531532            value = [smart_unicode(v) for v in value]
    532533        elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte') and hasattr(value, 'strftime'):
    533             value = value.strftime('%Y-%m-%d')
     534            value = datetime_safe.new_date(value).strftime('%Y-%m-%d')
    534535        else:
    535536            value = smart_unicode(value)
    536537        return Field.get_db_prep_lookup(self, lookup_type, value)
     
    562563        # Casts dates into string format for entry into database.
    563564        if value is not None:
    564565            try:
    565                 value = value.strftime('%Y-%m-%d')
     566                value = datetime_safe.new_date(value).strftime('%Y-%m-%d')
    566567            except AttributeError:
    567568                # If value is already a string it won't have a strftime method,
    568569                # so we'll just let it pass through.
     
    574575
    575576    def flatten_data(self, follow, obj=None):
    576577        val = self._get_val_from_obj(obj)
    577         return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')}
     578        if val is None:
     579            data = ''
     580        else:
     581            data = datetime_safe.new_date(val).strftime('%Y-%m-%d')
     582        return {self.attname: data}
    578583
    579584    def formfield(self, **kwargs):
    580585        defaults = {'form_class': forms.DateField}
     
    641646    def flatten_data(self,follow, obj = None):
    642647        val = self._get_val_from_obj(obj)
    643648        date_field, time_field = self.get_manipulator_field_names('')
    644         return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),
    645                 time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
     649        if val is None:
     650            date_data = ''
     651            time_data = ''
     652        else:
     653            d = datetime_safe.new_datetime(val)
     654            date_data = d.strftime('%Y-%m-%d')
     655            time_data = d.strftime('%H:%M:%S')
     656        return {date_field: date_data, time_field: time_data}
    646657
    647658    def formfield(self, **kwargs):
    648659        defaults = {'form_class': forms.DateTimeField}
     
    842853        self.path, self.match, self.recursive = path, match, recursive
    843854        kwargs['max_length'] = kwargs.get('max_length', 100)
    844855        Field.__init__(self, verbose_name, name, **kwargs)
    845    
     856
    846857    def formfield(self, **kwargs):
    847858        defaults = {
    848859            'path': self.path,
  • django/core/serializers/json.py

     
    66from django.utils import simplejson
    77from django.core.serializers.python import Serializer as PythonSerializer
    88from django.core.serializers.python import Deserializer as PythonDeserializer
     9from django.utils import datetime_safe
    910try:
    1011    from cStringIO import StringIO
    1112except ImportError:
     
    2021    Convert a queryset to JSON.
    2122    """
    2223    internal_use_only = False
    23    
     24
    2425    def end_serialization(self):
    2526        self.options.pop('stream', None)
    2627        self.options.pop('fields', None)
     
    5152
    5253    def default(self, o):
    5354        if isinstance(o, datetime.datetime):
    54             return o.strftime("%s %s" % (self.DATE_FORMAT, self.TIME_FORMAT))
     55            d = datetime_safe.new_datetime(o)
     56            return d.strftime("%s %s" % (self.DATE_FORMAT, self.TIME_FORMAT))
    5557        elif isinstance(o, datetime.date):
    56             return o.strftime(self.DATE_FORMAT)
     58            d = datetime_safe.new_date(o)
     59            return d.strftime(self.DATE_FORMAT)
    5760        elif isinstance(o, datetime.time):
    5861            return o.strftime(self.TIME_FORMAT)
    5962        elif isinstance(o, decimal.Decimal):
  • django/core/serializers/base.py

     
    88    from StringIO import StringIO
    99from django.db import models
    1010from django.utils.encoding import smart_str, smart_unicode
     11from django.utils import datetime_safe
    1112
    1213class SerializationError(Exception):
    1314    """Something bad happened during serialization."""
     
    5960        Convert a field's value to a string.
    6061        """
    6162        if isinstance(field, models.DateTimeField):
    62             value = getattr(obj, field.name).strftime("%Y-%m-%d %H:%M:%S")
     63            d = datetime_safe.new_datetime(getattr(obj, field.name))
     64            value = d.strftime("%Y-%m-%d %H:%M:%S")
    6365        else:
    6466            value = field.flatten_data(follow=None, obj=obj).get(field.name, "")
    6567        return smart_unicode(value)
  • django/core/validators.py

     
    141141    # Could use time.strptime here and catch errors, but datetime.date below
    142142    # produces much friendlier error messages.
    143143    year, month, day = map(int, date_string.split('-'))
    144     # This check is needed because strftime is used when saving the date
    145     # value to the database, and strftime requires that the year be >=1900.
    146     if year < 1900:
    147         raise ValidationError, _('Year must be 1900 or later.')
    148144    try:
    149145        date(year, month, day)
    150146    except ValueError, e:
     
    407403    """
    408404    Usage: If you create an instance of the IsPowerOf validator:
    409405        v = IsAPowerOf(2)
    410    
     406
    411407    The following calls will succeed:
    412         v(4, None) 
     408        v(4, None)
    413409        v(8, None)
    414410        v(16, None)
    415    
     411
    416412    But this call:
    417413        v(17, None)
    418414    will raise "django.core.validators.ValidationError: ['This value must be a power of 2.']"
  • django/utils/datetime_safe.py

     
     1# Python's datetime strftime doesn't handle dates before 1900.
     2# These classes override date and datetime to support the formatting of a date
     3# through its full "proleptic Gregorian" date range.
     4#
     5# Based on code submitted to comp.lang.python by Andrew Dalke
     6#
     7# >>> datetime_safe.date(1850, 8, 2).strftime("%Y/%M/%d was a %A")
     8# '1850/08/02 was a Friday'
     9
     10from datetime import date as real_date, datetime as real_datetime
     11import time
     12import re
     13
     14
     15class date(real_date):
     16    def strftime(self, fmt):
     17        return strftime(self, fmt)
     18
     19
     20class datetime(real_datetime):
     21    def strftime(self, fmt):
     22        return strftime(self, fmt)
     23    def combine(self, date, time):
     24        return datetime(date.year, date.month, date.day, time.hour, time.minute, time.microsecond, time.tzinfo)
     25    def date(self):
     26        return date(self.year, self.month, self.day)
     27
     28
     29def new_date(d):
     30    """ Generate a safe date from a datetime.date object """
     31    return date(d.year, d.month, d.day)
     32
     33
     34def new_datetime(d):
     35    """
     36    Generate a safe datetime from a datetime.date or datetime.datetime object
     37    """
     38    kw = [d.year, d.month, d.day]
     39    if isinstance(d, real_datetime):
     40        kw.extend([d.hour, d.minute, d.second, d.microsecond, d.tzinfo])
     41    return datetime(*kw)
     42
     43
     44# No support for strftime's "%s" or "%y".
     45# Allowed if there's an even number of "%"s because they are escaped.
     46_illegal_formatting = re.compile(r"((^|[^%])(%%)*%[sy])")
     47
     48def _findall(text, substr):
     49     # Also finds overlaps
     50     sites = []
     51     i = 0
     52     while 1:
     53         j = text.find(substr, i)
     54         if j == -1:
     55             break
     56         sites.append(j)
     57         i=j+1
     58     return sites
     59
     60def strftime(dt, fmt):
     61    if dt.year >= 1900:
     62        return super(type(dt), dt).strftime(fmt)
     63    illegal_formatting = _illegal_formatting.search(fmt)
     64    if illegal_formatting:
     65        raise TypeError("strftime of dates before 1900 does not handle" + illegal_formatting.group(0))
     66
     67    year = dt.year
     68    # For every non-leap year century, advance by
     69    # 6 years to get into the 28-year repeat cycle
     70    delta = 2000 - year
     71    off = 6*(delta // 100 + delta // 400)
     72    year = year + off
     73
     74    # Move to around the year 2000
     75    year = year + ((2000 - year)//28)*28
     76    timetuple = dt.timetuple()
     77    s1 = time.strftime(fmt, (year,) + timetuple[1:])
     78    sites1 = _findall(s1, str(year))
     79
     80    s2 = time.strftime(fmt, (year+28,) + timetuple[1:])
     81    sites2 = _findall(s2, str(year+28))
     82
     83    sites = []
     84    for site in sites1:
     85        if site in sites2:
     86            sites.append(site)
     87
     88    s = s1
     89    syear = "%4d" % (dt.year,)
     90    for site in sites:
     91        s = s[:site] + syear + s[site+4:]
     92    return s
  • django/contrib/databrowse/plugins/calendars.py

     
    88from django.utils.encoding import force_unicode
    99from django.utils.safestring import mark_safe
    1010from django.views.generic import date_based
     11from django.utils import datetime_safe
    1112
    1213class CalendarPlugin(DatabrowsePlugin):
    1314    def __init__(self, field_names=None):
     
    3334
    3435    def urls(self, plugin_name, easy_instance_field):
    3536        if isinstance(easy_instance_field.field, models.DateField):
     37            d = easy_instance_field.raw_value
    3638            return [mark_safe(u'%s%s/%s/%s/%s/%s/' % (
    3739                easy_instance_field.model.url(),
    3840                plugin_name, easy_instance_field.field.name,
    39                 easy_instance_field.raw_value.year,
    40                 easy_instance_field.raw_value.strftime('%b').lower(),
    41                 easy_instance_field.raw_value.day))]
     41                d.year,
     42                datetime_safe.new_date(d).strftime('%b').lower(),
     43                d.day))]
    4244
    4345    def model_view(self, request, model_databrowse, url):
    4446        self.model, self.site = model_databrowse.model, model_databrowse.site
  • django/newforms/widgets.py

     
    1515from django.utils.translation import ugettext
    1616from django.utils.encoding import StrAndUnicode, force_unicode
    1717from django.utils.safestring import mark_safe
     18from django.utils import datetime_safe
    1819from util import flatatt
    1920
    2021__all__ = (
     
    170171        if value is None:
    171172            value = ''
    172173        elif hasattr(value, 'strftime'):
     174            value = datetime_safe.new_datetime(value)
    173175            value = value.strftime(self.format)
    174176        return super(DateTimeInput, self).render(name, value, attrs)
    175177
  • tests/regressiontests/datetime_safe/tests.py

     
     1r"""
     2>>> original_datetime(*more_recent) == datetime(*more_recent)
     3True
     4>>> original_datetime(*really_old) == datetime(*really_old)
     5True
     6>>> original_date(*more_recent) == date(*more_recent)
     7True
     8>>> original_date(*really_old) == date(*really_old)
     9True
     10
     11>>> original_date(*just_safe).strftime('%Y-%m-%d') == date(*just_safe).strftime('%Y-%m-%d')
     12True
     13>>> original_datetime(*just_safe).strftime('%Y-%m-%d') == datetime(*just_safe).strftime('%Y-%m-%d')
     14True
     15
     16>>> date(*just_unsafe[:3]).strftime('%Y-%m-%d (weekday %w)')
     17'1899-12-31 (weekday 0)'
     18>>> date(*just_safe).strftime('%Y-%m-%d (weekday %w)')
     19'1900-01-01 (weekday 1)'
     20
     21>>> datetime(*just_unsafe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)')
     22'1899-12-31 23:59:59 (weekday 0)'
     23>>> datetime(*just_safe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)')
     24'1900-01-01 00:00:00 (weekday 1)'
     25
     26>>> date(*just_safe).strftime('%y')   # %y will error before this date
     27'00'
     28>>> datetime(*just_safe).strftime('%y')
     29'00'
     30"""
     31
     32from datetime import date as original_date, datetime as original_datetime
     33from django.utils.datetime_pg import date, datetime
     34
     35just_safe = (1900, 1, 1)
     36just_unsafe = (1899, 12, 31, 23, 59, 59)
     37really_old = (20, 1, 1)
     38more_recent = (2006, 1, 1)
Back to Top