Commits

rblank  committed 1563349

0.12.1dev: Made the due date for milestones a due date + time, and fixed `Milestone.is_late` when using different timezones.

Initial patch (and hence most of the work done by) Ryan J Ollos. Closes #9582.

  • Participants
  • Parent commits 02cd2fb
  • Branches 0.12-stable

Comments (0)

Files changed (10)

File trac/admin/templates/admin_milestones.html

           </div>
           <div class="field">
             <label>Due:<br />
-              <input type="text" id="duedate" name="duedate" size="${len(date_hint)}"
-                     value="${milestone.due and format_date(milestone.due)}" readonly="${readonly}"
-                     title="${_('Format: %(datehint)s', datehint=date_hint)}"/>
-              <em i18n:msg="datehint">Format: $date_hint</em>
+              <input type="text" id="duedate" name="duedate" size="${len(datetime_hint)}"
+                     value="${milestone.due and format_datetime(milestone.due)}" readonly="${readonly}"
+                     title="${_('Format: %(datehint)s', datehint=datetime_hint)}"/>
+              <em i18n:msg="datehint">Format: $datetime_hint</em>
             </label>
           </div>
           <div class="field">
       </form>
 
       <py:otherwise>
-
         <form class="addnew" id="addmilestone" method="post" action="" py:if="'MILESTONE_CREATE' in req.perm">
           <fieldset>
             <legend>Add Milestone:</legend>
             <div class="field">
-              <label>Name:<br />
-                <input type="text" name="name" id="name" size="20" />
-              </label>
+              <label>Name:<br /><input type="text" name="name" id="name" size="22" /></label>
             </div>
             <div class="field">
               <label>
                 Due:<br />
-                <input type="text" name="duedate" size="${len(date_hint)}"
-                       title="${_('Format: %(datehint)s', datehint=date_hint)}" />
-                <em i18n:msg="datehint">Format: $date_hint</em>
+                <input type="text" id="duedate" name="duedate" size="${len(datetime_hint)}"
+                       title="${_('Format: %(datehint)s', datehint=datetime_hint)}" /><br/>
+                <em i18n:msg="datetimehint">Format: $datetime_hint</em>
               </label>
             </div>
             <div class="buttons">
                   <a href="${panel_href(milestone.name)}">${milestone.name}</a>
                 </td>
                 <td><py:if test="milestone.due">
-                  ${format_date(milestone.due)}
+                  ${format_datetime(milestone.due)}
                 </py:if></td>
                 <td><py:if test="milestone.completed">
                   ${format_datetime(milestone.completed)}

File trac/admin/templates/admin_versions.html

           <fieldset>
             <legend>Add Version:</legend>
             <div class="field">
-              <label>Name:<br /><input type="text" name="name" id="name" /></label>
+              <label>Name:<br /><input type="text" name="name" id="name" size="22" /></label>
             </div>
             <div class="field">
-              <label>Released:<br />
+              <label>
+                Released:<br />
                 <input type="text" id="releaseddate" name="time" size="${len(datetime_hint)}"
                        title="${_('Format: %(datehint)s', datehint=datetime_hint)}"
                        value="${format_datetime()}" /><br />

File trac/htdocs/css/roadmap.css

 #edit fieldset { margin: 1em 0 }
 #edit em { color: #888; font-size: smaller }
 #edit .disabled em { color: #d7d7d7 }
-#edit .field { margin-top: 1.3em }
+#edit .field { margin: 0.5em 0 }
 #edit label { padding-left: .2em }
 #edit fieldset.iefix { margin-left: 1px; margin-right: 1px }
 #edit textarea#description { margin-left: -1px; margin-right: -1px; padding: 0; width: 100% }

File trac/ticket/admin.py

 from trac.resource import ResourceNotFound
 from trac.ticket import model
 from trac.util import getuser
-from trac.util.datefmt import utc, parse_date, get_date_format_hint, \
-                              get_datetime_format_hint, format_date, \
-                              format_datetime
+from trac.util.datefmt import utc, parse_date, get_datetime_format_hint, \
+                              format_date, format_datetime
 from trac.util.text import print_table, printout, exception_to_unicode
 from trac.util.translation import _, N_, gettext
 from trac.web.chrome import Chrome, add_notice, add_warning
                     mil.due = mil.completed = None
                     due = req.args.get('duedate', '')
                     if due:
-                        mil.due = parse_date(due, req.tz)
+                        mil.due = parse_date(due, req.tz, 'datetime')
                     if req.args.get('completed', False):
                         completed = req.args.get('completeddate', '')
-                        mil.completed = parse_date(completed, req.tz)
+                        mil.completed = parse_date(completed, req.tz,
+                                                   'datetime')
                         if mil.completed > datetime.now(utc):
                             raise TracError(_('Completion date may not be in '
                                               'the future'),
                         mil.name = name
                         if req.args.get('duedate'):
                             mil.due = parse_date(req.args.get('duedate'),
-                                                 req.tz)
+                                                 req.tz, 'datetime')
                         mil.insert()
                         add_notice(req, _('The milestone "%(name)s" has been '
                                           'added.', name=name))
                     'default': default}
 
         data.update({
-            'date_hint': get_date_format_hint(),
             'datetime_hint': get_datetime_format_hint()
         })
         return 'admin_milestones.html', data
         milestone = model.Milestone(self.env)
         milestone.name = name
         if due is not None:
-            milestone.due = parse_date(due)
+            milestone.due = parse_date(due, hint='datetime')
         milestone.insert()
     
     def _do_rename(self, name, newname):
         @self.env.with_transaction()
         def do_due(db):
             milestone = model.Milestone(self.env, name, db=db)
-            milestone.due = due and parse_date(due)
+            milestone.due = due and parse_date(due, hint='datetime')
             milestone.update()
     
     def _do_completed(self, name, completed):
         @self.env.with_transaction()
         def do_completed(db):
             milestone = model.Milestone(self.env, name, db=db)
-            milestone.completed = completed and parse_date(completed)
+            milestone.completed = completed and parse_date(completed,
+                                                           hint='datetime')
             milestone.update()
     
     def _do_remove(self, name):
                 if req.args.get('save'):
                     ver.name = req.args.get('name')
                     if req.args.get('time'):
-                        ver.time = parse_date(req.args.get('time'), req.tz)
+                        ver.time = parse_date(req.args.get('time'), req.tz,
+                                              'datetime')
                     else:
                         ver.time = None # unset
                     ver.description = req.args.get('description')
                         ver.name = name
                         if req.args.get('time'):
                             ver.time = parse_date(req.args.get('time'),
-                                                  req.tz)
+                                                  req.tz, 'datetime')
                         ver.insert()
                         add_notice(req, _('The version "%(name)s" has been '
                                           'added.', name=name))
         version = model.Version(self.env)
         version.name = name
         if time is not None:
-            version.time = time and parse_date(time)
+            version.time = time and parse_date(time, hint='datetime')
         version.insert()
     
     def _do_rename(self, name, newname):
         @self.env.with_transaction()
         def do_time(db):
             version = model.Version(self.env, name, db=db)
-            version.time = time and parse_date(time)
+            version.time = time and parse_date(time, hint='datetime')
             version.update()
     
     def _do_remove(self, name):

File trac/ticket/model.py

 #         Christopher Lenz <cmlenz@gmx.de>
 
 import re
-from datetime import date, datetime
+from datetime import datetime
 
 from trac.attachment import Attachment
 from trac.core import TracError
 
     exists = property(fget=lambda self: self._old['name'] is not None)
     is_completed = property(fget=lambda self: self.completed is not None)
-    is_late = property(fget=lambda self: self.due and \
-                                         self.due.date() < date.today())
+    is_late = property(fget=lambda self: self.due and
+                                         self.due < datetime.now(utc))
 
     def _from_database(self, row):
         name, due, completed, description = row

File trac/ticket/roadmap.py

 from trac.resource import *
 from trac.search import ISearchSource, search_to_sql, shorten_result
 from trac.util.datefmt import parse_date, utc, to_utimestamp, \
-                              get_date_format_hint, get_datetime_format_hint, \
-                              format_date, format_datetime, from_utimestamp
+                              get_datetime_format_hint, format_date, \
+                              format_datetime, from_utimestamp
 from trac.util.text import CRLF
 from trac.util.translation import _, tag_
 from trac.ticket import Milestone, Ticket, TicketSystem, group_milestones
         milestone.description = req.args.get('description', '')
 
         due = req.args.get('duedate', '')
