Add strftime_localized function, not called yet.
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
"""
|
||||
Convenience methods for working with datetime objects
|
||||
"""
|
||||
|
||||
from datetime import timedelta
|
||||
import re
|
||||
|
||||
from pytz import timezone, UTC, UnknownTimeZoneError
|
||||
from django.utils.translation import pgettext, ugettext
|
||||
|
||||
|
||||
def get_default_time_display(dtime):
|
||||
"""
|
||||
@@ -62,3 +67,299 @@ def almost_same_datetime(dt1, dt2, allowed_delta=timedelta(minutes=1)):
|
||||
:param dt2:
|
||||
"""
|
||||
return abs(dt1 - dt2) < allowed_delta
|
||||
|
||||
|
||||
DEFAULT_SHORT_DATE_FORMAT = "%b %d, %Y"
|
||||
DEFAULT_LONG_DATE_FORMAT = "%A, %B %d, %Y"
|
||||
DEFAULT_TIME_FORMAT = "%I:%M:%S %p"
|
||||
DEFAULT_DATE_TIME_FORMAT = "%b %d, %Y at %H:%M"
|
||||
|
||||
|
||||
def strftime_localized(dtime, format): # pylint: disable=redefined-builtin
|
||||
"""
|
||||
Format a datetime, just like the built-in strftime, but with localized words.
|
||||
|
||||
The format string can also be one of:
|
||||
|
||||
* "SHORT_DATE" for a date in brief form, localized.
|
||||
|
||||
* "LONG_DATE" for a longer form of date, localized.
|
||||
|
||||
* "DATE_TIME" for a date and time together, localized.
|
||||
|
||||
* "TIME" for just the time, localized.
|
||||
|
||||
The localization is based on the current language Django is using for the
|
||||
request. The exact format strings used for each of the names above is
|
||||
determined by the translator for each language.
|
||||
|
||||
Args:
|
||||
dtime (datetime): The datetime value to format.
|
||||
|
||||
format (str): The format string to use, as specified by
|
||||
:ref:`datetime.strftime`.
|
||||
|
||||
Returns:
|
||||
A unicode string with the formatted datetime.
|
||||
|
||||
"""
|
||||
|
||||
if format == "SHORT_DATE":
|
||||
format = "%x"
|
||||
elif format == "LONG_DATE":
|
||||
# Translators: the translation for "LONG_DATE_FORMAT" must be a format
|
||||
# string for formatting dates in a long form. For example, the
|
||||
# American English form is "%A, %B %d %Y".
|
||||
# See http://strftime.org for details.
|
||||
format = ugettext("LONG_DATE_FORMAT")
|
||||
if format == "LONG_DATE_FORMAT":
|
||||
format = DEFAULT_LONG_DATE_FORMAT
|
||||
elif format == "DATE_TIME":
|
||||
# Translators: the translation for "DATE_TIME_FORMAT" must be a format
|
||||
# string for formatting dates with times. For example, the American
|
||||
# English form is "%b %d, %Y at %H:%M".
|
||||
# See http://strftime.org for details.
|
||||
format = ugettext("DATE_TIME_FORMAT")
|
||||
if format == "DATE_TIME_FORMAT":
|
||||
format = DEFAULT_DATE_TIME_FORMAT
|
||||
elif format == "TIME":
|
||||
format = "%X"
|
||||
|
||||
def process_percent_code(match):
|
||||
"""
|
||||
Convert one percent-prefixed code in the format string.
|
||||
|
||||
Called by re.sub just below.
|
||||
|
||||
"""
|
||||
code = match.group()
|
||||
if code == "%":
|
||||
# This only happens if the string ends with a %, which is not legal.
|
||||
raise ValueError("strftime format ends with raw %")
|
||||
|
||||
if code == "%a":
|
||||
part = pgettext('abbreviated weekday name', WEEKDAYS_ABBREVIATED[dtime.weekday()])
|
||||
elif code == "%A":
|
||||
part = pgettext('weekday name', WEEKDAYS[dtime.weekday()])
|
||||
elif code == "%b":
|
||||
part = pgettext('abbreviated month name', MONTHS_ABBREVIATED[dtime.month])
|
||||
elif code == "%B":
|
||||
part = pgettext('month name', MONTHS[dtime.month])
|
||||
elif code == "%p":
|
||||
part = pgettext('am/pm indicator', AM_PM[dtime.hour // 12])
|
||||
elif code == "%x":
|
||||
# Get the localized short date format, and recurse.
|
||||
# Translators: the translation for "SHORT_DATE_FORMAT" must be a
|
||||
# format string for formatting dates in a brief form. For example,
|
||||
# the American English form is "%b %d %Y".
|
||||
# See http://strftime.org for details.
|
||||
actual_format = ugettext("SHORT_DATE_FORMAT")
|
||||
if actual_format == "SHORT_DATE_FORMAT":
|
||||
actual_format = DEFAULT_SHORT_DATE_FORMAT
|
||||
if "%x" in actual_format:
|
||||
# Prevent infinite accidental recursion.
|
||||
actual_format = DEFAULT_SHORT_DATE_FORMAT
|
||||
part = strftime_localized(dtime, actual_format)
|
||||
elif code == "%X":
|
||||
# Get the localized time format, and recurse.
|
||||
# Translators: the translation for "TIME_FORMAT" must be a format
|
||||
# string for formatting times. For example, the American English
|
||||
# form is "%H:%M:%S". See http://strftime.org for details.
|
||||
actual_format = ugettext("TIME_FORMAT")
|
||||
if actual_format == "TIME_FORMAT":
|
||||
actual_format = DEFAULT_TIME_FORMAT
|
||||
if "%X" in actual_format:
|
||||
# Prevent infinite accidental recursion.
|
||||
actual_format = DEFAULT_TIME_FORMAT
|
||||
part = strftime_localized(dtime, actual_format)
|
||||
else:
|
||||
# All the other format codes: just let built-in strftime take
|
||||
# care of them.
|
||||
part = dtime.strftime(code)
|
||||
|
||||
return part
|
||||
|
||||
formatted_date = re.sub(r"%.|%", process_percent_code, format)
|
||||
return formatted_date
|
||||
|
||||
|
||||
# In order to extract the strings below, we have to mark them with pgettext.
|
||||
# But we'll do the actual pgettext later, so use a no-op for now, and save the
|
||||
# real pgettext so we can assign it back to the global name later.
|
||||
real_pgettext = pgettext
|
||||
pgettext = lambda context, text: text # pylint: disable=invalid-name
|
||||
|
||||
AM_PM = {
|
||||
# Translators: This is an AM/PM indicator for displaying times. It is
|
||||
# used for the %p directive in date-time formats. See http://strftime.org
|
||||
# for details.
|
||||
0: pgettext('am/pm indicator', 'AM'),
|
||||
# Translators: This is an AM/PM indicator for displaying times. It is
|
||||
# used for the %p directive in date-time formats. See http://strftime.org
|
||||
# for details.
|
||||
1: pgettext('am/pm indicator', 'PM'),
|
||||
}
|
||||
|
||||
WEEKDAYS = {
|
||||
# Translators: this is a weekday name that will be used when displaying
|
||||
# dates, as in "Monday Februrary 10, 2014". It is used for the %A
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
0: pgettext('weekday name', 'Monday'),
|
||||
# Translators: this is a weekday name that will be used when displaying
|
||||
# dates, as in "Tuesday Februrary 11, 2014". It is used for the %A
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
1: pgettext('weekday name', 'Tuesday'),
|
||||
# Translators: this is a weekday name that will be used when displaying
|
||||
# dates, as in "Wednesday Februrary 12, 2014". It is used for the %A
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
2: pgettext('weekday name', 'Wednesday'),
|
||||
# Translators: this is a weekday name that will be used when displaying
|
||||
# dates, as in "Thursday Februrary 13, 2014". It is used for the %A
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
3: pgettext('weekday name', 'Thursday'),
|
||||
# Translators: this is a weekday name that will be used when displaying
|
||||
# dates, as in "Friday Februrary 14, 2014". It is used for the %A
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
4: pgettext('weekday name', 'Friday'),
|
||||
# Translators: this is a weekday name that will be used when displaying
|
||||
# dates, as in "Saturday Februrary 15, 2014". It is used for the %A
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
5: pgettext('weekday name', 'Saturday'),
|
||||
# Translators: this is a weekday name that will be used when displaying
|
||||
# dates, as in "Sunday Februrary 16, 2014". It is used for the %A
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
6: pgettext('weekday name', 'Sunday'),
|
||||
}
|
||||
|
||||
WEEKDAYS_ABBREVIATED = {
|
||||
# Translators: this is an abbreviated weekday name that will be used when
|
||||
# displaying dates, as in "Mon Feb 10, 2014". It is used for the %a
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
0: pgettext('abbreviated weekday name', 'Mon'),
|
||||
# Translators: this is an abbreviated weekday name that will be used when
|
||||
# displaying dates, as in "Tue Feb 11, 2014". It is used for the %a
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
1: pgettext('abbreviated weekday name', 'Tue'),
|
||||
# Translators: this is an abbreviated weekday name that will be used when
|
||||
# displaying dates, as in "Wed Feb 12, 2014". It is used for the %a
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
2: pgettext('abbreviated weekday name', 'Wed'),
|
||||
# Translators: this is an abbreviated weekday name that will be used when
|
||||
# displaying dates, as in "Thu Feb 13, 2014". It is used for the %a
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
3: pgettext('abbreviated weekday name', 'Thu'),
|
||||
# Translators: this is an abbreviated weekday name that will be used when
|
||||
# displaying dates, as in "Fri Feb 14, 2014". It is used for the %a
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
4: pgettext('abbreviated weekday name', 'Fri'),
|
||||
# Translators: this is an abbreviated weekday name that will be used when
|
||||
# displaying dates, as in "Sat Feb 15, 2014". It is used for the %a
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
5: pgettext('abbreviated weekday name', 'Sat'),
|
||||
# Translators: this is an abbreviated weekday name that will be used when
|
||||
# displaying dates, as in "Sun Feb 16, 2014". It is used for the %a
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
6: pgettext('abbreviated weekday name', 'Sun'),
|
||||
}
|
||||
|
||||
MONTHS_ABBREVIATED = {
|
||||
# Translators: this is an abbreviated month name that will be used when
|
||||
# displaying dates, as in "Jan 10, 2014". It is used for the %b
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
1: pgettext('abbreviated month name', 'Jan'),
|
||||
# Translators: this is an abbreviated month name that will be used when
|
||||
# displaying dates, as in "Feb 10, 2014". It is used for the %b
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
2: pgettext('abbreviated month name', 'Feb'),
|
||||
# Translators: this is an abbreviated month name that will be used when
|
||||
# displaying dates, as in "Mar 10, 2014". It is used for the %b
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
3: pgettext('abbreviated month name', 'Mar'),
|
||||
# Translators: this is an abbreviated month name that will be used when
|
||||
# displaying dates, as in "Apr 10, 2014". It is used for the %b
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
4: pgettext('abbreviated month name', 'Apr'),
|
||||
# Translators: this is an abbreviated month name that will be used when
|
||||
# displaying dates, as in "May 10, 2014". It is used for the %b
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
5: pgettext('abbreviated month name', 'May'),
|
||||
# Translators: this is an abbreviated month name that will be used when
|
||||
# displaying dates, as in "Jun 10, 2014". It is used for the %b
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
6: pgettext('abbreviated month name', 'Jun'),
|
||||
# Translators: this is an abbreviated month name that will be used when
|
||||
# displaying dates, as in "Jul 10, 2014". It is used for the %b
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
7: pgettext('abbreviated month name', 'Jul'),
|
||||
# Translators: this is an abbreviated month name that will be used when
|
||||
# displaying dates, as in "Aug 10, 2014". It is used for the %b
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
8: pgettext('abbreviated month name', 'Aug'),
|
||||
# Translators: this is an abbreviated month name that will be used when
|
||||
# displaying dates, as in "Sep 10, 2014". It is used for the %b
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
9: pgettext('abbreviated month name', 'Sep'),
|
||||
# Translators: this is an abbreviated month name that will be used when
|
||||
# displaying dates, as in "Oct 10, 2014". It is used for the %b
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
10: pgettext('abbreviated month name', 'Oct'),
|
||||
# Translators: this is an abbreviated month name that will be used when
|
||||
# displaying dates, as in "Nov 10, 2014". It is used for the %b
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
11: pgettext('abbreviated month name', 'Nov'),
|
||||
# Translators: this is an abbreviated month name that will be used when
|
||||
# displaying dates, as in "Dec 10, 2014". It is used for the %b
|
||||
# directive in date-time formats. See http://strftime.org for details.
|
||||
12: pgettext('abbreviated month name', 'Dec'),
|
||||
}
|
||||
|
||||
MONTHS = {
|
||||
# Translators: this is a month name that will be used when displaying
|
||||
# dates, as in "January 10, 2014". It is used for the %B directive in
|
||||
# date-time formats. See http://strftime.org for details.
|
||||
1: pgettext('month name', 'January'),
|
||||
# Translators: this is a month name that will be used when displaying
|
||||
# dates, as in "February 10, 2014". It is used for the %B directive in
|
||||
# date-time formats. See http://strftime.org for details.
|
||||
2: pgettext('month name', 'February'),
|
||||
# Translators: this is a month name that will be used when displaying
|
||||
# dates, as in "March 10, 2014". It is used for the %B directive in
|
||||
# date-time formats. See http://strftime.org for details.
|
||||
3: pgettext('month name', 'March'),
|
||||
# Translators: this is a month name that will be used when displaying
|
||||
# dates, as in "April 10, 2014". It is used for the %B directive in
|
||||
# date-time formats. See http://strftime.org for details.
|
||||
4: pgettext('month name', 'April'),
|
||||
# Translators: this is a month name that will be used when displaying
|
||||
# dates, as in "May 10, 2014". It is used for the %B directive in
|
||||
# date-time formats. See http://strftime.org for details.
|
||||
5: pgettext('month name', 'May'),
|
||||
# Translators: this is a month name that will be used when displaying
|
||||
# dates, as in "June 10, 2014". It is used for the %B directive in
|
||||
# date-time formats. See http://strftime.org for details.
|
||||
6: pgettext('month name', 'June'),
|
||||
# Translators: this is a month name that will be used when displaying
|
||||
# dates, as in "July 10, 2014". It is used for the %B directive in
|
||||
# date-time formats. See http://strftime.org for details.
|
||||
7: pgettext('month name', 'July'),
|
||||
# Translators: this is a month name that will be used when displaying
|
||||
# dates, as in "August 10, 2014". It is used for the %B directive in
|
||||
# date-time formats. See http://strftime.org for details.
|
||||
8: pgettext('month name', 'August'),
|
||||
# Translators: this is a month name that will be used when displaying
|
||||
# dates, as in "September 10, 2014". It is used for the %B directive in
|
||||
# date-time formats. See http://strftime.org for details.
|
||||
9: pgettext('month name', 'September'),
|
||||
# Translators: this is a month name that will be used when displaying
|
||||
# dates, as in "October 10, 2014". It is used for the %B directive in
|
||||
# date-time formats. See http://strftime.org for details.
|
||||
10: pgettext('month name', 'October'),
|
||||
# Translators: this is a month name that will be used when displaying
|
||||
# dates, as in "November 10, 2014". It is used for the %B directive in
|
||||
# date-time formats. See http://strftime.org for details.
|
||||
11: pgettext('month name', 'November'),
|
||||
# Translators: this is a month name that will be used when displaying
|
||||
# dates, as in "December 10, 2014". It is used for the %B directive in
|
||||
# date-time formats. See http://strftime.org for details.
|
||||
12: pgettext('month name', 'December'),
|
||||
}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
"""Tests for util.date_utils"""
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests for util.date_utils
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta, tzinfo
|
||||
from functools import partial
|
||||
import unittest
|
||||
|
||||
import ddt
|
||||
from mock import patch
|
||||
from nose.tools import assert_equals, assert_false # pylint: disable=E0611
|
||||
from pytz import UTC, timezone
|
||||
from pytz import UTC
|
||||
|
||||
from util.date_utils import get_default_time_display, get_time_display, almost_same_datetime
|
||||
from util.date_utils import (
|
||||
get_default_time_display, get_time_display, almost_same_datetime,
|
||||
strftime_localized,
|
||||
)
|
||||
|
||||
|
||||
def test_get_default_time_display():
|
||||
@@ -106,3 +116,106 @@ def test_almost_same_datetime():
|
||||
timedelta(minutes=10)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def fake_ugettext(text, translations):
|
||||
"""
|
||||
A fake implementation of ugettext, for testing.
|
||||
"""
|
||||
return translations.get(text, text)
|
||||
|
||||
|
||||
def fake_pgettext(context, text, translations):
|
||||
"""
|
||||
A fake implementation of pgettext, for testing.
|
||||
"""
|
||||
return translations.get((context, text), text)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class StrftimeLocalizedTest(unittest.TestCase):
|
||||
"""
|
||||
Tests for strftime_localized.
|
||||
"""
|
||||
@ddt.data(
|
||||
("%Y", "2013"),
|
||||
("%m/%d/%y", "02/14/13"),
|
||||
("hello", "hello"),
|
||||
(u'%Y년 %m월 %d일', u"2013년 02월 14일"),
|
||||
("%a, %b %d, %Y", "Thu, Feb 14, 2013"),
|
||||
("%I:%M:%S %p", "04:41:17 PM"),
|
||||
)
|
||||
def test_usual_strftime_behavior(self, (fmt, expected)):
|
||||
dtime = datetime(2013, 02, 14, 16, 41, 17)
|
||||
self.assertEqual(expected, strftime_localized(dtime, fmt))
|
||||
# strftime doesn't like Unicode, so do the work in UTF8.
|
||||
self.assertEqual(expected, dtime.strftime(fmt.encode('utf8')).decode('utf8'))
|
||||
|
||||
@ddt.data(
|
||||
("SHORT_DATE", "Feb 14, 2013"),
|
||||
("LONG_DATE", "Thursday, February 14, 2013"),
|
||||
("TIME", "04:41:17 PM"),
|
||||
("%x %X!", "Feb 14, 2013 04:41:17 PM!"),
|
||||
)
|
||||
def test_shortcuts(self, (fmt, expected)):
|
||||
dtime = datetime(2013, 02, 14, 16, 41, 17)
|
||||
self.assertEqual(expected, strftime_localized(dtime, fmt))
|
||||
|
||||
@patch('util.date_utils.pgettext', partial(fake_pgettext, translations={
|
||||
("abbreviated month name", "Feb"): "XXfebXX",
|
||||
("month name", "February"): "XXfebruaryXX",
|
||||
("abbreviated weekday name", "Thu"): "XXthuXX",
|
||||
("weekday name", "Thursday"): "XXthursdayXX",
|
||||
("am/pm indicator", "PM"): "XXpmXX",
|
||||
}))
|
||||
@ddt.data(
|
||||
("SHORT_DATE", "XXfebXX 14, 2013"),
|
||||
("LONG_DATE", "XXthursdayXX, XXfebruaryXX 14, 2013"),
|
||||
("DATE_TIME", "XXfebXX 14, 2013 at 16:41"),
|
||||
("TIME", "04:41:17 XXpmXX"),
|
||||
("%x %X!", "XXfebXX 14, 2013 04:41:17 XXpmXX!"),
|
||||
)
|
||||
def test_translated_words(self, (fmt, expected)):
|
||||
dtime = datetime(2013, 02, 14, 16, 41, 17)
|
||||
self.assertEqual(expected, strftime_localized(dtime, fmt))
|
||||
|
||||
@patch('util.date_utils.ugettext', partial(fake_ugettext, translations={
|
||||
"SHORT_DATE_FORMAT": "date(%Y.%m.%d)",
|
||||
"LONG_DATE_FORMAT": "date(%A.%Y.%B.%d)",
|
||||
"DATE_TIME_FORMAT": "date(%Y.%m.%d@%H.%M)",
|
||||
"TIME_FORMAT": "%Hh.%Mm.%Ss",
|
||||
}))
|
||||
@ddt.data(
|
||||
("SHORT_DATE", "date(2013.02.14)"),
|
||||
("Look: %x", "Look: date(2013.02.14)"),
|
||||
("LONG_DATE", "date(Thursday.2013.February.14)"),
|
||||
("DATE_TIME", "date(2013.02.14@16.41)"),
|
||||
("TIME", "16h.41m.17s"),
|
||||
("The time is: %X", "The time is: 16h.41m.17s"),
|
||||
("%x %X", "date(2013.02.14) 16h.41m.17s"),
|
||||
)
|
||||
def test_translated_formats(self, (fmt, expected)):
|
||||
dtime = datetime(2013, 02, 14, 16, 41, 17)
|
||||
self.assertEqual(expected, strftime_localized(dtime, fmt))
|
||||
|
||||
@patch('util.date_utils.ugettext', partial(fake_ugettext, translations={
|
||||
"SHORT_DATE_FORMAT": "oops date(%Y.%x.%d)",
|
||||
"TIME_FORMAT": "oops %Hh.%Xm.%Ss",
|
||||
}))
|
||||
@ddt.data(
|
||||
("SHORT_DATE", "Feb 14, 2013"),
|
||||
("TIME", "04:41:17 PM"),
|
||||
)
|
||||
def test_recursion_protection(self, (fmt, expected)):
|
||||
dtime = datetime(2013, 02, 14, 16, 41, 17)
|
||||
self.assertEqual(expected, strftime_localized(dtime, fmt))
|
||||
|
||||
@ddt.data(
|
||||
"%",
|
||||
"Hello%"
|
||||
"%Y/%m/%d%",
|
||||
)
|
||||
def test_invalid_format_strings(self, fmt):
|
||||
dtime = datetime(2013, 02, 14, 16, 41, 17)
|
||||
with self.assertRaises(ValueError):
|
||||
strftime_localized(dtime, fmt)
|
||||
|
||||
Reference in New Issue
Block a user