Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
dates.py1921 linesDownload Raw Back to babel
1"""2    babel.dates3    ~~~~~~~~~~~4 5    Locale dependent formatting and parsing of dates and times.6 7    The default locale for the functions in this module is determined by the8    following environment variables, in that order:9 10     * ``LC_TIME``,11     * ``LC_ALL``, and12     * ``LANG``13 14    :copyright: (c) 2013-2024 by the Babel Team.15    :license: BSD, see LICENSE for more details.16"""17 18from __future__ import annotations19 20import re21import warnings22from functools import lru_cache23from typing import TYPE_CHECKING, SupportsInt24 25try:26    import pytz27except ModuleNotFoundError:28    pytz = None29    import zoneinfo30 31import datetime32from collections.abc import Iterable33 34from babel import localtime35from babel.core import Locale, default_locale, get_global36from babel.localedata import LocaleDataDict37 38if TYPE_CHECKING:39    from typing_extensions import Literal, TypeAlias40    _Instant: TypeAlias = datetime.date | datetime.time | float | None41    _PredefinedTimeFormat: TypeAlias = Literal['full', 'long', 'medium', 'short']42    _Context: TypeAlias = Literal['format', 'stand-alone']43    _DtOrTzinfo: TypeAlias = datetime.datetime | datetime.tzinfo | str | int | datetime.time | None44 45# "If a given short metazone form is known NOT to be understood in a given46#  locale and the parent locale has this value such that it would normally47#  be inherited, the inheritance of this value can be explicitly disabled by48#  use of the 'no inheritance marker' as the value, which is 3 simultaneous [sic]49#  empty set characters ( U+2205 )."50#  - https://www.unicode.org/reports/tr35/tr35-dates.html#Metazone_Names51 52NO_INHERITANCE_MARKER = '\u2205\u2205\u2205'53 54UTC = datetime.timezone.utc55LOCALTZ = localtime.LOCALTZ56 57LC_TIME = default_locale('LC_TIME')58 59 60def _localize(tz: datetime.tzinfo, dt: datetime.datetime) -> datetime.datetime:61    # Support localizing with both pytz and zoneinfo tzinfos62    # nothing to do63    if dt.tzinfo is tz:64        return dt65 66    if hasattr(tz, 'localize'):  # pytz67        return tz.localize(dt)68 69    if dt.tzinfo is None:70        # convert naive to localized71        return dt.replace(tzinfo=tz)72 73    # convert timezones74    return dt.astimezone(tz)75 76 77def _get_dt_and_tzinfo(dt_or_tzinfo: _DtOrTzinfo) -> tuple[datetime.datetime | None, datetime.tzinfo]:78    """79    Parse a `dt_or_tzinfo` value into a datetime and a tzinfo.80 81    See the docs for this function's callers for semantics.82 83    :rtype: tuple[datetime, tzinfo]84    """85    if dt_or_tzinfo is None:86        dt = datetime.datetime.now()87        tzinfo = LOCALTZ88    elif isinstance(dt_or_tzinfo, str):89        dt = None90        tzinfo = get_timezone(dt_or_tzinfo)91    elif isinstance(dt_or_tzinfo, int):92        dt = None93        tzinfo = UTC94    elif isinstance(dt_or_tzinfo, (datetime.datetime, datetime.time)):95        dt = _get_datetime(dt_or_tzinfo)96        tzinfo = dt.tzinfo if dt.tzinfo is not None else UTC97    else:98        dt = None99        tzinfo = dt_or_tzinfo100    return dt, tzinfo101 102 103def _get_tz_name(dt_or_tzinfo: _DtOrTzinfo) -> str:104    """105    Get the timezone name out of a time, datetime, or tzinfo object.106 107    :rtype: str108    """109    dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo)110    if hasattr(tzinfo, 'zone'):  # pytz object111        return tzinfo.zone112    elif hasattr(tzinfo, 'key') and tzinfo.key is not None:  # ZoneInfo object113        return tzinfo.key114    else:115        return tzinfo.tzname(dt or datetime.datetime.now(UTC))116 117 118def _get_datetime(instant: _Instant) -> datetime.datetime:119    """120    Get a datetime out of an "instant" (date, time, datetime, number).121 122    .. warning:: The return values of this function may depend on the system clock.123 124    If the instant is None, the current moment is used.125    If the instant is a time, it's augmented with today's date.126 127    Dates are converted to naive datetimes with midnight as the time component.128 129    >>> from datetime import date, datetime130    >>> _get_datetime(date(2015, 1, 1))131    datetime.datetime(2015, 1, 1, 0, 0)132 133    UNIX timestamps are converted to datetimes.134 135    >>> _get_datetime(1400000000)136    datetime.datetime(2014, 5, 13, 16, 53, 20)137 138    Other values are passed through as-is.139 140    >>> x = datetime(2015, 1, 1)141    >>> _get_datetime(x) is x142    True143 144    :param instant: date, time, datetime, integer, float or None145    :type instant: date|time|datetime|int|float|None146    :return: a datetime147    :rtype: datetime148    """149    if instant is None:150        return datetime.datetime.now(UTC).replace(tzinfo=None)151    elif isinstance(instant, (int, float)):152        return datetime.datetime.fromtimestamp(instant, UTC).replace(tzinfo=None)153    elif isinstance(instant, datetime.time):154        return datetime.datetime.combine(datetime.date.today(), instant)155    elif isinstance(instant, datetime.date) and not isinstance(instant, datetime.datetime):156        return datetime.datetime.combine(instant, datetime.time())157    # TODO (3.x): Add an assertion/type check for this fallthrough branch:158    return instant159 160 161def _ensure_datetime_tzinfo(dt: datetime.datetime, tzinfo: datetime.tzinfo | None = None) -> datetime.datetime:162    """163    Ensure the datetime passed has an attached tzinfo.164 165    If the datetime is tz-naive to begin with, UTC is attached.166 167    If a tzinfo is passed in, the datetime is normalized to that timezone.168 169    >>> from datetime import datetime170    >>> _get_tz_name(_ensure_datetime_tzinfo(datetime(2015, 1, 1)))171    'UTC'172 173    >>> tz = get_timezone("Europe/Stockholm")174    >>> _ensure_datetime_tzinfo(datetime(2015, 1, 1, 13, 15, tzinfo=UTC), tzinfo=tz).hour175    14176 177    :param datetime: Datetime to augment.178    :param tzinfo: optional tzinfo179    :return: datetime with tzinfo180    :rtype: datetime181    """182    if dt.tzinfo is None:183        dt = dt.replace(tzinfo=UTC)184    if tzinfo is not None:185        dt = dt.astimezone(get_timezone(tzinfo))186        if hasattr(tzinfo, 'normalize'):  # pytz187            dt = tzinfo.normalize(dt)188    return dt189 190 191def _get_time(192    time: datetime.time | datetime.datetime | None,193    tzinfo: datetime.tzinfo | None = None,194) -> datetime.time:195    """196    Get a timezoned time from a given instant.197 198    .. warning:: The return values of this function may depend on the system clock.199 200    :param time: time, datetime or None201    :rtype: time202    """203    if time is None:204        time = datetime.datetime.now(UTC)205    elif isinstance(time, (int, float)):206        time = datetime.datetime.fromtimestamp(time, UTC)207 208    if time.tzinfo is None:209        time = time.replace(tzinfo=UTC)210 211    if isinstance(time, datetime.datetime):212        if tzinfo is not None:213            time = time.astimezone(tzinfo)214            if hasattr(tzinfo, 'normalize'):  # pytz215                time = tzinfo.normalize(time)216        time = time.timetz()217    elif tzinfo is not None:218        time = time.replace(tzinfo=tzinfo)219    return time220 221 222def get_timezone(zone: str | datetime.tzinfo | None = None) -> datetime.tzinfo:223    """Looks up a timezone by name and returns it.  The timezone object224    returned comes from ``pytz`` or ``zoneinfo``, whichever is available.225    It corresponds to the `tzinfo` interface and can be used with all of226    the functions of Babel that operate with dates.227 228    If a timezone is not known a :exc:`LookupError` is raised.  If `zone`229    is ``None`` a local zone object is returned.230 231    :param zone: the name of the timezone to look up.  If a timezone object232                 itself is passed in, it's returned unchanged.233    """234    if zone is None:235        return LOCALTZ236    if not isinstance(zone, str):237        return zone238 239    if pytz:240        try:241            return pytz.timezone(zone)242        except pytz.UnknownTimeZoneError as e:243            exc = e244    else:245        assert zoneinfo246        try:247            return zoneinfo.ZoneInfo(zone)248        except zoneinfo.ZoneInfoNotFoundError as e:249            exc = e250 251    raise LookupError(f"Unknown timezone {zone}") from exc252 253 254def get_period_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',255                     context: _Context = 'stand-alone', locale: Locale | str | None = LC_TIME) -> LocaleDataDict:256    """Return the names for day periods (AM/PM) used by the locale.257 258    >>> get_period_names(locale='en_US')['am']259    u'AM'260 261    :param width: the width to use, one of "abbreviated", "narrow", or "wide"262    :param context: the context, either "format" or "stand-alone"263    :param locale: the `Locale` object, or a locale string264    """265    return Locale.parse(locale).day_periods[context][width]266 267 268def get_day_names(width: Literal['abbreviated', 'narrow', 'short', 'wide'] = 'wide',269                  context: _Context = 'format', locale: Locale | str | None = LC_TIME) -> LocaleDataDict:270    """Return the day names used by the locale for the specified format.271 272    >>> get_day_names('wide', locale='en_US')[1]273    u'Tuesday'274    >>> get_day_names('short', locale='en_US')[1]275    u'Tu'276    >>> get_day_names('abbreviated', locale='es')[1]277    u'mar'278    >>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1]279    u'D'280 281    :param width: the width to use, one of "wide", "abbreviated", "short" or "narrow"282    :param context: the context, either "format" or "stand-alone"283    :param locale: the `Locale` object, or a locale string284    """285    return Locale.parse(locale).days[context][width]286 287 288def get_month_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',289                    context: _Context = 'format', locale: Locale | str | None = LC_TIME) -> LocaleDataDict:290    """Return the month names used by the locale for the specified format.291 292    >>> get_month_names('wide', locale='en_US')[1]293    u'January'294    >>> get_month_names('abbreviated', locale='es')[1]295    u'ene'296    >>> get_month_names('narrow', context='stand-alone', locale='de_DE')[1]297    u'J'298 299    :param width: the width to use, one of "wide", "abbreviated", or "narrow"300    :param context: the context, either "format" or "stand-alone"301    :param locale: the `Locale` object, or a locale string302    """303    return Locale.parse(locale).months[context][width]304 305 306def get_quarter_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',307                      context: _Context = 'format', locale: Locale | str | None = LC_TIME) -> LocaleDataDict:308    """Return the quarter names used by the locale for the specified format.309 310    >>> get_quarter_names('wide', locale='en_US')[1]311    u'1st quarter'312    >>> get_quarter_names('abbreviated', locale='de_DE')[1]313    u'Q1'314    >>> get_quarter_names('narrow', locale='de_DE')[1]315    u'1'316 317    :param width: the width to use, one of "wide", "abbreviated", or "narrow"318    :param context: the context, either "format" or "stand-alone"319    :param locale: the `Locale` object, or a locale string320    """321    return Locale.parse(locale).quarters[context][width]322 323 324def get_era_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',325                  locale: Locale | str | None = LC_TIME) -> LocaleDataDict:326    """Return the era names used by the locale for the specified format.327 328    >>> get_era_names('wide', locale='en_US')[1]329    u'Anno Domini'330    >>> get_era_names('abbreviated', locale='de_DE')[1]331    u'n. Chr.'332 333    :param width: the width to use, either "wide", "abbreviated", or "narrow"334    :param locale: the `Locale` object, or a locale string335    """336    return Locale.parse(locale).eras[width]337 338 339def get_date_format(format: _PredefinedTimeFormat = 'medium', locale: Locale | str | None = LC_TIME) -> DateTimePattern:340    """Return the date formatting patterns used by the locale for the specified341    format.342 343    >>> get_date_format(locale='en_US')344    <DateTimePattern u'MMM d, y'>345    >>> get_date_format('full', locale='de_DE')346    <DateTimePattern u'EEEE, d. MMMM y'>347 348    :param format: the format to use, one of "full", "long", "medium", or349                   "short"350    :param locale: the `Locale` object, or a locale string351    """352    return Locale.parse(locale).date_formats[format]353 354 355def get_datetime_format(format: _PredefinedTimeFormat = 'medium', locale: Locale | str | None = LC_TIME) -> DateTimePattern:356    """Return the datetime formatting patterns used by the locale for the357    specified format.358 359    >>> get_datetime_format(locale='en_US')360    u'{1}, {0}'361 362    :param format: the format to use, one of "full", "long", "medium", or363                   "short"364    :param locale: the `Locale` object, or a locale string365    """366    patterns = Locale.parse(locale).datetime_formats367    if format not in patterns:368        format = None369    return patterns[format]370 371 372def get_time_format(format: _PredefinedTimeFormat = 'medium', locale: Locale | str | None = LC_TIME) -> DateTimePattern:373    """Return the time formatting patterns used by the locale for the specified374    format.375 376    >>> get_time_format(locale='en_US')377    <DateTimePattern u'h:mm:ss\u202fa'>378    >>> get_time_format('full', locale='de_DE')379    <DateTimePattern u'HH:mm:ss zzzz'>380 381    :param format: the format to use, one of "full", "long", "medium", or382                   "short"383    :param locale: the `Locale` object, or a locale string384    """385    return Locale.parse(locale).time_formats[format]386 387 388def get_timezone_gmt(389    datetime: _Instant = None,390    width: Literal['long', 'short', 'iso8601', 'iso8601_short'] = 'long',391    locale: Locale | str | None = LC_TIME,392    return_z: bool = False,393) -> str:394    """Return the timezone associated with the given `datetime` object formatted395    as string indicating the offset from GMT.396 397    >>> from datetime import datetime398    >>> dt = datetime(2007, 4, 1, 15, 30)399    >>> get_timezone_gmt(dt, locale='en')400    u'GMT+00:00'401    >>> get_timezone_gmt(dt, locale='en', return_z=True)402    'Z'403    >>> get_timezone_gmt(dt, locale='en', width='iso8601_short')404    u'+00'405    >>> tz = get_timezone('America/Los_Angeles')406    >>> dt = _localize(tz, datetime(2007, 4, 1, 15, 30))407    >>> get_timezone_gmt(dt, locale='en')408    u'GMT-07:00'409    >>> get_timezone_gmt(dt, 'short', locale='en')410    u'-0700'411    >>> get_timezone_gmt(dt, locale='en', width='iso8601_short')412    u'-07'413 414    The long format depends on the locale, for example in France the acronym415    UTC string is used instead of GMT:416 417    >>> get_timezone_gmt(dt, 'long', locale='fr_FR')418    u'UTC-07:00'419 420    .. versionadded:: 0.9421 422    :param datetime: the ``datetime`` object; if `None`, the current date and423                     time in UTC is used424    :param width: either "long" or "short" or "iso8601" or "iso8601_short"425    :param locale: the `Locale` object, or a locale string426    :param return_z: True or False; Function returns indicator "Z"427                     when local time offset is 0428    """429    datetime = _ensure_datetime_tzinfo(_get_datetime(datetime))430    locale = Locale.parse(locale)431 432    offset = datetime.tzinfo.utcoffset(datetime)433    seconds = offset.days * 24 * 60 * 60 + offset.seconds434    hours, seconds = divmod(seconds, 3600)435    if return_z and hours == 0 and seconds == 0:436        return 'Z'437    elif seconds == 0 and width == 'iso8601_short':438        return '%+03d' % hours439    elif width == 'short' or width == 'iso8601_short':440        pattern = '%+03d%02d'441    elif width == 'iso8601':442        pattern = '%+03d:%02d'443    else:444        pattern = locale.zone_formats['gmt'] % '%+03d:%02d'445    return pattern % (hours, seconds // 60)446 447 448def get_timezone_location(449    dt_or_tzinfo: _DtOrTzinfo = None,450    locale: Locale | str | None = LC_TIME,451    return_city: bool = False,452) -> str:453    """Return a representation of the given timezone using "location format".454 455    The result depends on both the local display name of the country and the456    city associated with the time zone:457 458    >>> tz = get_timezone('America/St_Johns')459    >>> print(get_timezone_location(tz, locale='de_DE'))460    Kanada (St. John’s) (Ortszeit)461    >>> print(get_timezone_location(tz, locale='en'))462    Canada (St. John’s) Time463    >>> print(get_timezone_location(tz, locale='en', return_city=True))464    St. John’s465    >>> tz = get_timezone('America/Mexico_City')466    >>> get_timezone_location(tz, locale='de_DE')467    u'Mexiko (Mexiko-Stadt) (Ortszeit)'468 469    If the timezone is associated with a country that uses only a single470    timezone, just the localized country name is returned:471 472    >>> tz = get_timezone('Europe/Berlin')473    >>> get_timezone_name(tz, locale='de_DE')474    u'Mitteleurop\\xe4ische Zeit'475 476    .. versionadded:: 0.9477 478    :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines479                         the timezone; if `None`, the current date and time in480                         UTC is assumed481    :param locale: the `Locale` object, or a locale string482    :param return_city: True or False, if True then return exemplar city (location)483                        for the time zone484    :return: the localized timezone name using location format485 486    """487    locale = Locale.parse(locale)488 489    zone = _get_tz_name(dt_or_tzinfo)490 491    # Get the canonical time-zone code492    zone = get_global('zone_aliases').get(zone, zone)493 494    info = locale.time_zones.get(zone, {})495 496    # Otherwise, if there is only one timezone for the country, return the497    # localized country name498    region_format = locale.zone_formats['region']499    territory = get_global('zone_territories').get(zone)500    if territory not in locale.territories:501        territory = 'ZZ'  # invalid/unknown502    territory_name = locale.territories[territory]503    if not return_city and territory and len(get_global('territory_zones').get(territory, [])) == 1:504        return region_format % territory_name505 506    # Otherwise, include the city in the output507    fallback_format = locale.zone_formats['fallback']508    if 'city' in info:509        city_name = info['city']510    else:511        metazone = get_global('meta_zones').get(zone)512        metazone_info = locale.meta_zones.get(metazone, {})513        if 'city' in metazone_info:514            city_name = metazone_info['city']515        elif '/' in zone:516            city_name = zone.split('/', 1)[1].replace('_', ' ')517        else:518            city_name = zone.replace('_', ' ')519 520    if return_city:521        return city_name522    return region_format % (fallback_format % {523        '0': city_name,524        '1': territory_name,525    })526 527 528def get_timezone_name(529    dt_or_tzinfo: _DtOrTzinfo = None,530    width: Literal['long', 'short'] = 'long',531    uncommon: bool = False,532    locale: Locale | str | None = LC_TIME,533    zone_variant: Literal['generic', 'daylight', 'standard'] | None = None,534    return_zone: bool = False,535) -> str:536    r"""Return the localized display name for the given timezone. The timezone537    may be specified using a ``datetime`` or `tzinfo` object.538 539    >>> from datetime import time540    >>> dt = time(15, 30, tzinfo=get_timezone('America/Los_Angeles'))541    >>> get_timezone_name(dt, locale='en_US')  # doctest: +SKIP542    u'Pacific Standard Time'543    >>> get_timezone_name(dt, locale='en_US', return_zone=True)544    'America/Los_Angeles'545    >>> get_timezone_name(dt, width='short', locale='en_US')  # doctest: +SKIP546    u'PST'547 548    If this function gets passed only a `tzinfo` object and no concrete549    `datetime`,  the returned display name is independent of daylight savings550    time. This can be used for example for selecting timezones, or to set the551    time of events that recur across DST changes:552 553    >>> tz = get_timezone('America/Los_Angeles')554    >>> get_timezone_name(tz, locale='en_US')555    u'Pacific Time'556    >>> get_timezone_name(tz, 'short', locale='en_US')557    u'PT'558 559    If no localized display name for the timezone is available, and the timezone560    is associated with a country that uses only a single timezone, the name of561    that country is returned, formatted according to the locale:562 563    >>> tz = get_timezone('Europe/Berlin')564    >>> get_timezone_name(tz, locale='de_DE')565    u'Mitteleurop\xe4ische Zeit'566    >>> get_timezone_name(tz, locale='pt_BR')567    u'Hor\xe1rio da Europa Central'568 569    On the other hand, if the country uses multiple timezones, the city is also570    included in the representation:571 572    >>> tz = get_timezone('America/St_Johns')573    >>> get_timezone_name(tz, locale='de_DE')574    u'Neufundland-Zeit'575 576    Note that short format is currently not supported for all timezones and577    all locales.  This is partially because not every timezone has a short578    code in every locale.  In that case it currently falls back to the long579    format.580 581    For more information see `LDML Appendix J: Time Zone Display Names582    <https://www.unicode.org/reports/tr35/#Time_Zone_Fallback>`_583 584    .. versionadded:: 0.9585 586    .. versionchanged:: 1.0587       Added `zone_variant` support.588 589    :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines590                         the timezone; if a ``tzinfo`` object is used, the591                         resulting display name will be generic, i.e.592                         independent of daylight savings time; if `None`, the593                         current date in UTC is assumed594    :param width: either "long" or "short"595    :param uncommon: deprecated and ignored596    :param zone_variant: defines the zone variation to return.  By default the597                           variation is defined from the datetime object598                           passed in.  If no datetime object is passed in, the599                           ``'generic'`` variation is assumed.  The following600                           values are valid: ``'generic'``, ``'daylight'`` and601                           ``'standard'``.602    :param locale: the `Locale` object, or a locale string603    :param return_zone: True or False. If true then function604                        returns long time zone ID605    """606    dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo)607    locale = Locale.parse(locale)608 609    zone = _get_tz_name(dt_or_tzinfo)610 611    if zone_variant is None:612        if dt is None:613            zone_variant = 'generic'614        else:615            dst = tzinfo.dst(dt)616            zone_variant = "daylight" if dst else "standard"617    else:618        if zone_variant not in ('generic', 'standard', 'daylight'):619            raise ValueError('Invalid zone variation')620 621    # Get the canonical time-zone code622    zone = get_global('zone_aliases').get(zone, zone)623    if return_zone:624        return zone625    info = locale.time_zones.get(zone, {})626    # Try explicitly translated zone names first627    if width in info and zone_variant in info[width]:628        return info[width][zone_variant]629 630    metazone = get_global('meta_zones').get(zone)631    if metazone:632        metazone_info = locale.meta_zones.get(metazone, {})633        if width in metazone_info:634            name = metazone_info[width].get(zone_variant)635            if width == 'short' and name == NO_INHERITANCE_MARKER:636                # If the short form is marked no-inheritance,637                # try to fall back to the long name instead.638                name = metazone_info.get('long', {}).get(zone_variant)639            if name:640                return name641 642    # If we have a concrete datetime, we assume that the result can't be643    # independent of daylight savings time, so we return the GMT offset644    if dt is not None:645        return get_timezone_gmt(dt, width=width, locale=locale)646 647    return get_timezone_location(dt_or_tzinfo, locale=locale)648 649 650def format_date(651    date: datetime.date | None = None,652    format: _PredefinedTimeFormat | str = 'medium',653    locale: Locale | str | None = LC_TIME,654) -> str:655    """Return a date formatted according to the given pattern.656 657    >>> from datetime import date658    >>> d = date(2007, 4, 1)659    >>> format_date(d, locale='en_US')660    u'Apr 1, 2007'661    >>> format_date(d, format='full', locale='de_DE')662    u'Sonntag, 1. April 2007'663 664    If you don't want to use the locale default formats, you can specify a665    custom date pattern:666 667    >>> format_date(d, "EEE, MMM d, ''yy", locale='en')668    u"Sun, Apr 1, '07"669 670    :param date: the ``date`` or ``datetime`` object; if `None`, the current671                 date is used672    :param format: one of "full", "long", "medium", or "short", or a custom673                   date/time pattern674    :param locale: a `Locale` object or a locale identifier675    """676    if date is None:677        date = datetime.date.today()678    elif isinstance(date, datetime.datetime):679        date = date.date()680 681    locale = Locale.parse(locale)682    if format in ('full', 'long', 'medium', 'short'):683        format = get_date_format(format, locale=locale)684    pattern = parse_pattern(format)685    return pattern.apply(date, locale)686 687 688def format_datetime(689    datetime: _Instant = None,690    format: _PredefinedTimeFormat | str = 'medium',691    tzinfo: datetime.tzinfo | None = None,692    locale: Locale | str | None = LC_TIME,693) -> str:694    r"""Return a date formatted according to the given pattern.695 696    >>> from datetime import datetime697    >>> dt = datetime(2007, 4, 1, 15, 30)698    >>> format_datetime(dt, locale='en_US')699    u'Apr 1, 2007, 3:30:00\u202fPM'700 701    For any pattern requiring the display of the timezone:702 703    >>> format_datetime(dt, 'full', tzinfo=get_timezone('Europe/Paris'),704    ...                 locale='fr_FR')705    'dimanche 1 avril 2007, 17:30:00 heure d’été d’Europe centrale'706    >>> format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz",707    ...                 tzinfo=get_timezone('US/Eastern'), locale='en')708    u'2007.04.01 AD at 11:30:00 EDT'709 710    :param datetime: the `datetime` object; if `None`, the current date and711                     time is used712    :param format: one of "full", "long", "medium", or "short", or a custom713                   date/time pattern714    :param tzinfo: the timezone to apply to the time for display715    :param locale: a `Locale` object or a locale identifier716    """717    datetime = _ensure_datetime_tzinfo(_get_datetime(datetime), tzinfo)718 719    locale = Locale.parse(locale)720    if format in ('full', 'long', 'medium', 'short'):721        return get_datetime_format(format, locale=locale) \722            .replace("'", "") \723            .replace('{0}', format_time(datetime, format, tzinfo=None,724                                        locale=locale)) \725            .replace('{1}', format_date(datetime, format, locale=locale))726    else:727        return parse_pattern(format).apply(datetime, locale)728 729 730def format_time(731    time: datetime.time | datetime.datetime | float | None = None,732    format: _PredefinedTimeFormat | str = 'medium',733    tzinfo: datetime.tzinfo | None = None, locale: Locale | str | None = LC_TIME,734) -> str:735    r"""Return a time formatted according to the given pattern.736 737    >>> from datetime import datetime, time738    >>> t = time(15, 30)739    >>> format_time(t, locale='en_US')740    u'3:30:00\u202fPM'741    >>> format_time(t, format='short', locale='de_DE')742    u'15:30'743 744    If you don't want to use the locale default formats, you can specify a745    custom time pattern:746 747    >>> format_time(t, "hh 'o''clock' a", locale='en')748    u"03 o'clock PM"749 750    For any pattern requiring the display of the time-zone a751    timezone has to be specified explicitly:752 753    >>> t = datetime(2007, 4, 1, 15, 30)754    >>> tzinfo = get_timezone('Europe/Paris')755    >>> t = _localize(tzinfo, t)756    >>> format_time(t, format='full', tzinfo=tzinfo, locale='fr_FR')757    '15:30:00 heure d’été d’Europe centrale'758    >>> format_time(t, "hh 'o''clock' a, zzzz", tzinfo=get_timezone('US/Eastern'),759    ...             locale='en')760    u"09 o'clock AM, Eastern Daylight Time"761 762    As that example shows, when this function gets passed a763    ``datetime.datetime`` value, the actual time in the formatted string is764    adjusted to the timezone specified by the `tzinfo` parameter. If the765    ``datetime`` is "naive" (i.e. it has no associated timezone information),766    it is assumed to be in UTC.767 768    These timezone calculations are **not** performed if the value is of type769    ``datetime.time``, as without date information there's no way to determine770    what a given time would translate to in a different timezone without771    information about whether daylight savings time is in effect or not. This772    means that time values are left as-is, and the value of the `tzinfo`773    parameter is only used to display the timezone name if needed:774 775    >>> t = time(15, 30)776    >>> format_time(t, format='full', tzinfo=get_timezone('Europe/Paris'),777    ...             locale='fr_FR')  # doctest: +SKIP778    u'15:30:00 heure normale d\u2019Europe centrale'779    >>> format_time(t, format='full', tzinfo=get_timezone('US/Eastern'),780    ...             locale='en_US')  # doctest: +SKIP781    u'3:30:00\u202fPM Eastern Standard Time'782 783    :param time: the ``time`` or ``datetime`` object; if `None`, the current784                 time in UTC is used785    :param format: one of "full", "long", "medium", or "short", or a custom786                   date/time pattern787    :param tzinfo: the time-zone to apply to the time for display788    :param locale: a `Locale` object or a locale identifier789    """790 791    # get reference date for if we need to find the right timezone variant792    # in the pattern793    ref_date = time.date() if isinstance(time, datetime.datetime) else None794 795    time = _get_time(time, tzinfo)796 797    locale = Locale.parse(locale)798    if format in ('full', 'long', 'medium', 'short'):799        format = get_time_format(format, locale=locale)800    return parse_pattern(format).apply(time, locale, reference_date=ref_date)801 802 803def format_skeleton(804    skeleton: str,805    datetime: _Instant = None,806    tzinfo: datetime.tzinfo | None = None,807    fuzzy: bool = True,808    locale: Locale | str | None = LC_TIME,809) -> str:810    r"""Return a time and/or date formatted according to the given pattern.811 812    The skeletons are defined in the CLDR data and provide more flexibility813    than the simple short/long/medium formats, but are a bit harder to use.814    The are defined using the date/time symbols without order or punctuation815    and map to a suitable format for the given locale.816 817    >>> from datetime import datetime818    >>> t = datetime(2007, 4, 1, 15, 30)819    >>> format_skeleton('MMMEd', t, locale='fr')820    u'dim. 1 avr.'821    >>> format_skeleton('MMMEd', t, locale='en')822    u'Sun, Apr 1'823    >>> format_skeleton('yMMd', t, locale='fi')  # yMMd is not in the Finnish locale; yMd gets used824    u'1.4.2007'825    >>> format_skeleton('yMMd', t, fuzzy=False, locale='fi')  # yMMd is not in the Finnish locale, an error is thrown826    Traceback (most recent call last):827        ...828    KeyError: yMMd829 830    After the skeleton is resolved to a pattern `format_datetime` is called so831    all timezone processing etc is the same as for that.832 833    :param skeleton: A date time skeleton as defined in the cldr data.834    :param datetime: the ``time`` or ``datetime`` object; if `None`, the current835                 time in UTC is used836    :param tzinfo: the time-zone to apply to the time for display837    :param fuzzy: If the skeleton is not found, allow choosing a skeleton that's838                  close enough to it.839    :param locale: a `Locale` object or a locale identifier840    """841    locale = Locale.parse(locale)842    if fuzzy and skeleton not in locale.datetime_skeletons:843        skeleton = match_skeleton(skeleton, locale.datetime_skeletons)844    format = locale.datetime_skeletons[skeleton]845    return format_datetime(datetime, format, tzinfo, locale)846 847 848TIMEDELTA_UNITS: tuple[tuple[str, int], ...] = (849    ('year', 3600 * 24 * 365),850    ('month', 3600 * 24 * 30),851    ('week', 3600 * 24 * 7),852    ('day', 3600 * 24),853    ('hour', 3600),854    ('minute', 60),855    ('second', 1),856)857 858 859def format_timedelta(860    delta: datetime.timedelta | int,861    granularity: Literal['year', 'month', 'week', 'day', 'hour', 'minute', 'second'] = 'second',862    threshold: float = .85,863    add_direction: bool = False,864    format: Literal['narrow', 'short', 'medium', 'long'] = 'long',865    locale: Locale | str | None = LC_TIME,866) -> str:867    """Return a time delta according to the rules of the given locale.868 869    >>> from datetime import timedelta870    >>> format_timedelta(timedelta(weeks=12), locale='en_US')871    u'3 months'872    >>> format_timedelta(timedelta(seconds=1), locale='es')873    u'1 segundo'874 875    The granularity parameter can be provided to alter the lowest unit876    presented, which defaults to a second.877 878    >>> format_timedelta(timedelta(hours=3), granularity='day', locale='en_US')879    u'1 day'880 881    The threshold parameter can be used to determine at which value the882    presentation switches to the next higher unit. A higher threshold factor883    means the presentation will switch later. For example:884 885    >>> format_timedelta(timedelta(hours=23), threshold=0.9, locale='en_US')886    u'1 day'887    >>> format_timedelta(timedelta(hours=23), threshold=1.1, locale='en_US')888    u'23 hours'889 890    In addition directional information can be provided that informs891    the user if the date is in the past or in the future:892 893    >>> format_timedelta(timedelta(hours=1), add_direction=True, locale='en')894    u'in 1 hour'895    >>> format_timedelta(timedelta(hours=-1), add_direction=True, locale='en')896    u'1 hour ago'897 898    The format parameter controls how compact or wide the presentation is:899 900    >>> format_timedelta(timedelta(hours=3), format='short', locale='en')901    u'3 hr'902    >>> format_timedelta(timedelta(hours=3), format='narrow', locale='en')903    u'3h'904 905    :param delta: a ``timedelta`` object representing the time difference to906                  format, or the delta in seconds as an `int` value907    :param granularity: determines the smallest unit that should be displayed,908                        the value can be one of "year", "month", "week", "day",909                        "hour", "minute" or "second"910    :param threshold: factor that determines at which point the presentation911                      switches to the next higher unit912    :param add_direction: if this flag is set to `True` the return value will913                          include directional information.  For instance a914                          positive timedelta will include the information about915                          it being in the future, a negative will be information916                          about the value being in the past.917    :param format: the format, can be "narrow", "short" or "long". (918                   "medium" is deprecated, currently converted to "long" to919                   maintain compatibility)920    :param locale: a `Locale` object or a locale identifier921    """922    if format not in ('narrow', 'short', 'medium', 'long'):923        raise TypeError('Format must be one of "narrow", "short" or "long"')924    if format == 'medium':925        warnings.warn(926            '"medium" value for format param of format_timedelta'927            ' is deprecated. Use "long" instead',928            category=DeprecationWarning,929            stacklevel=2,930        )931        format = 'long'932    if isinstance(delta, datetime.timedelta):933        seconds = int((delta.days * 86400) + delta.seconds)934    else:935        seconds = delta936    locale = Locale.parse(locale)937 938    def _iter_patterns(a_unit):939        if add_direction:940            unit_rel_patterns = locale._data['date_fields'][a_unit]941            if seconds >= 0:942                yield unit_rel_patterns['future']943            else:944                yield unit_rel_patterns['past']945        a_unit = f"duration-{a_unit}"946        unit_pats = locale._data['unit_patterns'].get(a_unit, {})947        yield unit_pats.get(format)948        # We do not support `<alias>` tags at all while ingesting CLDR data,949        # so these aliases specified in `root.xml` are hard-coded here:950        # <unitLength type="long"><alias source="locale" path="../unitLength[@type='short']"/></unitLength>951        # <unitLength type="narrow"><alias source="locale" path="../unitLength[@type='short']"/></unitLength>952        if format in ("long", "narrow"):953            yield unit_pats.get("short")954 955    for unit, secs_per_unit in TIMEDELTA_UNITS:956        value = abs(seconds) / secs_per_unit957        if value >= threshold or unit == granularity:958            if unit == granularity and value > 0:959                value = max(1, value)960            value = int(round(value))961            plural_form = locale.plural_form(value)962            pattern = None963            for patterns in _iter_patterns(unit):964                if patterns is not None:965                    pattern = patterns.get(plural_form) or patterns.get('other')966                    if pattern:967                        break968            # This really should not happen969            if pattern is None:970                return ''971            return pattern.replace('{0}', str(value))972 973    return ''974 975 976def _format_fallback_interval(977    start: _Instant,978    end: _Instant,979    skeleton: str | None,980    tzinfo: datetime.tzinfo | None,981    locale: Locale | str | None = LC_TIME,982) -> str:983    if skeleton in locale.datetime_skeletons:  # Use the given skeleton984        format = lambda dt: format_skeleton(skeleton, dt, tzinfo, locale=locale)985    elif all((isinstance(d, datetime.date) and not isinstance(d, datetime.datetime)) for d in (start, end)):  # Both are just dates986        format = lambda dt: format_date(dt, locale=locale)987    elif all((isinstance(d, datetime.time) and not isinstance(d, datetime.date)) for d in (start, end)):  # Both are times988        format = lambda dt: format_time(dt, tzinfo=tzinfo, locale=locale)989    else:990        format = lambda dt: format_datetime(dt, tzinfo=tzinfo, locale=locale)991 992    formatted_start = format(start)993    formatted_end = format(end)994 995    if formatted_start == formatted_end:996        return format(start)997 998    return (999        locale.interval_formats.get(None, "{0}-{1}").1000        replace("{0}", formatted_start).1001        replace("{1}", formatted_end)1002    )1003 1004 1005def format_interval(1006    start: _Instant,1007    end: _Instant,1008    skeleton: str | None = None,1009    tzinfo: datetime.tzinfo | None = None,1010    fuzzy: bool = True,1011    locale: Locale | str | None = LC_TIME,1012) -> str:1013    """1014    Format an interval between two instants according to the locale's rules.1015 1016    >>> from datetime import date, time1017    >>> format_interval(date(2016, 1, 15), date(2016, 1, 17), "yMd", locale="fi")1018    u'15.\u201317.1.2016'1019 1020    >>> format_interval(time(12, 12), time(16, 16), "Hm", locale="en_GB")1021    '12:12\u201316:16'1022 1023    >>> format_interval(time(5, 12), time(16, 16), "hm", locale="en_US")1024    '5:12\u202fAM\u2009–\u20094:16\u202fPM'1025 1026    >>> format_interval(time(16, 18), time(16, 24), "Hm", locale="it")1027    '16:18\u201316:24'1028 1029    If the start instant equals the end instant, the interval is formatted like the instant.1030 1031    >>> format_interval(time(16, 18), time(16, 18), "Hm", locale="it")1032    '16:18'1033 1034    Unknown skeletons fall back to "default" formatting.1035 1036    >>> format_interval(date(2015, 1, 1), date(2017, 1, 1), "wzq", locale="ja")1037    '2015/01/01\uff5e2017/01/01'1038 1039    >>> format_interval(time(16, 18), time(16, 24), "xxx", locale="ja")1040    '16:18:00\uff5e16:24:00'1041 1042    >>> format_interval(date(2016, 1, 15), date(2016, 1, 17), "xxx", locale="de")1043    '15.01.2016\u2009–\u200917.01.2016'1044 1045    :param start: First instant (datetime/date/time)1046    :param end: Second instant (datetime/date/time)1047    :param skeleton: The "skeleton format" to use for formatting.1048    :param tzinfo: tzinfo to use (if none is already attached)1049    :param fuzzy: If the skeleton is not found, allow choosing a skeleton that's1050                  close enough to it.1051    :param locale: A locale object or identifier.1052    :return: Formatted interval1053    """1054    locale = Locale.parse(locale)1055 1056    # NB: The quote comments below are from the algorithm description in1057    #     https://www.unicode.org/reports/tr35/tr35-dates.html#intervalFormats1058 1059    # > Look for the intervalFormatItem element that matches the "skeleton",1060    # > starting in the current locale and then following the locale fallback1061    # > chain up to, but not including root.1062 1063    interval_formats = locale.interval_formats1064 1065    if skeleton not in interval_formats or not skeleton:1066        # > If no match was found from the previous step, check what the closest1067        # > match is in the fallback locale chain, as in availableFormats. That1068        # > is, this allows for adjusting the string value field's width,1069        # > including adjusting between "MMM" and "MMMM", and using different1070        # > variants of the same field, such as 'v' and 'z'.1071        if skeleton and fuzzy:1072            skeleton = match_skeleton(skeleton, interval_formats)1073        else:1074            skeleton = None1075        if not skeleton:  # Still no match whatsoever?1076            # > Otherwise, format the start and end datetime using the fallback pattern.1077            return _format_fallback_interval(start, end, skeleton, tzinfo, locale)1078 1079    skel_formats = interval_formats[skeleton]1080 1081    if start == end:1082        return format_skeleton(skeleton, start, tzinfo, fuzzy=fuzzy, locale=locale)1083 1084    start = _ensure_datetime_tzinfo(_get_datetime(start), tzinfo=tzinfo)1085    end = _ensure_datetime_tzinfo(_get_datetime(end), tzinfo=tzinfo)1086 1087    start_fmt = DateTimeFormat(start, locale=locale)1088    end_fmt = DateTimeFormat(end, locale=locale)1089 1090    # > If a match is found from previous steps, compute the calendar field1091    # > with the greatest difference between start and end datetime. If there1092    # > is no difference among any of the fields in the pattern, format as a1093    # > single date using availableFormats, and return.1094 1095    for field in PATTERN_CHAR_ORDER:  # These are in largest-to-smallest order1096        if field in skel_formats and start_fmt.extract(field) != end_fmt.extract(field):1097            # > If there is a match, use the pieces of the corresponding pattern to1098            # > format the start and end datetime, as above.1099            return "".join(1100                parse_pattern(pattern).apply(instant, locale)1101                for pattern, instant1102                in zip(skel_formats[field], (start, end))1103            )1104 1105    # > Otherwise, format the start and end datetime using the fallback pattern.1106 1107    return _format_fallback_interval(start, end, skeleton, tzinfo, locale)1108 1109 1110def get_period_id(1111    time: _Instant,1112    tzinfo: datetime.tzinfo | None = None,1113    type: Literal['selection'] | None = None,1114    locale: Locale | str | None = LC_TIME,1115) -> str:1116    """1117    Get the day period ID for a given time.1118 1119    This ID can be used as a key for the period name dictionary.1120 1121    >>> from datetime import time1122    >>> get_period_names(locale="de")[get_period_id(time(7, 42), locale="de")]1123    u'Morgen'1124 1125    >>> get_period_id(time(0), locale="en_US")1126    u'midnight'1127 1128    >>> get_period_id(time(0), type="selection", locale="en_US")1129    u'night1'1130 1131    :param time: The time to inspect.1132    :param tzinfo: The timezone for the time. See ``format_time``.1133    :param type: The period type to use. Either "selection" or None.1134                 The selection type is used for selecting among phrases such as1135                 “Your email arrived yesterday evening” or “Your email arrived last night”.1136    :param locale: the `Locale` object, or a locale string1137    :return: period ID. Something is always returned -- even if it's just "am" or "pm".1138    """1139    time = _get_time(time, tzinfo)1140    seconds_past_midnight = int(time.hour * 60 * 60 + time.minute * 60 + time.second)1141    locale = Locale.parse(locale)1142 1143    # The LDML rules state that the rules may not overlap, so iterating in arbitrary1144    # order should be alright, though `at` periods should be preferred.1145    rulesets = locale.day_period_rules.get(type, {}).items()1146 1147    for rule_id, rules in rulesets:1148        for rule in rules:1149            if "at" in rule and rule["at"] == seconds_past_midnight:1150                return rule_id1151 1152    for rule_id, rules in rulesets:1153        for rule in rules:1154            if "from" in rule and "before" in rule:1155                if rule["from"] < rule["before"]:1156                    if rule["from"] <= seconds_past_midnight < rule["before"]:1157                        return rule_id1158                else:1159                    # e.g. from="21:00" before="06:00"1160                    if rule["from"] <= seconds_past_midnight < 86400 or \1161                            0 <= seconds_past_midnight < rule["before"]:1162                        return rule_id1163 1164            start_ok = end_ok = False1165 1166            if "from" in rule and seconds_past_midnight >= rule["from"]:1167                start_ok = True1168            if "to" in rule and seconds_past_midnight <= rule["to"]:1169                # This rule type does not exist in the present CLDR data;1170                # excuse the lack of test coverage.1171                end_ok = True1172            if "before" in rule and seconds_past_midnight < rule["before"]:1173                end_ok = True1174            if "after" in rule:1175                raise NotImplementedError("'after' is deprecated as of CLDR 29.")1176 1177            if start_ok and end_ok:1178                return rule_id1179 1180    if seconds_past_midnight < 43200:1181        return "am"1182    else:1183        return "pm"1184 1185 1186class ParseError(ValueError):1187    pass1188 1189 1190def parse_date(1191    string: str,1192    locale: Locale | str | None = LC_TIME,1193    format: _PredefinedTimeFormat = 'medium',1194) -> datetime.date:1195    """Parse a date from a string.1196 1197    This function first tries to interpret the string as ISO-86011198    date format, then uses the date format for the locale as a hint to1199    determine the order in which the date fields appear in the string.1200 

Showing the first 1,200 of 1921 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai