cmlenz@3: # -*- coding: utf-8 -*- cmlenz@3: # cmlenz@3: # Copyright (C) 2007 Edgewall Software cmlenz@3: # All rights reserved. cmlenz@3: # cmlenz@3: # This software is licensed as described in the file COPYING, which cmlenz@3: # you should have received as part of this distribution. The terms cmlenz@3: # are also available at http://babel.edgewall.org/wiki/License. cmlenz@3: # cmlenz@3: # This software consists of voluntary contributions made by many cmlenz@3: # individuals. For the exact contribution history, see the revision cmlenz@3: # history and logs, available at http://babel.edgewall.org/log/. cmlenz@3: cmlenz@3: """Locale dependent formatting and parsing of dates and times. cmlenz@3: cmlenz@3: The default locale for the functions in this module is determined by the cmlenz@3: following environment variables, in that order: cmlenz@3: cmlenz@3: * ``LC_TIME``, cmlenz@3: * ``LC_ALL``, and cmlenz@3: * ``LANG`` cmlenz@3: """ cmlenz@3: cmlenz@31: from datetime import date, datetime, time, timedelta, tzinfo cmlenz@40: import re cmlenz@3: cmlenz@74: from babel.core import default_locale, Locale cmlenz@41: from babel.util import UTC cmlenz@3: cmlenz@3: __all__ = ['format_date', 'format_datetime', 'format_time', 'parse_date', cmlenz@3: 'parse_datetime', 'parse_time'] cmlenz@3: __docformat__ = 'restructuredtext en' cmlenz@3: cmlenz@74: LC_TIME = default_locale('LC_TIME') cmlenz@3: cmlenz@35: # Aliases for use in scopes where the modules are shadowed by local variables cmlenz@35: date_ = date cmlenz@35: datetime_ = datetime cmlenz@35: time_ = time cmlenz@35: cmlenz@3: def get_period_names(locale=LC_TIME): cmlenz@3: """Return the names for day periods (AM/PM) used by the locale. cmlenz@3: cmlenz@3: >>> get_period_names(locale='en_US')['am'] cmlenz@3: u'AM' cmlenz@3: cmlenz@3: :param locale: the `Locale` object, or a locale string cmlenz@3: :return: the dictionary of period names cmlenz@3: :rtype: `dict` cmlenz@3: """ cmlenz@3: return Locale.parse(locale).periods cmlenz@3: cmlenz@3: def get_day_names(width='wide', context='format', locale=LC_TIME): cmlenz@3: """Return the day names used by the locale for the specified format. cmlenz@3: cmlenz@3: >>> get_day_names('wide', locale='en_US')[1] cmlenz@17: u'Tuesday' cmlenz@3: >>> get_day_names('abbreviated', locale='es')[1] cmlenz@17: u'mar' cmlenz@3: >>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1] cmlenz@17: u'D' cmlenz@3: cmlenz@3: :param width: the width to use, one of "wide", "abbreviated", or "narrow" cmlenz@3: :param context: the context, either "format" or "stand-alone" cmlenz@3: :param locale: the `Locale` object, or a locale string cmlenz@3: :return: the dictionary of day names cmlenz@3: :rtype: `dict` cmlenz@3: """ cmlenz@3: return Locale.parse(locale).days[context][width] cmlenz@3: cmlenz@3: def get_month_names(width='wide', context='format', locale=LC_TIME): cmlenz@3: """Return the month names used by the locale for the specified format. cmlenz@3: cmlenz@3: >>> get_month_names('wide', locale='en_US')[1] cmlenz@3: u'January' cmlenz@3: >>> get_month_names('abbreviated', locale='es')[1] cmlenz@3: u'ene' cmlenz@3: >>> get_month_names('narrow', context='stand-alone', locale='de_DE')[1] cmlenz@3: u'J' cmlenz@3: cmlenz@3: :param width: the width to use, one of "wide", "abbreviated", or "narrow" cmlenz@3: :param context: the context, either "format" or "stand-alone" cmlenz@3: :param locale: the `Locale` object, or a locale string cmlenz@3: :return: the dictionary of month names cmlenz@3: :rtype: `dict` cmlenz@3: """ cmlenz@3: return Locale.parse(locale).months[context][width] cmlenz@3: cmlenz@3: def get_quarter_names(width='wide', context='format', locale=LC_TIME): cmlenz@3: """Return the quarter names used by the locale for the specified format. cmlenz@3: cmlenz@3: >>> get_quarter_names('wide', locale='en_US')[1] cmlenz@3: u'1st quarter' cmlenz@3: >>> get_quarter_names('abbreviated', locale='de_DE')[1] cmlenz@3: u'Q1' cmlenz@3: cmlenz@3: :param width: the width to use, one of "wide", "abbreviated", or "narrow" cmlenz@3: :param context: the context, either "format" or "stand-alone" cmlenz@3: :param locale: the `Locale` object, or a locale string cmlenz@3: :return: the dictionary of quarter names cmlenz@3: :rtype: `dict` cmlenz@3: """ cmlenz@3: return Locale.parse(locale).quarters[context][width] cmlenz@3: cmlenz@3: def get_era_names(width='wide', locale=LC_TIME): cmlenz@3: """Return the era names used by the locale for the specified format. cmlenz@3: cmlenz@3: >>> get_era_names('wide', locale='en_US')[1] cmlenz@3: u'Anno Domini' cmlenz@3: >>> get_era_names('abbreviated', locale='de_DE')[1] cmlenz@3: u'n. Chr.' cmlenz@3: cmlenz@3: :param width: the width to use, either "wide" or "abbreviated" cmlenz@3: :param locale: the `Locale` object, or a locale string cmlenz@3: :return: the dictionary of era names cmlenz@3: :rtype: `dict` cmlenz@3: """ cmlenz@3: return Locale.parse(locale).eras[width] cmlenz@3: cmlenz@3: def get_date_format(format='medium', locale=LC_TIME): cmlenz@3: """Return the date formatting patterns used by the locale for the specified cmlenz@3: format. cmlenz@3: cmlenz@3: >>> get_date_format(locale='en_US') cmlenz@14: cmlenz@3: >>> get_date_format('full', locale='de_DE') cmlenz@14: cmlenz@3: cmlenz@3: :param format: the format to use, one of "full", "long", "medium", or cmlenz@3: "short" cmlenz@3: :param locale: the `Locale` object, or a locale string cmlenz@3: :return: the date format pattern cmlenz@35: :rtype: `DateTimePattern` cmlenz@3: """ cmlenz@3: return Locale.parse(locale).date_formats[format] cmlenz@3: cmlenz@35: def get_datetime_format(format='medium', locale=LC_TIME): cmlenz@35: """Return the datetime formatting patterns used by the locale for the cmlenz@35: specified format. cmlenz@35: cmlenz@35: >>> get_datetime_format(locale='en_US') cmlenz@35: u'{1} {0}' cmlenz@35: cmlenz@35: :param format: the format to use, one of "full", "long", "medium", or cmlenz@35: "short" cmlenz@35: :param locale: the `Locale` object, or a locale string cmlenz@35: :return: the datetime format pattern cmlenz@35: :rtype: `unicode` cmlenz@35: """ cmlenz@35: patterns = Locale.parse(locale).datetime_formats cmlenz@35: if format not in patterns: cmlenz@35: format = None cmlenz@35: return patterns[format] cmlenz@35: cmlenz@3: def get_time_format(format='medium', locale=LC_TIME): cmlenz@3: """Return the time formatting patterns used by the locale for the specified cmlenz@3: format. cmlenz@3: cmlenz@3: >>> get_time_format(locale='en_US') cmlenz@14: cmlenz@3: >>> get_time_format('full', locale='de_DE') cmlenz@14: cmlenz@3: cmlenz@3: :param format: the format to use, one of "full", "long", "medium", or cmlenz@3: "short" cmlenz@3: :param locale: the `Locale` object, or a locale string cmlenz@3: :return: the time format pattern cmlenz@35: :rtype: `DateTimePattern` cmlenz@3: """ cmlenz@3: return Locale.parse(locale).time_formats[format] cmlenz@3: cmlenz@35: def format_date(date=None, format='medium', locale=LC_TIME): cmlenz@103: """Return a date formatted according to the given pattern. cmlenz@3: cmlenz@3: >>> d = date(2007, 04, 01) cmlenz@3: >>> format_date(d, locale='en_US') cmlenz@3: u'Apr 1, 2007' cmlenz@3: >>> format_date(d, format='full', locale='de_DE') cmlenz@3: u'Sonntag, 1. April 2007' cmlenz@3: cmlenz@18: If you don't want to use the locale default formats, you can specify a cmlenz@18: custom date pattern: cmlenz@18: cmlenz@31: >>> format_date(d, "EEE, MMM d, ''yy", locale='en') cmlenz@18: u"Sun, Apr 1, '07" cmlenz@18: cmlenz@35: :param date: the ``date`` or ``datetime`` object; if `None`, the current cmlenz@35: date is used cmlenz@18: :param format: one of "full", "long", "medium", or "short", or a custom cmlenz@18: date/time pattern cmlenz@21: :param locale: a `Locale` object or a locale identifier cmlenz@3: :rtype: `unicode` cmlenz@21: cmlenz@21: :note: If the pattern contains time fields, an `AttributeError` will be cmlenz@21: raised when trying to apply the formatting. This is also true if cmlenz@21: the value of ``date`` parameter is actually a ``datetime`` object, cmlenz@21: as this function automatically converts that to a ``date``. cmlenz@3: """ cmlenz@35: if date is None: cmlenz@35: date = date_.today() cmlenz@35: elif isinstance(date, datetime): cmlenz@20: date = date.date() cmlenz@36: cmlenz@3: locale = Locale.parse(locale) cmlenz@3: if format in ('full', 'long', 'medium', 'short'): cmlenz@3: format = get_date_format(format, locale=locale) cmlenz@3: pattern = parse_pattern(format) cmlenz@3: return parse_pattern(format).apply(date, locale) cmlenz@3: cmlenz@36: def format_datetime(datetime=None, format='medium', tzinfo=None, cmlenz@36: locale=LC_TIME): cmlenz@103: """Return a date formatted according to the given pattern. cmlenz@3: cmlenz@35: >>> dt = datetime(2007, 04, 01, 15, 30) cmlenz@35: >>> format_datetime(dt, locale='en_US') cmlenz@35: u'Apr 1, 2007 3:30:00 PM' cmlenz@35: cmlenz@36: For any pattern requiring the display of the time-zone, the third-party cmlenz@36: ``pytz`` package is needed to explicitly specify the time-zone: cmlenz@36: cmlenz@36: >>> from pytz import timezone cmlenz@36: >>> format_datetime(dt, 'full', tzinfo=timezone('Europe/Berlin'), cmlenz@36: ... locale='de_DE') cmlenz@36: u'Sonntag, 1. April 2007 17:30 Uhr MESZ' cmlenz@36: >>> format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz", cmlenz@36: ... tzinfo=timezone('US/Eastern'), locale='en') cmlenz@36: u'2007.04.01 AD at 11:30:00 EDT' cmlenz@36: cmlenz@35: :param datetime: the `datetime` object; if `None`, the current date and cmlenz@35: time is used cmlenz@18: :param format: one of "full", "long", "medium", or "short", or a custom cmlenz@18: date/time pattern cmlenz@31: :param tzinfo: the timezone to apply to the time for display cmlenz@21: :param locale: a `Locale` object or a locale identifier cmlenz@3: :rtype: `unicode` cmlenz@3: """ cmlenz@35: if datetime is None: cmlenz@35: datetime = datetime_.now() cmlenz@36: elif isinstance(datetime, (int, long)): cmlenz@36: datetime = datetime.fromtimestamp(datetime) cmlenz@36: elif isinstance(datetime, time): cmlenz@36: datetime = datetime_.combine(date.today(), datetime) cmlenz@36: if datetime.tzinfo is None: cmlenz@36: datetime = datetime.replace(tzinfo=UTC) cmlenz@36: if tzinfo is not None: cmlenz@36: datetime = datetime.astimezone(tzinfo) cmlenz@103: if hasattr(tzinfo, 'normalize'): # pytz cmlenz@36: datetime = tzinfo.normalize(datetime) cmlenz@36: cmlenz@20: locale = Locale.parse(locale) cmlenz@20: if format in ('full', 'long', 'medium', 'short'): cmlenz@35: return get_datetime_format(format, locale=locale) \ cmlenz@36: .replace('{0}', format_time(datetime, format, tzinfo=None, cmlenz@35: locale=locale)) \ cmlenz@35: .replace('{1}', format_date(datetime, format, locale=locale)) cmlenz@35: else: cmlenz@35: return parse_pattern(format).apply(datetime, locale) cmlenz@3: cmlenz@36: def format_time(time=None, format='medium', tzinfo=None, locale=LC_TIME): cmlenz@103: """Return a time formatted according to the given pattern. cmlenz@3: cmlenz@3: >>> t = time(15, 30) cmlenz@3: >>> format_time(t, locale='en_US') cmlenz@3: u'3:30:00 PM' cmlenz@3: >>> format_time(t, format='short', locale='de_DE') cmlenz@3: u'15:30' cmlenz@3: cmlenz@18: If you don't want to use the locale default formats, you can specify a cmlenz@18: custom time pattern: cmlenz@18: cmlenz@18: >>> format_time(t, "hh 'o''clock' a", locale='en') cmlenz@18: u"03 o'clock PM" cmlenz@18: cmlenz@31: For any pattern requiring the display of the time-zone, the third-party cmlenz@31: ``pytz`` package is needed to explicitly specify the time-zone: cmlenz@31: cmlenz@103: >>> from pytz import timezone cmlenz@103: >>> t = time(15, 30) cmlenz@36: >>> format_time(t, format='full', tzinfo=timezone('Europe/Berlin'), cmlenz@36: ... locale='de_DE') cmlenz@36: u'17:30 Uhr MESZ' cmlenz@36: >>> format_time(t, "hh 'o''clock' a, zzzz", tzinfo=timezone('US/Eastern'), cmlenz@36: ... locale='en') cmlenz@36: u"11 o'clock AM, Eastern Daylight Time" cmlenz@31: cmlenz@35: :param time: the ``time`` or ``datetime`` object; if `None`, the current cmlenz@35: time is used cmlenz@18: :param format: one of "full", "long", "medium", or "short", or a custom cmlenz@18: date/time pattern cmlenz@31: :param tzinfo: the time-zone to apply to the time for display cmlenz@21: :param locale: a `Locale` object or a locale identifier cmlenz@3: :rtype: `unicode` cmlenz@21: cmlenz@21: :note: If the pattern contains date fields, an `AttributeError` will be cmlenz@21: raised when trying to apply the formatting. This is also true if cmlenz@21: the value of ``time`` parameter is actually a ``datetime`` object, cmlenz@21: as this function automatically converts that to a ``time``. cmlenz@3: """ cmlenz@35: if time is None: cmlenz@35: time = datetime.now().time() cmlenz@35: elif isinstance(time, (int, long)): cmlenz@20: time = datetime.fromtimestamp(time).time() cmlenz@20: elif isinstance(time, datetime): cmlenz@36: time = time.timetz() cmlenz@31: if time.tzinfo is None: cmlenz@36: time = time.replace(tzinfo=UTC) cmlenz@36: if tzinfo is not None: cmlenz@36: dt = datetime.combine(date.today(), time).astimezone(tzinfo) cmlenz@103: if hasattr(tzinfo, 'normalize'): # pytz cmlenz@36: dt = tzinfo.normalize(dt) cmlenz@36: time = dt.timetz() cmlenz@36: cmlenz@3: locale = Locale.parse(locale) cmlenz@3: if format in ('full', 'long', 'medium', 'short'): cmlenz@3: format = get_time_format(format, locale=locale) cmlenz@3: return parse_pattern(format).apply(time, locale) cmlenz@3: cmlenz@3: def parse_date(string, locale=LC_TIME): cmlenz@40: """Parse a date from a string. cmlenz@40: cmlenz@40: This function uses the date format for the locale as a hint to determine cmlenz@40: the order in which the date fields appear in the string. cmlenz@40: cmlenz@40: >>> parse_date('4/1/04', locale='en_US') cmlenz@40: datetime.date(2004, 4, 1) cmlenz@40: >>> parse_date('01.04.2004', locale='de_DE') cmlenz@40: datetime.date(2004, 4, 1) cmlenz@40: cmlenz@40: :param string: the string containing the date cmlenz@40: :param locale: a `Locale` object or a locale identifier cmlenz@40: :return: the parsed date cmlenz@40: :rtype: `date` cmlenz@40: """ cmlenz@40: # TODO: try ISO format first? cmlenz@40: format = get_date_format(locale=locale).pattern.lower() cmlenz@40: year_idx = format.index('y') cmlenz@40: month_idx = format.index('m') cmlenz@40: if month_idx < 0: cmlenz@40: month_idx = format.index('l') cmlenz@40: day_idx = format.index('d') cmlenz@40: cmlenz@40: indexes = [(year_idx, 'Y'), (month_idx, 'M'), (day_idx, 'D')] cmlenz@40: indexes.sort() cmlenz@40: indexes = dict([(item[1], idx) for idx, item in enumerate(indexes)]) cmlenz@40: cmlenz@40: # FIXME: this currently only supports numbers, but should also support month cmlenz@40: # names, both in the requested locale, and english cmlenz@40: cmlenz@40: numbers = re.findall('(\d+)', string) cmlenz@40: year = numbers[indexes['Y']] cmlenz@40: if len(year) == 2: cmlenz@40: year = 2000 + int(year) cmlenz@40: else: cmlenz@40: year = int(year) cmlenz@40: month = int(numbers[indexes['M']]) cmlenz@40: day = int(numbers[indexes['D']]) cmlenz@40: if month > 12: cmlenz@40: month, day = day, month cmlenz@40: return date(year, month, day) cmlenz@3: cmlenz@3: def parse_datetime(string, locale=LC_TIME): cmlenz@48: """Parse a date and time from a string. cmlenz@48: cmlenz@48: This function uses the date and time formats for the locale as a hint to cmlenz@48: determine the order in which the time fields appear in the string. cmlenz@48: cmlenz@48: :param string: the string containing the date and time cmlenz@48: :param locale: a `Locale` object or a locale identifier cmlenz@48: :return: the parsed date/time cmlenz@48: :rtype: `datetime` cmlenz@48: """ cmlenz@3: raise NotImplementedError cmlenz@3: cmlenz@3: def parse_time(string, locale=LC_TIME): cmlenz@48: """Parse a time from a string. cmlenz@40: cmlenz@40: This function uses the time format for the locale as a hint to determine cmlenz@40: the order in which the time fields appear in the string. cmlenz@40: cmlenz@40: >>> parse_time('15:30:00', locale='en_US') cmlenz@40: datetime.time(15, 30) cmlenz@40: cmlenz@40: :param string: the string containing the time cmlenz@40: :param locale: a `Locale` object or a locale identifier cmlenz@40: :return: the parsed time cmlenz@40: :rtype: `time` cmlenz@40: """ cmlenz@40: # TODO: try ISO format first? cmlenz@40: format = get_time_format(locale=locale).pattern.lower() cmlenz@40: hour_idx = format.index('h') cmlenz@40: if hour_idx < 0: cmlenz@40: hour_idx = format.index('k') cmlenz@40: min_idx = format.index('m') cmlenz@40: sec_idx = format.index('s') cmlenz@40: cmlenz@40: indexes = [(hour_idx, 'H'), (min_idx, 'M'), (sec_idx, 'S')] cmlenz@40: indexes.sort() cmlenz@40: indexes = dict([(item[1], idx) for idx, item in enumerate(indexes)]) cmlenz@40: cmlenz@40: # FIXME: support 12 hour clock, and 0-based hour specification cmlenz@48: # and seconds should be optional, maybe minutes too cmlenz@48: # oh, and time-zones, of course cmlenz@40: cmlenz@40: numbers = re.findall('(\d+)', string) cmlenz@40: hour = int(numbers[indexes['H']]) cmlenz@40: minute = int(numbers[indexes['M']]) cmlenz@40: second = int(numbers[indexes['S']]) cmlenz@40: return time(hour, minute, second) cmlenz@3: cmlenz@3: cmlenz@14: class DateTimePattern(object): cmlenz@3: cmlenz@3: def __init__(self, pattern, format): cmlenz@3: self.pattern = pattern cmlenz@3: self.format = format cmlenz@3: cmlenz@3: def __repr__(self): cmlenz@3: return '<%s %r>' % (type(self).__name__, self.pattern) cmlenz@3: cmlenz@3: def __unicode__(self): cmlenz@3: return self.pattern cmlenz@3: cmlenz@3: def __mod__(self, other): cmlenz@3: assert type(other) is DateTimeFormat cmlenz@3: return self.format % other cmlenz@3: cmlenz@3: def apply(self, datetime, locale): cmlenz@3: return self % DateTimeFormat(datetime, locale) cmlenz@3: cmlenz@3: cmlenz@3: class DateTimeFormat(object): cmlenz@3: cmlenz@3: def __init__(self, value, locale): cmlenz@3: assert isinstance(value, (date, datetime, time)) cmlenz@31: if isinstance(value, (datetime, time)) and value.tzinfo is None: cmlenz@31: value = value.replace(tzinfo=UTC) cmlenz@3: self.value = value cmlenz@3: self.locale = Locale.parse(locale) cmlenz@3: cmlenz@3: def __getitem__(self, name): cmlenz@17: char = name[0] cmlenz@17: num = len(name) cmlenz@17: if char == 'G': cmlenz@17: return self.format_era(char, num) cmlenz@217: elif char in ('y', 'Y', 'u'): cmlenz@17: return self.format_year(char, num) cmlenz@17: elif char in ('Q', 'q'): cmlenz@17: return self.format_quarter(char, num) cmlenz@17: elif char in ('M', 'L'): cmlenz@17: return self.format_month(char, num) cmlenz@217: elif char in ('w', 'W'): cmlenz@217: return self.format_week(char, num) cmlenz@17: elif char == 'd': cmlenz@17: return self.format(self.value.day, num) cmlenz@17: elif char in ('E', 'e', 'c'): cmlenz@17: return self.format_weekday(char, num) cmlenz@17: elif char == 'a': cmlenz@17: return self.format_period(char) cmlenz@17: elif char == 'h': cmlenz@17: return self.format(self.value.hour % 12, num) cmlenz@17: elif char == 'H': cmlenz@17: return self.format(self.value.hour, num) cmlenz@20: elif char == 'K': cmlenz@20: return self.format(self.value.hour % 12 - 1, num) cmlenz@20: elif char == 'k': cmlenz@20: return self.format(self.value.hour + 1, num) cmlenz@17: elif char == 'm': cmlenz@17: return self.format(self.value.minute, num) cmlenz@17: elif char == 's': cmlenz@17: return self.format(self.value.second, num) cmlenz@31: elif char in ('z', 'Z', 'v'): cmlenz@31: return self.format_timezone(char, num) cmlenz@3: else: cmlenz@17: raise KeyError('Unsupported date/time field %r' % char) cmlenz@3: cmlenz@17: def format_era(self, char, num): cmlenz@3: width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)] cmlenz@3: era = int(self.value.year >= 0) cmlenz@3: return get_era_names(width, self.locale)[era] cmlenz@3: cmlenz@17: def format_year(self, char, num): cmlenz@17: if char.islower(): cmlenz@17: value = self.value.year cmlenz@17: else: cmlenz@17: value = self.value.isocalendar()[0] cmlenz@3: year = self.format(value, num) cmlenz@3: if num == 2: cmlenz@3: year = year[-2:] cmlenz@3: return year cmlenz@3: cmlenz@17: def format_month(self, char, num): cmlenz@3: if num <= 2: cmlenz@3: return ('%%0%dd' % num) % self.value.month cmlenz@3: width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] cmlenz@17: context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num] cmlenz@3: return get_month_names(width, context, self.locale)[self.value.month] cmlenz@3: cmlenz@217: def format_week(self, char, num): cmlenz@217: # FIXME: this should really be based on the first_week_day and cmlenz@217: # min_week_days locale data cmlenz@217: if char.islower(): cmlenz@217: return self.value.strftime('%W') cmlenz@217: else: cmlenz@217: return '%d' % ((self.value.day + 6 - self.value.weekday()) / 7 + 1) cmlenz@217: cmlenz@17: def format_weekday(self, char, num): cmlenz@17: if num < 3: cmlenz@17: if char.islower(): cmlenz@17: value = 7 - self.locale.first_week_day + self.value.weekday() cmlenz@17: return self.format(value % 7 + 1, num) cmlenz@17: num = 3 cmlenz@17: weekday = self.value.weekday() cmlenz@17: width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] cmlenz@17: context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num] cmlenz@3: return get_day_names(width, context, self.locale)[weekday] cmlenz@3: cmlenz@17: def format_period(self, char): cmlenz@3: period = {0: 'am', 1: 'pm'}[int(self.value.hour > 12)] cmlenz@3: return get_period_names(locale=self.locale)[period] cmlenz@3: cmlenz@31: def format_timezone(self, char, num): cmlenz@131: if char in ('z', 'v'): cmlenz@36: if hasattr(self.value.tzinfo, 'zone'): cmlenz@36: zone = self.value.tzinfo.zone cmlenz@31: else: cmlenz@36: zone = self.value.tzinfo.tzname(self.value) cmlenz@131: cmlenz@36: # Get the canonical time-zone code cmlenz@36: zone = self.locale.zone_aliases.get(zone, zone) cmlenz@36: cmlenz@36: # Try explicitly translated zone names first cmlenz@36: display = self.locale.time_zones.get(zone) cmlenz@36: if display: cmlenz@36: if 'long' in display: cmlenz@36: width = {3: 'short', 4: 'long'}[max(3, num)] cmlenz@131: if char == 'v': cmlenz@131: dst = 'generic' cmlenz@131: else: cmlenz@131: dst = self.value.dst() and 'daylight' or 'standard' cmlenz@36: return display[width][dst] cmlenz@36: elif 'city' in display: cmlenz@36: return display['city'] cmlenz@36: cmlenz@36: else: cmlenz@36: return zone.split('/', 1)[1] cmlenz@31: cmlenz@31: elif char == 'Z': cmlenz@31: offset = self.value.utcoffset() cmlenz@36: seconds = offset.days * 24 * 60 * 60 + offset.seconds cmlenz@36: hours, seconds = divmod(seconds, 3600) cmlenz@36: pattern = {3: '%+03d%02d', 4: 'GMT %+03d:%02d'}[max(3, num)] cmlenz@36: return pattern % (hours, seconds // 60) cmlenz@31: cmlenz@3: def format(self, value, length): cmlenz@3: return ('%%0%dd' % length) % value cmlenz@3: cmlenz@3: cmlenz@3: PATTERN_CHARS = { cmlenz@17: 'G': [1, 2, 3, 4, 5], # era cmlenz@17: 'y': None, 'Y': None, 'u': None, # year cmlenz@17: 'Q': [1, 2, 3, 4], 'q': [1, 2, 3, 4], # quarter cmlenz@17: 'M': [1, 2, 3, 4, 5], 'L': [1, 2, 3, 4, 5], # month cmlenz@17: 'w': [1, 2], 'W': [1], # week cmlenz@17: 'd': [1, 2], 'D': [1, 2, 3], 'F': [1], 'g': None, # day cmlenz@17: 'E': [1, 2, 3, 4, 5], 'e': [1, 2, 3, 4, 5], 'c': [1, 3, 4, 5], # week day cmlenz@17: 'a': [1], # period cmlenz@17: 'h': [1, 2], 'H': [1, 2], 'K': [1, 2], 'k': [1, 2], # hour cmlenz@17: 'm': [1, 2], # minute cmlenz@17: 's': [1, 2], 'S': None, 'A': None, # second cmlenz@17: 'z': [1, 2, 3, 4], 'Z': [1, 2, 3, 4], 'v': [1, 4] # zone cmlenz@3: } cmlenz@3: cmlenz@3: def parse_pattern(pattern): cmlenz@3: """Parse date, time, and datetime format patterns. cmlenz@3: cmlenz@3: >>> parse_pattern("MMMMd").format cmlenz@3: u'%(MMMM)s%(d)s' cmlenz@3: >>> parse_pattern("MMM d, yyyy").format cmlenz@3: u'%(MMM)s %(d)s, %(yyyy)s' cmlenz@18: cmlenz@18: Pattern can contain literal strings in single quotes: cmlenz@18: cmlenz@3: >>> parse_pattern("H:mm' Uhr 'z").format cmlenz@3: u'%(H)s:%(mm)s Uhr %(z)s' cmlenz@3: cmlenz@18: An actual single quote can be used by using two adjacent single quote cmlenz@18: characters: cmlenz@18: cmlenz@18: >>> parse_pattern("hh' o''clock'").format cmlenz@18: u"%(hh)s o'clock" cmlenz@18: cmlenz@3: :param pattern: the formatting pattern to parse cmlenz@3: """ cmlenz@14: if type(pattern) is DateTimePattern: cmlenz@3: return pattern cmlenz@3: cmlenz@3: result = [] cmlenz@3: quotebuf = None cmlenz@3: charbuf = [] cmlenz@3: fieldchar = [''] cmlenz@3: fieldnum = [0] cmlenz@3: cmlenz@3: def append_chars(): cmlenz@3: result.append(''.join(charbuf).replace('%', '%%')) cmlenz@3: del charbuf[:] cmlenz@3: cmlenz@3: def append_field(): cmlenz@3: limit = PATTERN_CHARS[fieldchar[0]] cmlenz@17: if limit and fieldnum[0] not in limit: cmlenz@3: raise ValueError('Invalid length for field: %r' cmlenz@3: % (fieldchar[0] * fieldnum[0])) cmlenz@3: result.append('%%(%s)s' % (fieldchar[0] * fieldnum[0])) cmlenz@3: fieldchar[0] = '' cmlenz@3: fieldnum[0] = 0 cmlenz@3: cmlenz@18: for idx, char in enumerate(pattern.replace("''", '\0')): cmlenz@3: if quotebuf is None: cmlenz@3: if char == "'": # quote started cmlenz@3: if fieldchar[0]: cmlenz@3: append_field() cmlenz@3: elif charbuf: cmlenz@3: append_chars() cmlenz@3: quotebuf = [] cmlenz@3: elif char in PATTERN_CHARS: cmlenz@3: if charbuf: cmlenz@3: append_chars() cmlenz@3: if char == fieldchar[0]: cmlenz@3: fieldnum[0] += 1 cmlenz@3: else: cmlenz@3: if fieldchar[0]: cmlenz@3: append_field() cmlenz@3: fieldchar[0] = char cmlenz@3: fieldnum[0] = 1 cmlenz@3: else: cmlenz@3: if fieldchar[0]: cmlenz@3: append_field() cmlenz@3: charbuf.append(char) cmlenz@3: cmlenz@3: elif quotebuf is not None: cmlenz@18: if char == "'": # end of quote cmlenz@3: charbuf.extend(quotebuf) cmlenz@3: quotebuf = None cmlenz@3: else: # inside quote cmlenz@3: quotebuf.append(char) cmlenz@3: cmlenz@3: if fieldchar[0]: cmlenz@3: append_field() cmlenz@3: elif charbuf: cmlenz@3: append_chars() cmlenz@3: cmlenz@18: return DateTimePattern(pattern, u''.join(result).replace('\0', "'"))