mirror of
https://github.com/django/django.git
synced 2024-12-23 01:25:58 +00:00
Fixed #1443 -- Django's various bits now support dates before 1900. Thanks to SmileyChris, Chris Green, Fredrik Lundh and others for patches and design help
git-svn-id: http://code.djangoproject.com/svn/django/trunk@7946 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
f6fafc02c8
commit
df2b19cc17
@ -8,6 +8,7 @@ from django.utils.translation import get_date_formats
|
||||
from django.utils.encoding import force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.views.generic import date_based
|
||||
from django.utils import datetime_safe
|
||||
|
||||
class CalendarPlugin(DatabrowsePlugin):
|
||||
def __init__(self, field_names=None):
|
||||
@ -33,12 +34,13 @@ class CalendarPlugin(DatabrowsePlugin):
|
||||
|
||||
def urls(self, plugin_name, easy_instance_field):
|
||||
if isinstance(easy_instance_field.field, models.DateField):
|
||||
d = easy_instance_field.raw_value
|
||||
return [mark_safe(u'%s%s/%s/%s/%s/%s/' % (
|
||||
easy_instance_field.model.url(),
|
||||
plugin_name, easy_instance_field.field.name,
|
||||
easy_instance_field.raw_value.year,
|
||||
easy_instance_field.raw_value.strftime('%b').lower(),
|
||||
easy_instance_field.raw_value.day))]
|
||||
d.year,
|
||||
datetime_safe.new_date(d).strftime('%b').lower(),
|
||||
d.day))]
|
||||
|
||||
def model_view(self, request, model_databrowse, url):
|
||||
self.model, self.site = model_databrowse.model, model_databrowse.site
|
||||
|
@ -8,6 +8,7 @@ except ImportError:
|
||||
from StringIO import StringIO
|
||||
from django.db import models
|
||||
from django.utils.encoding import smart_str, smart_unicode
|
||||
from django.utils import datetime_safe
|
||||
|
||||
class SerializationError(Exception):
|
||||
"""Something bad happened during serialization."""
|
||||
@ -59,7 +60,8 @@ class Serializer(object):
|
||||
Convert a field's value to a string.
|
||||
"""
|
||||
if isinstance(field, models.DateTimeField):
|
||||
value = getattr(obj, field.name).strftime("%Y-%m-%d %H:%M:%S")
|
||||
d = datetime_safe.new_datetime(getattr(obj, field.name))
|
||||
value = d.strftime("%Y-%m-%d %H:%M:%S")
|
||||
else:
|
||||
value = field.flatten_data(follow=None, obj=obj).get(field.name, "")
|
||||
return smart_unicode(value)
|
||||
|
@ -6,6 +6,7 @@ import datetime
|
||||
from django.utils import simplejson
|
||||
from django.core.serializers.python import Serializer as PythonSerializer
|
||||
from django.core.serializers.python import Deserializer as PythonDeserializer
|
||||
from django.utils import datetime_safe
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
@ -51,9 +52,11 @@ class DjangoJSONEncoder(simplejson.JSONEncoder):
|
||||
|
||||
def default(self, o):
|
||||
if isinstance(o, datetime.datetime):
|
||||
return o.strftime("%s %s" % (self.DATE_FORMAT, self.TIME_FORMAT))
|
||||
d = datetime_safe.new_datetime(o)
|
||||
return d.strftime("%s %s" % (self.DATE_FORMAT, self.TIME_FORMAT))
|
||||
elif isinstance(o, datetime.date):
|
||||
return o.strftime(self.DATE_FORMAT)
|
||||
d = datetime_safe.new_date(o)
|
||||
return d.strftime(self.DATE_FORMAT)
|
||||
elif isinstance(o, datetime.time):
|
||||
return o.strftime(self.TIME_FORMAT)
|
||||
elif isinstance(o, decimal.Decimal):
|
||||
|
@ -141,10 +141,6 @@ def _isValidDate(date_string):
|
||||
# Could use time.strptime here and catch errors, but datetime.date below
|
||||
# produces much friendlier error messages.
|
||||
year, month, day = map(int, date_string.split('-'))
|
||||
# This check is needed because strftime is used when saving the date
|
||||
# value to the database, and strftime requires that the year be >=1900.
|
||||
if year < 1900:
|
||||
raise ValidationError, _('Year must be 1900 or later.')
|
||||
try:
|
||||
date(year, month, day)
|
||||
except ValueError, e:
|
||||
|
@ -23,6 +23,7 @@ from django.utils.text import capfirst
|
||||
from django.utils.translation import ugettext_lazy, ugettext as _
|
||||
from django.utils.encoding import smart_unicode, force_unicode, smart_str
|
||||
from django.utils.maxlength import LegacyMaxlength
|
||||
from django.utils import datetime_safe
|
||||
|
||||
class NOT_PROVIDED:
|
||||
pass
|
||||
@ -557,7 +558,7 @@ class DateField(Field):
|
||||
if lookup_type in ('range', 'in'):
|
||||
value = [smart_unicode(v) for v in value]
|
||||
elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte') and hasattr(value, 'strftime'):
|
||||
value = value.strftime('%Y-%m-%d')
|
||||
value = datetime_safe.new_date(value).strftime('%Y-%m-%d')
|
||||
else:
|
||||
value = smart_unicode(value)
|
||||
return Field.get_db_prep_lookup(self, lookup_type, value)
|
||||
@ -589,7 +590,7 @@ class DateField(Field):
|
||||
# Casts dates into string format for entry into database.
|
||||
if value is not None:
|
||||
try:
|
||||
value = value.strftime('%Y-%m-%d')
|
||||
value = datetime_safe.new_date(value).strftime('%Y-%m-%d')
|
||||
except AttributeError:
|
||||
# If value is already a string it won't have a strftime method,
|
||||
# so we'll just let it pass through.
|
||||
@ -601,7 +602,11 @@ class DateField(Field):
|
||||
|
||||
def flatten_data(self, follow, obj=None):
|
||||
val = self._get_val_from_obj(obj)
|
||||
return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')}
|
||||
if val is None:
|
||||
data = ''
|
||||
else:
|
||||
data = datetime_safe.new_date(val).strftime("%Y-%m-%d")
|
||||
return {self.attname: data}
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {'form_class': forms.DateField}
|
||||
@ -668,8 +673,13 @@ class DateTimeField(DateField):
|
||||
def flatten_data(self,follow, obj = None):
|
||||
val = self._get_val_from_obj(obj)
|
||||
date_field, time_field = self.get_manipulator_field_names('')
|
||||
return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''),
|
||||
time_field: (val is not None and val.strftime("%H:%M:%S") or '')}
|
||||
if val is None:
|
||||
date_data = time_data = ''
|
||||
else:
|
||||
d = datetime_safe.new_datetime(val)
|
||||
date_data = d.strftime('%Y-%m-%d')
|
||||
time_data = d.strftime('%H:%M:%S')
|
||||
return {date_field: date_data, time_field: time_data}
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {'form_class': forms.DateTimeField}
|
||||
|
@ -9,6 +9,7 @@ from django.utils.datastructures import DotExpandedDict
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils import datetime_safe
|
||||
|
||||
def add_manipulators(sender):
|
||||
cls = sender
|
||||
@ -332,5 +333,6 @@ def manipulator_validator_unique_for_date(from_field, date_field, opts, lookup_t
|
||||
pass
|
||||
else:
|
||||
format_string = (lookup_type == 'date') and '%B %d, %Y' or '%B %Y'
|
||||
date_val = datetime_safe.new_datetime(date_val)
|
||||
raise validators.ValidationError, "Please enter a different %s. The one you entered is already being used for %s." % \
|
||||
(from_field.verbose_name, date_val.strftime(format_string))
|
||||
|
@ -15,6 +15,7 @@ from django.utils.html import escape, conditional_escape
|
||||
from django.utils.translation import ugettext
|
||||
from django.utils.encoding import StrAndUnicode, force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils import datetime_safe
|
||||
from util import flatatt
|
||||
|
||||
__all__ = (
|
||||
@ -170,6 +171,7 @@ class DateTimeInput(Input):
|
||||
if value is None:
|
||||
value = ''
|
||||
elif hasattr(value, 'strftime'):
|
||||
value = datetime_safe.new_datetime(value)
|
||||
value = value.strftime(self.format)
|
||||
return super(DateTimeInput, self).render(name, value, attrs)
|
||||
|
||||
|
89
django/utils/datetime_safe.py
Normal file
89
django/utils/datetime_safe.py
Normal file
@ -0,0 +1,89 @@
|
||||
# Python's datetime strftime doesn't handle dates before 1900.
|
||||
# These classes override date and datetime to support the formatting of a date
|
||||
# through its full "proleptic Gregorian" date range.
|
||||
#
|
||||
# Based on code submitted to comp.lang.python by Andrew Dalke
|
||||
#
|
||||
# >>> datetime_safe.date(1850, 8, 2).strftime("%Y/%M/%d was a %A")
|
||||
# '1850/08/02 was a Friday'
|
||||
|
||||
from datetime import date as real_date, datetime as real_datetime
|
||||
import re
|
||||
import time
|
||||
|
||||
class date(real_date):
|
||||
def strftime(self, fmt):
|
||||
return strftime(self, fmt)
|
||||
|
||||
class datetime(real_datetime):
|
||||
def strftime(self, fmt):
|
||||
return strftime(self, fmt)
|
||||
|
||||
def combine(self, date, time):
|
||||
return datetime(date.year, date.month, date.day, time.hour, time.minute, time.microsecond, time.tzinfo)
|
||||
|
||||
def date(self):
|
||||
return date(self.year, self.month, self.day)
|
||||
|
||||
def new_date(d):
|
||||
"Generate a safe date from a datetime.date object."
|
||||
return date(d.year, d.month, d.day)
|
||||
|
||||
def new_datetime(d):
|
||||
"""
|
||||
Generate a safe datetime from a datetime.date or datetime.datetime object.
|
||||
"""
|
||||
kw = [d.year, d.month, d.day]
|
||||
if isinstance(d, real_datetime):
|
||||
kw.extend([d.hour, d.minute, d.second, d.microsecond, d.tzinfo])
|
||||
return datetime(*kw)
|
||||
|
||||
# This library does not support strftime's "%s" or "%y" format strings.
|
||||
# Allowed if there's an even number of "%"s because they are escaped.
|
||||
_illegal_formatting = re.compile(r"((^|[^%])(%%)*%[sy])")
|
||||
|
||||
def _findall(text, substr):
|
||||
# Also finds overlaps
|
||||
sites = []
|
||||
i = 0
|
||||
while 1:
|
||||
j = text.find(substr, i)
|
||||
if j == -1:
|
||||
break
|
||||
sites.append(j)
|
||||
i=j+1
|
||||
return sites
|
||||
|
||||
def strftime(dt, fmt):
|
||||
if dt.year >= 1900:
|
||||
return super(type(dt), dt).strftime(fmt)
|
||||
illegal_formatting = _illegal_formatting.search(fmt)
|
||||
if illegal_formatting:
|
||||
raise TypeError("strftime of dates before 1900 does not handle" + illegal_formatting.group(0))
|
||||
|
||||
year = dt.year
|
||||
# For every non-leap year century, advance by
|
||||
# 6 years to get into the 28-year repeat cycle
|
||||
delta = 2000 - year
|
||||
off = 6 * (delta // 100 + delta // 400)
|
||||
year = year + off
|
||||
|
||||
# Move to around the year 2000
|
||||
year = year + ((2000 - year) // 28) * 28
|
||||
timetuple = dt.timetuple()
|
||||
s1 = time.strftime(fmt, (year,) + timetuple[1:])
|
||||
sites1 = _findall(s1, str(year))
|
||||
|
||||
s2 = time.strftime(fmt, (year+28,) + timetuple[1:])
|
||||
sites2 = _findall(s2, str(year+28))
|
||||
|
||||
sites = []
|
||||
for site in sites1:
|
||||
if site in sites2:
|
||||
sites.append(site)
|
||||
|
||||
s = s1
|
||||
syear = "%4d" % (dt.year,)
|
||||
for site in sites:
|
||||
s = s[:site] + syear + s[site+4:]
|
||||
return s
|
37
tests/regressiontests/datetime_safe/tests.py
Normal file
37
tests/regressiontests/datetime_safe/tests.py
Normal file
@ -0,0 +1,37 @@
|
||||
r"""
|
||||
>>> from datetime import date as original_date, datetime as original_datetime
|
||||
>>> from django.utils.datetime_safe import date, datetime
|
||||
>>> just_safe = (1900, 1, 1)
|
||||
>>> just_unsafe = (1899, 12, 31, 23, 59, 59)
|
||||
>>> really_old = (20, 1, 1)
|
||||
>>> more_recent = (2006, 1, 1)
|
||||
|
||||
>>> original_datetime(*more_recent) == datetime(*more_recent)
|
||||
True
|
||||
>>> original_datetime(*really_old) == datetime(*really_old)
|
||||
True
|
||||
>>> original_date(*more_recent) == date(*more_recent)
|
||||
True
|
||||
>>> original_date(*really_old) == date(*really_old)
|
||||
True
|
||||
|
||||
>>> original_date(*just_safe).strftime('%Y-%m-%d') == date(*just_safe).strftime('%Y-%m-%d')
|
||||
True
|
||||
>>> original_datetime(*just_safe).strftime('%Y-%m-%d') == datetime(*just_safe).strftime('%Y-%m-%d')
|
||||
True
|
||||
|
||||
>>> date(*just_unsafe[:3]).strftime('%Y-%m-%d (weekday %w)')
|
||||
'1899-12-31 (weekday 0)'
|
||||
>>> date(*just_safe).strftime('%Y-%m-%d (weekday %w)')
|
||||
'1900-01-01 (weekday 1)'
|
||||
|
||||
>>> datetime(*just_unsafe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)')
|
||||
'1899-12-31 23:59:59 (weekday 0)'
|
||||
>>> datetime(*just_safe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)')
|
||||
'1900-01-01 00:00:00 (weekday 1)'
|
||||
|
||||
>>> date(*just_safe).strftime('%y') # %y will error before this date
|
||||
'00'
|
||||
>>> datetime(*just_safe).strftime('%y')
|
||||
'00'
|
||||
"""
|
Loading…
Reference in New Issue
Block a user