Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit aa67128

Browse files
authored
Merge pull request #5376 from DataShades/formatters-with-babel
[#5370] Update date formatters
2 parents 0c7d783 + cff1bd0 commit aa67128

8 files changed

Lines changed: 154 additions & 147 deletions

File tree

ckan/lib/formatters.py

Lines changed: 30 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,28 @@
22

33
import datetime
44
import pytz
5-
from babel import numbers
5+
import six
66

7-
import ckan.lib.i18n as i18n
7+
from flask_babel import (
8+
format_number,
9+
format_datetime,
10+
format_date,
11+
format_timedelta
12+
)
813

9-
from ckan.common import _, ungettext
10-
11-
12-
##################################################
13-
# #
14-
# Month translations #
15-
# #
16-
##################################################
17-
18-
def _month_jan():
19-
return _('January')
20-
21-
22-
def _month_feb():
23-
return _('February')
24-
25-
26-
def _month_mar():
27-
return _('March')
28-
29-
30-
def _month_apr():
31-
return _('April')
32-
33-
34-
def _month_may():
35-
return _('May')
36-
37-
38-
def _month_june():
39-
return _('June')
40-
41-
42-
def _month_july():
43-
return _('July')
44-
45-
46-
def _month_aug():
47-
return _('August')
48-
49-
50-
def _month_sept():
51-
return _('September')
52-
53-
54-
def _month_oct():
55-
return _('October')
56-
57-
58-
def _month_nov():
59-
return _('November')
60-
61-
62-
def _month_dec():
63-
return _('December')
64-
65-
66-
# _MONTH_FUNCTIONS provides an easy way to get a localised month via
67-
# _MONTH_FUNCTIONS[month]() where months are zero based ie jan = 0, dec = 11
68-
_MONTH_FUNCTIONS = [_month_jan, _month_feb, _month_mar, _month_apr,
69-
_month_may, _month_june, _month_july, _month_aug,
70-
_month_sept, _month_oct, _month_nov, _month_dec]
14+
from ckan.common import _
7115

7216

7317
def localised_nice_date(datetime_, show_date=False, with_hours=False,
74-
with_seconds=False):
18+
with_seconds=False, format=None):
7519
''' Returns a friendly localised unicode representation of a datetime.
7620
e.g. '31 minutes ago'
7721
'1 day ago'
7822
'April 24, 2013' (show_date=True)
23+
'October 25, 2017, 16:03 (UTC)' (show_date=True, with_hours=True)
24+
'Apr 3, 2020, 4:00:31 PM' (
25+
show_date=True, with_hours=True, format='medium')
26+
'April 03, 20' (show_date=True, format='MMMM dd, YY')
7927
8028
:param datetime_: The date to format
8129
:type datetime_: datetime
@@ -85,95 +33,42 @@ def localised_nice_date(datetime_, show_date=False, with_hours=False,
8533
:type with_hours: bool
8634
:param with_seconds: should the `hours:mins:seconds` be shown for dates
8735
:type with_seconds: bool
36+
:param format: override format of datetime representation using babel
37+
date/time pattern syntax of predefined pattern.
38+
:type format: str
39+
8840
8941
:rtype: sting
9042
'''
91-
92-
def months_between(date1, date2):
93-
if date1 > date2:
94-
date1, date2 = date2, date1
95-
m1 = date1.year * 12 + date1.month
96-
m2 = date2.year * 12 + date2.month
97-
months = m2 - m1
98-
if date1.day > date2.day:
99-
months -= 1
100-
elif date1.day == date2.day:
101-
seconds1 = date1.hour * 3600 + date1.minute + date1.second
102-
seconds2 = date2.hour * 3600 + date2.minute + date2.second
103-
if seconds1 > seconds2:
104-
months -= 1
105-
return months
106-
43+
if datetime_.tzinfo is None:
44+
datetime_ = datetime_.replace(tzinfo=pytz.utc)
10745
if not show_date:
10846
now = datetime.datetime.now(pytz.utc)
109-
if datetime_.tzinfo is None:
110-
datetime_ = datetime_.replace(tzinfo=pytz.utc)
111-
112-
date_diff = now - datetime_
113-
days = date_diff.days
114-
if days < 1 and now > datetime_:
115-
# less than one day
116-
seconds = date_diff.seconds
117-
if seconds < 3600:
118-
# less than one hour
119-
if seconds < 60:
120-
return _('Just now')
121-
else:
122-
return ungettext('{mins} minute ago', '{mins} minutes ago',
123-
seconds // 60).format(mins=seconds // 60)
124-
else:
125-
return ungettext('{hours} hour ago', '{hours} hours ago',
126-
seconds // 3600).format(hours=seconds // 3600)
127-
# more than one day
128-
months = months_between(datetime_, now)
129-
130-
if months < 1:
131-
return ungettext('{days} day ago', '{days} days ago',
132-
days).format(days=days)
133-
if months < 13:
134-
return ungettext('{months} month ago', '{months} months ago',
135-
months).format(months=months)
136-
return ungettext('over {years} year ago', 'over {years} years ago',
137-
months // 12).format(years=months // 12)
138-
139-
# actual date
140-
details = {
141-
'sec': int(datetime_.second),
142-
'min': datetime_.minute,
143-
'hour': datetime_.hour,
144-
'day': datetime_.day,
145-
'year': datetime_.year,
146-
'month': _MONTH_FUNCTIONS[datetime_.month - 1](),
147-
'timezone': datetime_.tzname(),
148-
}
47+
date_diff = datetime_ - now
48+
if abs(date_diff) < datetime.timedelta(seconds=1):
49+
return _('Just now')
50+
return format_timedelta(date_diff, add_direction=True)
14951

15052
if with_seconds:
151-
return (
152-
# Example output: `April 24, 2013, 10:45:21 (Europe/Zurich)`
153-
_('{month} {day}, {year}, {hour:02}:{min:02}:{sec:02} ({timezone})') \
154-
.format(**details))
53+
return format_datetime(datetime_, format or 'long')
15554
elif with_hours:
156-
return (
157-
# Example output: `April 24, 2013, 10:45 (Europe/Zurich)`
158-
_('{month} {day}, {year}, {hour:02}:{min:02} ({timezone})') \
159-
.format(**details))
55+
fmt_str = "MMMM d, YYYY, HH:mm (z)"
56+
return format_datetime(datetime_, format or fmt_str)
16057
else:
161-
return (
162-
# Example output: `April 24, 2013`
163-
_('{month} {day}, {year}').format(**details))
58+
return format_date(datetime_, format or 'long')
16459

16560

16661
def localised_number(number):
16762
''' Returns a localised unicode representation of number '''
168-
return numbers.format_number(number, locale=i18n.get_lang())
63+
return format_number(number)
16964

17065

17166
def localised_filesize(number):
17267
''' Returns a localised unicode representation of a number in bytes, MiB
17368
etc '''
17469
def rnd(number, divisor):
17570
# round to 1 decimal place
176-
return localised_number(float(number * 10 / divisor) / 10)
71+
return localised_number(float(number * 10 // divisor) / 10)
17772

17873
if number < 1024:
17974
return _('{bytes} bytes').format(bytes=localised_number(number))
@@ -193,7 +88,7 @@ def localised_SI_number(number):
19388

19489
def rnd(number, divisor):
19590
# round to 1 decimal place
196-
return localised_number(float(number * 10 / divisor) / 10)
91+
return localised_number(float(number * 10 // divisor) / 10)
19792

19893
if number < 1000:
19994
return _('{n}').format(n=localised_number(number))

ckan/tests/legacy/test_coding_standards.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,6 @@ class TestPep8(object):
317317
"ckan/lib/email_notifications.py",
318318
"ckan/lib/fanstatic_extensions.py",
319319
"ckan/lib/fanstatic_resources.py",
320-
"ckan/lib/formatters.py",
321320
"ckan/lib/hash.py",
322321
"ckan/lib/help/flash_messages.py",
323322
"ckan/lib/jinja_extensions.py",

ckan/tests/lib/test_formatters.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import six
4+
import pytest
5+
import pytz
6+
from ckan.lib import formatters as f
7+
from datetime import datetime
8+
9+
10+
@pytest.mark.parametrize(
11+
u"number,expected",
12+
[
13+
(1, u"1"),
14+
(1e3, u"1k"),
15+
(1e6, u"1M"),
16+
(1e6 + 32e3, u"1M"),
17+
(1e9, u"1G"),
18+
(1e9 + 1, u"1G"),
19+
(1e12, u"1T"),
20+
(1e12 + 9e11, u"1.9T"),
21+
(1e15, u"1P"),
22+
(1e18, u"1E"),
23+
(1e21, u"1Z"),
24+
(1e25, u"10Y"),
25+
],
26+
)
27+
@pytest.mark.usefixtures(u"with_request_context")
28+
def test_localized_SI_number(number, expected):
29+
assert f.localised_SI_number(number) == expected
30+
31+
32+
@pytest.mark.parametrize(
33+
u"size,expected",
34+
[
35+
(1, u"1 bytes"),
36+
(1024, u"1 KiB"),
37+
(1024 ** 2, u"1 MiB"),
38+
(1024 ** 2 + 1024 * 31, u"1 MiB"),
39+
(1024 ** 3, u"1 GiB"),
40+
(1024 ** 3 + 1, u"1 GiB"),
41+
(1024 ** 4, u"1 TiB"),
42+
(1024 ** 4 + 1024 ** 3 * 900, u"1.8 TiB"),
43+
],
44+
)
45+
@pytest.mark.usefixtures(u"with_request_context")
46+
def test_localized_filesize(size, expected):
47+
assert f.localised_filesize(size) == expected
48+
49+
50+
_now = datetime(2017, 10, 23, 16, 3, 52, tzinfo=pytz.UTC)
51+
52+
53+
@pytest.mark.freeze_time(_now)
54+
@pytest.mark.usefixtures(u"with_request_context")
55+
class TestLocalizedNiceDate(object):
56+
@pytest.mark.parametrize(
57+
u"dt,date,hours,seconds,expected",
58+
[
59+
(_now, False, False, False, u"Just now"),
60+
(_now, True, False, False, u"October 23, 2017"),
61+
(_now, True, True, False, u"October 23, 2017, 16:03 (UTC)"),
62+
(_now, True, True, True, u"October 23, 2017 at 4:03:52 PM UTC"),
63+
(_now, False, True, True, u"Just now"),
64+
(_now, False, False, True, u"Just now"),
65+
(_now, False, True, False, u"Just now"),
66+
],
67+
)
68+
def test_params(self, dt, date, hours, seconds, expected):
69+
assert f.localised_nice_date(dt, date, hours, seconds) == expected
70+
71+
@pytest.mark.parametrize(
72+
u"dt,expected",
73+
[
74+
(_now, u"Just now"),
75+
(_now.replace(second=_now.second - 30), u"30 seconds ago"),
76+
(_now.replace(minute=_now.minute - 2), u"2 minutes ago"),
77+
(_now.replace(hour=_now.hour - 4), u"4 hours ago"),
78+
(_now.replace(day=_now.day - 5), u"5 days ago"),
79+
(_now.replace(day=_now.day - 8), u"1 week ago"),
80+
(_now.replace(month=_now.month - 4), u"4 months ago"),
81+
(_now.replace(year=_now.year - 5), u"5 years ago"),
82+
(_now.replace(second=_now.second + 5), u"in 5 seconds"),
83+
(_now.replace(minute=_now.minute + 2), u"in 2 minutes"),
84+
(_now.replace(hour=_now.hour + 4), u"in 4 hours"),
85+
(_now.replace(day=_now.day + 5), u"in 5 days"),
86+
(_now.replace(day=_now.day + 8), u"in 1 week"),
87+
(_now.replace(month=_now.month + 1), u"in 1 month"),
88+
(_now.replace(year=_now.year + 5), u"in 5 years"),
89+
],
90+
)
91+
def test_relative_dates(self, dt, expected):
92+
assert f.localised_nice_date(dt) == expected
93+
94+
@pytest.mark.parametrize(
95+
u"dt,hours,seconds,fmt,expected",
96+
[
97+
(_now, False, False, None, u"October 23, 2017"),
98+
(_now, False, False, u"MMM, YY", u"Oct, 17"),
99+
(_now, True, False, None, u"October 23, 2017, 16:03 (UTC)"),
100+
(_now, True, False, u"EEE, HH:mm", u"Mon, 16:03"),
101+
(_now, True, True, None, u"October 23, 2017 at 4:03:52 PM UTC"),
102+
(
103+
_now,
104+
True,
105+
False,
106+
u"MMM dd, yy. EEEE 'at' hh:mm:ss [z]",
107+
u"Oct 23, 17. Monday at 04:03:52 [UTC]",
108+
),
109+
],
110+
)
111+
def test_with_dates(self, dt, hours, seconds, fmt, expected):
112+
assert f.localised_nice_date(dt, True, hours, seconds, fmt) == expected

0 commit comments

Comments
 (0)