Index: django/core/validators.py
===================================================================
--- django/core/validators.py	(revision 3817)
+++ django/core/validators.py	(working copy)
@@ -132,10 +132,6 @@
     # 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, gettext('Year must be 1900 or later.')
     try:
         date(year, month, day)
     except ValueError, e:
Index: django/db/backends/util.py
===================================================================
--- django/db/backends/util.py	(revision 3817)
+++ django/db/backends/util.py	(working copy)
@@ -1,4 +1,5 @@
 import datetime
+from django.utils import datetime_pg
 from time import time
 
 class CursorDebugWrapper(object):
@@ -43,7 +44,7 @@
 ###############################################
 
 def typecast_date(s):
-    return s and datetime.date(*map(int, s.split('-'))) or None # returns None if s is null
+    return s and datetime_pg.date(*map(int, s.split('-'))) or None # returns None if s is null
 
 def typecast_time(s): # does NOT store time zone information
     if not s: return None
@@ -77,7 +78,7 @@
         seconds, microseconds = seconds.split('.')
     else:
         microseconds = '0'
-    return datetime.datetime(int(dates[0]), int(dates[1]), int(dates[2]),
+    return datetime_pg.datetime(int(dates[0]), int(dates[1]), int(dates[2]),
         int(times[0]), int(times[1]), int(seconds), int(float('.'+microseconds) * 1000000))
 
 def typecast_boolean(s):
Index: django/db/models/fields/__init__.py
===================================================================
--- django/db/models/fields/__init__.py	(revision 3817)
+++ django/db/models/fields/__init__.py	(working copy)
@@ -8,6 +8,7 @@
 from django.utils.text import capfirst
 from django.utils.translation import gettext, gettext_lazy
 import datetime, os, time
+from django.utils import datetime_pg
 
 class NOT_PROVIDED:
     pass
@@ -404,13 +405,13 @@
         Field.__init__(self, verbose_name, name, **kwargs)
 
     def to_python(self, value):
-        if isinstance(value, datetime.datetime):
-            return value.date()
-        if isinstance(value, datetime.date):
+        if isinstance(value, datetime_pg.date):
             return value
+        if isinstance(value, (datetime.date, datetime.datetime)):
+            return datetime_pg.new_date_pg(value)
         validators.isValidANSIDate(value, None)
         try:
-            return datetime.date(*time.strptime(value, '%Y-%m-%d')[:3])
+            return datetime_pg.date(*time.strptime(value, '%Y-%m-%d')[:3])
         except ValueError:
             raise validators.ValidationError, gettext('Enter a valid date in YYYY-MM-DD format.')
 
@@ -461,18 +462,18 @@
 
 class DateTimeField(DateField):
     def to_python(self, value):
-        if isinstance(value, datetime.datetime):
+        if isinstance(value, datetime_pg.datetime):
             return value
-        if isinstance(value, datetime.date):
-            return datetime.datetime(value.year, value.month, value.day)
+        if isinstance(value, (datetime.date, datetime.datetime)):
+            return datetime_pg.new_datetime_pg(value)
         try: # Seconds are optional, so try converting seconds first.
-            return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6])
+            return datetime_pg.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6])
         except ValueError:
             try: # Try without seconds.
-                return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5])
+                return datetime_pg.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5])
             except ValueError: # Try without hour/minutes/seconds.
                 try:
