Adds the timesince ability which fixes #394
authorJessica T <xray7224@googlemail.com>
Thu, 11 Apr 2013 21:37:48 +0000 (22:37 +0100)
committerJessica T <xray7224@googlemail.com>
Thu, 11 Apr 2013 21:37:48 +0000 (22:37 +0100)
mediagoblin/templates/mediagoblin/user_pages/media.html
mediagoblin/tools/template.py
mediagoblin/tools/timesince.py [new file with mode: 0644]
mediagoblin/tools/translate.py
setup.py

index b77c12b9bcae377eb007dfa93e907b6e5de9f289..58b9cdce1808fc7a0742373e904c5d5ce1617467 100644 (file)
                             comment=comment.id,
                             user=media.get_uploader.username,
                             media=media.slug_or_id) }}#comment">
-              {{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}
+              <span title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'>
+                {{ timesince(comment.created) }}
+              </span>
             </a>:
           </div>
           <div class="comment_content">
     {% endif %}
   </div>
   <div class="media_sidebar">
-    {% trans date=media.created.strftime("%Y-%m-%d") -%}
+    {% trans date=media.created.strftime("%Y-%m-%d"), formatted_time=timesince(media.created) -%}
       <h3>Added on</h3>
-      <p>{{ date }}</p>
+      <p><span title="{{ date }}">{{ formatted_time }}</span></p>
     {%- endtrans %}
     {% if media.tags %}
       {% include "mediagoblin/utils/tags.html" %}
index 74d811eb8460bd23e9de418b705ef8817329bc06..78d656542c55d4e4678ab5120530f650f3fbeddd 100644 (file)
@@ -29,9 +29,11 @@ from mediagoblin import _version
 from mediagoblin.tools import common
 from mediagoblin.tools.translate import get_gettext_translation
 from mediagoblin.tools.pluginapi import get_hook_templates
+from mediagoblin.tools.timesince import timesince
 from mediagoblin.meddleware.csrf import render_csrf_form_token
 
 
+
 SETUP_JINJA_ENVS = {}
 
 
@@ -73,6 +75,9 @@ def get_jinja_env(template_loader, locale):
 
     template_env.filters['urlencode'] = url_quote_plus
 
+    # add human readable fuzzy date time
+    template_env.globals['timesince'] = timesince
+
     # allow for hooking up plugin templates
     template_env.globals['get_hook_templates'] = get_hook_templates
 
diff --git a/mediagoblin/tools/timesince.py b/mediagoblin/tools/timesince.py
new file mode 100644 (file)
index 0000000..feea330
--- /dev/null
@@ -0,0 +1,102 @@
+# Copyright (c) Django Software Foundation and individual contributors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+#    1. Redistributions of source code must retain the above copyright notice,
+#       this list of conditions and the following disclaimer.
+#    
+#    2. Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#
+#    3. Neither the name of Django nor the names of its contributors may be used
+#       to endorse or promote products derived from this software without
+#       specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import unicode_literals
+
+import datetime
+import pytz
+
+from mediagoblin.tools.translate import pass_to_ugettext, lazy_pass_to_ungettext as _
+
+"""UTC time zone as a tzinfo instance."""
+utc = pytz.utc if pytz else UTC()
+
+def is_aware(value):
+    """
+    Determines if a given datetime.datetime is aware.
+
+    The logic is described in Python's docs:
+    http://docs.python.org/library/datetime.html#datetime.tzinfo
+    """
+    return value.tzinfo is not None and value.tzinfo.utcoffset(value) is not None
+
+def timesince(d, now=None, reversed=False):
+    """
+    Takes two datetime objects and returns the time between d and now
+    as a nicely formatted string, e.g. "10 minutes".  If d occurs after now,
+    then "0 minutes" is returned.
+
+    Units used are years, months, weeks, days, hours, and minutes.
+    Seconds and microseconds are ignored.  Up to two adjacent units will be
+    displayed.  For example, "2 weeks, 3 days" and "1 year, 3 months" are
+    possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not.
+
+    Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
+    """
+    chunks = (
+      (60 * 60 * 24 * 365, lambda n: _('year', 'years', n)),
+      (60 * 60 * 24 * 30, lambda n: _('month', 'months', n)),
+      (60 * 60 * 24 * 7, lambda n : _('week', 'weeks', n)),
+      (60 * 60 * 24, lambda n : _('day', 'days', n)),
+      (60 * 60, lambda n: _('hour', 'hours', n)),
+      (60, lambda n: _('minute', 'minutes', n))
+    )
+    # Convert datetime.date to datetime.datetime for comparison.
+    if not isinstance(d, datetime.datetime):
+        d = datetime.datetime(d.year, d.month, d.day)
+    if now and not isinstance(now, datetime.datetime):
+        now = datetime.datetime(now.year, now.month, now.day)
+
+    if not now:
+        now = datetime.datetime.now(utc if is_aware(d) else None)
+
+    delta = (d - now) if reversed else (now - d)
+    # ignore microseconds
+    since = delta.days * 24 * 60 * 60 + delta.seconds
+    if since <= 0:
+        # d is in the future compared to now, stop processing.
+        return '0 ' + pass_to_ugettext('minutes')
+    for i, (seconds, name) in enumerate(chunks):
+        count = since // seconds
+        if count != 0:
+            break
+    s = pass_to_ugettext('%(number)d %(type)s') % {'number': count, 'type': name(count)}
+    if i + 1 < len(chunks):
+        # Now get the second item
+        seconds2, name2 = chunks[i + 1]
+        count2 = (since - (seconds * count)) // seconds2
+        if count2 != 0:
+            s += pass_to_ugettext(', %(number)d %(type)s') % {'number': count2, 'type': name2(count2)}
+    return s
+
+def timeuntil(d, now=None):
+    """
+    Like timesince, but returns a string measuring the time until
+    the given time.
+    """
+    return timesince(d, now, reversed=True)
index 1d37c4defba9c0b0c1ddf149465af37f097f2941..4acafac788157fd0d9aeef2fd1bfefff8a4b3325 100644 (file)
@@ -123,6 +123,16 @@ def pass_to_ugettext(*args, **kwargs):
         *args, **kwargs)
 
 
+def pass_to_ungettext(*args, **kwargs):
+    """
+    Pass a translation on to the appropriate ungettext method.
+
+    The reason we can't have a global ugettext method is because
+    mg_globals gets swapped out by the application per-request.
+    """
+    return mg_globals.thread_scope.translations.ungettext(
+        *args, **kwargs)
+
 def lazy_pass_to_ugettext(*args, **kwargs):
     """
     Lazily pass to ugettext.
@@ -158,6 +168,16 @@ def lazy_pass_to_ngettext(*args, **kwargs):
     """
     return LazyProxy(pass_to_ngettext, *args, **kwargs)
 
+def lazy_pass_to_ungettext(*args, **kwargs):
+    """
+    Lazily pass to ungettext.
+
+    This is useful if you have to define a translation on a module
+    level but you need it to not translate until the time that it's
+    used as a string.
+    """
+    return LazyProxy(pass_to_ungettext, *args, **kwargs)
+
 
 def fake_ugettext_passthrough(string):
     """
index a98cd013b5c7e46ded48eb448b1967c85ffd7fb2..ce1e41029e83871bf20c06f882e0c99c71de3628 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -61,6 +61,7 @@ setup(
         'sqlalchemy-migrate',
         'mock',
         'itsdangerous',
+        'pytz',
         ## This is optional!
         # 'translitcodec',
         ## For now we're expecting that users will install this from