-        milestone.due = due and parse_date(due, tzinfo=req.tz) or None
+        milestone.due = due and parse_date(due, req.tz, 'datetime') or None
 
         completed = req.args.get('completeddate', '')
         retarget_to = req.args.get('target')
 
         # -- check completed date
         if 'completed' in req.args:
-            completed = completed and parse_date(completed, req.tz) or None
+            completed = completed and parse_date(completed, req.tz,
+                                                 'datetime') or None
             if completed and completed > datetime.now(utc):
                 warn(_('Completion date may not be in the future'))
         else:
     def _render_editor(self, req, db, milestone):
         data = {
             'milestone': milestone,
-            'date_hint': get_date_format_hint(),
             'datetime_hint': get_datetime_format_hint(),
             'milestone_groups': [],
         }

File trac/ticket/templates/milestone_edit.html

         </div>
         <fieldset>
           <legend>Schedule</legend>
-          <label>Due:<br />
-            <input type="text" id="duedate" name="duedate" size="${len(date_hint)}"
-                   value="${milestone.due and format_date(milestone.due)}" 
-                   title="${_('Format: %(datehint)s', datehint=date_hint)}" />
-            <em i18n:msg="datehint">Format: ${date_hint}</em>
-          </label>
+          <div class="field">
+            <label>Due:<br />
+              <input type="text" id="duedate" name="duedate" size="${len(datetime_hint)}"
+                     value="${milestone.due and format_datetime(milestone.due)}" 
+                     title="${_('Format: %(datehint)s', datehint=datetime_hint)}" />
+              <em i18n:msg="datehint">Format: ${datetime_hint}</em>        
+            </label>
+          </div>          
           <div class="field">
             <label>
               <input type="checkbox" id="completed" name="completed"

File trac/ticket/templates/milestone_view.html

           </p>
           <p py:when="milestone.is_late" class="date">
             <i18n:msg params="duration, date">
-              <strong>${dateinfo(milestone.due)} late</strong> (${format_date(milestone.due)})
+              <strong>${dateinfo(milestone.due)} late</strong> (${format_datetime(milestone.due)})
             </i18n:msg>
           </p>
           <p py:when="milestone.due" class="date">
             <i18n:msg params="duration, date">
-              Due in ${dateinfo(milestone.due)} (${format_date(milestone.due)})
+              Due in ${dateinfo(milestone.due)} (${format_datetime(milestone.due)})
             </i18n:msg>
           </p>
           <p py:otherwise="" class="date">

File trac/ticket/templates/roadmap.html

             <py:choose>
               <p py:when="milestone.completed" class="date">
                 <i18n:msg params="duration, date">
-                  Completed ${dateinfo(milestone.completed)} ago (${format_date(milestone.completed)})
+                  Completed ${dateinfo(milestone.completed)} ago (${format_datetime(milestone.completed)})
                 </i18n:msg>
               </p>
               <p py:when="milestone.is_late" class="date">
                 <i18n:msg params="duration, date">
-                  <strong>${dateinfo(milestone.due)} late</strong> (${format_date(milestone.due)})
+                  <strong>${dateinfo(milestone.due)} late</strong> (${format_datetime(milestone.due)})
                 </i18n:msg>
               </p>
               <p py:when="milestone.due" class="date">
                 <i18n:msg params="duration, date">
-                  Due in ${dateinfo(milestone.due)} (${format_date(milestone.due)})
+                  Due in ${dateinfo(milestone.due)} (${format_datetime(milestone.due)})
                 </i18n:msg>
               </p>
               <p py:otherwise="" class="date">

File trac/util/datefmt.py

     (Z?(?:([-+])?(\d\d):?(\d\d)?)?)?$       # timezone
     ''', re.VERBOSE)
 
-def parse_date(text, tzinfo=None):
+def parse_date(text, tzinfo=None, hint='date'):
     tzinfo = tzinfo or localtz
     dt = None
     text = text.strip()
     if dt is None:
         dt = _parse_relative_time(text, tzinfo)
     if dt is None:
-        hint = get_date_format_hint()        
+        hint = {'datetime': get_datetime_format_hint,
+                'date': get_date_format_hint}.get(hint, lambda: hint)()
         raise TracError(_('"%(date)s" is an invalid date, or the date format '
                           'is not known. Try "%(hint)s" instead.', 
                           date=text, hint=hint), _('Invalid Date'))