Commits

Peter Suter  committed 0098373

Added support for pretty printing relative dates in the future.
Remove `pretty_format_time`.
Use `pretty_dateinfo` instead to distinct between relative dates in future and past.
Added slightly more robust handling of very old dates.

  • Participants
  • Parent commits 9e0a436

Comments (0)

Files changed (4)

File customtimefields.patch

 Convert between DB time strings and datetime objects in the model.
 Parse ticket time input to datetime objects.
 Add format hint to ticket time input controls.
-Add a `.format` attribute to custom time fields to allow rendering as an age, a date or a datetime.
+Add a `.format` attribute to custom time fields to allow rendering as relative, a date or a datetime.
 Render time fields in ticket box, ticket csv export and ticket changelog.
 Render time fields in ticket queries.
 Render time fields in notification emails.
-Support parsing relative time values in the future (e.g. ''tomorrow'', ''next month'' or ''in 3 hours'').
+Support parsing and pretty printing relative time values in the future (e.g. ''tomorrow'', ''next month'' or ''in 3 hours'').
 
 diff -r 798a158cc10b trac/ticket/api.py
 --- a/trac/ticket/api.py	Thu Mar 01 23:30:39 2012 +0100
-+++ b/trac/ticket/api.py	Thu Mar 01 23:32:22 2012 +0100
-@@ -351,9 +351,9 @@
-         fields.append({'name': 'cc', 'type': 'text', 'label': N_('Cc')})
++++ b/trac/ticket/api.py	Fri Mar 02 20:31:13 2012 +0100
+@@ -352,9 +352,9 @@
  
          # Date/time fields
--        fields.append({'name': 'time', 'type': 'time',
-+        fields.append({'name': 'time', 'type': 'time', 'format': 'age',
-                        'label': N_('Created')})
--        fields.append({'name': 'changetime', 'type': 'time',
-+        fields.append({'name': 'changetime', 'type': 'time', 'format': 'age',
-                        'label': N_('Modified')})
+         fields.append({'name': 'time', 'type': 'time',
+-                       'label': N_('Created')})
++                       'format': 'relative', 'label': N_('Created')})
+         fields.append({'name': 'changetime', 'type': 'time',
+-                       'label': N_('Modified')})
++                       'format': 'relative', 'label': N_('Modified')})
  
          for field in self.custom_fields:
