Skip to content

Commit 3a51c41

Browse files
committed
dates: Add basic format_interval implementation
Refs #276
1 parent 81a19c5 commit 3a51c41

File tree

1 file changed

+93
-0
lines changed

1 file changed

+93
-0
lines changed

babel/dates.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,93 @@ def _iter_patterns(a_unit):
859859
return u''
860860

861861

862+
863+
def _format_fallback_interval(start, end, skeleton, tzinfo, locale):
864+
if skeleton in locale.datetime_skeletons: # If the skeleton is absolutely unknown, don't crash
865+
format = lambda dt: format_skeleton(skeleton, dt, tzinfo, locale=locale)
866+
else:
867+
format = lambda dt: format_datetime(dt, tzinfo=tzinfo, locale=locale)
868+
869+
return (
870+
locale.interval_formats.get(None, "{0}-{1}").
871+
replace("{0}", format(start)).
872+
replace("{1}", format(end))
873+
)
874+
875+
876+
def format_interval(start, end, skeleton, tzinfo=None, locale=LC_TIME):
877+
"""
878+
Format an interval between two instants according to the locale's rules.
879+
880+
>>> format_interval(date(2016, 1, 15), date(2016, 1, 17), "yMd", locale="fi")
881+
u'15.\u201317.1.2016'
882+
883+
>>> format_interval(time(12, 12), time(16, 16), "Hm", locale="en_GB")
884+
'12:12 \u2013 16:16'
885+
886+
>>> format_interval(time(5, 12), time(16, 16), "hm", locale="en_US")
887+
'5:12 AM \u2013 4:16 PM'
888+
889+
>>> format_interval(time(16, 18), time(16, 24), "Hm", locale="it")
890+
'16:18\u201316:24'
891+
892+
>>> format_interval(time(16, 18), time(16, 24), "wzq", locale="ja")
893+
'2016/01/04 16:18:00\uff5e2016/01/04 16:24:00'
894+
895+
:param start: First instant (datetime/date/time)
896+
:param end: Second instant (datetime/date/time)
897+
:param skeleton: The "skeleton format" to use for formatting.
898+
:param tzinfo: tzinfo to use (if none is already attached)
899+
:param locale: A locale object or identifier.
900+
:return: Formatted interval
901+
"""
902+
locale = Locale.parse(locale)
903+
904+
start = _ensure_datetime_tzinfo(_get_datetime(start), tzinfo=tzinfo)
905+
end = _ensure_datetime_tzinfo(_get_datetime(end), tzinfo=tzinfo)
906+
907+
# NB: The quote comments below are from the algorithm description in
908+
# http://www.unicode.org/reports/tr35/tr35-dates.html#intervalFormats
909+
910+
# > Look for the intervalFormatItem element that matches the "skeleton",
911+
# > starting in the current locale and then following the locale fallback
912+
# > chain up to, but not including root.
913+
914+
if skeleton not in locale.interval_formats:
915+
# > If no match was found from the previous step, check what the closest
916+
# > match is in the fallback locale chain, as in availableFormats. That
917+
# > is, this allows for adjusting the string value field's width,
918+
# > including adjusting between "MMM" and "MMMM", and using different
919+
# > variants of the same field, such as 'v' and 'z'.
920+
# TODO: Implement closest-match instead of immediately falling back
921+
return _format_fallback_interval(start, end, skeleton, tzinfo, locale)
922+
923+
skel_formats = locale.interval_formats[skeleton]
924+
925+
start_fmt = DateTimeFormat(start, locale=locale)
926+
end_fmt = DateTimeFormat(end, locale=locale)
927+
928+
# > If a match is found from previous steps, compute the calendar field
929+
# > with the greatest difference between start and end datetime. If there
930+
# > is no difference among any of the fields in the pattern, format as a
931+
# > single date using availableFormats, and return.
932+
933+
for field in PATTERN_CHAR_ORDER: # These are in largest-to-smallest order
934+
if field in skel_formats:
935+
if start_fmt.extract(field) != end_fmt.extract(field):
936+
# > If there is a match, use the pieces of the corresponding pattern to
937+
# > format the start and end datetime, as above.
938+
return "".join(
939+
parse_pattern(pattern).apply(instant, locale)
940+
for pattern, instant
941+
in zip(skel_formats[field], (start, end))
942+
)
943+
944+
# > Otherwise, format the start and end datetime using the fallback pattern.
945+
946+
return _format_fallback_interval(start, end, skeleton, tzinfo, locale)
947+
948+
862949
def parse_date(string, locale=LC_TIME):
863950
"""Parse a date from a string.
864951
@@ -1185,8 +1272,14 @@ def get_week_number(self, day_of_period, day_of_week=None):
11851272
'z': [1, 2, 3, 4], 'Z': [1, 2, 3, 4], 'v': [1, 4], 'V': [1, 4] # zone
11861273
}
11871274

1275+
#: The pattern characters declared in the Date Field Symbol Table
1276+
#: (http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table)
1277+
#: in order of decreasing magnitude.
1278+
PATTERN_CHAR_ORDER = "GyYuUQqMLlwWdDFgEecabBChHKkjJmsSAzZvV"
1279+
11881280
_pattern_cache = {}
11891281

1282+
11901283
def parse_pattern(pattern):
11911284
"""Parse date, time, and datetime format patterns.
11921285

0 commit comments

Comments
 (0)