==== Patch <dateformat> level 1
Source: d410f328-f502-0410-afc1-bf1322476e67:/django/patches/djtime:2758
Target: d410f328-f502-0410-afc1-bf1322476e67:/django/trunk:2731
Log:
 r2748@moria:  sune | 2005-10-13 08:38:55 +0200
 Existing patches.
 r2754@moria:  sune | 2005-10-13 10:37:00 +0200
 Revert timezone-parsing in db typecasts.
 r2755@moria:  sune | 2005-10-13 10:37:21 +0200
 Add dateformat unittests.
 r2756@moria:  sune | 2005-10-13 10:39:02 +0200
 Grab timezone-name from __init__ argument.
 r2757@moria:  sune | 2005-10-13 10:39:29 +0200
 Implement / fix all but "Swatch internet time" formats.
 r2758@moria:  sune | 2005-10-13 11:00:04 +0200
 Fix dateformat to handle escaped chars.

=== django/utils/tzinfo.py
==================================================================
--- django/utils/tzinfo.py	(revision 2731)
+++ django/utils/tzinfo.py	(patch dateformat level 1)
@@ -0,0 +1,57 @@
+"""Implementation of a tzinfo-classes for use with datetime.datetime."""
+
+import time
+from datetime import timedelta, tzinfo
+
+ZERO = timedelta(0)
+STDOFFSET = timedelta(seconds=-time.timezone)
+DSTOFFSET = timedelta(seconds=-time.altzone)
+DSTDIFF = DSTOFFSET - STDOFFSET
+
+class FixedOffset(tzinfo):
+    """Fixed offset in minutes east from UTC."""
+
+    def __init__(self, offset):
+        self.__offset = timedelta(minutes=offset)
+        # FIXME -- Not really a name...
+        self.__name = "%+03d%02d" % (offset / 60, offset % 60)
+
+    def __repr__(self):
+        return self.__name
+
+    def utcoffset(self, dt):
+        return self.__offset
+
+    def tzname(self, dt):
+        return self.__name
+
+    def dst(self, dt):
+        return ZERO
+
+class LocalTimezone(tzinfo):
+    """Proxy timezone information from time module."""
+    def __init__(self, dt):
+        tzinfo.__init__(self, dt)
+        self._tzname = time.tzname[self._isdst(dt)]
+    def __repr__(self):
+        return self._tzname
+
+    def utcoffset(self, dt):
+        if self._isdst(dt):
+            return DSTOFFSET
+        else:
+            return STDOFFSET
+    def dst(self, dt):
+        if self._isdst(dt):
+            return DSTDIFF
+        else:
+            return ZERO
+    def tzname(self, dt):
+        return time.tzname[self._isdst(dt)]
+    def _isdst(self, dt):
+        tt = (dt.year, dt.month, dt.day,
+              dt.hour, dt.minute, dt.second,
+              dt.weekday(), 0, -1)
+        stamp = time.mktime(tt)
+        tt = time.localtime(stamp)
+        return tt.tm_isdst > 0
=== django/utils/timesince.py
==================================================================
--- django/utils/timesince.py	(revision 2731)
+++ django/utils/timesince.py	(patch dateformat level 1)
@@ -1,4 +1,5 @@
 import time, math, datetime
+from tzinfo import LocalTimezone
 
 def timesince(d, now=None):
     """
@@ -6,7 +7,6 @@
     as a nicely formatted string, e.g "10 minutes"
     Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
     """
-    original = time.mktime(d.timetuple())
     chunks = (
       (60 * 60 * 24 * 365, 'year'),
       (60 * 60 * 24 * 30, 'month'),
@@ -14,9 +14,17 @@
       (60 * 60, 'hour'),
       (60, 'minute')
     )
-    if not now:
-        now = time.time()
-    since = now - original
+    if now:
+        t = time.mktime(now)
+    else:
+        t = time.localtime()
+    if d.tzinfo:
+        tz = LocalTimezone()
+    else:
+        tz = None
+    now = datetime.datetime(t[0], t[1], t[2], t[3], t[4], t[5], tzinfo=tz)
+    delta = now - d
+    since = delta.days * 24 * 60 * 60 + delta.seconds
     # Crazy iteration syntax because we need i to be current index
     for i, (seconds, name) in zip(range(len(chunks)), chunks):
         count = math.floor(since / seconds)
