1# -*- coding: utf-8 -*-
2#
3# Gramps - a GTK+/GNOME based genealogy program
4#
5# Copyright (C) 2004-2006  Donald N. Allingham
6# Copyright (C) 2013       Vassilii Khachaturov
7# Copyright (C) 2014-2018  Paul Franklin
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22#
23
24"""
25U.S English date display class. Should serve as the base class for all
26localized tasks.
27"""
28
29#-------------------------------------------------------------------------
30#
31# Python modules
32#
33#-------------------------------------------------------------------------
34import datetime
35
36#-------------------------------------------------------------------------
37#
38# set up logging
39#
40#-------------------------------------------------------------------------
41import logging
42log = logging.getLogger(".DateDisplay")
43
44#-------------------------------------------------------------------------
45#
46# Gramps modules
47#
48#-------------------------------------------------------------------------
49from ..lib.date import Date
50from ..const import GRAMPS_LOCALE as glocale
51from ..utils.grampslocale import GrampsLocale
52from ._datestrings import DateStrings
53
54# _T_ is a gramps-defined keyword -- see po/update_po.py and po/genpot.sh
55def _T_(value): # enable deferred translations (see Python docs 22.1.3.4)
56    return value
57
58#-------------------------------------------------------------------------
59#
60# DateDisplay
61#
62#-------------------------------------------------------------------------
63class DateDisplay:
64    """
65    Base date display class.
66    """
67
68    formats = (
69        # format 0 - must always be ISO
70        _T_("YYYY-MM-DD (ISO)"),
71
72        # format # 1 - must always be locale-preferred numerical format
73        # such as YY.MM.DD, MM-DD-YY, or whatever your locale prefers.
74        # This should be the format that is used under the locale by
75        # strftime() for '%x'.
76        # You may translate this as "Numerical", "System preferred", or similar.
77        _T_("date format|Numerical"),
78
79        # Full month name, day, year
80        _T_("Month Day, Year"),
81
82        # Abbreviated month name, day, year
83        _T_("MON DAY, YEAR"),
84
85        # Day, full month name, year
86        _T_("Day Month Year"),
87
88        # Day, abbreviated month name, year
89        _T_("DAY MON YEAR")
90        )
91    """
92    .. note:: Will be overridden if a locale-specific date displayer exists.
93
94    If your localized :meth:`~_display_calendar`/:meth:`~_display_gregorian`
95    are overridden, you should override the whole formats list according
96    to your own formats, and you need not localize the format names here.
97    This ``formats`` must agree with
98    :meth:`~_display_calendar`/:meth:`~_display_gregorian`.
99    """
100
101    newyear = ("", "Mar1", "Mar25", "Sep1")
102
103    _bce_str = "%s B.C.E."
104    # this will be overridden if a locale-specific date displayer exists
105
106    def __init__(self, format=None, blocale=None):
107        """
108        :param blocale: allow translation of dates and date formats
109        :type blocale: a :class:`.GrampsLocale` instance
110        """
111        from ._datehandler import locale_tformat # circular import if above
112        if blocale:
113            self._locale = blocale
114        elif not hasattr(self, '_locale'):
115            self._locale = glocale
116        if self._locale.calendar in locale_tformat:
117            self.dhformat = locale_tformat[self._locale.calendar] # date format
118        else:
119            self.dhformat = locale_tformat['en_GB'] # something is required
120        self.formats_changed() # allow overriding so a subclass can modify
121        self._ds = DateStrings(self._locale)
122        calendar = list(self._ds.calendar)
123        calendar[Date.CAL_GREGORIAN] = "" # that string only used in parsing,
124        # gregorian cal name shouldn't be output!
125        self.calendar = tuple(calendar)
126        self.short_months = self._ds.short_months
127        self.swedish = self.long_months = self._ds.long_months
128        self.hebrew = self._ds.hebrew
129        self.french = self._ds.french
130        self.persian = self._ds.persian
131        self.islamic = self._ds.islamic
132        self.display_cal = [
133            self._display_gregorian,
134            self._display_julian,
135            self._display_hebrew,
136            self._display_french,
137            self._display_persian,
138            self._display_islamic,
139            self._display_swedish]
140        self._mod_str = self._ds.modifiers
141        self._qual_str = self._ds.qualifiers
142        self.long_days = self._ds.long_days
143        self.short_days = self._ds.short_days # Icelandic needs this
144
145        if format is None:
146            self.format = 0
147        else:
148            self.format = format
149
150        self._ = _ = self._locale.translation.sgettext
151        self.FORMATS_long_month_year = {
152# Inflection control due to modifier.
153# Protocol: DateDisplayXX passes a key to the dictionary in the
154# parameter ``inflect`` to ``_display_calendar``.
155# The modifier passed is not necessarily the one printed, it's just
156# a representative that induces the same inflection control.
157# For example, in Russian "before May", "after May", and "about May"
158# all require genitive form for May, whereas no modifier (just "May 1234")
159# require nominative, so DateDisplayRU.display will pass "before"
160# in all 3 cases, collapsing the 3 modifiers into 1.
161#
162# Another example in Russian is that "between April 1234 and June 1235"
163# requires the same inflection for both April and June, so just "between"
164# is used by DateDisplayRU.display, collapsing two more modifiers into 1.
165#
166# If inflect is not specified, then it means that the modifier doesn't have
167# grammatical control over the format, and so the format can be
168# localized in a context-free way.
169# The translator is responsible for:
170# 1) proper collapse of modifier classes
171# 2) translating the formats that are selected in runtime
172# 3) ignoring the other formats in .po (it does no harm to translate them,
173# it's just a lot of extra work)
174#
175# To prevent POT pollution, not all possibilities are populated here yet.
176# To be amended as the actual localized handlers use it.
177#
178# Not moving to DateStrings, as this is part of display code only,
179# coupled tightly with the formats used in this file.
180                ""
181                : _("{long_month} {year}"),
182
183                "from"
184                # first date in a span
185                # If "from <Month>" needs a special inflection in your
186                # language, translate this to "{long_month.f[X]} {year}"
187                # (where X is one of the month-name inflections you defined)
188                # else leave it untranslated
189                : _("from|{long_month} {year}"),
190
191                "to"
192                # second date in a span
193                # If "to <Month>" needs a special inflection in your
194                # language, translate this to "{long_month.f[X]} {year}"
195                # (where X is one of the month-name inflections you defined)
196                # else leave it untranslated
197                : _("to|{long_month} {year}"),
198
199                "between"
200                # first date in a range
201                # If "between <Month>" needs a special inflection in your
202                # language, translate this to "{long_month.f[X]} {year}"
203                # (where X is one of the month-name inflections you defined)
204                # else leave it untranslated
205                : _("between|{long_month} {year}"),
206
207                "and"
208                # second date in a range
209                # If "and <Month>" needs a special inflection in your
210                # language, translate this to "{long_month.f[X]} {year}"
211                # (where X is one of the month-name inflections you defined)
212                # else leave it untranslated
213                : _("and|{long_month} {year}"),
214
215                "before"
216                # If "before <Month>" needs a special inflection in your
217                # language, translate this to "{long_month.f[X]} {year}"
218                # (where X is one of the month-name inflections you defined)
219                # else leave it untranslated
220                : _("before|{long_month} {year}"),
221
222                "after"
223                # If "after <Month>" needs a special inflection in your
224                # language, translate this to "{long_month.f[X]} {year}"
225                # (where X is one of the month-name inflections you defined)
226                # else leave it untranslated
227                : _("after|{long_month} {year}"),
228
229                "about"
230                # If "about <Month>" needs a special inflection in your
231                # language, translate this to "{long_month.f[X]} {year}"
232                # (where X is one of the month-name inflections you defined)
233                # else leave it untranslated
234                : _("about|{long_month} {year}"),
235
236                "estimated"
237                # If "estimated <Month>" needs a special inflection in your
238                # language, translate this to "{long_month.f[X]} {year}"
239                # (where X is one of the month-name inflections you defined)
240                # else leave it untranslated
241                : _("estimated|{long_month} {year}"),
242
243                "calculated"
244                # If "calculated <Month>" needs a special inflection in your
245                # language, translate this to "{long_month.f[X]} {year}"
246                # (where X is one of the month-name inflections you defined)
247                # else leave it untranslated
248                : _("calculated|{long_month} {year}"),
249        }
250
251        self.FORMATS_short_month_year = {
252                ""
253                : _("{short_month} {year}"),
254
255                "from"
256                # first date in a span
257                # If "from <Month>" needs a special inflection in your
258                # language, translate this to "{short_month.f[X]} {year}"
259                # (where X is one of the month-name inflections you defined)
260                # else leave it untranslated
261                : _("from|{short_month} {year}"),
262
263                "to"
264                # second date in a span
265                # If "to <Month>" needs a special inflection in your
266                # language, translate this to "{short_month.f[X]} {year}"
267                # (where X is one of the month-name inflections you defined)
268                # else leave it untranslated
269                : _("to|{short_month} {year}"),
270
271                "between"
272                # first date in a range
273                # If "between <Month>" needs a special inflection in your
274                # language, translate this to "{short_month.f[X]} {year}"
275                # (where X is one of the month-name inflections you defined)
276                # else leave it untranslated
277                : _("between|{short_month} {year}"),
278
279                "and"
280                # second date in a range
281                # If "and <Month>" needs a special inflection in your
282                # language, translate this to "{short_month.f[X]} {year}"
283                # (where X is one of the month-name inflections you defined)
284                # else leave it untranslated
285                : _("and|{short_month} {year}"),
286
287                "before"
288                # If "before <Month>" needs a special inflection in your
289                # language, translate this to "{short_month.f[X]} {year}"
290                # (where X is one of the month-name inflections you defined)
291                # else leave it untranslated
292                : _("before|{short_month} {year}"),
293
294                "after"
295                # If "after <Month>" needs a special inflection in your
296                # language, translate this to "{short_month.f[X]} {year}"
297                # (where X is one of the month-name inflections you defined)
298                # else leave it untranslated
299                : _("after|{short_month} {year}"),
300
301                "about"
302                # If "about <Month>" needs a special inflection in your
303                # language, translate this to "{short_month.f[X]} {year}"
304                # (where X is one of the month-name inflections you defined)
305                # else leave it untranslated
306                : _("about|{short_month} {year}"),
307
308                "estimated"
309                # If "estimated <Month>" needs a special inflection in your
310                # language, translate this to "{short_month.f[X]} {year}"
311                # (where X is one of the month-name inflections you defined)
312                # else leave it untranslated
313                : _("estimated|{short_month} {year}"),
314
315                "calculated"
316                # If "calculated <Month>" needs a special inflection in your
317                # language, translate this to "{short_month.f[X]} {year}"
318                # (where X is one of the month-name inflections you defined)
319                # else leave it untranslated
320                : _("calculated|{short_month} {year}"),
321        }
322
323    def formats_changed(self):
324        """ Allow overriding so a subclass can modify """
325        pass
326
327    def set_format(self, format):
328        self.format = format
329
330    def format_extras(self, cal, newyear):
331        """
332        Formats the extra items (calendar, newyear) for a date.
333        """
334        scal = self.calendar[cal]
335        if isinstance(newyear, int) and newyear <= len(self.newyear):
336            snewyear = self.newyear[newyear]
337        elif isinstance(newyear, (list, tuple)):
338            snewyear = "%s-%s" % (newyear[0], newyear[1])
339        else:
340            snewyear = "Err"
341        retval = ""
342        for item in [scal, snewyear]:
343            if item:
344                if retval:
345                    # TODO for Arabic, should the next comma be translated?
346                    retval += ", "
347                retval += item
348        if retval:
349            return " (%s)" % retval
350        return ""
351
352    def display(self, date):
353        """
354        Return a text string representing the date.
355
356        Disregard any format settings and use display_iso for each date.
357
358        (Will be overridden if a locale-specific date displayer exists.)
359
360        (The usage is "displayer.display(...)" (or a variant, e.g. _dd.display)
361        so any subclass must have a "display" method, somehow, or use this.)
362        """
363        mod = date.get_modifier()
364        cal = date.get_calendar()
365        qual = date.get_quality()
366        start = date.get_start_date()
367        newyear = date.get_new_year()
368
369        qual_str = self._qual_str[qual]
370
371        if mod == Date.MOD_TEXTONLY:
372            return date.get_text()
373        elif start == Date.EMPTY:
374            return ""
375        elif mod == Date.MOD_SPAN or mod == Date.MOD_RANGE:
376            d1 = self.display_iso(start)
377            d2 = self.display_iso(date.get_stop_date())
378            scal = self.format_extras(cal, newyear)
379            return "%s %s - %s%s" % (qual_str, d1, d2, scal)
380        else:
381            text = self.display_iso(start)
382            scal = self.format_extras(cal, newyear)
383            return "%s%s%s%s" % (qual_str, self._mod_str[mod], text, scal)
384
385    def _slash_year(self, val, slash):
386        if val < 0:
387            val = - val
388
389        if slash:
390            if (val-1) % 100 == 99:
391                year = "%d/%d" % (val - 1, (val%1000))
392            elif (val-1) % 10 == 9:
393                year = "%d/%d" % (val - 1, (val%100))
394            else:
395                year = "%d/%d" % (val - 1, (val%10))
396        else:
397            year = "%d" % (val)
398
399        return year
400
401    def display_iso(self, date_val):
402        # YYYY-MM-DD (ISO)
403        year = self._slash_year(date_val[2], date_val[3])
404        # This produces 1789, 1789-00-11 and 1789-11-00 for incomplete dates.
405        if date_val[0] == date_val[1] == 0:
406            # No month and no day -> year
407            value = year
408        else:
409            value = "%s-%02d-%02d" % (year, date_val[1], date_val[0])
410        if date_val[2] < 0:
411            return self._bce_str % value
412        else:
413            return value
414
415    def dd_span(self, date):
416        """
417        Return a text string representing the span date
418        (it may be overridden if a locale-specific date displayer exists)
419        """
420        cal = date.get_calendar()
421        qual_str = self._qual_str[date.get_quality()]
422        scal = self.format_extras(cal, date.get_new_year())
423        d1 = self.display_cal[cal](date.get_start_date(),
424            # If there is no special inflection for "from <Month>"
425            # in your language, DON'T translate this string.  Otherwise,
426            # "translate" this to "from" in ENGLISH!!! ENGLISH!!!
427                                   inflect=self._("from-date|"))
428        d2 = self.display_cal[cal](date.get_stop_date(),
429            # If there is no special inflection for "to <Month>"
430            # in your language, DON'T translate this string.  Otherwise,
431            # "translate" this to "to" in ENGLISH!!! ENGLISH!!!
432                                   inflect=self._("to-date|"))
433        return self._("{date_quality}from {date_start} to {date_stop}"
434                      "{nonstd_calendar_and_ny}").format(
435                            date_quality=qual_str,
436                            date_start=d1,
437                            date_stop=d2,
438                            nonstd_calendar_and_ny=scal)
439
440    def dd_range(self, date):
441        """
442        Return a text string representing the range date
443        (it may be overridden if a locale-specific date displayer exists)
444        """
445        cal = date.get_calendar()
446        qual_str = self._qual_str[date.get_quality()]
447        scal = self.format_extras(cal, date.get_new_year())
448        d1 = self.display_cal[cal](date.get_start_date(),
449            # If there is no special inflection for "between <Month>"
450            # in your language, DON'T translate this string.  Otherwise,
451            # "translate" this to "between" in ENGLISH!!! ENGLISH!!!
452                                   inflect=self._("between-date|"))
453        d2 = self.display_cal[cal](date.get_stop_date(),
454            # If there is no special inflection for "and <Month>"
455            # in your language, DON'T translate this string.  Otherwise,
456            # "translate" this to "and" in ENGLISH!!! ENGLISH!!!
457                                   inflect=self._("and-date|"))
458        return self._("{date_quality}between {date_start} and {date_stop}"
459                      "{nonstd_calendar_and_ny}").format(
460                            date_quality=qual_str,
461                            date_start=d1,
462                            date_stop=d2,
463                            nonstd_calendar_and_ny=scal)
464
465    def display_formatted(self, date):
466        """
467        Return a text string representing the date, according to the format.
468        """
469        mod = date.get_modifier()
470        cal = date.get_calendar()
471        qual = date.get_quality()
472        start = date.get_start_date()
473        newyear = date.get_new_year()
474
475        qual_str = self._qual_str[qual]
476        _ = self._
477
478        if mod == Date.MOD_TEXTONLY:
479            return date.get_text()
480        elif start == Date.EMPTY:
481            return ""
482        elif mod == Date.MOD_SPAN:
483            return self.dd_span(date)
484        elif mod == Date.MOD_RANGE:
485            return self.dd_range(date)
486        else:
487            if mod == Date.MOD_BEFORE:
488                # If there is no special inflection for "before <Month>"
489                # in your language, DON'T translate this string.  Otherwise,
490                # "translate" this to "before" in ENGLISH!!! ENGLISH!!!
491                date_type = _("before-date|")
492            elif mod == Date.MOD_AFTER:
493                # If there is no special inflection for "after <Month>"
494                # in your language, DON'T translate this string.  Otherwise,
495                # "translate" this to "after" in ENGLISH!!! ENGLISH!!!
496                date_type = _("after-date|")
497            elif mod == Date.MOD_ABOUT:
498                # If there is no special inflection for "about <Month>"
499                # in your language, DON'T translate this string.  Otherwise,
500                # "translate" this to "about" in ENGLISH!!! ENGLISH!!!
501                date_type = _("about-date|")
502            elif qual == Date.QUAL_ESTIMATED:
503                # If there is no special inflection for "estimated <Month>"
504                # in your language, DON'T translate this string.  Otherwise,
505                # "translate" this to "estimated" in ENGLISH!!! ENGLISH!!!
506                date_type = _("estimated-date|")
507            elif qual == Date.QUAL_CALCULATED:
508                # If there is no special inflection for "calculated <Month>"
509                # in your language, DON'T translate this string.  Otherwise,
510                # "translate" this to "calculated" in ENGLISH!!! ENGLISH!!!
511                date_type = _("calculated-date|")
512            else:
513                date_type = ""
514            # TODO -- do "estimated" and "calculated" need their own "if"?
515            # i.e., what happens if a date is both "modified" and "qualified"?
516            # it won't matter if the month gets the same lexeme type, but
517            # what should be done if the types differ? there can only be one
518            # lexeme type for any month so which one should be last?  so we
519            # will wait and see if any language ever requires such fine tuning
520            # as maybe it will be as simple as putting the "elif" choices for
521            # "estimated" and "calculated" before the others, or something
522            text = self.display_cal[cal](start, inflect=date_type)
523            modifier = self._mod_str[mod]
524            # some languages have a modifier after the date (e.g. Finnish)
525            # (if so, they are specified in DateParser.modifier_after_to_int)
526            if modifier.startswith(' '):
527                text += modifier
528                modifier = ''
529            scal = self.format_extras(cal, newyear)
530            return _("{date_quality}{noncompound_modifier}{date}"
531                     "{nonstd_calendar_and_ny}").format(
532                         date_quality=qual_str,
533                         noncompound_modifier=modifier,
534                         date=text,
535                         nonstd_calendar_and_ny=scal)
536
537    def _display_gregorian(self, date_val, **kwargs):
538        return self._display_calendar(date_val, self.long_months,
539                self.short_months, **kwargs)
540
541    # Julian and Swedish date display is the same as Gregorian
542    _display_julian = _display_swedish = _display_gregorian
543
544    def format_long_month_year(self, month, year, inflect, long_months):
545        if not hasattr(long_months[1], 'f'): # not a Lexeme: no inflection
546            return "{long_month} {year}".format(
547                     long_month = long_months[month], year = year)
548        return self.FORMATS_long_month_year[inflect].format(
549                     long_month = long_months[month], year = year)
550
551    def format_short_month_year(self, month, year, inflect, short_months):
552        if not hasattr(short_months[1], 'f'): # not a Lexeme: no inflection
553            return "{short_month} {year}".format(
554                     short_month = short_months[month], year = year)
555        return self.FORMATS_short_month_year[inflect].format(
556                     short_month = short_months[month], year = year)
557
558    def format_long_month(self, month, inflect, long_months):
559        if not hasattr(long_months[1], 'f'): # not a Lexeme: no inflection
560            return "{long_month}".format(long_month = long_months[month])
561        return self.FORMATS_long_month_year[inflect].format(
562                     long_month = long_months[month], year = '').rstrip()
563
564    def format_short_month(self, month, inflect, short_months):
565        if not hasattr(short_months[1], 'f'): # not a Lexeme: no inflection
566            return "{short_month}".format(short_month = short_months[month])
567        return self.FORMATS_short_month_year[inflect].format(
568                     short_month = short_months[month], year = '').rstrip()
569
570    def _get_short_weekday(self, date_val):
571        if (date_val[0] == 0 or date_val[1] == 0 # no day or no month or both
572                or date_val[1] == 13 # Hebrew has 13 months
573                or date_val[2] > datetime.MAXYEAR # bug 10815
574                or date_val[2] < 0): # B.C.E. date
575            return ''
576        w_day = datetime.date(date_val[2], date_val[1], date_val[0]) # y, m, d
577        return self.short_days[((w_day.weekday() + 1) % 7) + 1]
578
579    def _get_long_weekday(self, date_val):
580        if (date_val[0] == 0 or date_val[1] == 0 # no day or no month or both
581                or date_val[1] == 13 # Hebrew has 13 months
582                or date_val[2] > datetime.MAXYEAR # bug 10815
583                or date_val[2] < 0): # B.C.E. date
584            return ''
585        w_day = datetime.date(date_val[2], date_val[1], date_val[0]) # y, m, d
586        return self.long_days[((w_day.weekday() + 1) % 7) + 1]
587
588    def _get_localized_year(self, year):
589        """ Allow a subclass to modify the year, e.g. add a period """
590        return year
591
592    def dd_dformat01(self, date_val):
593        """
594        numerical
595
596        this must agree with DateDisplayEn's "formats" definition
597        (it may be overridden if a locale-specific date displayer exists)
598        """
599        if date_val[3]:
600            return self.display_iso(date_val)
601        else:
602            if date_val[0] == date_val[1] == 0:
603                return self._get_localized_year(str(date_val[2]))
604            else:
605                value = self.dhformat.replace('%m', str(date_val[1]))
606                if '%b' in value or '%B' in value:
607                    # some locales have %b for the month (ar_EG, is_IS, nb_NO)
608                    # so it would be "Jan" but as it's "numeric" make it "1"
609                    value = value.replace('%b', str(date_val[1]))
610                    # some locales have %B for the month, e.g. ta_IN
611                    # so it would be "January" but as it's "numeric" make it 1
612                    value = value.replace('%B', str(date_val[1]))
613                if '%a' in value or '%A' in value:
614                    # some locales have %a for the abbreviated day, e.g. is_IS
615                    value = value.replace('%a',
616                                          self._get_short_weekday(date_val))
617                    # some locales have %A for the long/full day, e.g. ta_IN
618                    value = value.replace('%A',
619                                          self._get_long_weekday(date_val))
620                if date_val[0] == 0: # ignore the zero day and its delimiter
621                    i_day = value.find('%d')
622                    if len(value) == i_day + 2: # delimiter is left of the day
623                        value = value.replace(value[i_day-1:i_day+2], '')
624                    else: # delimiter is to the right of the day
625                        value = value.replace(value[i_day:i_day+3], '')
626                value = value.replace('%d', str(date_val[0]))
627                value = value.replace('%Y', str(abs(date_val[2])))
628                return value.replace('-', '/')
629
630    def dd_dformat02(self, date_val, inflect, long_months):
631        """
632        month_name day, year
633
634        this must agree with DateDisplayEn's "formats" definition
635        (it may be overridden if a locale-specific date displayer exists)
636        """
637        _ = self._locale.translation.sgettext
638        year = self._slash_year(date_val[2], date_val[3])
639        if date_val[0] == 0:
640            if date_val[1] == 0:
641                return self._get_localized_year(year)
642            else:
643                return self.format_long_month_year(date_val[1], year,
644                                                   inflect, long_months)
645        elif date_val[1] == 0: # month is zero but day is not (see 8477)
646            return self.display_iso(date_val)
647        else:
648            # TRANSLATORS: this month is ALREADY inflected: ignore it
649            return _("{long_month} {day:d}, {year}").format(
650                       long_month = self.format_long_month(date_val[1],
651                                                           inflect,
652                                                           long_months),
653                       day = date_val[0],
654                       year = year)
655
656    def dd_dformat03(self, date_val, inflect, short_months):
657        """
658        month_abbreviation day, year
659
660        this must agree with DateDisplayEn's "formats" definition
661        (it may be overridden if a locale-specific date displayer exists)
662        """
663        _ = self._locale.translation.sgettext
664        year = self._slash_year(date_val[2], date_val[3])
665        if date_val[0] == 0:
666            if date_val[1] == 0:
667                return self._get_localized_year(year)
668            else:
669                return self.format_short_month_year(date_val[1], year,
670                                                    inflect, short_months)
671        elif date_val[1] == 0: # month is zero but day is not (see 8477)
672            return self.display_iso(date_val)
673        else:
674            # TRANSLATORS: this month is ALREADY inflected: ignore it
675            return _("{short_month} {day:d}, {year}").format(
676                       short_month = self.format_short_month(date_val[1],
677                                                             inflect,
678                                                             short_months),
679                       day = date_val[0],
680                       year = year)
681
682    def dd_dformat04(self, date_val, inflect, long_months):
683        """
684        day month_name year
685
686        this must agree with DateDisplayEn's "formats" definition
687        (it may be overridden if a locale-specific date displayer exists)
688        """
689        _ = self._locale.translation.sgettext
690        year = self._slash_year(date_val[2], date_val[3])
691        if date_val[0] == 0:
692            if date_val[1] == 0:
693                return self._get_localized_year(year)
694            else:
695                return self.format_long_month_year(date_val[1], year,
696                                                   inflect, long_months)
697        elif date_val[1] == 0: # month is zero but day is not (see 8477)
698            return self.display_iso(date_val)
699        else:
700            # TRANSLATORS: this month is ALREADY inflected: ignore it
701            return _("{day:d} {long_month} {year}").format(
702                       day = date_val[0],
703                       long_month = self.format_long_month(date_val[1],
704                                                           inflect,
705                                                           long_months),
706                       year = year)
707
708    def dd_dformat05(self, date_val, inflect, short_months):
709        """
710        day month_abbreviation year
711
712        this must agree with DateDisplayEn's "formats" definition
713        (it may be overridden if a locale-specific date displayer exists)
714        """
715        _ = self._locale.translation.sgettext
716        year = self._slash_year(date_val[2], date_val[3])
717        if date_val[0] == 0:
718            if date_val[1] == 0:
719                return self._get_localized_year(year)
720            else:
721                return self.format_short_month_year(date_val[1], year,
722                                                    inflect, short_months)
723        elif date_val[1] == 0: # month is zero but day is not (see 8477)
724            return self.display_iso(date_val)
725        else:
726            # TRANSLATORS: this month is ALREADY inflected: ignore it
727            return _("{day:d} {short_month} {year}").format(
728                       day = date_val[0],
729                       short_month = self.format_short_month(date_val[1],
730                                                             inflect,
731                                                             short_months),
732                       year = year)
733
734    def _display_calendar(self, date_val, long_months, short_months = None,
735                          inflect=""):
736        """
737        this must agree with DateDisplayEn's "formats" definition
738        (it may be overridden if a locale-specific date displayer exists)
739        """
740
741        if short_months is None:
742            # Let the short formats work the same as long formats
743            short_months = long_months
744
745        if self.format == 0:
746            return self.display_iso(date_val)
747        elif self.format == 1:
748            # numerical
749            value = self.dd_dformat01(date_val)
750        elif self.format == 2:
751            # month_name day, year
752            value = self.dd_dformat02(date_val, inflect, long_months)
753        elif self.format == 3:
754            # month_abbreviation day, year
755            value = self.dd_dformat03(date_val, inflect, short_months)
756        elif self.format == 4:
757            # day month_name year
758            value = self.dd_dformat04(date_val, inflect, long_months)
759        # elif self.format == 5:
760        else:
761            # day month_abbreviation year
762            value = self.dd_dformat05(date_val, inflect, short_months)
763        if date_val[2] < 0:
764            # TODO fix BUG 7064: non-Gregorian calendars wrongly use BCE notation for negative dates
765            return self._bce_str % value
766        else:
767            return value
768
769
770    def _display_french(self, date_val, **kwargs):
771        return self._display_calendar(date_val, self.french, **kwargs)
772
773    def _display_hebrew(self, date_val, **kwargs):
774        return self._display_calendar(date_val, self.hebrew, **kwargs)
775
776    def _display_persian(self, date_val, **kwargs):
777        return self._display_calendar(date_val, self.persian, **kwargs)
778
779    def _display_islamic(self, date_val, **kwargs):
780        return self._display_calendar(date_val, self.islamic, **kwargs)
781
782class DateDisplayEn(DateDisplay):
783    """
784    English language date display class.
785    """
786    display = DateDisplay.display_formatted
787
788class DateDisplayGB(DateDisplay):
789    """
790    British-English language date display class (its format is different).
791    """
792    display = DateDisplay.display_formatted
793