-                    return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3])
+                    return datetime_pg.datetime(*time.strptime(value, '%Y-%m-%d')[:3])
                 except ValueError:
                     raise validators.ValidationError, gettext('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
 
@@ -508,7 +509,7 @@
             d = new_data.get(date_field, None)
             t = new_data.get(time_field, None)
         if d is not None and t is not None:
-            return datetime.datetime.combine(d, t)
+            return datetime_pg.datetime.combine(d, t)
         return self.get_default()
 
     def flatten_data(self,follow, obj = None):
Index: django/oldforms/__init__.py
===================================================================
--- django/oldforms/__init__.py	(revision 3817)
+++ django/oldforms/__init__.py	(working copy)
@@ -3,6 +3,7 @@
 from django.utils.html import escape
 from django.conf import settings
 from django.utils.translation import gettext, ngettext
+from django.utils import datetime_pg
 
 FORM_FIELD_ID_PREFIX = 'id_'
 
@@ -809,7 +810,7 @@
         import time, datetime
         try:
             time_tuple = time.strptime(data, '%Y-%m-%d')
-            return datetime.date(*time_tuple[0:3])
+            return datetime_pg.date(*time_tuple[0:3])
         except (ValueError, TypeError):
             return None
     html2python = staticmethod(html2python)
Index: django/utils/datetime_pg.py
===================================================================
--- django/utils/datetime_pg.py	(revision 0)
+++ django/utils/datetime_pg.py	(revision 0)
@@ -0,0 +1,90 @@
+# 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
+#
+# >>> date_pg(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 time
+import re
+
+
+class date_pg(real_date):
+    def strftime(self, fmt):
+        return strftime(self, fmt)
+date = date_pg
+
+
+class datetime_pg(real_datetime):
+    def strftime(self, fmt):
+        return strftime(self, fmt)
+    def combine(self, date, time):
+        return datetime_pg(date.year, date.month, date.day, time.hour, time.minute, time.microsecond, time.tzinfo)
+datetime = datetime_pg
+
+
+def new_date_pg(d):
+    """ Generate a date_pg from a datetime.date object """
+    return date_pg(d.year, d.month, d.day)
+
+def new_datetime_pg(d):
+    """ Generate a datetime_pg 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_pg(*kw)
+
+
+# No support for strftime's "%s" or "%y".
+# 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
Index: tests/modeltests/basic/models.py
===================================================================
--- tests/modeltests/basic/models.py	(revision 3817)
+++ tests/modeltests/basic/models.py	(working copy)
@@ -19,7 +19,7 @@
 []
 
 # Create an Article.
->>> from datetime import datetime
+>>> from django.utils.datetime_pg import datetime
 >>> a = Article(id=None, headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
 
 # Save it into the database. You have to call save() explicitly.
@@ -33,7 +33,7 @@
 >>> a.headline
 'Area man programs in Python'
 >>> a.pub_date
-datetime.datetime(2005, 7, 28, 0, 0)
+datetime_pg(2005, 7, 28, 0, 0)
 
 # Change values by changing the attributes, then calling save().
 >>> a.headline = 'Area woman programs in Python'
@@ -101,7 +101,7 @@
 >>> a2.headline
 'Second article'
 >>> a2.pub_date
-datetime.datetime(2005, 7, 29, 0, 0)
+datetime_pg(2005, 7, 29, 0, 0)
 
 # ...or, you can use keyword arguments.
 >>> a3 = Article(id=None, headline='Third article', pub_date=datetime(2005, 7, 30))
@@ -111,7 +111,7 @@
 >>> a3.headline
 'Third article'
 >>> a3.pub_date
-datetime.datetime(2005, 7, 30, 0, 0)
+datetime_pg(2005, 7, 30, 0, 0)
 
 # You can also mix and match position and keyword arguments, but be sure not to
 # duplicate field information.
@@ -146,12 +146,12 @@
 >>> a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 31, 12, 30))
 >>> a7.save()
 >>> Article.objects.get(id__exact=7).pub_date
-datetime.datetime(2005, 7, 31, 12, 30)
+datetime_pg(2005, 7, 31, 12, 30)
 
 >>> a8 = Article(headline='Article 8', pub_date=datetime(2005, 7, 31, 12, 30, 45))
 >>> a8.save()
 >>> Article.objects.get(id__exact=8).pub_date
-datetime.datetime(2005, 7, 31, 12, 30, 45)
+datetime_pg(2005, 7, 31, 12, 30, 45)
 >>> a8.id
 8L
 
@@ -177,15 +177,15 @@
 
 # dates() returns a list of available dates of the given scope for the given field.
 >>> Article.objects.dates('pub_date', 'year')
-[datetime.datetime(2005, 1, 1, 0, 0)]
+[datetime_pg(2005, 1, 1, 0, 0)]
 >>> Article.objects.dates('pub_date', 'month')
-[datetime.datetime(2005, 7, 1, 0, 0)]
+[datetime_pg(2005, 7, 1, 0, 0)]
 >>> Article.objects.dates('pub_date', 'day')
-[datetime.datetime(2005, 7, 28, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 31, 0, 0)]
+[datetime_pg(2005, 7, 28, 0, 0), datetime_pg(2005, 7, 29, 0, 0), datetime_pg(2005, 7, 30, 0, 0), datetime_pg(2005, 7, 31, 0, 0)]
 >>> Article.objects.dates('pub_date', 'day', order='ASC')
-[datetime.datetime(2005, 7, 28, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 31, 0, 0)]
+[datetime_pg(2005, 7, 28, 0, 0), datetime_pg(2005, 7, 29, 0, 0), datetime_pg(2005, 7, 30, 0, 0), datetime_pg(2005, 7, 31, 0, 0)]
 >>> Article.objects.dates('pub_date', 'day', order='DESC')
-[datetime.datetime(2005, 7, 31, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 28, 0, 0)]
+[datetime_pg(2005, 7, 31, 0, 0), datetime_pg(2005, 7, 30, 0, 0), datetime_pg(2005, 7, 29, 0, 0), datetime_pg(2005, 7, 28, 0, 0)]
 
 # dates() requires valid arguments.
 
@@ -213,10 +213,10 @@
 # result one at a time, to save memory.
 >>> for a in Article.objects.dates('pub_date', 'day', order='DESC').iterator():
 ...     print repr(a)
-datetime.datetime(2005, 7, 31, 0, 0)
-datetime.datetime(2005, 7, 30, 0, 0)
-datetime.datetime(2005, 7, 29, 0, 0)
-datetime.datetime(2005, 7, 28, 0, 0)
+datetime_pg(2005, 7, 31, 0, 0)
+datetime_pg(2005, 7, 30, 0, 0)
+datetime_pg(2005, 7, 29, 0, 0)
+datetime_pg(2005, 7, 28, 0, 0)
 
 # You can combine queries with & and |.
 >>> s1 = Article.objects.filter(id__exact=1)
@@ -325,7 +325,7 @@
 >>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
 >>> a9.save()
 >>> Article.objects.get(id__exact=9).pub_date
-datetime.datetime(2005, 7, 31, 12, 30, 45, 180)
+datetime_pg(2005, 7, 31, 12, 30, 45, 180)
 """
 
 if building_docs or settings.DATABASE_ENGINE == 'mysql':
@@ -335,7 +335,7 @@
 >>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
 >>> a9.save()
 >>> Article.objects.get(id__exact=9).pub_date
-datetime.datetime(2005, 7, 31, 12, 30, 45)
+datetime_pg(2005, 7, 31, 12, 30, 45)
 """
 
 __test__['API_TESTS'] += """
Index: tests/modeltests/manipulators/models.py
===================================================================
--- tests/modeltests/manipulators/models.py	(revision 3817)
+++ tests/modeltests/manipulators/models.py	(working copy)
@@ -87,5 +87,5 @@
 >>> a2
 <Album: Ultimate Ella>
 >>> a2.release_date
-datetime.date(2005, 2, 13)
+date_pg(2005, 2, 13)
 """}
Index: tests/modeltests/or_lookups/models.py
===================================================================
--- tests/modeltests/or_lookups/models.py	(revision 3817)
+++ tests/modeltests/or_lookups/models.py	(working copy)
@@ -85,7 +85,7 @@
 3
 
 >>> list(Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')).values())
-[{'headline': 'Hello and goodbye', 'pub_date': datetime.datetime(2005, 11, 29, 0, 0), 'id': 3}]
+[{'headline': 'Hello and goodbye', 'pub_date': datetime_pg(2005, 11, 29, 0, 0), 'id': 3}]
 
 >>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2])
 {1: <Article: Hello>}
Index: tests/modeltests/reserved_names/models.py
===================================================================
--- tests/modeltests/reserved_names/models.py	(revision 3817)
+++ tests/modeltests/reserved_names/models.py	(working copy)
@@ -49,7 +49,7 @@
 [<Thing: a>, <Thing: h>]
 
 >>> Thing.objects.dates('where', 'year')
-[datetime.datetime(2005, 1, 1, 0, 0), datetime.datetime(2006, 1, 1, 0, 0)]
+[datetime_pg(2005, 1, 1, 0, 0), datetime_pg(2006, 1, 1, 0, 0)]
 
 >>> Thing.objects.filter(where__month=1)
 [<Thing: a>]
Index: tests/modeltests/validation/models.py
===================================================================
--- tests/modeltests/validation/models.py	(revision 3817)
+++ tests/modeltests/validation/models.py	(working copy)
@@ -94,25 +94,25 @@
 >>> p.validate()
 {}
 >>> p.birthdate
-datetime.date(2000, 5, 3)
+date_pg(2000, 5, 3)
 
 >>> p = Person(**dict(valid_params, birthdate=datetime.datetime(2000, 5, 3)))
 >>> p.validate()
 {}
 >>> p.birthdate
-datetime.date(2000, 5, 3)
+date_pg(2000, 5, 3)
 
 >>> p = Person(**dict(valid_params, birthdate='2000-05-03'))
 >>> p.validate()
 {}
 >>> p.birthdate
-datetime.date(2000, 5, 3)
+date_pg(2000, 5, 3)
 
 >>> p = Person(**dict(valid_params, birthdate='2000-5-3'))
 >>> p.validate()
 {}
 >>> p.birthdate
-datetime.date(2000, 5, 3)
+date_pg(2000, 5, 3)
 
 >>> p = Person(**dict(valid_params, birthdate='foo'))
 >>> p.validate()
@@ -122,13 +122,13 @@
 >>> p.validate()
 {}
 >>> p.favorite_moment
-datetime.datetime(2002, 4, 3, 13, 23)
+datetime_pg(2002, 4, 3, 13, 23)
 
 >>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3)))
 >>> p.validate()
 {}
 >>> p.favorite_moment
-datetime.datetime(2002, 4, 3, 0, 0)
+datetime_pg(2002, 4, 3, 0, 0)
 
 >>> p = Person(**dict(valid_params, email='john@example.com'))
 >>> p.validate()
Index: tests/regressiontests/datetime_pg/__init__.py
===================================================================
Index: tests/regressiontests/datetime_pg/models.py
===================================================================
Index: tests/regressiontests/datetime_pg/tests.py
===================================================================
--- tests/regressiontests/datetime_pg/tests.py	(revision 0)
+++ tests/regressiontests/datetime_pg/tests.py	(revision 0)
@@ -0,0 +1,38 @@
+r"""
+>>> 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'
+"""
+
+from datetime import date as original_date, datetime as original_datetime
+from django.utils.datetime_pg 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)