=== django/utils/dateformat.py
==================================================================
--- django/utils/dateformat.py	(revision 2731)
+++ django/utils/dateformat.py	(patch dateformat level 1)
@@ -13,13 +13,24 @@
 
 from calendar import isleap
 from dates import MONTHS, MONTHS_AP, WEEKDAYS
+from tzinfo import LocalTimezone
+import time
+import re
 
+re_formatchars = re.compile(r'(?<!\\)([aABdDfFgGhHiIjlLmMnNOPrsStTUwWyYzZ])')
+split_formatstr = re_formatchars.split
+re_escaped = re.compile(r'\\(.)')
+
 class DateFormat:
     year_days = [None, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
 
     def __init__(self, d):
         self.date = d
 
+        self.timezone = getattr(self.date, 'tzinfo', None)
+        if not self.timezone:
+            self.timezone = LocalTimezone(self.date)
+
     def a(self):
         "'a.m.' or 'p.m.'"
         if self.date.hour > 11:
@@ -84,7 +95,10 @@
 
     def I(self):
         "'1' if Daylight Savings Time, '0' otherwise."
-        raise NotImplementedError
+        if self.timezone.dst(self.date):
+            return '1'
+        else:
+            return '0'
 
     def j(self):
         "Day of the month without leading zeros; i.e. '1' to '31'"
@@ -116,7 +130,8 @@
 
     def O(self):
         "Difference to Greenwich time in hours; e.g. '+0200'"
-        raise NotImplementedError
+        tz = self.timezone.utcoffset(self.date)
+        return "%+03d%02d" % (tz.seconds / 3600, (tz.seconds / 60) % 60)
 
     def P(self):
         """
@@ -133,7 +148,7 @@
 
     def r(self):
         "RFC 822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"
-        raise NotImplementedError
+        return self.format('D, j M Y H:i:s O')
 
     def s(self):
         "Seconds; i.e. '00' to '59'"
@@ -158,18 +173,19 @@
 
     def T(self):
         "Time zone of this machine; e.g. 'EST' or 'MDT'"
-        raise NotImplementedError
+        name = self.timezone.tzname(self.date)
+        if name is None:
+            name = self.format('O')
+        return name
 
     def U(self):
         "Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)"
-        raise NotImplementedError
+        off = self.timezone.utcoffset(self.date)
+        return int(time.mktime(self.date.timetuple())) + off.seconds * 60
 
     def w(self):
         "Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday)"
-        weekday = self.date.weekday()
-        if weekday == 0:
-            return 6
-        return weekday - 1
+        return (self.date.weekday() + 1) % 7
 
     def W(self):
         "ISO-8601 week number of year, weeks starting on Monday"
@@ -197,14 +213,14 @@
                     week_number -= 1
         return week_number
 
+    def y(self):
+        "Year, 2 digits; e.g. '99'"
+        return str(self.date.year)[2:]
+
     def Y(self):
         "Year, 4 digits; e.g. '1999'"
         return self.date.year
 
-    def y(self):
-        "Year, 2 digits; e.g. '99'"
-        return str(self.date.year)[2:]
-
     def z(self):
         "Day of the year; i.e. '0' to '365'"
         doy = self.year_days[self.date.month] + self.date.day
@@ -216,16 +232,16 @@
         """Time zone offset in seconds (i.e. '-43200' to '43200'). The offset
         for timezones west of UTC is always negative, and for those east of UTC
         is always positive."""
-        raise NotImplementedError
+        return self.timezone.utcoffset(self.date).seconds
 
     def format(self, formatstr):
-        result = ''
-        for char in formatstr:
-            try:
-                result += str(getattr(self, char)())
-            except AttributeError:
-                result += char
-        return result
+        pieces = []
+        for i, piece in enumerate(split_formatstr(formatstr)):
+            if i % 2:
+                pieces.append(str(getattr(self, piece)()))
+            elif piece:
+                pieces.append(re_escaped.sub(r'\1', piece))
+        return ''.join(pieces)
 
 class TimeFormat:
     def __init__(self, t):
=== tests/othertests/db_typecasts.py
==================================================================
--- tests/othertests/db_typecasts.py	(revision 2731)
+++ tests/othertests/db_typecasts.py	(patch dateformat level 1)
@@ -1,6 +1,7 @@
 # Unit tests for django.core.db.typecasts
 
 from django.core.db import typecasts
+from django.utils.tzinfo import FixedOffset
 import datetime
 
 TEST_CASES = {
=== tests/othertests/dateformat.py
==================================================================
--- tests/othertests/dateformat.py	(revision 2731)
+++ tests/othertests/dateformat.py	(patch dateformat level 1)
@@ -0,0 +1,76 @@
+"""
+>>> format(my_birthday, '')
+''
+>>> format(my_birthday, 'a')
+'p.m.'
+>>> format(my_birthday, 'A')
+'PM'
+>>> format(my_birthday, 'j')
+'7'
+>>> format(my_birthday, 'l')
+'Saturday'
+>>> format(my_birthday, 'L')
+'False'
+>>> format(my_birthday, 'm')
+'07'
+>>> format(my_birthday, 'M')
+'Jul'
+>>> format(my_birthday, 'n')
+'7'
+>>> format(my_birthday, 'N')
+'July'
+>>> format(my_birthday, 'O')
+'+0100'
+>>> format(my_birthday, 'P')
+'10 p.m.'
+>>> format(my_birthday, 'r')
+'Sat, 7 Jul 1979 22:00:00 +0100'
+>>> format(my_birthday, 's')
+'00'
+>>> format(my_birthday, 'S')
+'th'
+>>> format(my_birthday, 't')
+Traceback (most recent call last):
+    ...
+NotImplementedError
+>>> format(my_birthday, 'T')
+'CET'
+>>> format(my_birthday, 'U')
+'300445200'
+>>> format(my_birthday, 'w')
+'6'
+>>> format(my_birthday, 'W')
+'27'
+>>> format(my_birthday, 'y')
+'79'
+>>> format(my_birthday, 'Y')
+'1979'
+>>> format(my_birthday, 'z')
+'188'
+>>> format(my_birthday, 'Z')
+'3600'
+
+>>> format(summertime, 'I')
+'1'
+>>> format(summertime, 'O')
+'+0200'
+>>> format(wintertime, 'I')
+'0'
+>>> format(wintertime, 'O')
+'+0100'
+
+>>> format(my_birthday, 'Y z \\C\\E\\T')
+'1979 188 CET'
+"""
+from django.utils.dateformat import format
+import datetime
+
+import os
+import time
+
+os.environ['TZ'] = 'Europe/Copenhagen'
+time.tzset()
+
+my_birthday = datetime.datetime(1979, 7, 7, 22, 00)
+summertime = datetime.datetime(2005, 10, 30, 1, 00)
+wintertime = datetime.datetime(2005, 10, 30, 4, 00)

==== BEGIN SVK PATCH BLOCK ====
Version: svk 1.04 (linux)

eJydWN1v29YVF5phbYQAXbtuWQqsu3GVSlokmd+k1MZzGlHyt71YSWdbhkKRlzYdidRIKrYErTWp
DztO060bUOxlGDCg2MOwPe1te9/T/qedS0qy5DhJMUO+5L333N+595xzzwcLdunjebozN0d1YjTV
2Xy4nMttKK66f4vmOjGhgzXDtewY36nhJ7gWYzs1ay/GdUyljmHWsZq2Sl5cxd7DLnkx1MfYnZuj
AU4K4eQAYgQboFYV1zKdWDaAr7g2xjGmQ1PzNN2ZZ8mvEqOzHQfDXABcsfETwzEsE1gzIi8BRYzu
0ABgNbBZsS3LHc0wZC3VUWuWgysEngDyhJyJwZECes2wsQp7asFWtAPF3LMCtCFCQCxeQsx3XOy4
ziRtsFN+yO2cEihgIyESewkSzQz5zjZdo+YEm3sdIjPPdeYFgsjFpI6iaRXdqIHcpqFm3bZh6lam
QZawFRBrmg73wY+kFS7jLywz6tgxTBUPV04JQ5heKkwv1RQX65ZdV9wLa196FJYIRwhnlUaj1qq4
+MjVcM1VyPEqlZjIZcF41iKRyJfv+Kbv/HuR/TIv9hcZb3lpUPzx6dLB81XvjaeymBefKt6Puote
7mTJm/cXvXd9+YPnhetnsv+eL/vvfy0/7BbyeT/5+6LPF/2P8v7neb/WLfrCinel6M97Rd9e9Nt/
3vDb+e4qEAm9Bb8xWPG3ekXfWPaPBivd8pK/1S34QtF/7K34h2s9Hjow5sOKam97rXfPL/e28p8v
+NcGRU/8pf+zvryR93Je/oZfuCY/lCv9wr3P/J3VnlK47t0ffLrutwv9a8XuDzx4eIXuFT/f/7Rn
9t6D7h7wLXbfWDy5ueS3lnsHi9487K/34PQnS/6VSj93cPr9vNeongpy/5HR/55+8nbh5GZpsJzv
vn2aH6wP5LPl3urpDS9/ttItni3J/l0YKXiJzbPd3y0PnvQWTq75edFfHjTlszlZ9PL9uvysID8T
lwZPFp4e9Qpnsf7MzMxivVHDdWy6igvXDlk6UlBoWmm1pjgOdhDoHDUdjA4Ndx8RIyA2lIG10ahR
b1i2i6K6bdVRoNdUNLot319HdxJUMrpZyq8XCptyycGqZWrOnXTbMnEymt8sKTU3v1gooDsojaJR
VDCOsLau6+AKEslcFMEfMEAWMkxUN8wmXEiEFcdFD0r3NKyjSsUwwaNUEg6u6alMpXLnQ1RY/NWq
jNJptGa5yMZKrdaCwxAPlslkgNHMrdsUq92iGG0G3UKJWSRQ8BSopI0bto3dpg18VM1ta86KpSq1
0oZtHbWQXa9bWrOWyewYjuYmdw0d1xyc0NxMCyt2pm6Z7r6mtFJo32oeYvwY3hPJFKJSKE27Sr1R
f5xwa27GraM5REXhck/4reCekYvOMh0ec7zESKyQZXlFpFhd5TSOFViVp8QqL2mho3nJXeIJAsOL
FK9SLCvqmOF4ReJ1XmUpLqtUsc7pQoyVmPC2nR19ufWu/+hfEc9+P+KlfeHYvxUZbPc+GCqUaDiF
4Kbvp8YaD5Uc2gYa0oVygknQ62GoNvLngrQDI6k/Jo+EaR0mg0kiuUvIagQmoAzJDB1pmZDTBHUb
yCcZjqgvgBKyNZgNRoAzdMdGO3pJuDvUbgq5O3TQMkHLBi0XtDxpgw3ccdshm0DUAEYg00gLxgJP
ShiQOUBvOejniOGgAdsKmtvDueENuFT9ofK4Ks0KEkVroshpWMuqAssJYAxZms6ylPRK9QsEgcW6
QNMcJemYxzwj6qKe1QWFYRVWUziKgqUiHer/P/R/r957Vor4bOTYW4t4N3zm2Lsaeb7QlY/LkcFv
vv7ouBHp2X/85NtIn/5T4bi7ETkxvr1/vBl59Hc60r35D/PYNyJ/uP7P919jFDg6YVGjdxtHozac
Pogl6r5iOyBCG2dUq94AiSTseOIXn9wsl5OJHeXup1peL+wV9xeMxYPaSn3VXFvfsJ1Nt/Tg8LPW
Vnt7NxlPRp1GDbxBCOi4dgA3iZ8JCAhT7KhKA2sXGZbLiQwABToFj5Jxh9sHOkhLFNe1A08TGFAK
xcMDx1OBoSXHtgd2a4LvmUI4t8zLwKftecwiCUhTpBnNcSemp0FD34XidHw8PH0nJomoeHBDptHB
71mh8z3nEb2w8oL7dNsjm0aziBUo8HfTY+BYQ/caLg+AQ5Uk4vkUOkCraAst5Iycg9bjSeKmX9iW
2ybDl+2JSJqsMJxABdNHncQacQQWFw9EyKLDq62jBwGbCcnObA6PEl5ydx+jB6ZxhHDDUvdRYkkx
m4rdQjSisyKFKCoX/FBxtZScGYOAVP8vYRumm5j0oGPKAMZtQtBOJJNJcC8ANRY68TjnJzp84UR5
pUUiPDkKiVQpZDbr2DbUFDIyOENsAyU2myb4sSRyLRQXSF+BDZGRmYt7nNjUOO7BhmiidrH1AvMt
iJYpxCDN2DNc52OEM3vAMpuNvwDsTN62IMomd5jc7oQdvVKWI3GcS2JoBEGugMZeYmJzDQOrmHih
nd3xGEl7jFQ4RdIQHAgLGCQueJvEOeKFiwlWaoAwmOnRc4YZ8OTY1BIEZNLNDLkmiYqnluIaQAZz
r4M8d3UZp1klHo6Oj2BfsLZ4PHNgGWYihEheGqSCECOpEFY4lREEyFZUTlQ5gYccBbINNssyijYu
hqTLiiEhrKtmLTBAe1Riia+tIsRxZZedqk9gPxfxZrVqxW01sAqpohNWKdIIH0oqUntO1lQsdQnC
hSpHGtZWL93eqyNzlmwzq1KQggmSCjKiBUWXxCojcVmscrSoC7wSE4Z52VeLXzXfuh3ZiPwt8ry9
HmbVQQmWCUqwzHSgnUiaL1VZwFtXNIWm9SymBYbCSpXOSqJAq5qiVWlB06uvKNGIuKBGE4ICn9Ro
z97xo37pL9tXvPxPezs3ofWD1gva06DthyNl7014dKHzyLsCxdcPy97seLZbuesVvdg3j/wvPNn7
dbl7JYQ6/jSA7gZEJ7u9q2PsAE8GJOjJXeTtDiTyJOzl/oKX711dufIVDKzeXD1hi357o3+/t3Yi
7pwYK357Z/DxM7kfk7sPe3I/mY98U3jrpPimV3wazZ9+kT/JegtPr/vwX3j64V+he7b8tr/49AP5
9HMylx9sd+WzG6dLJ+yDs8hvC2cfLp0cqmesB2+kBpqbmxv5l3qrUjXsYTUQh4ATjyuNTD1zd2P1
QKyNHOlKQYHgXKdWl5o1SGZuUzRFbdAUAucoIhgj8SSLGGYYTyDV2XcBq2QrKq4q6mOUqFsOSaJU
qNoQpA81BIWaO3Q9UOpEof4Zl3VYk23bskv35NIDlqI4jmcOhc+YVnYr25akbcFp1sGrhfl+fPEQ
4g5qo3L5Xrksl8u0JKEXjZDckKEJRqOWYzkZbD4xbMvciZe247vgReNy04Z7OnuPXNZ9ZQ+b8SjY
blDcgVe+k4CjMgzUSFAjMhTFpxAN6QML/zR3iSUH1xZMWeUwFihG4lmRoyWRUaEq0ll4arykSxwf
fLDhwVqRzYicNF+3bEPJQdLVhFyrgwinNE2laRZRUo6VcjyPblMwGkXykeG4hrmHGiRhBC8aJRA8
9woImsqxIlHQEOI+foKHeS6JTOkGpJ0EESKHVkXnTilE5l+LzNAj5Luahs69EmpC4Ru4qiGU8Bqo
bI5iRlBFW6mebzHIkwL9jspppNh7TWI2Q2zxtdhMdoQ9NjnI/XTIk4hhVpsu5FGHRKgkpcG2iUMR
zYzC8JDRq5RF08FV4EaMwO1NygMSlX3F1GoYjXL7MOWPEis6p5ubYzrkI1rwqbIUfG3M5SChA605
Su0WRAeuA9rfj7EdGz+BTrNpaGTB7PAj2NA0oEsOMIwqMVboaBxN6SwjpXWeYtIU9NKKrtLpqk6z
DMOJAhbEuSTTeRkPmh3zcO2m+TiAZunvCh0TuO9El7v8IDlyjP8B3/j2Pw==
==== END SVK PATCH BLOCK ====
