aboutsummaryrefslogtreecommitdiffstats
path: root/jython-tosca-parser/src/main/resources/Lib/site-packages/babel-2.3.4-py2.7.egg/babel/dates.py
diff options
context:
space:
mode:
Diffstat (limited to 'jython-tosca-parser/src/main/resources/Lib/site-packages/babel-2.3.4-py2.7.egg/babel/dates.py')
-rw-r--r--jython-tosca-parser/src/main/resources/Lib/site-packages/babel-2.3.4-py2.7.egg/babel/dates.py1754
1 files changed, 1754 insertions, 0 deletions
diff --git a/jython-tosca-parser/src/main/resources/Lib/site-packages/babel-2.3.4-py2.7.egg/babel/dates.py b/jython-tosca-parser/src/main/resources/Lib/site-packages/babel-2.3.4-py2.7.egg/babel/dates.py
new file mode 100644
index 0000000..4a0bbd3
--- /dev/null
+++ b/jython-tosca-parser/src/main/resources/Lib/site-packages/babel-2.3.4-py2.7.egg/babel/dates.py
@@ -0,0 +1,1754 @@
+# -*- coding: utf-8 -*-
+"""
+ babel.dates
+ ~~~~~~~~~~~
+
+ Locale dependent formatting and parsing of dates and times.
+
+ The default locale for the functions in this module is determined by the
+ following environment variables, in that order:
+
+ * ``LC_TIME``,
+ * ``LC_ALL``, and
+ * ``LANG``
+
+ :copyright: (c) 2013 by the Babel Team.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import division
+
+import re
+import warnings
+import pytz as _pytz
+
+from datetime import date, datetime, time, timedelta
+from bisect import bisect_right
+
+from babel.core import default_locale, get_global, Locale
+from babel.util import UTC, LOCALTZ
+from babel._compat import string_types, integer_types, number_types
+
+
+LC_TIME = default_locale('LC_TIME')
+
+# Aliases for use in scopes where the modules are shadowed by local variables
+date_ = date
+datetime_ = datetime
+time_ = time
+
+
+def _get_dt_and_tzinfo(dt_or_tzinfo):
+ """
+ Parse a `dt_or_tzinfo` value into a datetime and a tzinfo.
+
+ See the docs for this function's callers for semantics.
+
+ :rtype: tuple[datetime, tzinfo]
+ """
+ if dt_or_tzinfo is None:
+ dt = datetime.now()
+ tzinfo = LOCALTZ
+ elif isinstance(dt_or_tzinfo, string_types):
+ dt = None
+ tzinfo = get_timezone(dt_or_tzinfo)
+ elif isinstance(dt_or_tzinfo, integer_types):
+ dt = None
+ tzinfo = UTC
+ elif isinstance(dt_or_tzinfo, (datetime, time)):
+ dt = _get_datetime(dt_or_tzinfo)
+ if dt.tzinfo is not None:
+ tzinfo = dt.tzinfo
+ else:
+ tzinfo = UTC
+ else:
+ dt = None
+ tzinfo = dt_or_tzinfo
+ return dt, tzinfo
+
+
+def _get_datetime(instant):
+ """
+ Get a datetime out of an "instant" (date, time, datetime, number).
+
+ .. warning:: The return values of this function may depend on the system clock.
+
+ If the instant is None, the current moment is used.
+ If the instant is a time, it's augmented with today's date.
+
+ Dates are converted to naive datetimes with midnight as the time component.
+
+ >>> _get_datetime(date(2015, 1, 1))
+ datetime.datetime(2015, 1, 1, 0, 0)
+
+ UNIX timestamps are converted to datetimes.
+
+ >>> _get_datetime(1400000000)
+ datetime.datetime(2014, 5, 13, 16, 53, 20)
+
+ Other values are passed through as-is.
+
+ >>> x = datetime(2015, 1, 1)
+ >>> _get_datetime(x) is x
+ True
+
+ :param instant: date, time, datetime, integer, float or None
+ :type instant: date|time|datetime|int|float|None
+ :return: a datetime
+ :rtype: datetime
+ """
+ if instant is None:
+ return datetime_.utcnow()
+ elif isinstance(instant, integer_types) or isinstance(instant, float):
+ return datetime_.utcfromtimestamp(instant)
+ elif isinstance(instant, time):
+ return datetime_.combine(date.today(), instant)
+ elif isinstance(instant, date) and not isinstance(instant, datetime):
+ return datetime_.combine(instant, time())
+ # TODO (3.x): Add an assertion/type check for this fallthrough branch:
+ return instant
+
+
+def _ensure_datetime_tzinfo(datetime, tzinfo=None):
+ """
+ Ensure the datetime passed has an attached tzinfo.
+
+ If the datetime is tz-naive to begin with, UTC is attached.
+
+ If a tzinfo is passed in, the datetime is normalized to that timezone.
+
+ >>> _ensure_datetime_tzinfo(datetime(2015, 1, 1)).tzinfo.zone
+ 'UTC'
+
+ >>> tz = get_timezone("Europe/Stockholm")
+ >>> _ensure_datetime_tzinfo(datetime(2015, 1, 1, 13, 15, tzinfo=UTC), tzinfo=tz).hour
+ 14
+
+ :param datetime: Datetime to augment.
+ :param tzinfo: Optional tznfo.
+ :return: datetime with tzinfo
+ :rtype: datetime
+ """
+ if datetime.tzinfo is None:
+ datetime = datetime.replace(tzinfo=UTC)
+ if tzinfo is not None:
+ datetime = datetime.astimezone(get_timezone(tzinfo))
+ if hasattr(tzinfo, 'normalize'): # pytz
+ datetime = tzinfo.normalize(datetime)
+ return datetime
+
+
+def _get_time(time, tzinfo=None):
+ """
+ Get a timezoned time from a given instant.
+
+ .. warning:: The return values of this function may depend on the system clock.
+
+ :param time: time, datetime or None
+ :rtype: time
+ """
+ if time is None:
+ time = datetime.utcnow()
+ elif isinstance(time, number_types):
+ time = datetime.utcfromtimestamp(time)
+ if time.tzinfo is None:
+ time = time.replace(tzinfo=UTC)
+ if isinstance(time, datetime):
+ if tzinfo is not None:
+ time = time.astimezone(tzinfo)
+ if hasattr(tzinfo, 'normalize'): # pytz
+ time = tzinfo.normalize(time)
+ time = time.timetz()
+ elif tzinfo is not None:
+ time = time.replace(tzinfo=tzinfo)
+ return time
+
+
+def get_timezone(zone=None):
+ """Looks up a timezone by name and returns it. The timezone object
+ returned comes from ``pytz`` and corresponds to the `tzinfo` interface and
+ can be used with all of the functions of Babel that operate with dates.
+
+ If a timezone is not known a :exc:`LookupError` is raised. If `zone`
+ is ``None`` a local zone object is returned.
+
+ :param zone: the name of the timezone to look up. If a timezone object
+ itself is passed in, mit's returned unchanged.
+ """
+ if zone is None:
+ return LOCALTZ
+ if not isinstance(zone, string_types):
+ return zone
+ try:
+ return _pytz.timezone(zone)
+ except _pytz.UnknownTimeZoneError:
+ raise LookupError('Unknown timezone %s' % zone)
+
+
+def get_next_timezone_transition(zone=None, dt=None):
+ """Given a timezone it will return a :class:`TimezoneTransition` object
+ that holds the information about the next timezone transition that's going
+ to happen. For instance this can be used to detect when the next DST
+ change is going to happen and how it looks like.
+
+ The transition is calculated relative to the given datetime object. The
+ next transition that follows the date is used. If a transition cannot
+ be found the return value will be `None`.
+
+ Transition information can only be provided for timezones returned by
+ the :func:`get_timezone` function.
+
+ :param zone: the timezone for which the transition should be looked up.
+ If not provided the local timezone is used.
+ :param dt: the date after which the next transition should be found.
+ If not given the current time is assumed.
+ """
+ zone = get_timezone(zone)
+ dt = _get_datetime(dt).replace(tzinfo=None)
+
+ if not hasattr(zone, '_utc_transition_times'):
+ raise TypeError('Given timezone does not have UTC transition '
+ 'times. This can happen because the operating '
+ 'system fallback local timezone is used or a '
+ 'custom timezone object')
+
+ try:
+ idx = max(0, bisect_right(zone._utc_transition_times, dt))
+ old_trans = zone._transition_info[idx - 1]
+ new_trans = zone._transition_info[idx]
+ old_tz = zone._tzinfos[old_trans]
+ new_tz = zone._tzinfos[new_trans]
+ except (LookupError, ValueError):
+ return None
+
+ return TimezoneTransition(
+ activates=zone._utc_transition_times[idx],
+ from_tzinfo=old_tz,
+ to_tzinfo=new_tz,
+ reference_date=dt
+ )
+
+
+class TimezoneTransition(object):
+ """A helper object that represents the return value from
+ :func:`get_next_timezone_transition`.
+ """
+
+ def __init__(self, activates, from_tzinfo, to_tzinfo, reference_date=None):
+ #: the time of the activation of the timezone transition in UTC.
+ self.activates = activates
+ #: the timezone from where the transition starts.
+ self.from_tzinfo = from_tzinfo
+ #: the timezone for after the transition.
+ self.to_tzinfo = to_tzinfo
+ #: the reference date that was provided. This is the `dt` parameter
+ #: to the :func:`get_next_timezone_transition`.
+ self.reference_date = reference_date
+
+ @property
+ def from_tz(self):
+ """The name of the timezone before the transition."""
+ return self.from_tzinfo._tzname
+
+ @property
+ def to_tz(self):
+ """The name of the timezone after the transition."""
+ return self.to_tzinfo._tzname
+
+ @property
+ def from_offset(self):
+ """The UTC offset in seconds before the transition."""
+ return int(self.from_tzinfo._utcoffset.total_seconds())
+
+ @property
+ def to_offset(self):
+ """The UTC offset in seconds after the transition."""
+ return int(self.to_tzinfo._utcoffset.total_seconds())
+
+ def __repr__(self):
+ return '<TimezoneTransition %s -> %s (%s)>' % (
+ self.from_tz,
+ self.to_tz,
+ self.activates,
+ )
+
+
+def get_period_names(width='wide', context='stand-alone', locale=LC_TIME):
+ """Return the names for day periods (AM/PM) used by the locale.
+
+ >>> get_period_names(locale='en_US')['am']
+ u'AM'
+
+ :param width: the width to use, one of "abbreviated", "narrow", or "wide"
+ :param context: the context, either "format" or "stand-alone"
+ :param locale: the `Locale` object, or a locale string
+ """
+ return Locale.parse(locale).day_periods[context][width]
+
+
+def get_day_names(width='wide', context='format', locale=LC_TIME):
+ """Return the day names used by the locale for the specified format.
+
+ >>> get_day_names('wide', locale='en_US')[1]
+ u'Tuesday'
+ >>> get_day_names('short', locale='en_US')[1]
+ u'Tu'
+ >>> get_day_names('abbreviated', locale='es')[1]
+ u'mar.'
+ >>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1]
+ u'D'
+
+ :param width: the width to use, one of "wide", "abbreviated", "short" or "narrow"
+ :param context: the context, either "format" or "stand-alone"
+ :param locale: the `Locale` object, or a locale string
+ """
+ return Locale.parse(locale).days[context][width]
+
+
+def get_month_names(width='wide', context='format', locale=LC_TIME):
+ """Return the month names used by the locale for the specified format.
+
+ >>> get_month_names('wide', locale='en_US')[1]
+ u'January'
+ >>> get_month_names('abbreviated', locale='es')[1]
+ u'ene.'
+ >>> get_month_names('narrow', context='stand-alone', locale='de_DE')[1]
+ u'J'
+
+ :param width: the width to use, one of "wide", "abbreviated", or "narrow"
+ :param context: the context, either "format" or "stand-alone"
+ :param locale: the `Locale` object, or a locale string
+ """
+ return Locale.parse(locale).months[context][width]
+
+
+def get_quarter_names(width='wide', context='format', locale=LC_TIME):
+ """Return the quarter names used by the locale for the specified format.
+
+ >>> get_quarter_names('wide', locale='en_US')[1]
+ u'1st quarter'
+ >>> get_quarter_names('abbreviated', locale='de_DE')[1]
+ u'Q1'
+ >>> get_quarter_names('narrow', locale='de_DE')[1]
+ u'1'
+
+ :param width: the width to use, one of "wide", "abbreviated", or "narrow"
+ :param context: the context, either "format" or "stand-alone"
+ :param locale: the `Locale` object, or a locale string
+ """
+ return Locale.parse(locale).quarters[context][width]
+
+
+def get_era_names(width='wide', locale=LC_TIME):
+ """Return the era names used by the locale for the specified format.
+
+ >>> get_era_names('wide', locale='en_US')[1]
+ u'Anno Domini'
+ >>> get_era_names('abbreviated', locale='de_DE')[1]
+ u'n. Chr.'
+
+ :param width: the width to use, either "wide", "abbreviated", or "narrow"
+ :param locale: the `Locale` object, or a locale string
+ """
+ return Locale.parse(locale).eras[width]
+
+
+def get_date_format(format='medium', locale=LC_TIME):
+ """Return the date formatting patterns used by the locale for the specified
+ format.
+
+ >>> get_date_format(locale='en_US')
+ <DateTimePattern u'MMM d, y'>
+ >>> get_date_format('full', locale='de_DE')
+ <DateTimePattern u'EEEE, d. MMMM y'>
+
+ :param format: the format to use, one of "full", "long", "medium", or
+ "short"
+ :param locale: the `Locale` object, or a locale string
+ """
+ return Locale.parse(locale).date_formats[format]
+
+
+def get_datetime_format(format='medium', locale=LC_TIME):
+ """Return the datetime formatting patterns used by the locale for the
+ specified format.
+
+ >>> get_datetime_format(locale='en_US')
+ u'{1}, {0}'
+
+ :param format: the format to use, one of "full", "long", "medium", or
+ "short"
+ :param locale: the `Locale` object, or a locale string
+ """
+ patterns = Locale.parse(locale).datetime_formats
+ if format not in patterns:
+ format = None
+ return patterns[format]
+
+
+def get_time_format(format='medium', locale=LC_TIME):
+ """Return the time formatting patterns used by the locale for the specified
+ format.
+
+ >>> get_time_format(locale='en_US')
+ <DateTimePattern u'h:mm:ss a'>
+ >>> get_time_format('full', locale='de_DE')
+ <DateTimePattern u'HH:mm:ss zzzz'>
+
+ :param format: the format to use, one of "full", "long", "medium", or
+ "short"
+ :param locale: the `Locale` object, or a locale string
+ """
+ return Locale.parse(locale).time_formats[format]
+
+
+def get_timezone_gmt(datetime=None, width='long', locale=LC_TIME, return_z=False):
+ """Return the timezone associated with the given `datetime` object formatted
+ as string indicating the offset from GMT.
+
+ >>> dt = datetime(2007, 4, 1, 15, 30)
+ >>> get_timezone_gmt(dt, locale='en')
+ u'GMT+00:00'
+ >>> get_timezone_gmt(dt, locale='en', return_z=True)
+ 'Z'
+ >>> get_timezone_gmt(dt, locale='en', width='iso8601_short')
+ u'+00'
+ >>> tz = get_timezone('America/Los_Angeles')
+ >>> dt = tz.localize(datetime(2007, 4, 1, 15, 30))
+ >>> get_timezone_gmt(dt, locale='en')
+ u'GMT-07:00'
+ >>> get_timezone_gmt(dt, 'short', locale='en')
+ u'-0700'
+ >>> get_timezone_gmt(dt, locale='en', width='iso8601_short')
+ u'-07'
+
+ The long format depends on the locale, for example in France the acronym
+ UTC string is used instead of GMT:
+
+ >>> get_timezone_gmt(dt, 'long', locale='fr_FR')
+ u'UTC-07:00'
+
+ .. versionadded:: 0.9
+
+ :param datetime: the ``datetime`` object; if `None`, the current date and
+ time in UTC is used
+ :param width: either "long" or "short" or "iso8601" or "iso8601_short"
+ :param locale: the `Locale` object, or a locale string
+ :param return_z: True or False; Function returns indicator "Z"
+ when local time offset is 0
+ """
+ datetime = _ensure_datetime_tzinfo(_get_datetime(datetime))
+ locale = Locale.parse(locale)
+
+ offset = datetime.tzinfo.utcoffset(datetime)
+ seconds = offset.days * 24 * 60 * 60 + offset.seconds
+ hours, seconds = divmod(seconds, 3600)
+ if return_z and hours == 0 and seconds == 0:
+ return 'Z'
+ elif seconds == 0 and width == 'iso8601_short':
+ return u'%+03d' % hours
+ elif width == 'short' or width == 'iso8601_short':
+ pattern = u'%+03d%02d'
+ elif width == 'iso8601':
+ pattern = u'%+03d:%02d'
+ else:
+ pattern = locale.zone_formats['gmt'] % '%+03d:%02d'
+ return pattern % (hours, seconds // 60)
+
+
+def get_timezone_location(dt_or_tzinfo=None, locale=LC_TIME, return_city=False):
+ u"""Return a representation of the given timezone using "location format".
+
+ The result depends on both the local display name of the country and the
+ city associated with the time zone:
+
+ >>> tz = get_timezone('America/St_Johns')
+ >>> print(get_timezone_location(tz, locale='de_DE'))
+ Kanada (St. John’s) Zeit
+ >>> print(get_timezone_location(tz, locale='en'))
+ Canada (St. John’s) Time
+ >>> print(get_timezone_location(tz, locale='en', return_city=True))
+ St. John’s
+ >>> tz = get_timezone('America/Mexico_City')
+ >>> get_timezone_location(tz, locale='de_DE')
+ u'Mexiko (Mexiko-Stadt) Zeit'
+
+ If the timezone is associated with a country that uses only a single
+ timezone, just the localized country name is returned:
+
+ >>> tz = get_timezone('Europe/Berlin')
+ >>> get_timezone_name(tz, locale='de_DE')
+ u'Mitteleurop\\xe4ische Zeit'
+
+ .. versionadded:: 0.9
+
+ :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines
+ the timezone; if `None`, the current date and time in
+ UTC is assumed
+ :param locale: the `Locale` object, or a locale string
+ :param return_city: True or False, if True then return exemplar city (location)
+ for the time zone
+ :return: the localized timezone name using location format
+
+ """
+ dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo)
+ locale = Locale.parse(locale)
+
+ if hasattr(tzinfo, 'zone'):
+ zone = tzinfo.zone
+ else:
+ zone = tzinfo.tzname(dt or datetime.utcnow())
+
+ # Get the canonical time-zone code
+ zone = get_global('zone_aliases').get(zone, zone)
+
+ info = locale.time_zones.get(zone, {})
+
+ # Otherwise, if there is only one timezone for the country, return the
+ # localized country name
+ region_format = locale.zone_formats['region']
+ territory = get_global('zone_territories').get(zone)
+ if territory not in locale.territories:
+ territory = 'ZZ' # invalid/unknown
+ territory_name = locale.territories[territory]
+ if not return_city and territory and len(get_global('territory_zones').get(territory, [])) == 1:
+ return region_format % (territory_name)
+
+ # Otherwise, include the city in the output
+ fallback_format = locale.zone_formats['fallback']
+ if 'city' in info:
+ city_name = info['city']
+ else:
+ metazone = get_global('meta_zones').get(zone)
+ metazone_info = locale.meta_zones.get(metazone, {})
+ if 'city' in metazone_info:
+ city_name = metazone_info['city']
+ elif '/' in zone:
+ city_name = zone.split('/', 1)[1].replace('_', ' ')
+ else:
+ city_name = zone.replace('_', ' ')
+
+ if return_city:
+ return city_name
+ return region_format % (fallback_format % {
+ '0': city_name,
+ '1': territory_name
+ })
+
+
+def get_timezone_name(dt_or_tzinfo=None, width='long', uncommon=False,
+ locale=LC_TIME, zone_variant=None, return_zone=False):
+ r"""Return the localized display name for the given timezone. The timezone
+ may be specified using a ``datetime`` or `tzinfo` object.
+
+ >>> dt = time(15, 30, tzinfo=get_timezone('America/Los_Angeles'))
+ >>> get_timezone_name(dt, locale='en_US')
+ u'Pacific Standard Time'
+ >>> get_timezone_name(dt, locale='en_US', return_zone=True)
+ 'America/Los_Angeles'
+ >>> get_timezone_name(dt, width='short', locale='en_US')
+ u'PST'
+
+ If this function gets passed only a `tzinfo` object and no concrete
+ `datetime`, the returned display name is indenpendent of daylight savings
+ time. This can be used for example for selecting timezones, or to set the
+ time of events that recur across DST changes:
+
+ >>> tz = get_timezone('America/Los_Angeles')
+ >>> get_timezone_name(tz, locale='en_US')
+ u'Pacific Time'
+ >>> get_timezone_name(tz, 'short', locale='en_US')
+ u'PT'
+
+ If no localized display name for the timezone is available, and the timezone
+ is associated with a country that uses only a single timezone, the name of
+ that country is returned, formatted according to the locale:
+
+ >>> tz = get_timezone('Europe/Berlin')
+ >>> get_timezone_name(tz, locale='de_DE')
+ u'Mitteleurop\xe4ische Zeit'
+ >>> get_timezone_name(tz, locale='pt_BR')
+ u'Hor\xe1rio da Europa Central'
+
+ On the other hand, if the country uses multiple timezones, the city is also
+ included in the representation:
+
+ >>> tz = get_timezone('America/St_Johns')
+ >>> get_timezone_name(tz, locale='de_DE')
+ u'Neufundland-Zeit'
+
+ Note that short format is currently not supported for all timezones and
+ all locales. This is partially because not every timezone has a short
+ code in every locale. In that case it currently falls back to the long
+ format.
+
+ For more information see `LDML Appendix J: Time Zone Display Names
+ <http://www.unicode.org/reports/tr35/#Time_Zone_Fallback>`_
+
+ .. versionadded:: 0.9
+
+ .. versionchanged:: 1.0
+ Added `zone_variant` support.
+
+ :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines
+ the timezone; if a ``tzinfo`` object is used, the
+ resulting display name will be generic, i.e.
+ independent of daylight savings time; if `None`, the
+ current date in UTC is assumed
+ :param width: either "long" or "short"
+ :param uncommon: deprecated and ignored
+ :param zone_variant: defines the zone variation to return. By default the
+ variation is defined from the datetime object
+ passed in. If no datetime object is passed in, the
+ ``'generic'`` variation is assumed. The following
+ values are valid: ``'generic'``, ``'daylight'`` and
+ ``'standard'``.
+ :param locale: the `Locale` object, or a locale string
+ :param return_zone: True or False. If true then function
+ returns long time zone ID
+ """
+ dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo)
+ locale = Locale.parse(locale)
+
+ if hasattr(tzinfo, 'zone'):
+ zone = tzinfo.zone
+ else:
+ zone = tzinfo.tzname(dt)
+
+ if zone_variant is None:
+ if dt is None:
+ zone_variant = 'generic'
+ else:
+ dst = tzinfo.dst(dt)
+ if dst:
+ zone_variant = 'daylight'
+ else:
+ zone_variant = 'standard'
+ else:
+ if zone_variant not in ('generic', 'standard', 'daylight'):
+ raise ValueError('Invalid zone variation')
+
+ # Get the canonical time-zone code
+ zone = get_global('zone_aliases').get(zone, zone)
+ if return_zone:
+ return zone
+ info = locale.time_zones.get(zone, {})
+ # Try explicitly translated zone names first
+ if width in info:
+ if zone_variant in info[width]:
+ return info[width][zone_variant]
+
+ metazone = get_global('meta_zones').get(zone)
+ if metazone:
+ metazone_info = locale.meta_zones.get(metazone, {})
+ if width in metazone_info:
+ if zone_variant in metazone_info[width]:
+ return metazone_info[width][zone_variant]
+
+ # If we have a concrete datetime, we assume that the result can't be
+ # independent of daylight savings time, so we return the GMT offset
+ if dt is not None:
+ return get_timezone_gmt(dt, width=width, locale=locale)
+
+ return get_timezone_location(dt_or_tzinfo, locale=locale)
+
+
+def format_date(date=None, format='medium', locale=LC_TIME):
+ """Return a date formatted according to the given pattern.
+
+ >>> d = date(2007, 4, 1)
+ >>> format_date(d, locale='en_US')
+ u'Apr 1, 2007'
+ >>> format_date(d, format='full', locale='de_DE')
+ u'Sonntag, 1. April 2007'
+
+ If you don't want to use the locale default formats, you can specify a
+ custom date pattern:
+
+ >>> format_date(d, "EEE, MMM d, ''yy", locale='en')
+ u"Sun, Apr 1, '07"
+
+ :param date: the ``date`` or ``datetime`` object; if `None`, the current
+ date is used
+ :param format: one of "full", "long", "medium", or "short", or a custom
+ date/time pattern
+ :param locale: a `Locale` object or a locale identifier
+ """
+ if date is None:
+ date = date_.today()
+ elif isinstance(date, datetime):
+ date = date.date()
+
+ locale = Locale.parse(locale)
+ if format in ('full', 'long', 'medium', 'short'):
+ format = get_date_format(format, locale=locale)
+ pattern = parse_pattern(format)
+ return pattern.apply(date, locale)
+
+
+def format_datetime(datetime=None, format='medium', tzinfo=None,
+ locale=LC_TIME):
+ r"""Return a date formatted according to the given pattern.
+
+ >>> dt = datetime(2007, 4, 1, 15, 30)
+ >>> format_datetime(dt, locale='en_US')
+ u'Apr 1, 2007, 3:30:00 PM'
+
+ For any pattern requiring the display of the time-zone, the third-party
+ ``pytz`` package is needed to explicitly specify the time-zone:
+
+ >>> format_datetime(dt, 'full', tzinfo=get_timezone('Europe/Paris'),
+ ... locale='fr_FR')
+ u'dimanche 1 avril 2007 \xe0 17:30:00 heure d\u2019\xe9t\xe9 d\u2019Europe centrale'
+ >>> format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz",
+ ... tzinfo=get_timezone('US/Eastern'), locale='en')
+ u'2007.04.01 AD at 11:30:00 EDT'
+
+ :param datetime: the `datetime` object; if `None`, the current date and
+ time is used
+ :param format: one of "full", "long", "medium", or "short", or a custom
+ date/time pattern
+ :param tzinfo: the timezone to apply to the time for display
+ :param locale: a `Locale` object or a locale identifier
+ """
+ datetime = _ensure_datetime_tzinfo(_get_datetime(datetime), tzinfo)
+
+ locale = Locale.parse(locale)
+ if format in ('full', 'long', 'medium', 'short'):
+ return get_datetime_format(format, locale=locale) \
+ .replace("'", "") \
+ .replace('{0}', format_time(datetime, format, tzinfo=None,
+ locale=locale)) \
+ .replace('{1}', format_date(datetime, format, locale=locale))
+ else:
+ return parse_pattern(format).apply(datetime, locale)
+
+
+def format_time(time=None, format='medium', tzinfo=None, locale=LC_TIME):
+ r"""Return a time formatted according to the given pattern.
+
+ >>> t = time(15, 30)
+ >>> format_time(t, locale='en_US')
+ u'3:30:00 PM'
+ >>> format_time(t, format='short', locale='de_DE')
+ u'15:30'
+
+ If you don't want to use the locale default formats, you can specify a
+ custom time pattern:
+
+ >>> format_time(t, "hh 'o''clock' a", locale='en')
+ u"03 o'clock PM"
+
+ For any pattern requiring the display of the time-zone a
+ timezone has to be specified explicitly:
+
+ >>> t = datetime(2007, 4, 1, 15, 30)
+ >>> tzinfo = get_timezone('Europe/Paris')
+ >>> t = tzinfo.localize(t)
+ >>> format_time(t, format='full', tzinfo=tzinfo, locale='fr_FR')
+ u'15:30:00 heure d\u2019\xe9t\xe9 d\u2019Europe centrale'
+ >>> format_time(t, "hh 'o''clock' a, zzzz", tzinfo=get_timezone('US/Eastern'),
+ ... locale='en')
+ u"09 o'clock AM, Eastern Daylight Time"
+
+ As that example shows, when this function gets passed a
+ ``datetime.datetime`` value, the actual time in the formatted string is
+ adjusted to the timezone specified by the `tzinfo` parameter. If the
+ ``datetime`` is "naive" (i.e. it has no associated timezone information),
+ it is assumed to be in UTC.
+
+ These timezone calculations are **not** performed if the value is of type
+ ``datetime.time``, as without date information there's no way to determine
+ what a given time would translate to in a different timezone without
+ information about whether daylight savings time is in effect or not. This
+ means that time values are left as-is, and the value of the `tzinfo`
+ parameter is only used to display the timezone name if needed:
+
+ >>> t = time(15, 30)
+ >>> format_time(t, format='full', tzinfo=get_timezone('Europe/Paris'),
+ ... locale='fr_FR')
+ u'15:30:00 heure normale d\u2019Europe centrale'
+ >>> format_time(t, format='full', tzinfo=get_timezone('US/Eastern'),
+ ... locale='en_US')
+ u'3:30:00 PM Eastern Standard Time'
+
+ :param time: the ``time`` or ``datetime`` object; if `None`, the current
+ time in UTC is used
+ :param format: one of "full", "long", "medium", or "short", or a custom
+ date/time pattern
+ :param tzinfo: the time-zone to apply to the time for display
+ :param locale: a `Locale` object or a locale identifier
+ """
+ time = _get_time(time, tzinfo)
+
+ locale = Locale.parse(locale)
+ if format in ('full', 'long', 'medium', 'short'):
+ format = get_time_format(format, locale=locale)
+ return parse_pattern(format).apply(time, locale)
+
+
+def format_skeleton(skeleton, datetime=None, tzinfo=None, fuzzy=True, locale=LC_TIME):
+ r"""Return a time and/or date formatted according to the given pattern.
+
+ The skeletons are defined in the CLDR data and provide more flexibility
+ than the simple short/long/medium formats, but are a bit harder to use.
+ The are defined using the date/time symbols without order or punctuation
+ and map to a suitable format for the given locale.
+
+ >>> t = datetime(2007, 4, 1, 15, 30)
+ >>> format_skeleton('MMMEd', t, locale='fr')
+ u'dim. 1 avr.'
+ >>> format_skeleton('MMMEd', t, locale='en')
+ u'Sun, Apr 1'
+ >>> format_skeleton('yMMd', t, locale='fi') # yMMd is not in the Finnish locale; yMd gets used
+ u'1.4.2007'
+ >>> format_skeleton('yMMd', t, fuzzy=False, locale='fi') # yMMd is not in the Finnish locale, an error is thrown
+ Traceback (most recent call last):
+ ...
+ KeyError: yMMd
+
+ After the skeleton is resolved to a pattern `format_datetime` is called so
+ all timezone processing etc is the same as for that.
+
+ :param skeleton: A date time skeleton as defined in the cldr data.
+ :param datetime: the ``time`` or ``datetime`` object; if `None`, the current
+ time in UTC is used
+ :param tzinfo: the time-zone to apply to the time for display
+ :param fuzzy: If the skeleton is not found, allow choosing a skeleton that's
+ close enough to it.
+ :param locale: a `Locale` object or a locale identifier
+ """
+ locale = Locale.parse(locale)
+ if fuzzy and skeleton not in locale.datetime_skeletons:
+ skeleton = match_skeleton(skeleton, locale.datetime_skeletons)
+ format = locale.datetime_skeletons[skeleton]
+ return format_datetime(datetime, format, tzinfo, locale)
+
+
+TIMEDELTA_UNITS = (
+ ('year', 3600 * 24 * 365),
+ ('month', 3600 * 24 * 30),
+ ('week', 3600 * 24 * 7),
+ ('day', 3600 * 24),
+ ('hour', 3600),
+ ('minute', 60),
+ ('second', 1)
+)
+
+
+def format_timedelta(delta, granularity='second', threshold=.85,
+ add_direction=False, format='long',
+ locale=LC_TIME):
+ """Return a time delta according to the rules of the given locale.
+
+ >>> format_timedelta(timedelta(weeks=12), locale='en_US')
+ u'3 months'
+ >>> format_timedelta(timedelta(seconds=1), locale='es')
+ u'1 segundo'
+
+ The granularity parameter can be provided to alter the lowest unit
+ presented, which defaults to a second.
+
+ >>> format_timedelta(timedelta(hours=3), granularity='day',
+ ... locale='en_US')
+ u'1 day'
+
+ The threshold parameter can be used to determine at which value the
+ presentation switches to the next higher unit. A higher threshold factor
+ means the presentation will switch later. For example:
+
+ >>> format_timedelta(timedelta(hours=23), threshold=0.9, locale='en_US')
+ u'1 day'
+ >>> format_timedelta(timedelta(hours=23), threshold=1.1, locale='en_US')
+ u'23 hours'
+
+ In addition directional information can be provided that informs
+ the user if the date is in the past or in the future:
+
+ >>> format_timedelta(timedelta(hours=1), add_direction=True, locale='en')
+ u'in 1 hour'
+ >>> format_timedelta(timedelta(hours=-1), add_direction=True, locale='en')
+ u'1 hour ago'
+
+ The format parameter controls how compact or wide the presentation is:
+
+ >>> format_timedelta(timedelta(hours=3), format='short', locale='en')
+ u'3 hr'
+ >>> format_timedelta(timedelta(hours=3), format='narrow', locale='en')
+ u'3h'
+
+ :param delta: a ``timedelta`` object representing the time difference to
+ format, or the delta in seconds as an `int` value
+ :param granularity: determines the smallest unit that should be displayed,
+ the value can be one of "year", "month", "week", "day",
+ "hour", "minute" or "second"
+ :param threshold: factor that determines at which point the presentation
+ switches to the next higher unit
+ :param add_direction: if this flag is set to `True` the return value will
+ include directional information. For instance a
+ positive timedelta will include the information about
+ it being in the future, a negative will be information
+ about the value being in the past.
+ :param format: the format, can be "narrow", "short" or "long". (
+ "medium" is deprecated, currently converted to "long" to
+ maintain compatibility)
+ :param locale: a `Locale` object or a locale identifier
+ """
+ if format not in ('narrow', 'short', 'medium', 'long'):
+ raise TypeError('Format must be one of "narrow", "short" or "long"')
+ if format == 'medium':
+ warnings.warn('"medium" value for format param of format_timedelta'
+ ' is deprecated. Use "long" instead',
+ category=DeprecationWarning)
+ format = 'long'
+ if isinstance(delta, timedelta):
+ seconds = int((delta.days * 86400) + delta.seconds)
+ else:
+ seconds = delta
+ locale = Locale.parse(locale)
+
+ def _iter_patterns(a_unit):
+ if add_direction:
+ unit_rel_patterns = locale._data['date_fields'][a_unit]
+ if seconds >= 0:
+ yield unit_rel_patterns['future']
+ else:
+ yield unit_rel_patterns['past']
+ a_unit = 'duration-' + a_unit
+ yield locale._data['unit_patterns'].get(a_unit, {}).get(format)
+
+ for unit, secs_per_unit in TIMEDELTA_UNITS:
+ value = abs(seconds) / secs_per_unit
+ if value >= threshold or unit == granularity:
+ if unit == granularity and value > 0:
+ value = max(1, value)
+ value = int(round(value))
+ plural_form = locale.plural_form(value)
+ pattern = None
+ for patterns in _iter_patterns(unit):
+ if patterns is not None:
+ pattern = patterns[plural_form]
+ break
+ # This really should not happen
+ if pattern is None:
+ return u''
+ return pattern.replace('{0}', str(value))
+
+ return u''
+
+
+def _format_fallback_interval(start, end, skeleton, tzinfo, locale):
+ if skeleton in locale.datetime_skeletons: # Use the given skeleton
+ format = lambda dt: format_skeleton(skeleton, dt, tzinfo, locale=locale)
+ elif all((isinstance(d, date) and not isinstance(d, datetime)) for d in (start, end)): # Both are just dates
+ format = lambda dt: format_date(dt, locale=locale)
+ elif all((isinstance(d, time) and not isinstance(d, date)) for d in (start, end)): # Both are times
+ format = lambda dt: format_time(dt, tzinfo=tzinfo, locale=locale)
+ else:
+ format = lambda dt: format_datetime(dt, tzinfo=tzinfo, locale=locale)
+
+ formatted_start = format(start)
+ formatted_end = format(end)
+
+ if formatted_start == formatted_end:
+ return format(start)
+
+ return (
+ locale.interval_formats.get(None, "{0}-{1}").
+ replace("{0}", formatted_start).
+ replace("{1}", formatted_end)
+ )
+
+
+def format_interval(start, end, skeleton=None, tzinfo=None, fuzzy=True, locale=LC_TIME):
+ """
+ Format an interval between two instants according to the locale's rules.
+
+ >>> format_interval(date(2016, 1, 15), date(2016, 1, 17), "yMd", locale="fi")
+ u'15.\u201317.1.2016'
+
+ >>> format_interval(time(12, 12), time(16, 16), "Hm", locale="en_GB")
+ '12:12 \u2013 16:16'
+
+ >>> format_interval(time(5, 12), time(16, 16), "hm", locale="en_US")
+ '5:12 AM \u2013 4:16 PM'
+
+ >>> format_interval(time(16, 18), time(16, 24), "Hm", locale="it")
+ '16:18\u201316:24'
+
+ If the start instant equals the end instant, the interval is formatted like the instant.
+
+ >>> format_interval(time(16, 18), time(16, 18), "Hm", locale="it")
+ '16:18'
+
+ Unknown skeletons fall back to "default" formatting.
+
+ >>> format_interval(date(2015, 1, 1), date(2017, 1, 1), "wzq", locale="ja")
+ '2015/01/01\uff5e2017/01/01'
+
+ >>> format_interval(time(16, 18), time(16, 24), "xxx", locale="ja")
+ '16:18:00\uff5e16:24:00'
+
+ >>> format_interval(date(2016, 1, 15), date(2016, 1, 17), "xxx", locale="de")
+ '15.01.2016 \u2013 17.01.2016'
+
+ :param start: First instant (datetime/date/time)
+ :param end: Second instant (datetime/date/time)
+ :param skeleton: The "skeleton format" to use for formatting.
+ :param tzinfo: tzinfo to use (if none is already attached)
+ :param fuzzy: If the skeleton is not found, allow choosing a skeleton that's
+ close enough to it.
+ :param locale: A locale object or identifier.
+ :return: Formatted interval
+ """
+ locale = Locale.parse(locale)
+
+ # NB: The quote comments below are from the algorithm description in
+ # http://www.unicode.org/reports/tr35/tr35-dates.html#intervalFormats
+
+ # > Look for the intervalFormatItem element that matches the "skeleton",
+ # > starting in the current locale and then following the locale fallback
+ # > chain up to, but not including root.
+
+ interval_formats = locale.interval_formats
+
+ if skeleton not in interval_formats or not skeleton:
+ # > If no match was found from the previous step, check what the closest
+ # > match is in the fallback locale chain, as in availableFormats. That
+ # > is, this allows for adjusting the string value field's width,
+ # > including adjusting between "MMM" and "MMMM", and using different
+ # > variants of the same field, such as 'v' and 'z'.
+ if skeleton and fuzzy:
+ skeleton = match_skeleton(skeleton, interval_formats)
+ else:
+ skeleton = None
+ if not skeleton: # Still no match whatsoever?
+ # > Otherwise, format the start and end datetime using the fallback pattern.
+ return _format_fallback_interval(start, end, skeleton, tzinfo, locale)
+
+ skel_formats = interval_formats[skeleton]
+
+ if start == end:
+ return format_skeleton(skeleton, start, tzinfo, fuzzy=fuzzy, locale=locale)
+
+ start = _ensure_datetime_tzinfo(_get_datetime(start), tzinfo=tzinfo)
+ end = _ensure_datetime_tzinfo(_get_datetime(end), tzinfo=tzinfo)
+
+ start_fmt = DateTimeFormat(start, locale=locale)
+ end_fmt = DateTimeFormat(end, locale=locale)
+
+ # > If a match is found from previous steps, compute the calendar field
+ # > with the greatest difference between start and end datetime. If there
+ # > is no difference among any of the fields in the pattern, format as a
+ # > single date using availableFormats, and return.
+
+ for field in PATTERN_CHAR_ORDER: # These are in largest-to-smallest order
+ if field in skel_formats:
+ if start_fmt.extract(field) != end_fmt.extract(field):
+ # > If there is a match, use the pieces of the corresponding pattern to
+ # > format the start and end datetime, as above.
+ return "".join(
+ parse_pattern(pattern).apply(instant, locale)
+ for pattern, instant
+ in zip(skel_formats[field], (start, end))
+ )
+
+ # > Otherwise, format the start and end datetime using the fallback pattern.
+
+ return _format_fallback_interval(start, end, skeleton, tzinfo, locale)
+
+
+def get_period_id(time, tzinfo=None, type=None, locale=LC_TIME):
+ """
+ Get the day period ID for a given time.
+
+ This ID can be used as a key for the period name dictionary.
+
+ >>> get_period_names(locale="de")[get_period_id(time(7, 42), locale="de")]
+ u'Morgen'
+
+ :param time: The time to inspect.
+ :param tzinfo: The timezone for the time. See ``format_time``.
+ :param type: The period type to use. Either "selection" or None.
+ The selection type is used for selecting among phrases such as
+ “Your email arrived yesterday evening” or “Your email arrived last night”.
+ :param locale: the `Locale` object, or a locale string
+ :return: period ID. Something is always returned -- even if it's just "am" or "pm".
+ """
+ time = _get_time(time, tzinfo)
+ seconds_past_midnight = int(time.hour * 60 * 60 + time.minute * 60 + time.second)
+ locale = Locale.parse(locale)
+
+ # The LDML rules state that the rules may not overlap, so iterating in arbitrary
+ # order should be alright.
+ for rule_id, rules in locale.day_period_rules.get(type, {}).items():
+ for rule in rules:
+ if "at" in rule and rule["at"] == seconds_past_midnight:
+ return rule_id
+
+ start_ok = end_ok = False
+
+ if "from" in rule and seconds_past_midnight >= rule["from"]:
+ start_ok = True
+ if "to" in rule and seconds_past_midnight <= rule["to"]:
+ # This rule type does not exist in the present CLDR data;
+ # excuse the lack of test coverage.
+ end_ok = True
+ if "before" in rule and seconds_past_midnight < rule["before"]:
+ end_ok = True
+ if "after" in rule and seconds_past_midnight > rule["after"]:
+ start_ok = True
+
+ if start_ok and end_ok:
+ return rule_id
+
+ if seconds_past_midnight < 43200:
+ return "am"
+ else:
+ return "pm"
+
+
+def parse_date(string, locale=LC_TIME):
+ """Parse a date from a string.
+
+ This function uses the date format for the locale as a hint to determine
+ the order in which the date fields appear in the string.
+
+ >>> parse_date('4/1/04', locale='en_US')
+ datetime.date(2004, 4, 1)
+ >>> parse_date('01.04.2004', locale='de_DE')
+ datetime.date(2004, 4, 1)
+
+ :param string: the string containing the date
+ :param locale: a `Locale` object or a locale identifier
+ """
+ # TODO: try ISO format first?
+ format = get_date_format(locale=locale).pattern.lower()
+ year_idx = format.index('y')
+ month_idx = format.index('m')
+ if month_idx < 0:
+ month_idx = format.index('l')
+ day_idx = format.index('d')
+
+ indexes = [(year_idx, 'Y'), (month_idx, 'M'), (day_idx, 'D')]
+ indexes.sort()
+ indexes = dict([(item[1], idx) for idx, item in enumerate(indexes)])
+
+ # FIXME: this currently only supports numbers, but should also support month
+ # names, both in the requested locale, and english
+
+ numbers = re.findall('(\d+)', string)
+ year = numbers[indexes['Y']]
+ if len(year) == 2:
+ year = 2000 + int(year)
+ else:
+ year = int(year)
+ month = int(numbers[indexes['M']])
+ day = int(numbers[indexes['D']])
+ if month > 12:
+ month, day = day, month
+ return date(year, month, day)
+
+
+def parse_time(string, locale=LC_TIME):
+ """Parse a time from a string.
+
+ This function uses the time format for the locale as a hint to determine
+ the order in which the time fields appear in the string.
+
+ >>> parse_time('15:30:00', locale='en_US')
+ datetime.time(15, 30)
+
+ :param string: the string containing the time
+ :param locale: a `Locale` object or a locale identifier
+ :return: the parsed time
+ :rtype: `time`
+ """
+ # TODO: try ISO format first?
+ format = get_time_format(locale=locale).pattern.lower()
+ hour_idx = format.index('h')
+ if hour_idx < 0:
+ hour_idx = format.index('k')
+ min_idx = format.index('m')
+ sec_idx = format.index('s')
+
+ indexes = [(hour_idx, 'H'), (min_idx, 'M'), (sec_idx, 'S')]
+ indexes.sort()
+ indexes = dict([(item[1], idx) for idx, item in enumerate(indexes)])
+
+ # FIXME: support 12 hour clock, and 0-based hour specification
+ # and seconds should be optional, maybe minutes too
+ # oh, and time-zones, of course
+
+ numbers = re.findall('(\d+)', string)
+ hour = int(numbers[indexes['H']])
+ minute = int(numbers[indexes['M']])
+ second = int(numbers[indexes['S']])
+ return time(hour, minute, second)
+
+
+class DateTimePattern(object):
+
+ def __init__(self, pattern, format):
+ self.pattern = pattern
+ self.format = format
+
+ def __repr__(self):
+ return '<%s %r>' % (type(self).__name__, self.pattern)
+
+ def __unicode__(self):
+ return self.pattern
+
+ def __mod__(self, other):
+ if type(other) is not DateTimeFormat:
+ return NotImplemented
+ return self.format % other
+
+ def apply(self, datetime, locale):
+ return self % DateTimeFormat(datetime, locale)
+
+
+class DateTimeFormat(object):
+
+ def __init__(self, value, locale):
+ assert isinstance(value, (date, datetime, time))
+ if isinstance(value, (datetime, time)) and value.tzinfo is None:
+ value = value.replace(tzinfo=UTC)
+ self.value = value
+ self.locale = Locale.parse(locale)
+
+ def __getitem__(self, name):
+ char = name[0]
+ num = len(name)
+ if char == 'G':
+ return self.format_era(char, num)
+ elif char in ('y', 'Y', 'u'):
+ return self.format_year(char, num)
+ elif char in ('Q', 'q'):
+ return self.format_quarter(char, num)
+ elif char in ('M', 'L'):
+ return self.format_month(char, num)
+ elif char in ('w', 'W'):
+ return self.format_week(char, num)
+ elif char == 'd':
+ return self.format(self.value.day, num)
+ elif char == 'D':
+ return self.format_day_of_year(num)
+ elif char == 'F':
+ return self.format_day_of_week_in_month()
+ elif char in ('E', 'e', 'c'):
+ return self.format_weekday(char, num)
+ elif char == 'a':
+ # TODO: Add support for the rest of the period formats (a*, b*, B*)
+ return self.format_period(char)
+ elif char == 'h':
+ if self.value.hour % 12 == 0:
+ return self.format(12, num)
+ else:
+ return self.format(self.value.hour % 12, num)
+ elif char == 'H':
+ return self.format(self.value.hour, num)
+ elif char == 'K':
+ return self.format(self.value.hour % 12, num)
+ elif char == 'k':
+ if self.value.hour == 0:
+ return self.format(24, num)
+ else:
+ return self.format(self.value.hour, num)
+ elif char == 'm':
+ return self.format(self.value.minute, num)
+ elif char == 's':
+ return self.format(self.value.second, num)
+ elif char == 'S':
+ return self.format_frac_seconds(num)
+ elif char == 'A':
+ return self.format_milliseconds_in_day(num)
+ elif char in ('z', 'Z', 'v', 'V', 'x', 'X', 'O'):
+ return self.format_timezone(char, num)
+ else:
+ raise KeyError('Unsupported date/time field %r' % char)
+
+ def extract(self, char):
+ char = str(char)[0]
+ if char == 'y':
+ return self.value.year
+ elif char == 'M':
+ return self.value.month
+ elif char == 'd':
+ return self.value.day
+ elif char == 'H':
+ return self.value.hour
+ elif char == 'h':
+ return (self.value.hour % 12 or 12)
+ elif char == 'm':
+ return self.value.minute
+ elif char == 'a':
+ return int(self.value.hour >= 12) # 0 for am, 1 for pm
+ else:
+ raise NotImplementedError("Not implemented: extracting %r from %r" % (char, self.value))
+
+ def format_era(self, char, num):
+ width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)]
+ era = int(self.value.year >= 0)
+ return get_era_names(width, self.locale)[era]
+
+ def format_year(self, char, num):
+ value = self.value.year
+ if char.isupper():
+ week = self.get_week_number(self.get_day_of_year())
+ if week == 0:
+ value -= 1
+ year = self.format(value, num)
+ if num == 2:
+ year = year[-2:]
+ return year
+
+ def format_quarter(self, char, num):
+ quarter = (self.value.month - 1) // 3 + 1
+ if num <= 2:
+ return ('%%0%dd' % num) % quarter
+ width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num]
+ context = {'Q': 'format', 'q': 'stand-alone'}[char]
+ return get_quarter_names(width, context, self.locale)[quarter]
+
+ def format_month(self, char, num):
+ if num <= 2:
+ return ('%%0%dd' % num) % self.value.month
+ width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num]
+ context = {'M': 'format', 'L': 'stand-alone'}[char]
+ return get_month_names(width, context, self.locale)[self.value.month]
+
+ def format_week(self, char, num):
+ if char.islower(): # week of year
+ day_of_year = self.get_day_of_year()
+ week = self.get_week_number(day_of_year)
+ if week == 0:
+ date = self.value - timedelta(days=day_of_year)
+ week = self.get_week_number(self.get_day_of_year(date),
+ date.weekday())
+ return self.format(week, num)
+ else: # week of month
+ week = self.get_week_number(self.value.day)
+ if week == 0:
+ date = self.value - timedelta(days=self.value.day)
+ week = self.get_week_number(date.day, date.weekday())
+ return '%d' % week
+
+ def format_weekday(self, char='E', num=4):
+ """
+ Return weekday from parsed datetime according to format pattern.
+
+ >>> format = DateTimeFormat(date(2016, 2, 28), Locale.parse('en_US'))
+ >>> format.format_weekday()
+ u'Sunday'
+
+ 'E': Day of week - Use one through three letters for the abbreviated day name, four for the full (wide) name,
+ five for the narrow name, or six for the short name.
+ >>> format.format_weekday('E',2)
+ u'Sun'
+
+ 'e': Local day of week. Same as E except adds a numeric value that will depend on the local starting day of the
+ week, using one or two letters. For this example, Monday is the first day of the week.
+ >>> format.format_weekday('e',2)
+ '01'
+
+ 'c': Stand-Alone local day of week - Use one letter for the local numeric value (same as 'e'), three for the
+ abbreviated day name, four for the full (wide) name, five for the narrow name, or six for the short name.
+ >>> format.format_weekday('c',1)
+ '1'
+
+ :param char: pattern format character ('e','E','c')
+ :param num: count of format character
+
+ """
+ if num < 3:
+ if char.islower():
+ value = 7 - self.locale.first_week_day + self.value.weekday()
+ return self.format(value % 7 + 1, num)
+ num = 3
+ weekday = self.value.weekday()
+ width = {3: 'abbreviated', 4: 'wide', 5: 'narrow', 6: 'short'}[num]
+ if char == 'c':
+ context = 'stand-alone'
+ else:
+ context = 'format'
+ return get_day_names(width, context, self.locale)[weekday]
+
+ def format_day_of_year(self, num):
+ return self.format(self.get_day_of_year(), num)
+
+ def format_day_of_week_in_month(self):
+ return '%d' % ((self.value.day - 1) // 7 + 1)
+
+ def format_period(self, char):
+ period = {0: 'am', 1: 'pm'}[int(self.value.hour >= 12)]
+ for width in ('wide', 'narrow', 'abbreviated'):
+ period_names = get_period_names(context='format', width=width, locale=self.locale)
+ if period in period_names:
+ return period_names[period]
+ raise ValueError('Could not format period %s in %s' % (period, self.locale))
+
+ def format_frac_seconds(self, num):
+ """ Return fractional seconds.
+
+ Rounds the time's microseconds to the precision given by the number \
+ of digits passed in.
+ """
+ value = self.value.microsecond / 1000000
+ return self.format(round(value, num) * 10**num, num)
+
+ def format_milliseconds_in_day(self, num):
+ msecs = self.value.microsecond // 1000 + self.value.second * 1000 + \
+ self.value.minute * 60000 + self.value.hour * 3600000
+ return self.format(msecs, num)
+
+ def format_timezone(self, char, num):
+ width = {3: 'short', 4: 'long', 5: 'iso8601'}[max(3, num)]
+ if char == 'z':
+ return get_timezone_name(self.value, width, locale=self.locale)
+ elif char == 'Z':
+ if num == 5:
+ return get_timezone_gmt(self.value, width, locale=self.locale, return_z=True)
+ return get_timezone_gmt(self.value, width, locale=self.locale)
+ elif char == 'O':
+ if num == 4:
+ return get_timezone_gmt(self.value, width, locale=self.locale)
+ # TODO: To add support for O:1
+ elif char == 'v':
+ return get_timezone_name(self.value.tzinfo, width,
+ locale=self.locale)
+ elif char == 'V':
+ if num == 1:
+ return get_timezone_name(self.value.tzinfo, width,
+ uncommon=True, locale=self.locale)
+ elif num == 2:
+ return get_timezone_name(self.value.tzinfo, locale=self.locale, return_zone=True)
+ elif num == 3:
+ return get_timezone_location(self.value.tzinfo, locale=self.locale, return_city=True)
+ return get_timezone_location(self.value.tzinfo, locale=self.locale)
+ # Included additional elif condition to add support for 'Xx' in timezone format
+ elif char == 'X':
+ if num == 1:
+ return get_timezone_gmt(self.value, width='iso8601_short', locale=self.locale,
+ return_z=True)
+ elif num in (2, 4):
+ return get_timezone_gmt(self.value, width='short', locale=self.locale,
+ return_z=True)
+ elif num in (3, 5):
+ return get_timezone_gmt(self.value, width='iso8601', locale=self.locale,
+ return_z=True)
+ elif char == 'x':
+ if num == 1:
+ return get_timezone_gmt(self.value, width='iso8601_short', locale=self.locale)
+ elif num in (2, 4):
+ return get_timezone_gmt(self.value, width='short', locale=self.locale)
+ elif num in (3, 5):
+ return get_timezone_gmt(self.value, width='iso8601', locale=self.locale)
+
+ def format(self, value, length):
+ return ('%%0%dd' % length) % value
+
+ def get_day_of_year(self, date=None):
+ if date is None:
+ date = self.value
+ return (date - date.replace(month=1, day=1)).days + 1
+
+ def get_week_number(self, day_of_period, day_of_week=None):
+ """Return the number of the week of a day within a period. This may be
+ the week number in a year or the week number in a month.
+
+ Usually this will return a value equal to or greater than 1, but if the
+ first week of the period is so short that it actually counts as the last
+ week of the previous period, this function will return 0.
+
+ >>> format = DateTimeFormat(date(2006, 1, 8), Locale.parse('de_DE'))
+ >>> format.get_week_number(6)
+ 1
+
+ >>> format = DateTimeFormat(date(2006, 1, 8), Locale.parse('en_US'))
+ >>> format.get_week_number(6)
+ 2
+
+ :param day_of_period: the number of the day in the period (usually
+ either the day of month or the day of year)
+ :param day_of_week: the week day; if ommitted, the week day of the
+ current date is assumed
+ """
+ if day_of_week is None:
+ day_of_week = self.value.weekday()
+ first_day = (day_of_week - self.locale.first_week_day -
+ day_of_period + 1) % 7
+ if first_day < 0:
+ first_day += 7
+ week_number = (day_of_period + first_day - 1) // 7
+ if 7 - first_day >= self.locale.min_week_days:
+ week_number += 1
+ return week_number
+
+
+PATTERN_CHARS = {
+ 'G': [1, 2, 3, 4, 5], # era
+ 'y': None, 'Y': None, 'u': None, # year
+ 'Q': [1, 2, 3, 4, 5], 'q': [1, 2, 3, 4, 5], # quarter
+ 'M': [1, 2, 3, 4, 5], 'L': [1, 2, 3, 4, 5], # month
+ 'w': [1, 2], 'W': [1], # week
+ 'd': [1, 2], 'D': [1, 2, 3], 'F': [1], 'g': None, # day
+ 'E': [1, 2, 3, 4, 5, 6], 'e': [1, 2, 3, 4, 5, 6], 'c': [1, 3, 4, 5, 6], # week day
+ 'a': [1], # period
+ 'h': [1, 2], 'H': [1, 2], 'K': [1, 2], 'k': [1, 2], # hour
+ 'm': [1, 2], # minute
+ 's': [1, 2], 'S': None, 'A': None, # second
+ 'z': [1, 2, 3, 4], 'Z': [1, 2, 3, 4, 5], 'O': [1, 4], 'v': [1, 4], # zone
+ 'V': [1, 2, 3, 4], 'x': [1, 2, 3, 4, 5], 'X': [1, 2, 3, 4, 5] # zone
+}
+
+#: The pattern characters declared in the Date Field Symbol Table
+#: (http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table)
+#: in order of decreasing magnitude.
+PATTERN_CHAR_ORDER = "GyYuUQqMLlwWdDFgEecabBChHKkjJmsSAzZOvVXx"
+
+_pattern_cache = {}
+
+
+def parse_pattern(pattern):
+ """Parse date, time, and datetime format patterns.
+
+ >>> parse_pattern("MMMMd").format
+ u'%(MMMM)s%(d)s'
+ >>> parse_pattern("MMM d, yyyy").format
+ u'%(MMM)s %(d)s, %(yyyy)s'
+
+ Pattern can contain literal strings in single quotes:
+
+ >>> parse_pattern("H:mm' Uhr 'z").format
+ u'%(H)s:%(mm)s Uhr %(z)s'
+
+ An actual single quote can be used by using two adjacent single quote
+ characters:
+
+ >>> parse_pattern("hh' o''clock'").format
+ u"%(hh)s o'clock"
+
+ :param pattern: the formatting pattern to parse
+ """
+ if type(pattern) is DateTimePattern:
+ return pattern
+
+ if pattern in _pattern_cache:
+ return _pattern_cache[pattern]
+
+ result = []
+
+ for tok_type, tok_value in tokenize_pattern(pattern):
+ if tok_type == "chars":
+ result.append(tok_value.replace('%', '%%'))
+ elif tok_type == "field":
+ fieldchar, fieldnum = tok_value
+ limit = PATTERN_CHARS[fieldchar]
+ if limit and fieldnum not in limit:
+ raise ValueError('Invalid length for field: %r'
+ % (fieldchar * fieldnum))
+ result.append('%%(%s)s' % (fieldchar * fieldnum))
+ else:
+ raise NotImplementedError("Unknown token type: %s" % tok_type)
+
+ _pattern_cache[pattern] = pat = DateTimePattern(pattern, u''.join(result))
+ return pat
+
+
+def tokenize_pattern(pattern):
+ """
+ Tokenize date format patterns.
+
+ Returns a list of (token_type, token_value) tuples.
+
+ ``token_type`` may be either "chars" or "field".
+
+ For "chars" tokens, the value is the literal value.
+
+ For "field" tokens, the value is a tuple of (field character, repetition count).
+
+ :param pattern: Pattern string
+ :type pattern: str
+ :rtype: list[tuple]
+ """
+ result = []
+ quotebuf = None
+ charbuf = []
+ fieldchar = ['']
+ fieldnum = [0]
+
+ def append_chars():
+ result.append(('chars', ''.join(charbuf).replace('\0', "'")))
+ del charbuf[:]
+
+ def append_field():
+ result.append(('field', (fieldchar[0], fieldnum[0])))
+ fieldchar[0] = ''
+ fieldnum[0] = 0
+
+ for idx, char in enumerate(pattern.replace("''", '\0')):
+ if quotebuf is None:
+ if char == "'": # quote started
+ if fieldchar[0]:
+ append_field()
+ elif charbuf:
+ append_chars()
+ quotebuf = []
+ elif char in PATTERN_CHARS:
+ if charbuf:
+ append_chars()
+ if char == fieldchar[0]:
+ fieldnum[0] += 1
+ else:
+ if fieldchar[0]:
+ append_field()
+ fieldchar[0] = char
+ fieldnum[0] = 1
+ else:
+ if fieldchar[0]:
+ append_field()
+ charbuf.append(char)
+
+ elif quotebuf is not None:
+ if char == "'": # end of quote
+ charbuf.extend(quotebuf)
+ quotebuf = None
+ else: # inside quote
+ quotebuf.append(char)
+
+ if fieldchar[0]:
+ append_field()
+ elif charbuf:
+ append_chars()
+
+ return result
+
+
+def untokenize_pattern(tokens):
+ """
+ Turn a date format pattern token stream back into a string.
+
+ This is the reverse operation of ``tokenize_pattern``.
+
+ :type tokens: Iterable[tuple]
+ :rtype: str
+ """
+ output = []
+ for tok_type, tok_value in tokens:
+ if tok_type == "field":
+ output.append(tok_value[0] * tok_value[1])
+ elif tok_type == "chars":
+ if not any(ch in PATTERN_CHARS for ch in tok_value): # No need to quote
+ output.append(tok_value)
+ else:
+ output.append("'%s'" % tok_value.replace("'", "''"))
+ return "".join(output)
+
+
+def split_interval_pattern(pattern):
+ """
+ Split an interval-describing datetime pattern into multiple pieces.
+
+ > The pattern is then designed to be broken up into two pieces by determining the first repeating field.
+ - http://www.unicode.org/reports/tr35/tr35-dates.html#intervalFormats
+
+ >>> split_interval_pattern(u'E d.M. \u2013 E d.M.')
+ [u'E d.M. \u2013 ', 'E d.M.']
+ >>> split_interval_pattern("Y 'text' Y 'more text'")
+ ["Y 'text '", "Y 'more text'"]
+ >>> split_interval_pattern(u"E, MMM d \u2013 E")
+ [u'E, MMM d \u2013 ', u'E']
+ >>> split_interval_pattern("MMM d")
+ ['MMM d']
+ >>> split_interval_pattern("y G")
+ ['y G']
+ >>> split_interval_pattern(u"MMM d \u2013 d")
+ [u'MMM d \u2013 ', u'd']
+
+ :param pattern: Interval pattern string
+ :return: list of "subpatterns"
+ """
+
+ seen_fields = set()
+ parts = [[]]
+
+ for tok_type, tok_value in tokenize_pattern(pattern):
+ if tok_type == "field":
+ if tok_value[0] in seen_fields: # Repeated field
+ parts.append([])
+ seen_fields.clear()
+ seen_fields.add(tok_value[0])
+ parts[-1].append((tok_type, tok_value))
+
+ return [untokenize_pattern(tokens) for tokens in parts]
+
+
+def match_skeleton(skeleton, options, allow_different_fields=False):
+ """
+ Find the closest match for the given datetime skeleton among the options given.
+
+ This uses the rules outlined in the TR35 document.
+
+ >>> match_skeleton('yMMd', ('yMd', 'yMMMd'))
+ 'yMd'
+
+ >>> match_skeleton('yMMd', ('jyMMd',), allow_different_fields=True)
+ 'jyMMd'
+
+ >>> match_skeleton('yMMd', ('qyMMd',), allow_different_fields=False)
+
+ >>> match_skeleton('hmz', ('hmv',))
+ 'hmv'
+
+ :param skeleton: The skeleton to match
+ :type skeleton: str
+ :param options: An iterable of other skeletons to match against
+ :type options: Iterable[str]
+ :return: The closest skeleton match, or if no match was found, None.
+ :rtype: str|None
+ """
+
+ # TODO: maybe implement pattern expansion?
+
+ # Based on the implementation in
+ # http://source.icu-project.org/repos/icu/icu4j/trunk/main/classes/core/src/com/ibm/icu/text/DateIntervalInfo.java
+
+ # Filter out falsy values and sort for stability; when `interval_formats` is passed in, there may be a None key.
+ options = sorted(option for option in options if option)
+
+ if 'z' in skeleton and not any('z' in option for option in options):
+ skeleton = skeleton.replace('z', 'v')
+
+ get_input_field_width = dict(t[1] for t in tokenize_pattern(skeleton) if t[0] == "field").get
+ best_skeleton = None
+ best_distance = None
+ for option in options:
+ get_opt_field_width = dict(t[1] for t in tokenize_pattern(option) if t[0] == "field").get
+ distance = 0
+ for field in PATTERN_CHARS:
+ input_width = get_input_field_width(field, 0)
+ opt_width = get_opt_field_width(field, 0)
+ if input_width == opt_width:
+ continue
+ if opt_width == 0 or input_width == 0:
+ if not allow_different_fields: # This one is not okay
+ option = None
+ break
+ distance += 0x1000 # Magic weight constant for "entirely different fields"
+ elif field == 'M' and ((input_width > 2 and opt_width <= 2) or (input_width <= 2 and opt_width > 2)):
+ distance += 0x100 # Magic weight for "text turns into a number"
+ else:
+ distance += abs(input_width - opt_width)
+
+ if not option: # We lost the option along the way (probably due to "allow_different_fields")
+ continue
+
+ if not best_skeleton or distance < best_distance:
+ best_skeleton = option
+ best_distance = distance
+
+ if distance == 0: # Found a perfect match!
+ break
+
+ return best_skeleton