root/galaxy-central/eggs/Babel-0.9.4-py2.6.egg/babel/dates.py

リビジョン 3, 36.2 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2007 Edgewall Software
4# All rights reserved.
5#
6# This software is licensed as described in the file COPYING, which
7# you should have received as part of this distribution. The terms
8# are also available at http://babel.edgewall.org/wiki/License.
9#
10# This software consists of voluntary contributions made by many
11# individuals. For the exact contribution history, see the revision
12# history and logs, available at http://babel.edgewall.org/log/.
13
14"""Locale dependent formatting and parsing of dates and times.
15
16The default locale for the functions in this module is determined by the
17following environment variables, in that order:
18
19 * ``LC_TIME``,
20 * ``LC_ALL``, and
21 * ``LANG``
22"""
23
24from datetime import date, datetime, time, timedelta, tzinfo
25import re
26
27from babel.core import default_locale, get_global, Locale
28from babel.util import UTC
29
30__all__ = ['format_date', 'format_datetime', 'format_time',
31           'get_timezone_name', 'parse_date', 'parse_datetime', 'parse_time']
32__docformat__ = 'restructuredtext en'
33
34LC_TIME = default_locale('LC_TIME')
35
36# Aliases for use in scopes where the modules are shadowed by local variables
37date_ = date
38datetime_ = datetime
39time_ = time
40
41def get_period_names(locale=LC_TIME):
42    """Return the names for day periods (AM/PM) used by the locale.
43   
44    >>> get_period_names(locale='en_US')['am']
45    u'AM'
46   
47    :param locale: the `Locale` object, or a locale string
48    :return: the dictionary of period names
49    :rtype: `dict`
50    """
51    return Locale.parse(locale).periods
52
53def get_day_names(width='wide', context='format', locale=LC_TIME):
54    """Return the day names used by the locale for the specified format.
55   
56    >>> get_day_names('wide', locale='en_US')[1]
57    u'Tuesday'
58    >>> get_day_names('abbreviated', locale='es')[1]
59    u'mar'
60    >>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1]
61    u'D'
62   
63    :param width: the width to use, one of "wide", "abbreviated", or "narrow"
64    :param context: the context, either "format" or "stand-alone"
65    :param locale: the `Locale` object, or a locale string
66    :return: the dictionary of day names
67    :rtype: `dict`
68    """
69    return Locale.parse(locale).days[context][width]
70
71def get_month_names(width='wide', context='format', locale=LC_TIME):
72    """Return the month names used by the locale for the specified format.
73   
74    >>> get_month_names('wide', locale='en_US')[1]
75    u'January'
76    >>> get_month_names('abbreviated', locale='es')[1]
77    u'ene'
78    >>> get_month_names('narrow', context='stand-alone', locale='de_DE')[1]
79    u'J'
80   
81    :param width: the width to use, one of "wide", "abbreviated", or "narrow"
82    :param context: the context, either "format" or "stand-alone"
83    :param locale: the `Locale` object, or a locale string
84    :return: the dictionary of month names
85    :rtype: `dict`
86    """
87    return Locale.parse(locale).months[context][width]
88
89def get_quarter_names(width='wide', context='format', locale=LC_TIME):
90    """Return the quarter names used by the locale for the specified format.
91   
92    >>> get_quarter_names('wide', locale='en_US')[1]
93    u'1st quarter'
94    >>> get_quarter_names('abbreviated', locale='de_DE')[1]
95    u'Q1'
96   
97    :param width: the width to use, one of "wide", "abbreviated", or "narrow"
98    :param context: the context, either "format" or "stand-alone"
99    :param locale: the `Locale` object, or a locale string
100    :return: the dictionary of quarter names
101    :rtype: `dict`
102    """
103    return Locale.parse(locale).quarters[context][width]
104
105def get_era_names(width='wide', locale=LC_TIME):
106    """Return the era names used by the locale for the specified format.
107   
108    >>> get_era_names('wide', locale='en_US')[1]
109    u'Anno Domini'
110    >>> get_era_names('abbreviated', locale='de_DE')[1]
111    u'n. Chr.'
112   
113    :param width: the width to use, either "wide", "abbreviated", or "narrow"
114    :param locale: the `Locale` object, or a locale string
115    :return: the dictionary of era names
116    :rtype: `dict`
117    """
118    return Locale.parse(locale).eras[width]
119
120def get_date_format(format='medium', locale=LC_TIME):
121    """Return the date formatting patterns used by the locale for the specified
122    format.
123   
124    >>> get_date_format(locale='en_US')
125    <DateTimePattern u'MMM d, yyyy'>
126    >>> get_date_format('full', locale='de_DE')
127    <DateTimePattern u'EEEE, d. MMMM yyyy'>
128   
129    :param format: the format to use, one of "full", "long", "medium", or
130                   "short"
131    :param locale: the `Locale` object, or a locale string
132    :return: the date format pattern
133    :rtype: `DateTimePattern`
134    """
135    return Locale.parse(locale).date_formats[format]
136
137def get_datetime_format(format='medium', locale=LC_TIME):
138    """Return the datetime formatting patterns used by the locale for the
139    specified format.
140   
141    >>> get_datetime_format(locale='en_US')
142    u'{1} {0}'
143   
144    :param format: the format to use, one of "full", "long", "medium", or
145                   "short"
146    :param locale: the `Locale` object, or a locale string
147    :return: the datetime format pattern
148    :rtype: `unicode`
149    """
150    patterns = Locale.parse(locale).datetime_formats
151    if format not in patterns:
152        format = None
153    return patterns[format]
154
155def get_time_format(format='medium', locale=LC_TIME):
156    """Return the time formatting patterns used by the locale for the specified
157    format.
158   
159    >>> get_time_format(locale='en_US')
160    <DateTimePattern u'h:mm:ss a'>
161    >>> get_time_format('full', locale='de_DE')
162    <DateTimePattern u'HH:mm:ss v'>
163   
164    :param format: the format to use, one of "full", "long", "medium", or
165                   "short"
166    :param locale: the `Locale` object, or a locale string
167    :return: the time format pattern
168    :rtype: `DateTimePattern`
169    """
170    return Locale.parse(locale).time_formats[format]
171
172def get_timezone_gmt(datetime=None, width='long', locale=LC_TIME):
173    """Return the timezone associated with the given `datetime` object formatted
174    as string indicating the offset from GMT.
175   
176    >>> dt = datetime(2007, 4, 1, 15, 30)
177    >>> get_timezone_gmt(dt, locale='en')
178    u'GMT+00:00'
179   
180    >>> from pytz import timezone
181    >>> tz = timezone('America/Los_Angeles')
182    >>> dt = datetime(2007, 4, 1, 15, 30, tzinfo=tz)
183    >>> get_timezone_gmt(dt, locale='en')
184    u'GMT-08:00'
185    >>> get_timezone_gmt(dt, 'short', locale='en')
186    u'-0800'
187   
188    The long format depends on the locale, for example in France the acronym
189    UTC string is used instead of GMT:
190   
191    >>> get_timezone_gmt(dt, 'long', locale='fr_FR')
192    u'UTC-08:00'
193   
194    :param datetime: the ``datetime`` object; if `None`, the current date and
195                     time in UTC is used
196    :param width: either "long" or "short"
197    :param locale: the `Locale` object, or a locale string
198    :return: the GMT offset representation of the timezone
199    :rtype: `unicode`
200    :since: version 0.9
201    """
202    if datetime is None:
203        datetime = datetime_.utcnow()
204    elif isinstance(datetime, (int, long)):
205        datetime = datetime_.utcfromtimestamp(datetime).time()
206    if datetime.tzinfo is None:
207        datetime = datetime.replace(tzinfo=UTC)
208    locale = Locale.parse(locale)
209
210    offset = datetime.utcoffset()
211    seconds = offset.days * 24 * 60 * 60 + offset.seconds
212    hours, seconds = divmod(seconds, 3600)
213    if width == 'short':
214        pattern = u'%+03d%02d'
215    else:
216        pattern = locale.zone_formats['gmt'] % '%+03d:%02d'
217    return pattern % (hours, seconds // 60)
218
219def get_timezone_location(dt_or_tzinfo=None, locale=LC_TIME):
220    """Return a representation of the given timezone using "location format".
221   
222    The result depends on both the local display name of the country and the
223    city assocaited with the time zone:
224   
225    >>> from pytz import timezone
226    >>> tz = timezone('America/St_Johns')
227    >>> get_timezone_location(tz, locale='de_DE')
228    u"Kanada (St. John's)"
229    >>> tz = timezone('America/Mexico_City')
230    >>> get_timezone_location(tz, locale='de_DE')
231    u'Mexiko (Mexiko-Stadt)'
232   
233    If the timezone is associated with a country that uses only a single
234    timezone, just the localized country name is returned:
235   
236    >>> tz = timezone('Europe/Berlin')
237    >>> get_timezone_name(tz, locale='de_DE')
238    u'Deutschland'
239   
240    :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines
241                         the timezone; if `None`, the current date and time in
242                         UTC is assumed
243    :param locale: the `Locale` object, or a locale string
244    :return: the localized timezone name using location format
245    :rtype: `unicode`
246    :since: version 0.9
247    """
248    if dt_or_tzinfo is None or isinstance(dt_or_tzinfo, (int, long)):
249        dt = None
250        tzinfo = UTC
251    elif isinstance(dt_or_tzinfo, (datetime, time)):
252        dt = dt_or_tzinfo
253        if dt.tzinfo is not None:
254            tzinfo = dt.tzinfo
255        else:
256            tzinfo = UTC
257    else:
258        dt = None
259        tzinfo = dt_or_tzinfo
260    locale = Locale.parse(locale)
261
262    if hasattr(tzinfo, 'zone'):
263        zone = tzinfo.zone
264    else:
265        zone = tzinfo.tzname(dt or datetime.utcnow())
266
267    # Get the canonical time-zone code
268    zone = get_global('zone_aliases').get(zone, zone)
269
270    info = locale.time_zones.get(zone, {})
271
272    # Otherwise, if there is only one timezone for the country, return the
273    # localized country name
274    region_format = locale.zone_formats['region']
275    territory = get_global('zone_territories').get(zone)
276    if territory not in locale.territories:
277        territory = 'ZZ' # invalid/unknown
278    territory_name = locale.territories[territory]
279    if territory and len(get_global('territory_zones').get(territory, [])) == 1:
280        return region_format % (territory_name)
281
282    # Otherwise, include the city in the output
283    fallback_format = locale.zone_formats['fallback']
284    if 'city' in info:
285        city_name = info['city']
286    else:
287        metazone = get_global('meta_zones').get(zone)
288        metazone_info = locale.meta_zones.get(metazone, {})
289        if 'city' in metazone_info:
290            city_name = metainfo['city']
291        elif '/' in zone:
292            city_name = zone.split('/', 1)[1].replace('_', ' ')
293        else:
294            city_name = zone.replace('_', ' ')
295
296    return region_format % (fallback_format % {
297        '0': city_name,
298        '1': territory_name
299    })
300
301def get_timezone_name(dt_or_tzinfo=None, width='long', uncommon=False,
302                      locale=LC_TIME):
303    r"""Return the localized display name for the given timezone. The timezone
304    may be specified using a ``datetime`` or `tzinfo` object.
305   
306    >>> from pytz import timezone
307    >>> dt = time(15, 30, tzinfo=timezone('America/Los_Angeles'))
308    >>> get_timezone_name(dt, locale='en_US')
309    u'Pacific Standard Time'
310    >>> get_timezone_name(dt, width='short', locale='en_US')
311    u'PST'
312   
313    If this function gets passed only a `tzinfo` object and no concrete
314    `datetime`,  the returned display name is indenpendent of daylight savings
315    time. This can be used for example for selecting timezones, or to set the
316    time of events that recur across DST changes:
317   
318    >>> tz = timezone('America/Los_Angeles')
319    >>> get_timezone_name(tz, locale='en_US')
320    u'Pacific Time'
321    >>> get_timezone_name(tz, 'short', locale='en_US')
322    u'PT'
323   
324    If no localized display name for the timezone is available, and the timezone
325    is associated with a country that uses only a single timezone, the name of
326    that country is returned, formatted according to the locale:
327   
328    >>> tz = timezone('Europe/Berlin')
329    >>> get_timezone_name(tz, locale='de_DE')
330    u'Deutschland'
331    >>> get_timezone_name(tz, locale='pt_BR')
332    u'Hor\xe1rio Alemanha'
333   
334    On the other hand, if the country uses multiple timezones, the city is also
335    included in the representation:
336   
337    >>> tz = timezone('America/St_Johns')
338    >>> get_timezone_name(tz, locale='de_DE')
339    u"Kanada (St. John's)"
340   
341    The `uncommon` parameter can be set to `True` to enable the use of timezone
342    representations that are not commonly used by the requested locale. For
343    example, while in frensh the central europian timezone is usually
344    abbreviated as "HEC", in Canadian French, this abbreviation is not in
345    common use, so a generic name would be chosen by default:
346   
347    >>> tz = timezone('Europe/Paris')
348    >>> get_timezone_name(tz, 'short', locale='fr_CA')
349    u'France'
350    >>> get_timezone_name(tz, 'short', uncommon=True, locale='fr_CA')
351    u'HEC'
352   
353    :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines
354                         the timezone; if a ``tzinfo`` object is used, the
355                         resulting display name will be generic, i.e.
356                         independent of daylight savings time; if `None`, the
357                         current date in UTC is assumed
358    :param width: either "long" or "short"
359    :param uncommon: whether even uncommon timezone abbreviations should be used
360    :param locale: the `Locale` object, or a locale string
361    :return: the timezone display name
362    :rtype: `unicode`
363    :since: version 0.9
364    :see:  `LDML Appendix J: Time Zone Display Names
365            <http://www.unicode.org/reports/tr35/#Time_Zone_Fallback>`_
366    """
367    if dt_or_tzinfo is None or isinstance(dt_or_tzinfo, (int, long)):
368        dt = None
369        tzinfo = UTC
370    elif isinstance(dt_or_tzinfo, (datetime, time)):
371        dt = dt_or_tzinfo
372        if dt.tzinfo is not None:
373            tzinfo = dt.tzinfo
374        else:
375            tzinfo = UTC
376    else:
377        dt = None
378        tzinfo = dt_or_tzinfo
379    locale = Locale.parse(locale)
380
381    if hasattr(tzinfo, 'zone'):
382        zone = tzinfo.zone
383    else:
384        zone = tzinfo.tzname(dt)
385
386    # Get the canonical time-zone code
387    zone = get_global('zone_aliases').get(zone, zone)
388
389    info = locale.time_zones.get(zone, {})
390    # Try explicitly translated zone names first
391    if width in info:
392        if dt is None:
393            field = 'generic'
394        else:
395            dst = tzinfo.dst(dt)
396            if dst is None:
397                field = 'generic'
398            elif dst == 0:
399                field = 'standard'
400            else:
401                field = 'daylight'
402        if field in info[width]:
403            return info[width][field]
404
405    metazone = get_global('meta_zones').get(zone)
406    if metazone:
407        metazone_info = locale.meta_zones.get(metazone, {})
408        if width in metazone_info and (uncommon or metazone_info.get('common')):
409            if dt is None:
410                field = 'generic'
411            else:
412                field = tzinfo.dst(dt) and 'daylight' or 'standard'
413            if field in metazone_info[width]:
414                return metazone_info[width][field]
415
416    # If we have a concrete datetime, we assume that the result can't be
417    # independent of daylight savings time, so we return the GMT offset
418    if dt is not None:
419        return get_timezone_gmt(dt, width=width, locale=locale)
420
421    return get_timezone_location(dt_or_tzinfo, locale=locale)
422
423def format_date(date=None, format='medium', locale=LC_TIME):
424    """Return a date formatted according to the given pattern.
425   
426    >>> d = date(2007, 04, 01)
427    >>> format_date(d, locale='en_US')
428    u'Apr 1, 2007'
429    >>> format_date(d, format='full', locale='de_DE')
430    u'Sonntag, 1. April 2007'
431   
432    If you don't want to use the locale default formats, you can specify a
433    custom date pattern:
434   
435    >>> format_date(d, "EEE, MMM d, ''yy", locale='en')
436    u"Sun, Apr 1, '07"
437   
438    :param date: the ``date`` or ``datetime`` object; if `None`, the current
439                 date is used
440    :param format: one of "full", "long", "medium", or "short", or a custom
441                   date/time pattern
442    :param locale: a `Locale` object or a locale identifier
443    :rtype: `unicode`
444   
445    :note: If the pattern contains time fields, an `AttributeError` will be
446           raised when trying to apply the formatting. This is also true if
447           the value of ``date`` parameter is actually a ``datetime`` object,
448           as this function automatically converts that to a ``date``.
449    """
450    if date is None:
451        date = date_.today()
452    elif isinstance(date, datetime):
453        date = date.date()
454
455    locale = Locale.parse(locale)
456    if format in ('full', 'long', 'medium', 'short'):
457        format = get_date_format(format, locale=locale)
458    pattern = parse_pattern(format)
459    return parse_pattern(format).apply(date, locale)
460
461def format_datetime(datetime=None, format='medium', tzinfo=None,
462                    locale=LC_TIME):
463    """Return a date formatted according to the given pattern.
464   
465    >>> dt = datetime(2007, 04, 01, 15, 30)
466    >>> format_datetime(dt, locale='en_US')
467    u'Apr 1, 2007 3:30:00 PM'
468   
469    For any pattern requiring the display of the time-zone, the third-party
470    ``pytz`` package is needed to explicitly specify the time-zone:
471   
472    >>> from pytz import timezone
473    >>> format_datetime(dt, 'full', tzinfo=timezone('Europe/Paris'),
474    ...                 locale='fr_FR')
475    u'dimanche 1 avril 2007 17:30:00 HEC'
476    >>> format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz",
477    ...                 tzinfo=timezone('US/Eastern'), locale='en')
478    u'2007.04.01 AD at 11:30:00 EDT'
479   
480    :param datetime: the `datetime` object; if `None`, the current date and
481                     time is used
482    :param format: one of "full", "long", "medium", or "short", or a custom
483                   date/time pattern
484    :param tzinfo: the timezone to apply to the time for display
485    :param locale: a `Locale` object or a locale identifier
486    :rtype: `unicode`
487    """
488    if datetime is None:
489        datetime = datetime_.utcnow()
490    elif isinstance(datetime, (int, long)):
491        datetime = datetime_.utcfromtimestamp(datetime)
492    elif isinstance(datetime, time):
493        datetime = datetime_.combine(date.today(), datetime)
494    if datetime.tzinfo is None:
495        datetime = datetime.replace(tzinfo=UTC)
496    if tzinfo is not None:
497        datetime = datetime.astimezone(tzinfo)
498        if hasattr(tzinfo, 'normalize'): # pytz
499            datetime = tzinfo.normalize(datetime)
500
501    locale = Locale.parse(locale)
502    if format in ('full', 'long', 'medium', 'short'):
503        return get_datetime_format(format, locale=locale) \
504            .replace('{0}', format_time(datetime, format, tzinfo=None,
505                                        locale=locale)) \
506            .replace('{1}', format_date(datetime, format, locale=locale))
507    else:
508        return parse_pattern(format).apply(datetime, locale)
509
510def format_time(time=None, format='medium', tzinfo=None, locale=LC_TIME):
511    """Return a time formatted according to the given pattern.
512   
513    >>> t = time(15, 30)
514    >>> format_time(t, locale='en_US')
515    u'3:30:00 PM'
516    >>> format_time(t, format='short', locale='de_DE')
517    u'15:30'
518   
519    If you don't want to use the locale default formats, you can specify a
520    custom time pattern:
521   
522    >>> format_time(t, "hh 'o''clock' a", locale='en')
523    u"03 o'clock PM"
524   
525    For any pattern requiring the display of the time-zone, the third-party
526    ``pytz`` package is needed to explicitly specify the time-zone:
527   
528    >>> from pytz import timezone
529    >>> t = datetime(2007, 4, 1, 15, 30)
530    >>> tzinfo = timezone('Europe/Paris')
531    >>> t = tzinfo.localize(t)
532    >>> format_time(t, format='full', tzinfo=tzinfo, locale='fr_FR')
533    u'15:30:00 HEC'
534    >>> format_time(t, "hh 'o''clock' a, zzzz", tzinfo=timezone('US/Eastern'),
535    ...             locale='en')
536    u"09 o'clock AM, Eastern Daylight Time"
537   
538    As that example shows, when this function gets passed a
539    ``datetime.datetime`` value, the actual time in the formatted string is
540    adjusted to the timezone specified by the `tzinfo` parameter. If the
541    ``datetime`` is "naive" (i.e. it has no associated timezone information),
542    it is assumed to be in UTC.
543   
544    These timezone calculations are **not** performed if the value is of type
545    ``datetime.time``, as without date information there's no way to determine
546    what a given time would translate to in a different timezone without
547    information about whether daylight savings time is in effect or not. This
548    means that time values are left as-is, and the value of the `tzinfo`
549    parameter is only used to display the timezone name if needed:
550   
551    >>> t = time(15, 30)
552    >>> format_time(t, format='full', tzinfo=timezone('Europe/Paris'),
553    ...             locale='fr_FR')
554    u'15:30:00 HEC'
555    >>> format_time(t, format='full', tzinfo=timezone('US/Eastern'),
556    ...             locale='en_US')
557    u'3:30:00 PM ET'
558   
559    :param time: the ``time`` or ``datetime`` object; if `None`, the current
560                 time in UTC is used
561    :param format: one of "full", "long", "medium", or "short", or a custom
562                   date/time pattern
563    :param tzinfo: the time-zone to apply to the time for display
564    :param locale: a `Locale` object or a locale identifier
565    :rtype: `unicode`
566   
567    :note: If the pattern contains date fields, an `AttributeError` will be
568           raised when trying to apply the formatting. This is also true if
569           the value of ``time`` parameter is actually a ``datetime`` object,
570           as this function automatically converts that to a ``time``.
571    """
572    if time is None:
573        time = datetime.utcnow()
574    elif isinstance(time, (int, long)):
575        time = datetime.utcfromtimestamp(time)
576    if time.tzinfo is None:
577        time = time.replace(tzinfo=UTC)
578    if isinstance(time, datetime):
579        if tzinfo is not None:
580            time = time.astimezone(tzinfo)
581            if hasattr(tzinfo, 'localize'): # pytz
582                time = tzinfo.normalize(time)
583        time = time.timetz()
584    elif tzinfo is not None:
585        time = time.replace(tzinfo=tzinfo)
586
587    locale = Locale.parse(locale)
588    if format in ('full', 'long', 'medium', 'short'):
589        format = get_time_format(format, locale=locale)
590    return parse_pattern(format).apply(time, locale)
591
592def parse_date(string, locale=LC_TIME):
593    """Parse a date from a string.
594   
595    This function uses the date format for the locale as a hint to determine
596    the order in which the date fields appear in the string.
597   
598    >>> parse_date('4/1/04', locale='en_US')
599    datetime.date(2004, 4, 1)
600    >>> parse_date('01.04.2004', locale='de_DE')
601    datetime.date(2004, 4, 1)
602   
603    :param string: the string containing the date
604    :param locale: a `Locale` object or a locale identifier
605    :return: the parsed date
606    :rtype: `date`
607    """
608    # TODO: try ISO format first?
609    format = get_date_format(locale=locale).pattern.lower()
610    year_idx = format.index('y')
611    month_idx = format.index('m')
612    if month_idx < 0:
613        month_idx = format.index('l')
614    day_idx = format.index('d')
615
616    indexes = [(year_idx, 'Y'), (month_idx, 'M'), (day_idx, 'D')]
617    indexes.sort()
618    indexes = dict([(item[1], idx) for idx, item in enumerate(indexes)])
619
620    # FIXME: this currently only supports numbers, but should also support month
621    #        names, both in the requested locale, and english
622
623    numbers = re.findall('(\d+)', string)
624    year = numbers[indexes['Y']]
625    if len(year) == 2:
626        year = 2000 + int(year)
627    else:
628        year = int(year)
629    month = int(numbers[indexes['M']])
630    day = int(numbers[indexes['D']])
631    if month > 12:
632        month, day = day, month
633    return date(year, month, day)
634
635def parse_datetime(string, locale=LC_TIME):
636    """Parse a date and time from a string.
637   
638    This function uses the date and time formats for the locale as a hint to
639    determine the order in which the time fields appear in the string.
640   
641    :param string: the string containing the date and time
642    :param locale: a `Locale` object or a locale identifier
643    :return: the parsed date/time
644    :rtype: `datetime`
645    """
646    raise NotImplementedError
647
648def parse_time(string, locale=LC_TIME):
649    """Parse a time from a string.
650   
651    This function uses the time format for the locale as a hint to determine
652    the order in which the time fields appear in the string.
653   
654    >>> parse_time('15:30:00', locale='en_US')
655    datetime.time(15, 30)
656   
657    :param string: the string containing the time
658    :param locale: a `Locale` object or a locale identifier
659    :return: the parsed time
660    :rtype: `time`
661    """
662    # TODO: try ISO format first?
663    format = get_time_format(locale=locale).pattern.lower()
664    hour_idx = format.index('h')
665    if hour_idx < 0:
666        hour_idx = format.index('k')
667    min_idx = format.index('m')
668    sec_idx = format.index('s')
669
670    indexes = [(hour_idx, 'H'), (min_idx, 'M'), (sec_idx, 'S')]
671    indexes.sort()
672    indexes = dict([(item[1], idx) for idx, item in enumerate(indexes)])
673
674    # FIXME: support 12 hour clock, and 0-based hour specification
675    #        and seconds should be optional, maybe minutes too
676    #        oh, and time-zones, of course
677
678    numbers = re.findall('(\d+)', string)
679    hour = int(numbers[indexes['H']])
680    minute = int(numbers[indexes['M']])
681    second = int(numbers[indexes['S']])
682    return time(hour, minute, second)
683
684
685class DateTimePattern(object):
686
687    def __init__(self, pattern, format):
688        self.pattern = pattern
689        self.format = format
690
691    def __repr__(self):
692        return '<%s %r>' % (type(self).__name__, self.pattern)
693
694    def __unicode__(self):
695        return self.pattern
696
697    def __mod__(self, other):
698        assert type(other) is DateTimeFormat
699        return self.format % other
700
701    def apply(self, datetime, locale):
702        return self % DateTimeFormat(datetime, locale)
703
704
705class DateTimeFormat(object):
706
707    def __init__(self, value, locale):
708        assert isinstance(value, (date, datetime, time))
709        if isinstance(value, (datetime, time)) and value.tzinfo is None:
710            value = value.replace(tzinfo=UTC)
711        self.value = value
712        self.locale = Locale.parse(locale)
713
714    def __getitem__(self, name):
715        char = name[0]
716        num = len(name)
717        if char == 'G':
718            return self.format_era(char, num)
719        elif char in ('y', 'Y', 'u'):
720            return self.format_year(char, num)
721        elif char in ('Q', 'q'):
722            return self.format_quarter(char, num)
723        elif char in ('M', 'L'):
724            return self.format_month(char, num)
725        elif char in ('w', 'W'):
726            return self.format_week(char, num)
727        elif char == 'd':
728            return self.format(self.value.day, num)
729        elif char == 'D':
730            return self.format_day_of_year(num)
731        elif char == 'F':
732            return self.format_day_of_week_in_month()
733        elif char in ('E', 'e', 'c'):
734            return self.format_weekday(char, num)
735        elif char == 'a':
736            return self.format_period(char)
737        elif char == 'h':
738            if self.value.hour % 12 == 0:
739                return self.format(12, num)
740            else:
741                return self.format(self.value.hour % 12, num)
742        elif char == 'H':
743            return self.format(self.value.hour, num)
744        elif char == 'K':
745            return self.format(self.value.hour % 12, num)
746        elif char == 'k':
747            if self.value.hour == 0:
748                return self.format(24, num)
749            else:
750                return self.format(self.value.hour, num)
751        elif char == 'm':
752            return self.format(self.value.minute, num)
753        elif char == 's':
754            return self.format(self.value.second, num)
755        elif char == 'S':
756            return self.format_frac_seconds(num)
757        elif char == 'A':
758            return self.format_milliseconds_in_day(num)
759        elif char in ('z', 'Z', 'v', 'V'):
760            return self.format_timezone(char, num)
761        else:
762            raise KeyError('Unsupported date/time field %r' % char)
763
764    def format_era(self, char, num):
765        width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)]
766        era = int(self.value.year >= 0)
767        return get_era_names(width, self.locale)[era]
768
769    def format_year(self, char, num):
770        value = self.value.year
771        if char.isupper():
772            week = self.get_week_number(self.get_day_of_year())
773            if week == 0:
774                value -= 1
775        year = self.format(value, num)
776        if num == 2:
777            year = year[-2:]
778        return year
779
780    def format_quarter(self, char, num):
781        quarter = (self.value.month - 1) // 3 + 1
782        if num <= 2:
783            return ('%%0%dd' % num) % quarter
784        width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num]
785        context = {'Q': 'format', 'q': 'stand-alone'}[char]
786        return get_quarter_names(width, context, self.locale)[quarter]
787
788    def format_month(self, char, num):
789        if num <= 2:
790            return ('%%0%dd' % num) % self.value.month
791        width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num]
792        context = {'M': 'format', 'L': 'stand-alone'}[char]
793        return get_month_names(width, context, self.locale)[self.value.month]
794
795    def format_week(self, char, num):
796        if char.islower(): # week of year
797            day_of_year = self.get_day_of_year()
798            week = self.get_week_number(day_of_year)
799            if week == 0:
800                date = self.value - timedelta(days=day_of_year)
801                week = self.get_week_number(self.get_day_of_year(date),
802                                            date.weekday())
803            return self.format(week, num)
804        else: # week of month
805            week = self.get_week_number(self.value.day)
806            if week == 0:
807                date = self.value - timedelta(days=self.value.day)
808                week = self.get_week_number(date.day, date.weekday())
809                pass
810            return '%d' % week
811
812    def format_weekday(self, char, num):
813        if num < 3:
814            if char.islower():
815                value = 7 - self.locale.first_week_day + self.value.weekday()
816                return self.format(value % 7 + 1, num)
817            num = 3
818        weekday = self.value.weekday()
819        width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num]
820        context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num]
821        return get_day_names(width, context, self.locale)[weekday]
822
823    def format_day_of_year(self, num):
824        return self.format(self.get_day_of_year(), num)
825
826    def format_day_of_week_in_month(self):
827        return '%d' % ((self.value.day - 1) / 7 + 1)
828
829    def format_period(self, char):
830        period = {0: 'am', 1: 'pm'}[int(self.value.hour >= 12)]
831        return get_period_names(locale=self.locale)[period]
832
833    def format_frac_seconds(self, num):
834        value = str(self.value.microsecond)
835        return self.format(round(float('.%s' % value), num) * 10**num, num)
836
837    def format_milliseconds_in_day(self, num):
838        msecs = self.value.microsecond // 1000 + self.value.second * 1000 + \
839                self.value.minute * 60000 + self.value.hour * 3600000
840        return self.format(msecs, num)
841
842    def format_timezone(self, char, num):
843        width = {3: 'short', 4: 'long'}[max(3, num)]
844        if char == 'z':
845            return get_timezone_name(self.value, width, locale=self.locale)
846        elif char == 'Z':
847            return get_timezone_gmt(self.value, width, locale=self.locale)
848        elif char == 'v':
849            return get_timezone_name(self.value.tzinfo, width,
850                                     locale=self.locale)
851        elif char == 'V':
852            if num == 1:
853                return get_timezone_name(self.value.tzinfo, width,
854                                         uncommon=True, locale=self.locale)
855            return get_timezone_location(self.value.tzinfo, locale=self.locale)
856
857    def format(self, value, length):
858        return ('%%0%dd' % length) % value
859
860    def get_day_of_year(self, date=None):
861        if date is None:
862            date = self.value
863        return (date - date_(date.year, 1, 1)).days + 1
864
865    def get_week_number(self, day_of_period, day_of_week=None):
866        """Return the number of the week of a day within a period. This may be
867        the week number in a year or the week number in a month.
868       
869        Usually this will return a value equal to or greater than 1, but if the
870        first week of the period is so short that it actually counts as the last
871        week of the previous period, this function will return 0.
872       
873        >>> format = DateTimeFormat(date(2006, 1, 8), Locale.parse('de_DE'))
874        >>> format.get_week_number(6)
875        1
876       
877        >>> format = DateTimeFormat(date(2006, 1, 8), Locale.parse('en_US'))
878        >>> format.get_week_number(6)
879        2
880       
881        :param day_of_period: the number of the day in the period (usually
882                              either the day of month or the day of year)
883        :param day_of_week: the week day; if ommitted, the week day of the
884                            current date is assumed
885        """
886        if day_of_week is None:
887            day_of_week = self.value.weekday()
888        first_day = (day_of_week - self.locale.first_week_day -
889                     day_of_period + 1) % 7
890        if first_day < 0:
891            first_day += 7
892        week_number = (day_of_period + first_day - 1) / 7
893        if 7 - first_day >= self.locale.min_week_days:
894            week_number += 1
895        return week_number
896
897
898PATTERN_CHARS = {
899    'G': [1, 2, 3, 4, 5],                                           # era
900    'y': None, 'Y': None, 'u': None,                                # year
901    'Q': [1, 2, 3, 4], 'q': [1, 2, 3, 4],                           # quarter
902    'M': [1, 2, 3, 4, 5], 'L': [1, 2, 3, 4, 5],                     # month
903    'w': [1, 2], 'W': [1],                                          # week
904    'd': [1, 2], 'D': [1, 2, 3], 'F': [1], 'g': None,               # day
905    'E': [1, 2, 3, 4, 5], 'e': [1, 2, 3, 4, 5], 'c': [1, 3, 4, 5],  # week day
906    'a': [1],                                                       # period
907    'h': [1, 2], 'H': [1, 2], 'K': [1, 2], 'k': [1, 2],             # hour
908    'm': [1, 2],                                                    # minute
909    's': [1, 2], 'S': None, 'A': None,                              # second
910    'z': [1, 2, 3, 4], 'Z': [1, 2, 3, 4], 'v': [1, 4], 'V': [1, 4]  # zone
911}
912
913def parse_pattern(pattern):
914    """Parse date, time, and datetime format patterns.
915   
916    >>> parse_pattern("MMMMd").format
917    u'%(MMMM)s%(d)s'
918    >>> parse_pattern("MMM d, yyyy").format
919    u'%(MMM)s %(d)s, %(yyyy)s'
920   
921    Pattern can contain literal strings in single quotes:
922   
923    >>> parse_pattern("H:mm' Uhr 'z").format
924    u'%(H)s:%(mm)s Uhr %(z)s'
925   
926    An actual single quote can be used by using two adjacent single quote
927    characters:
928   
929    >>> parse_pattern("hh' o''clock'").format
930    u"%(hh)s o'clock"
931   
932    :param pattern: the formatting pattern to parse
933    """
934    if type(pattern) is DateTimePattern:
935        return pattern
936
937    result = []
938    quotebuf = None
939    charbuf = []
940    fieldchar = ['']
941    fieldnum = [0]
942
943    def append_chars():
944        result.append(''.join(charbuf).replace('%', '%%'))
945        del charbuf[:]
946
947    def append_field():
948        limit = PATTERN_CHARS[fieldchar[0]]
949        if limit and fieldnum[0] not in limit:
950            raise ValueError('Invalid length for field: %r'
951                             % (fieldchar[0] * fieldnum[0]))
952        result.append('%%(%s)s' % (fieldchar[0] * fieldnum[0]))
953        fieldchar[0] = ''
954        fieldnum[0] = 0
955
956    for idx, char in enumerate(pattern.replace("''", '\0')):
957        if quotebuf is None:
958            if char == "'": # quote started
959                if fieldchar[0]:
960                    append_field()
961                elif charbuf:
962                    append_chars()
963                quotebuf = []
964            elif char in PATTERN_CHARS:
965                if charbuf:
966                    append_chars()
967                if char == fieldchar[0]:
968                    fieldnum[0] += 1
969                else:
970                    if fieldchar[0]:
971                        append_field()
972                    fieldchar[0] = char
973                    fieldnum[0] = 1
974            else:
975                if fieldchar[0]:
976                    append_field()
977                charbuf.append(char)
978
979        elif quotebuf is not None:
980            if char == "'": # end of quote
981                charbuf.extend(quotebuf)
982                quotebuf = None
983            else: # inside quote
984                quotebuf.append(char)
985
986    if fieldchar[0]:
987        append_field()
988    elif charbuf:
989        append_chars()
990
991    return DateTimePattern(pattern, u''.join(result).replace('\0', "'"))
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。