Source

custom-time / add-default_now-attr.patch

Full commit
diff --git a/trac/util/datefmt.py b/trac/util/datefmt.py
--- a/trac/util/datefmt.py
+++ b/trac/util/datefmt.py
@@ -31,19 +31,25 @@
 
 # -- conversion
 
-def to_datetime(t, tzinfo=None):
+def to_datetime(t, tzinfo=None, default_now=True):
     """Convert `t` into a `datetime` object, using the following rules:
     
      - If `t` is already a `datetime` object, it is simply returned.
-     - If `t` is None, the current time will be used.
+     - If `t` is None, the current time will be used by default.
      - If `t` is a number, it is interpreted as a timestamp.
      
+    Replacement of missing date/time value with current time is switchable.
+    This is required for preserving empty value for custom time fields.
+
     If no `tzinfo` is given, the local timezone will be used.
 
     Any other input will trigger a `TypeError`.
     """
     if t is None:
-        return datetime.now(tzinfo or localtz)
+        if default_now:
+            return datetime.now(tzinfo or localtz)
+        else:
+            return t
     elif isinstance(t, datetime):
         return t
     elif isinstance(t, date):
@@ -53,24 +59,43 @@
     raise TypeError('expecting datetime, int, long, float, or None; got %s' %
                     type(t))
 
-def to_timestamp(dt):
-    """Return the corresponding POSIX timestamp"""
-    if dt:
-        diff = dt - _epoc
-        return diff.days * 86400 + diff.seconds
-    else:
-        return 0
+def to_timestamp(dt, default_now=True):
+    """Return the corresponding POSIX timestamp for a given `datetime`.
 
-def to_utimestamp(dt):
-    """Return a microsecond POSIX timestamp for the given `datetime`."""
+    Replacement of missing date/time value with current time is switchable.
+    This is required for preserving empty value for custom time fields.
+    """
     if not dt:
-        return 0
+        if default_now:
+            return 0
+        else:
+            return None
+    diff = dt - _epoc
+    return diff.days * 86400 + diff.seconds
+
+def to_utimestamp(dt, default_now=True):
+    """Return a microsecond POSIX timestamp for the given `datetime`.
+
+    Replacement of missing date/time value with current time is switchable.
+    This is required for preserving empty value for custom time fields.
+    """
+    if not dt:
+        if default_now:
+            return 0
+        else:
+            return None
     diff = dt - _epoc
     return (diff.days * 86400000000L + diff.seconds * 1000000
             + diff.microseconds)
 
-def from_utimestamp(ts):
-    """Return the `datetime` for the given microsecond POSIX timestamp."""
+def from_utimestamp(ts, default_now=True):
+    """Return the `datetime` for the given microsecond POSIX timestamp.
+
+    Replacement of missing date/time value with current time is switchable.
+    This is required for preserving empty value for custom time fields.
+    """
+    if not ts and not default_now:
+        return None
     return _epoc + timedelta(microseconds=ts or 0)
 
 # -- formatting
@@ -109,17 +134,22 @@
     return ''
 
     
-def format_datetime(t=None, format='%x %X', tzinfo=None):
+def format_datetime(t=None, format='%x %X', tzinfo=None, default_now=True):
     """Format the `datetime` object `t` into an `unicode` string
 
-    If `t` is None, the current time will be used.
+    If `t` is None, the current time will be used by default.
     
+    Replacement of missing date/time value with current time is switchable.
+    This is required for preserving empty value for custom time fields.
+
     The formatting will be done using the given `format`, which consist
     of conventional `strftime` keys. In addition the format can be 'iso8601'
     to specify the international date format (compliant with RFC 3339).
 
     `tzinfo` will default to the local timezone if left to `None`.
     """
+    if t is None and not default_now:
+        return ''
     tz = tzinfo or localtz
     t = to_datetime(t, tzinfo).astimezone(tz)
     normalize_Z = False
@@ -202,7 +232,12 @@
     (Z?(?:([-+])?(\d\d):?(\d\d)?)?)?$       # timezone
     ''', re.VERBOSE)
 
-def parse_date(text, tzinfo=None):
+def parse_date(text, tzinfo=None, default_now=True):
+    """Parse date/time string to corresponding datetime.date value.
+
+    TracError on missing date/time value with current time is switchable.
+    This is required for accepting empty string for custom time fields.
+    """
     tzinfo = tzinfo or localtz
     dt = None
     text = text.strip()
@@ -242,7 +277,7 @@
                 continue
     if dt is None:
         dt = _parse_relative_time(text, tzinfo)
-    if dt is None:
+    if dt is None and default_now:
         hint = get_date_format_hint()        
         raise TracError(_('"%(date)s" is an invalid date, or the date format '
                           'is not known. Try "%(hint)s" instead.', 
@@ -250,7 +285,7 @@
     # Make sure we can convert it to a timestamp and back - fromtimestamp()
     # may raise ValueError if larger than platform C localtime() or gmtime()
     try:
-        to_datetime(to_timestamp(dt), tzinfo)
+        to_datetime(to_timestamp(dt, default_now), tzinfo, default_now)
     except ValueError:
         raise TracError(_('The date "%(date)s" is outside valid range. '
                           'Try a date closer to present time.', date=text),