From: Jessica T Date: Thu, 11 Apr 2013 21:37:48 +0000 (+0100) Subject: Adds the timesince ability which fixes #394 X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=f1c3807db73adcc5eb817d693d160386f87bb15b;p=mediagoblin.git Adds the timesince ability which fixes #394 --- diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index b77c12b9..58b9cdce 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -125,7 +125,9 @@ comment=comment.id, user=media.get_uploader.username, media=media.slug_or_id) }}#comment"> - {{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}} + + {{ timesince(comment.created) }} + :
@@ -141,9 +143,9 @@ {% endif %}
- {% trans date=media.created.strftime("%Y-%m-%d") -%} + {% trans date=media.created.strftime("%Y-%m-%d"), formatted_time=timesince(media.created) -%}

Added on

-

{{ date }}

+

{{ formatted_time }}

{%- endtrans %} {% if media.tags %} {% include "mediagoblin/utils/tags.html" %} diff --git a/mediagoblin/tools/template.py b/mediagoblin/tools/template.py index 74d811eb..78d65654 100644 --- a/mediagoblin/tools/template.py +++ b/mediagoblin/tools/template.py @@ -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 index 00000000..feea3303 --- /dev/null +++ b/mediagoblin/tools/timesince.py @@ -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) diff --git a/mediagoblin/tools/translate.py b/mediagoblin/tools/translate.py index 1d37c4de..4acafac7 100644 --- a/mediagoblin/tools/translate.py +++ b/mediagoblin/tools/translate.py @@ -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): """ diff --git a/setup.py b/setup.py index a98cd013..ce1e4102 100644 --- 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