+             if field['name'] in [f['name'] for f in fields]:
 @@ -406,6 +406,8 @@
                  field['format'] = config.get(name + '.format', 'plain')
                  field['width'] = config.getint(name + '.cols')
          fields.sort(lambda x, y: cmp((x['order'], x['name']),
 diff -r 798a158cc10b trac/ticket/model.py
 --- a/trac/ticket/model.py	Thu Mar 01 23:30:39 2012 +0100
-+++ b/trac/ticket/model.py	Thu Mar 01 23:32:22 2012 +0100
++++ b/trac/ticket/model.py	Fri Mar 02 20:31:13 2012 +0100
 @@ -28,7 +28,8 @@
  from trac.ticket.api import TicketSystem
  from trac.util import embedded_numbers, partition
          """Delete the ticket.
 diff -r 798a158cc10b trac/ticket/notification.py
 --- a/trac/ticket/notification.py	Thu Mar 01 23:30:39 2012 +0100
-+++ b/trac/ticket/notification.py	Thu Mar 01 23:32:22 2012 +0100
++++ b/trac/ticket/notification.py	Fri Mar 02 20:31:13 2012 +0100
 @@ -26,7 +26,8 @@
  from trac.config import *
  from trac.notification import NotifyEmail
  from trac.ticket.api import TicketSystem
 -from trac.util.datefmt import to_utimestamp
-+from trac.util.datefmt import pretty_format_time, get_timezone, \
++from trac.util.datefmt import format_date_or_datetime, get_timezone, \
 +                              to_utimestamp
  from trac.util.text import obfuscate_email_address, text_width, wrap
  from trac.util.translation import deactivate, reactivate
              if fname in ['owner', 'reporter']:
                  fval = self.obfuscate_email(fval)
              if f['type'] == 'textarea' or '\n' in unicode(fval):
-@@ -320,6 +331,10 @@
+@@ -320,6 +331,11 @@
          
          return template.generate(**data).render('text', encoding=None).strip()
  
 +    def format_time_field(self, value, format):
 +        tzinfo = get_timezone(self.config.get('trac', 'default_timezone'))
-+        return pretty_format_time(None, value, format, tzinfo=tzinfo)
++        return format_date_or_datetime(format, value, tzinfo=tzinfo) \
++               if value else ''
 +
      def get_recipients(self, tktid):
          notify_reporter = self.config.getbool('notification',
                                                'always_notify_reporter')
 diff -r 798a158cc10b trac/ticket/query.py
 --- a/trac/ticket/query.py	Thu Mar 01 23:30:39 2012 +0100
-+++ b/trac/ticket/query.py	Thu Mar 01 23:32:22 2012 +0100
++++ b/trac/ticket/query.py	Fri Mar 02 20:31:13 2012 +0100
 @@ -34,8 +34,9 @@
  from trac.ticket.api import TicketSystem
  from trac.ticket.model import Milestone, group_milestones
  from trac.util import Ranges, as_bool
 -from trac.util.datefmt import format_datetime, from_utimestamp, parse_date, \
 -                              to_timestamp, to_utimestamp, utc, user_time
-+from trac.util.datefmt import from_utimestamp, parse_date, \
-+                              pretty_format_time, to_timestamp, \
-+                              to_utimestamp, utc, user_time
++from trac.util.datefmt import from_utimestamp, format_date_or_datetime, \
++                              parse_date, to_timestamp, to_utimestamp, utc, \
++                              user_time
  from trac.util.presentation import Paginator
  from trac.util.text import empty, shorten_line, quote_query_string
  from trac.util.translation import _, tag_, cleandoc_
                      elif field and field['type'] == 'checkbox':
                          try:
                              val = bool(int(val))
-@@ -1135,8 +1136,8 @@
+@@ -1135,8 +1136,9 @@
                          value = Chrome(self.env).format_emails(
                                      context.child(ticket), value)
                      elif col in query.time_fields:
 -                        value = format_datetime(value, '%Y-%m-%d %H:%M:%S',
 -                                                tzinfo=req.tz)
 +                        format = query.fields.by_name(col).get('format')
-+                        value = pretty_format_time(req, value, format)
++                        value = user_time(req, format_date_or_datetime,
++                                          format, value) if value else ''
                      values.append(unicode(value).encode('utf-8'))
                  writer.writerow(values)
          return (content.getvalue(), '%s;charset=utf-8' % mimetype)
 diff -r 798a158cc10b trac/ticket/templates/query_results.html
 --- a/trac/ticket/templates/query_results.html	Thu Mar 01 23:30:39 2012 +0100
-+++ b/trac/ticket/templates/query_results.html	Thu Mar 01 23:32:22 2012 +0100
++++ b/trac/ticket/templates/query_results.html	Fri Mar 02 20:31:13 2012 +0100
 @@ -75,7 +75,7 @@
                          class="${classes(closed=result.status == 'closed')}">#$result.id</a></td>
                      <td py:otherwise="" class="$name" py:choose="">
                        <a py:when="name == 'summary'" href="$result.href" title="View ticket">$value</a>
 -                      <py:when test="isinstance(value, datetime)">${pretty_dateinfo(value, dateonly=True)}</py:when>
-+                      <py:when test="header.field.type == 'time'">${pretty_format_time(req, value, header.field.format, relative=True)}</py:when>
++                      <py:when test="header.field.type == 'time'">${pretty_dateinfo(value, header.field.format)}</py:when>
                        <py:when test="name == 'reporter'">${authorinfo(value)}</py:when>
                        <py:when test="name == 'cc'">${format_emails(ticket_context, value)}</py:when>
                        <py:when test="name == 'owner' and value">${authorinfo(value)}</py:when>
 diff -r 798a158cc10b trac/ticket/templates/ticket.html
 --- a/trac/ticket/templates/ticket.html	Thu Mar 01 23:30:39 2012 +0100
-+++ b/trac/ticket/templates/ticket.html	Thu Mar 01 23:32:22 2012 +0100
++++ b/trac/ticket/templates/ticket.html	Fri Mar 02 20:31:13 2012 +0100
 @@ -277,6 +277,8 @@
                                   checked="${value == option or None}" />
                            ${option}
                          <py:otherwise><!--! Text input fields -->
                            <py:choose>
                              <span py:when="field.cc_entry"><!--! Special case for Cc: field -->
+diff -r 798a158cc10b trac/ticket/templates/ticket_box.html
+--- a/trac/ticket/templates/ticket_box.html	Thu Mar 01 23:30:39 2012 +0100
++++ b/trac/ticket/templates/ticket_box.html	Fri Mar 02 20:31:13 2012 +0100
+@@ -48,6 +48,7 @@
+             colspan="${3 if fullrow else None}">
+           <py:if test="field">
+             <py:choose test="">
++              <py:when test="'dateinfo' in field">${pretty_dateinfo(field.dateinfo, field.format)}</py:when>
+               <py:when test="'rendered' in field">${field.rendered}</py:when>
+               <py:otherwise>${ticket[field.name]}</py:otherwise>
+             </py:choose>
 diff -r 798a158cc10b trac/ticket/web_ui.py
 --- a/trac/ticket/web_ui.py	Thu Mar 01 23:30:39 2012 +0100
-+++ b/trac/ticket/web_ui.py	Thu Mar 01 23:32:22 2012 +0100
++++ b/trac/ticket/web_ui.py	Fri Mar 02 20:31:13 2012 +0100
 @@ -37,8 +37,9 @@
  from trac.ticket.notification import TicketNotifyEmail
  from trac.timeline.api import ITimelineEventProvider
  from trac.util import as_bool, as_int, get_reporter_id
 -from trac.util.datefmt import format_datetime, from_utimestamp, \
 -                              to_utimestamp, utc
-+from trac.util.datefmt import (pretty_format_time, from_utimestamp,
-+                               get_date_format_hint, get_datetime_format_hint,
-+                               parse_date, to_utimestamp, user_time, utc)
++from trac.util.datefmt import format_date_or_datetime, from_utimestamp, \
++                              get_date_format_hint, get_datetime_format_hint, \
++                              parse_date, to_utimestamp, user_time, utc
  from trac.util.text import exception_to_unicode, obfuscate_email_address, \
                             shorten_line, to_unicode
  from trac.util.presentation import separated
          ticket.populate(fields)
          # special case for updating the Cc: field
          if 'cc_update' in req.args:
-@@ -1064,8 +1077,8 @@
+@@ -1064,8 +1077,9 @@
              if name in ('cc', 'reporter'):
                  value = Chrome(self.env).format_emails(context, value, ' ')
              elif name in ticket.time_fields:
 -                value = format_datetime(value, '%Y-%m-%d %H:%M:%S',
 -                                        tzinfo=req.tz)
 +                format = ticket.fields.by_name(name).get('format')
-+                value = pretty_format_time(req, value, format)
++                value = user_time(req, format_date_or_datetime, format,
++                                  value) if value else ''
              cols.append(value.encode('utf-8'))
          writer.writerow(cols)
          return (content.getvalue(), '%s;charset=utf-8' % mimetype)
-@@ -1204,6 +1217,22 @@
+@@ -1204,6 +1218,22 @@
              # Shouldn't happen in "normal" circumstances, hence not a warning
              raise InvalidTicket(_("Invalid comment threading identifier"))
  
          # Custom validation rules
          for manipulator in self.ticket_manipulators:
              for field, message in manipulator.validate_ticket(req, ticket):
-@@ -1370,7 +1399,7 @@
+@@ -1370,7 +1400,7 @@
              type_ = field['type']
   
              # enable a link to custom query for all choice fields
                  field['rendered'] = self._query_link(req, name, ticket[name])
  
              # per field settings
-@@ -1445,6 +1474,17 @@
+@@ -1445,6 +1475,20 @@
                      field['rendered'] = \
                          format_to_html(self.env, context, ticket[name],
                                  escape_newlines=self.must_preserve_newlines)
 +            elif type_ == 'time':
 +                value = ticket[name]
++                field['timevalue'] = value
 +                format = field.get('format', 'datetime')
-+                field['rendered'] = pretty_format_time(req, value, format,
-+                                                            relative=True)
-+                field['edit'] = pretty_format_time(req, value, format)
++                field['rendered'] =  user_time(req, format_date_or_datetime, 
++                                               format, value) if value else ''
++                field['dateinfo'] = value
++                field['edit'] = user_time(req, format_date_or_datetime,
++                                          format, value) if value else ''
 +                locale = getattr(req, 'lc_time', None)
 +                if format == 'date':
 +                    field['format_hint'] = get_date_format_hint(locale)
              
              # ensure sane defaults
              field.setdefault('optional', False)
-@@ -1610,6 +1650,10 @@
+@@ -1610,6 +1654,12 @@
                                                    resource_new)
              if rendered:
                  changes['rendered'] = rendered
 +            elif ticket.fields.by_name(field, {}).get('type') == 'time':
 +                format = ticket.fields.by_name(field).get('format')
-+                changes['old'] = pretty_format_time(req, old, format)
-+                changes['new'] = pretty_format_time(req, new, format)
++                changes['old'] = user_time(req, format_date_or_datetime,
++                                           format, old) if old else ''
++                changes['new'] = user_time(req, format_date_or_datetime,
++                                           format, new) if new else ''
  
      def _render_property_diff(self, req, ticket, field, old, new, 
                                resource_new=None):
+diff -r 798a158cc10b trac/timeline/web_ui.py
+--- a/trac/timeline/web_ui.py	Thu Mar 01 23:30:39 2012 +0100
++++ b/trac/timeline/web_ui.py	Fri Mar 02 20:31:13 2012 +0100
+@@ -30,7 +30,7 @@
+ from trac.util import as_int
+ from trac.util.datefmt import format_date, format_datetime, format_time, \
+                               parse_date, to_utimestamp, utc, \
+-                              pretty_timedelta,  user_time
++                              pretty_timedelta,  user_time, localtz
+ from trac.util.text import exception_to_unicode, to_unicode
+ from trac.util.translation import _, tag_
+ from trac.web import IRequestHandler, IRequestFilter
+@@ -268,27 +268,46 @@
+     def post_process_request(self, req, template, data, content_type):
+         if data:
+             def pretty_dateinfo(date, format=None, dateonly=False):
+-                absolute = user_time(req, format_datetime, date)
+-                relative = pretty_timedelta(date)
++                if not date:
++                    return ''
++                if format == 'date':
++                    absolute = user_time(req, format_date, date)
++                else:
++                    absolute = user_time(req, format_datetime, date)
++                now = datetime.now(localtz)
++                relative = pretty_timedelta(date, now)
+                 if not format:
+                     format = req.session.get('dateinfo',
+                                  Chrome(self.env).default_dateinfo_format)
+-                if format == 'absolute':
++                if format == 'relative':
++                    if date > now:
++                        label = _("in %(relative)s", relative=relative) \
++                                if not dateonly else relative
++                        title = _("on %(date)s at %(time)s",
++                                  date=user_time(req, format_date, date),
++                                  time=user_time(req, format_time, date))
++                        return tag.span(label, title=title)
++                    else:
++                        label = _("%(relative)s ago", relative=relative) \
++                                if not dateonly else relative
++                        title = _("See timeline at %(absolutetime)s",
++                                  absolutetime=absolute)
++                else:
+                     if dateonly:
+                         label = absolute
+                     elif req.lc_time == 'iso8601':
+                         label = _("at %(iso8601)s", iso8601=absolute)
++                    elif format == 'date':
++                        label = _("on %(date)s", date=absolute)
+                     else:
+                         label = _("on %(date)s at %(time)s",
+                                   date=user_time(req, format_date, date),
+                                   time=user_time(req, format_time, date))
++                    if date > now:
++                        title = _("in %(relative)s", relative=relative)
++                        return tag.span(label, title=title)
+                     title = _("See timeline %(relativetime)s ago",
+                               relativetime=relative)
+-                else:
+-                    label = _("%(relativetime)s ago", relativetime=relative) \
+-                            if not dateonly else relative
+-                    title = _("See timeline at %(absolutetime)s",
+-                              absolutetime=absolute)
+                 return self.get_timeline_link(req, date, label,
+                                               precision='second', title=title)
+             def dateinfo(date):
 diff -r 798a158cc10b trac/util/datefmt.py
 --- a/trac/util/datefmt.py	Thu Mar 01 23:30:39 2012 +0100
-+++ b/trac/util/datefmt.py	Thu Mar 01 23:32:22 2012 +0100
-@@ -391,6 +391,23 @@
-         weekdays[t.weekday()], t.day, months[t.month - 1], t.year,
-         t.hour, t.minute, t.second)
- 
-+def pretty_format_time(req, value, format, relative=False, *args, **kwargs):
-+    """Format a `datetime` object `value` depending on `format` which can be:
-+    'date', 'datetime' or 'age' (if `relative` is `True`, otherwise fall back
-+    to 'datetime').
-+    The `req` is used for timezone and locale information if available.
-+    """
-+    if not value:
-+        return u''
-+    if not isinstance(value, datetime):
-+        # Return invalid timestamps unchanged.
-+        return unicode(value)
-+    if format == 'age' and relative:
-+        return pretty_timedelta(value) 
-+    elif format == 'date':
-+        return user_time(req, format_date, value)
-+    else:
-+        return user_time(req, format_datetime, value)
- 
- # -- parsing
- 
-@@ -451,7 +468,8 @@
++++ b/trac/util/datefmt.py	Fri Mar 02 20:31:13 2012 +0100
+@@ -451,7 +451,8 @@
          dt = _parse_relative_time(text, tzinfo)
      if dt is None:
          hint = {'datetime': get_datetime_format_hint,
 -                'date': get_date_format_hint
 +                'date': get_date_format_hint,
-+                'age': get_datetime_format_hint
++                'relative': get_datetime_format_hint
                 }.get(hint, lambda(l): hint)(locale)
          raise TracError(_('"%(date)s" is an invalid date, or the date format '
                            'is not known. Try "%(hint)s" instead.', 
-@@ -608,10 +626,12 @@
+@@ -608,10 +609,12 @@
      t = tzinfo.localize(datetime(*(values[k] for k in 'yMdhms')))
      return tzinfo.normalize(t)
  
  _time_intervals = dict(
      second=lambda v: timedelta(seconds=v),
      minute=lambda v: timedelta(minutes=v),
-@@ -626,7 +646,7 @@
+@@ -626,7 +629,7 @@
      m=lambda v: timedelta(days=30 * v),
      y=lambda v: timedelta(days=365 * v),
  )
                              r'(second|minute|hour|day|week|month|year)$')
  _time_starts = dict(
      second=lambda now: now.replace(microsecond=0),
-@@ -650,8 +670,15 @@
+@@ -650,8 +653,15 @@
      if text == 'yesterday':
          return now.replace(microsecond=0, second=0, minute=0, hour=0) \
                 - timedelta(days=1)
          value, interval = match.groups()
          return now - _time_intervals[interval](float(value))
      match = _TIME_START_RE.match(text)
-@@ -666,6 +693,14 @@
+@@ -666,6 +676,14 @@
                      dt = dt.replace(year=dt.year - 1, month=12)
              else:
                  dt -= _time_intervals[start](1)
          return dt
  
  # -- formatting/parsing helper functions
+@@ -687,6 +705,12 @@
+         kwargs['locale'] = getattr(req, 'lc_time', None)
+     return func(*args, **kwargs)
+ 
++def format_date_or_datetime(format, *args, **kwargs):
++    if format == 'date':
++        return format_date(*args, **kwargs)
++    else:
++        return format_datetime(*args, **kwargs)
++    
+ # -- timezone utilities
+ 
+ class FixedOffset(tzinfo):
 diff -r 798a158cc10b trac/web/chrome.py
 --- a/trac/web/chrome.py	Thu Mar 01 23:30:39 2012 +0100
-+++ b/trac/web/chrome.py	Thu Mar 01 23:32:22 2012 +0100
++++ b/trac/web/chrome.py	Fri Mar 02 20:31:13 2012 +0100
 @@ -54,8 +54,9 @@
  from trac.util.text import pretty_size, obfuscate_email_address, \
                             shorten_line, unicode_quote_plus, to_unicode, \
                             javascript_quote, exception_to_unicode
 -from trac.util.datefmt import pretty_timedelta, format_datetime, format_date, \
 -                              format_time, from_utimestamp, http_date, utc, \
-+from trac.util.datefmt import pretty_timedelta, pretty_format_time, \
-+                              format_datetime, format_date, format_time, \
-+                              from_utimestamp, http_date, utc, \
++from trac.util.datefmt import pretty_timedelta, format_datetime, \
++                              format_date, format_time, from_utimestamp, \
++                              http_date, utc, localtz, \
                                get_date_format_jquery_ui, is_24_hours, \
                                get_time_format_jquery_ui, user_time, \
                                get_month_names_jquery_ui, \
-@@ -919,6 +920,7 @@
-             'fromtimestamp': partial(datetime.datetime.fromtimestamp,
-                                      tz=req and req.tz),
-             'from_utimestamp': from_utimestamp,
-+            'pretty_format_time': pretty_format_time,
+@@ -865,19 +866,27 @@
+             show_email_addresses = False
  
-             # Wiki-formatting functions
-             'wiki_to': partial(format_to, self.env),
+         def pretty_dateinfo(date, format=None, dateonly=False):
+-            absolute = user_time(req, format_datetime, date)
+-            relative = pretty_timedelta(date)
++            if not date:
++                return ''
++            if format == 'date':
++                absolute = user_time(req, format_date, date)
++            else:
++                absolute = user_time(req, format_datetime, date)
++            now = datetime.datetime.now(localtz)
++            relative = pretty_timedelta(date, now)
+             if not format:
+                 format = req.session.get('dateinfo',
+                                          self.default_dateinfo_format)
+-            if format == 'absolute':
++            in_or_ago = _("in %(relative)s", relative=relative) \
++                        if date > now else \
++                        _("%(relative)s ago", relative=relative)
++            if format == 'relative':
++                label = in_or_ago if not dateonly else relative
++                title = absolute
++            else:
+                 label = absolute
+-                title = _("%(relativetime)s ago", relativetime=relative)
+-            else:
+-                label = _("%(relativetime)s ago", relativetime=relative) \
+-                        if not dateonly else relative
+-                title = absolute
+-            return tag.span(label, title=title)
++                title = in_or_ago
++            return tag.span(label, title=title)  
+ 
+         def dateinfo(date):
+             return pretty_dateinfo(date, format='relative', dateonly=True)

File datetimepicker.patch

 # HG changeset patch
-# Parent 7c09c0515dd9cc53b7e730bcca5b7703e4c484e6
+# Parent c4a263e1f20de52c7de081a6c3976835cccc11a2
 Use jQuery datetime picker for custom time fields.
 
-diff -r 7c09c0515dd9 trac/htdocs/js/query.js
---- a/trac/htdocs/js/query.js	Thu Mar 01 23:32:30 2012 +0100
-+++ b/trac/htdocs/js/query.js	Fri Mar 02 09:41:39 2012 +0100
+diff -r c4a263e1f20d trac/htdocs/js/query.js
+--- a/trac/htdocs/js/query.js	Fri Mar 02 20:31:47 2012 +0100
++++ b/trac/htdocs/js/query.js	Fri Mar 02 20:36:20 2012 +0100
 @@ -179,11 +179,19 @@
              .append(createRadio(propertyName, "0", propertyName + "_off"))
              .append(" ").append(createLabel(_("no"), propertyName + "_off"));
          }
          tr.append(td);
        } else {
-diff -r 7c09c0515dd9 trac/ticket/query.py
---- a/trac/ticket/query.py	Thu Mar 01 23:32:30 2012 +0100
-+++ b/trac/ticket/query.py	Fri Mar 02 09:41:39 2012 +0100
+diff -r c4a263e1f20d trac/ticket/query.py
+--- a/trac/ticket/query.py	Fri Mar 02 20:31:47 2012 +0100
++++ b/trac/ticket/query.py	Fri Mar 02 20:36:20 2012 +0100
 @@ -1107,13 +1107,14 @@
  
          properties = dict((name, dict((key, field[key])
  
          return 'query.html', data, None
  
-diff -r 7c09c0515dd9 trac/ticket/templates/query.html
---- a/trac/ticket/templates/query.html	Thu Mar 01 23:32:30 2012 +0100
-+++ b/trac/ticket/templates/query.html	Fri Mar 02 09:41:39 2012 +0100
+diff -r c4a263e1f20d trac/ticket/templates/query.html
+--- a/trac/ticket/templates/query.html	Fri Mar 02 20:31:47 2012 +0100
++++ b/trac/ticket/templates/query.html	Fri Mar 02 20:36:20 2012 +0100
 @@ -21,6 +21,8 @@
            $("#filters").toggleClass("collapsed");
          /* Hide the columns by default. */
                            </i18n:msg>
                          </py:when>
                        </td>
-diff -r 7c09c0515dd9 trac/ticket/templates/ticket.html
---- a/trac/ticket/templates/ticket.html	Thu Mar 01 23:32:30 2012 +0100
-+++ b/trac/ticket/templates/ticket.html	Fri Mar 02 09:41:39 2012 +0100
+diff -r c4a263e1f20d trac/ticket/templates/ticket.html
+--- a/trac/ticket/templates/ticket.html	Fri Mar 02 20:31:47 2012 +0100
++++ b/trac/ticket/templates/ticket.html	Fri Mar 02 20:36:20 2012 +0100
 @@ -13,11 +13,13 @@
        <py:when test="ticket.exists">#${ticket.id} (${ticket.summary})</py:when>
        <py:otherwise>New Ticket</py:otherwise>
                          </label>
                          <input py:when="'time'" type="text" id="field-${field.name}" title="${field.format_hint}"
 -                               name="field_${field.name}" value="${field.edit}" />
-+                               name="field_${field.name}" value="${field.edit}" class="${field.date_or_datetime}picker" />
++                               name="field_${field.name}" value="${field.edit}" class="${field.format}picker" />
                          <py:otherwise><!--! Text input fields -->
                            <py:choose>
                              <span py:when="field.cc_entry"><!--! Special case for Cc: field -->
-diff -r 7c09c0515dd9 trac/ticket/web_ui.py
---- a/trac/ticket/web_ui.py	Thu Mar 01 23:32:30 2012 +0100
-+++ b/trac/ticket/web_ui.py	Fri Mar 02 09:41:39 2012 +0100
+diff -r c4a263e1f20d trac/ticket/web_ui.py
+--- a/trac/ticket/web_ui.py	Fri Mar 02 20:31:47 2012 +0100
++++ b/trac/ticket/web_ui.py	Fri Mar 02 20:36:20 2012 +0100
 @@ -451,6 +451,7 @@
          add_script(req, 'common/js/folding.js')
          Chrome(self.env).add_wiki_toolbars(req)
  
          # Add registered converters
          for conversion in mime.get_supported_conversions('trac.ticket.Ticket'):
-@@ -1477,6 +1479,7 @@
-             elif type_ == 'time':
-                 value = ticket[name]
-                 format = field.get('format', 'datetime')
-+                field['date_or_datetime'] = format
-                 field['rendered'] = pretty_format_time(req, value, format,
-                                                             relative=True)
-                 field['edit'] = pretty_format_time(req, value, format)

File robust_old_dates.diff

+# HG changeset patch
+# Parent 60d90d3bd1b779ab631592b4610143c68667ce0b
+More robust handling of really old dates.
+`time.mktime()` apparently can raise `ValueError` or `OverflowError` depending no platform / date.
+For me dates before 1900 raise ValueError, dates between 1900 and 1970 raise OverflowError.
+
+Related but unsolved: `datetime.strftime()` dislikes years before 1900 too
+See also http://bugs.python.org/issue1777412
+
+diff -r 60d90d3bd1b7 trac/util/datefmt.py
+--- a/trac/util/datefmt.py	Fri Mar 02 09:41:39 2012 +0100
++++ b/trac/util/datefmt.py	Fri Mar 02 14:01:16 2012 +0100
+@@ -802,6 +802,8 @@
+             return tt.tm_isdst > 0
+         except OverflowError:
+             return False
++        except ValueError:
++            return False
+ 
+     def localize(self, dt, is_dst=False):
+         if dt.tzinfo is not None:
 MinorStyleCleanup.patch
 TicketFieldList.patch
 customtimefields.patch
+robust_old_dates.diff
 ct-tests.patch
 datetimepicker.patch