@@ -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.\u2013 17.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\u2013 16:24'
891+
892+ >>> format_interval(time(16, 18), time(16, 24), "wzq", locale="ja")
893+ '2016/01/04 16:18:00\uff5e 2016/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+
862949def 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+
11901283def parse_pattern (pattern ):
11911284 """Parse date, time, and datetime format patterns.
11921285
0 commit comments