Mercurial > babel > mirror
annotate babel/dates.py @ 34:464fbcefedde trunk
Extended time-zone support.
author | cmlenz |
---|---|
date | Mon, 04 Jun 2007 16:03:12 +0000 |
parents | 75a64f5a176e |
children | 7ae4722af473 |
rev | line source |
---|---|
1 | 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 | |
16 The default locale for the functions in this module is determined by the | |
17 following environment variables, in that order: | |
18 | |
19 * ``LC_TIME``, | |
20 * ``LC_ALL``, and | |
21 * ``LANG`` | |
22 """ | |
23 | |
29 | 24 from datetime import date, datetime, time, timedelta, tzinfo |
1 | 25 |
26 from babel.core import Locale | |
29 | 27 from babel.util import default_locale, UTC |
1 | 28 |
29 __all__ = ['format_date', 'format_datetime', 'format_time', 'parse_date', | |
30 'parse_datetime', 'parse_time'] | |
31 __docformat__ = 'restructuredtext en' | |
32 | |
33 LC_TIME = default_locale('LC_TIME') | |
34 | |
33 | 35 # Aliases for use in scopes where the modules are shadowed by local variables |
36 date_ = date | |
37 datetime_ = datetime | |
38 time_ = time | |
39 | |
1 | 40 def get_period_names(locale=LC_TIME): |
41 """Return the names for day periods (AM/PM) used by the locale. | |
42 | |
43 >>> get_period_names(locale='en_US')['am'] | |
44 u'AM' | |
45 | |
46 :param locale: the `Locale` object, or a locale string | |
47 :return: the dictionary of period names | |
48 :rtype: `dict` | |
49 """ | |
50 return Locale.parse(locale).periods | |
51 | |
52 def get_day_names(width='wide', context='format', locale=LC_TIME): | |
53 """Return the day names used by the locale for the specified format. | |
54 | |
55 >>> get_day_names('wide', locale='en_US')[1] | |
15 | 56 u'Tuesday' |
1 | 57 >>> get_day_names('abbreviated', locale='es')[1] |
15 | 58 u'mar' |
1 | 59 >>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1] |
15 | 60 u'D' |
1 | 61 |
62 :param width: the width to use, one of "wide", "abbreviated", or "narrow" | |
63 :param context: the context, either "format" or "stand-alone" | |
64 :param locale: the `Locale` object, or a locale string | |
65 :return: the dictionary of day names | |
66 :rtype: `dict` | |
67 """ | |
68 return Locale.parse(locale).days[context][width] | |
69 | |
70 def get_month_names(width='wide', context='format', locale=LC_TIME): | |
71 """Return the month names used by the locale for the specified format. | |
72 | |
73 >>> get_month_names('wide', locale='en_US')[1] | |
74 u'January' | |
75 >>> get_month_names('abbreviated', locale='es')[1] | |
76 u'ene' | |
77 >>> get_month_names('narrow', context='stand-alone', locale='de_DE')[1] | |
78 u'J' | |
79 | |
80 :param width: the width to use, one of "wide", "abbreviated", or "narrow" | |
81 :param context: the context, either "format" or "stand-alone" | |
82 :param locale: the `Locale` object, or a locale string | |
83 :return: the dictionary of month names | |
84 :rtype: `dict` | |
85 """ | |
86 return Locale.parse(locale).months[context][width] | |
87 | |
88 def get_quarter_names(width='wide', context='format', locale=LC_TIME): | |
89 """Return the quarter names used by the locale for the specified format. | |
90 | |
91 >>> get_quarter_names('wide', locale='en_US')[1] | |
92 u'1st quarter' | |
93 >>> get_quarter_names('abbreviated', locale='de_DE')[1] | |
94 u'Q1' | |
95 | |
96 :param width: the width to use, one of "wide", "abbreviated", or "narrow" | |
97 :param context: the context, either "format" or "stand-alone" | |
98 :param locale: the `Locale` object, or a locale string | |
99 :return: the dictionary of quarter names | |
100 :rtype: `dict` | |
101 """ | |
102 return Locale.parse(locale).quarters[context][width] | |
103 | |
104 def get_era_names(width='wide', locale=LC_TIME): | |
105 """Return the era names used by the locale for the specified format. | |
106 | |
107 >>> get_era_names('wide', locale='en_US')[1] | |
108 u'Anno Domini' | |
109 >>> get_era_names('abbreviated', locale='de_DE')[1] | |
110 u'n. Chr.' | |
111 | |
112 :param width: the width to use, either "wide" or "abbreviated" | |
113 :param locale: the `Locale` object, or a locale string | |
114 :return: the dictionary of era names | |
115 :rtype: `dict` | |
116 """ | |
117 return Locale.parse(locale).eras[width] | |
118 | |
119 def get_date_format(format='medium', locale=LC_TIME): | |
120 """Return the date formatting patterns used by the locale for the specified | |
121 format. | |
122 | |
123 >>> get_date_format(locale='en_US') | |
12
e6ba3e878b10
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
124 <DateTimePattern u'MMM d, yyyy'> |
1 | 125 >>> get_date_format('full', locale='de_DE') |
12
e6ba3e878b10
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
126 <DateTimePattern u'EEEE, d. MMMM yyyy'> |
1 | 127 |
128 :param format: the format to use, one of "full", "long", "medium", or | |
129 "short" | |
130 :param locale: the `Locale` object, or a locale string | |
131 :return: the date format pattern | |
33 | 132 :rtype: `DateTimePattern` |
1 | 133 """ |
134 return Locale.parse(locale).date_formats[format] | |
135 | |
33 | 136 def get_datetime_format(format='medium', locale=LC_TIME): |
137 """Return the datetime formatting patterns used by the locale for the | |
138 specified format. | |
139 | |
140 >>> get_datetime_format(locale='en_US') | |
141 u'{1} {0}' | |
142 | |
143 :param format: the format to use, one of "full", "long", "medium", or | |
144 "short" | |
145 :param locale: the `Locale` object, or a locale string | |
146 :return: the datetime format pattern | |
147 :rtype: `unicode` | |
148 """ | |
149 patterns = Locale.parse(locale).datetime_formats | |
150 if format not in patterns: | |
151 format = None | |
152 return patterns[format] | |
153 | |
1 | 154 def get_time_format(format='medium', locale=LC_TIME): |
155 """Return the time formatting patterns used by the locale for the specified | |
156 format. | |
157 | |
158 >>> get_time_format(locale='en_US') | |
12
e6ba3e878b10
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
159 <DateTimePattern u'h:mm:ss a'> |
1 | 160 >>> get_time_format('full', locale='de_DE') |
12
e6ba3e878b10
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
161 <DateTimePattern u"H:mm' Uhr 'z"> |
1 | 162 |
163 :param format: the format to use, one of "full", "long", "medium", or | |
164 "short" | |
165 :param locale: the `Locale` object, or a locale string | |
166 :return: the time format pattern | |
33 | 167 :rtype: `DateTimePattern` |
1 | 168 """ |
169 return Locale.parse(locale).time_formats[format] | |
170 | |
33 | 171 def format_date(date=None, format='medium', locale=LC_TIME): |
1 | 172 """Returns a date formatted according to the given pattern. |
173 | |
174 >>> d = date(2007, 04, 01) | |
175 >>> format_date(d, locale='en_US') | |
176 u'Apr 1, 2007' | |
177 >>> format_date(d, format='full', locale='de_DE') | |
178 u'Sonntag, 1. April 2007' | |
179 | |
16 | 180 If you don't want to use the locale default formats, you can specify a |
181 custom date pattern: | |
182 | |
29 | 183 >>> format_date(d, "EEE, MMM d, ''yy", locale='en') |
16 | 184 u"Sun, Apr 1, '07" |
185 | |
33 | 186 :param date: the ``date`` or ``datetime`` object; if `None`, the current |
187 date is used | |
16 | 188 :param format: one of "full", "long", "medium", or "short", or a custom |
189 date/time pattern | |
19
d8352fbaca65
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
190 :param locale: a `Locale` object or a locale identifier |
1 | 191 :rtype: `unicode` |
19
d8352fbaca65
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
192 |
d8352fbaca65
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
193 :note: If the pattern contains time fields, an `AttributeError` will be |
d8352fbaca65
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
194 raised when trying to apply the formatting. This is also true if |
d8352fbaca65
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
195 the value of ``date`` parameter is actually a ``datetime`` object, |
d8352fbaca65
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
196 as this function automatically converts that to a ``date``. |
1 | 197 """ |
33 | 198 if date is None: |
199 date = date_.today() | |
200 elif isinstance(date, datetime): | |
18
990909fdf98b
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
201 date = date.date() |
34 | 202 |
1 | 203 locale = Locale.parse(locale) |
204 if format in ('full', 'long', 'medium', 'short'): | |
205 format = get_date_format(format, locale=locale) | |
206 pattern = parse_pattern(format) | |
207 return parse_pattern(format).apply(date, locale) | |
208 | |
34 | 209 def format_datetime(datetime=None, format='medium', tzinfo=None, |
210 locale=LC_TIME): | |
1 | 211 """Returns a date formatted according to the given pattern. |
212 | |
33 | 213 >>> dt = datetime(2007, 04, 01, 15, 30) |
214 >>> format_datetime(dt, locale='en_US') | |
215 u'Apr 1, 2007 3:30:00 PM' | |
216 | |
34 | 217 For any pattern requiring the display of the time-zone, the third-party |
218 ``pytz`` package is needed to explicitly specify the time-zone: | |
219 | |
220 >>> from pytz import timezone | |
221 >>> format_datetime(dt, 'full', tzinfo=timezone('Europe/Berlin'), | |
222 ... locale='de_DE') | |
223 u'Sonntag, 1. April 2007 17:30 Uhr MESZ' | |
224 >>> format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz", | |
225 ... tzinfo=timezone('US/Eastern'), locale='en') | |
226 u'2007.04.01 AD at 11:30:00 EDT' | |
227 | |
33 | 228 :param datetime: the `datetime` object; if `None`, the current date and |
229 time is used | |
16 | 230 :param format: one of "full", "long", "medium", or "short", or a custom |
231 date/time pattern | |
29 | 232 :param tzinfo: the timezone to apply to the time for display |
19
d8352fbaca65
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
233 :param locale: a `Locale` object or a locale identifier |
1 | 234 :rtype: `unicode` |
235 """ | |
33 | 236 if datetime is None: |
237 datetime = datetime_.now() | |
34 | 238 elif isinstance(datetime, (int, long)): |
239 datetime = datetime.fromtimestamp(datetime) | |
240 elif isinstance(datetime, time): | |
241 datetime = datetime_.combine(date.today(), datetime) | |
242 if datetime.tzinfo is None: | |
243 datetime = datetime.replace(tzinfo=UTC) | |
244 if tzinfo is not None: | |
245 datetime = datetime.astimezone(tzinfo) | |
246 if hasattr(tzinfo, 'normalize'): | |
247 datetime = tzinfo.normalize(datetime) | |
248 | |
18
990909fdf98b
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
249 locale = Locale.parse(locale) |
990909fdf98b
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
250 if format in ('full', 'long', 'medium', 'short'): |
33 | 251 return get_datetime_format(format, locale=locale) \ |
34 | 252 .replace('{0}', format_time(datetime, format, tzinfo=None, |
33 | 253 locale=locale)) \ |
254 .replace('{1}', format_date(datetime, format, locale=locale)) | |
255 else: | |
256 return parse_pattern(format).apply(datetime, locale) | |
1 | 257 |
34 | 258 def format_time(time=None, format='medium', tzinfo=None, locale=LC_TIME): |
1 | 259 """Returns a time formatted according to the given pattern. |
260 | |
261 >>> t = time(15, 30) | |
262 >>> format_time(t, locale='en_US') | |
263 u'3:30:00 PM' | |
264 >>> format_time(t, format='short', locale='de_DE') | |
265 u'15:30' | |
266 | |
16 | 267 If you don't want to use the locale default formats, you can specify a |
268 custom time pattern: | |
269 | |
270 >>> format_time(t, "hh 'o''clock' a", locale='en') | |
271 u"03 o'clock PM" | |
272 | |
29 | 273 For any pattern requiring the display of the time-zone, the third-party |
274 ``pytz`` package is needed to explicitly specify the time-zone: | |
275 | |
34 | 276 >>> from pytz import timezone, utc |
277 >>> t = time(15, 30, tzinfo=utc) | |
278 >>> format_time(t, format='full', tzinfo=timezone('Europe/Berlin'), | |
279 ... locale='de_DE') | |
280 u'17:30 Uhr MESZ' | |
281 >>> format_time(t, "hh 'o''clock' a, zzzz", tzinfo=timezone('US/Eastern'), | |
282 ... locale='en') | |
283 u"11 o'clock AM, Eastern Daylight Time" | |
29 | 284 |
33 | 285 :param time: the ``time`` or ``datetime`` object; if `None`, the current |
286 time is used | |
16 | 287 :param format: one of "full", "long", "medium", or "short", or a custom |
288 date/time pattern | |
29 | 289 :param tzinfo: the time-zone to apply to the time for display |
19
d8352fbaca65
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
290 :param locale: a `Locale` object or a locale identifier |
1 | 291 :rtype: `unicode` |
19
d8352fbaca65
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
292 |
d8352fbaca65
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
293 :note: If the pattern contains date fields, an `AttributeError` will be |
d8352fbaca65
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
294 raised when trying to apply the formatting. This is also true if |
d8352fbaca65
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
295 the value of ``time`` parameter is actually a ``datetime`` object, |
d8352fbaca65
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
296 as this function automatically converts that to a ``time``. |
1 | 297 """ |
33 | 298 if time is None: |
299 time = datetime.now().time() | |
300 elif isinstance(time, (int, long)): | |
18
990909fdf98b
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
301 time = datetime.fromtimestamp(time).time() |
990909fdf98b
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
302 elif isinstance(time, datetime): |
34 | 303 time = time.timetz() |
29 | 304 if time.tzinfo is None: |
34 | 305 time = time.replace(tzinfo=UTC) |
306 if tzinfo is not None: | |
307 dt = datetime.combine(date.today(), time).astimezone(tzinfo) | |
308 if hasattr(tzinfo, 'normalize'): | |
309 dt = tzinfo.normalize(dt) | |
310 time = dt.timetz() | |
311 | |
1 | 312 locale = Locale.parse(locale) |
313 if format in ('full', 'long', 'medium', 'short'): | |
314 format = get_time_format(format, locale=locale) | |
315 return parse_pattern(format).apply(time, locale) | |
316 | |
317 def parse_date(string, locale=LC_TIME): | |
318 raise NotImplementedError | |
319 | |
320 def parse_datetime(string, locale=LC_TIME): | |
321 raise NotImplementedError | |
322 | |
323 def parse_time(string, locale=LC_TIME): | |
324 raise NotImplementedError | |
325 | |
326 | |
12
e6ba3e878b10
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
327 class DateTimePattern(object): |
1 | 328 |
329 def __init__(self, pattern, format): | |
330 self.pattern = pattern | |
331 self.format = format | |
332 | |
333 def __repr__(self): | |
334 return '<%s %r>' % (type(self).__name__, self.pattern) | |
335 | |
336 def __unicode__(self): | |
337 return self.pattern | |
338 | |
339 def __mod__(self, other): | |
340 assert type(other) is DateTimeFormat | |
341 return self.format % other | |
342 | |
343 def apply(self, datetime, locale): | |
344 return self % DateTimeFormat(datetime, locale) | |
345 | |
346 | |
347 class DateTimeFormat(object): | |
348 | |
349 def __init__(self, value, locale): | |
350 assert isinstance(value, (date, datetime, time)) | |
29 | 351 if isinstance(value, (datetime, time)) and value.tzinfo is None: |
352 value = value.replace(tzinfo=UTC) | |
1 | 353 self.value = value |
354 self.locale = Locale.parse(locale) | |
355 | |
356 def __getitem__(self, name): | |
357 # TODO: a number of fields missing here | |
15 | 358 char = name[0] |
359 num = len(name) | |
360 if char == 'G': | |
361 return self.format_era(char, num) | |
362 elif char in ('y', 'Y'): | |
363 return self.format_year(char, num) | |
364 elif char in ('Q', 'q'): | |
365 return self.format_quarter(char, num) | |
366 elif char in ('M', 'L'): | |
367 return self.format_month(char, num) | |
368 elif char == 'd': | |
369 return self.format(self.value.day, num) | |
370 elif char in ('E', 'e', 'c'): | |
371 return self.format_weekday(char, num) | |
372 elif char == 'a': | |
373 return self.format_period(char) | |
374 elif char == 'h': | |
375 return self.format(self.value.hour % 12, num) | |
376 elif char == 'H': | |
377 return self.format(self.value.hour, num) | |
18
990909fdf98b
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
378 elif char == 'K': |
990909fdf98b
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
379 return self.format(self.value.hour % 12 - 1, num) |
990909fdf98b
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
380 elif char == 'k': |
990909fdf98b
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
381 return self.format(self.value.hour + 1, num) |
15 | 382 elif char == 'm': |
383 return self.format(self.value.minute, num) | |
384 elif char == 's': | |
385 return self.format(self.value.second, num) | |
29 | 386 elif char in ('z', 'Z', 'v'): |
387 return self.format_timezone(char, num) | |
1 | 388 else: |
15 | 389 raise KeyError('Unsupported date/time field %r' % char) |
1 | 390 |
15 | 391 def format_era(self, char, num): |
1 | 392 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)] |
393 era = int(self.value.year >= 0) | |
394 return get_era_names(width, self.locale)[era] | |
395 | |
15 | 396 def format_year(self, char, num): |
397 if char.islower(): | |
398 value = self.value.year | |
399 else: | |
400 value = self.value.isocalendar()[0] | |
1 | 401 year = self.format(value, num) |
402 if num == 2: | |
403 year = year[-2:] | |
404 return year | |
405 | |
15 | 406 def format_month(self, char, num): |
1 | 407 if num <= 2: |
408 return ('%%0%dd' % num) % self.value.month | |
409 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] | |
15 | 410 context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num] |
1 | 411 return get_month_names(width, context, self.locale)[self.value.month] |
412 | |
15 | 413 def format_weekday(self, char, num): |
414 if num < 3: | |
415 if char.islower(): | |
416 value = 7 - self.locale.first_week_day + self.value.weekday() | |
417 return self.format(value % 7 + 1, num) | |
418 num = 3 | |
419 weekday = self.value.weekday() | |
420 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] | |
421 context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num] | |
1 | 422 return get_day_names(width, context, self.locale)[weekday] |
423 | |
15 | 424 def format_period(self, char): |
1 | 425 period = {0: 'am', 1: 'pm'}[int(self.value.hour > 12)] |
426 return get_period_names(locale=self.locale)[period] | |
427 | |
29 | 428 def format_timezone(self, char, num): |
429 if char == 'z': | |
34 | 430 if hasattr(self.value.tzinfo, 'zone'): |
431 zone = self.value.tzinfo.zone | |
29 | 432 else: |
34 | 433 zone = self.value.tzinfo.tzname(self.value) |
434 | |
435 # Get the canonical time-zone code | |
436 zone = self.locale.zone_aliases.get(zone, zone) | |
437 | |
438 # Try explicitly translated zone names first | |
439 display = self.locale.time_zones.get(zone) | |
440 if display: | |
441 if 'long' in display: | |
442 width = {3: 'short', 4: 'long'}[max(3, num)] | |
443 dst = self.value.dst() and 'daylight' or 'standard' | |
444 return display[width][dst] | |
445 elif 'city' in display: | |
446 return display['city'] | |
447 | |
448 else: | |
449 return zone.split('/', 1)[1] | |
29 | 450 |
451 elif char == 'Z': | |
452 offset = self.value.utcoffset() | |
34 | 453 seconds = offset.days * 24 * 60 * 60 + offset.seconds |
454 hours, seconds = divmod(seconds, 3600) | |
455 pattern = {3: '%+03d%02d', 4: 'GMT %+03d:%02d'}[max(3, num)] | |
456 return pattern % (hours, seconds // 60) | |
29 | 457 |
458 elif char == 'v': | |
459 raise NotImplementedError | |
460 | |
1 | 461 def format(self, value, length): |
462 return ('%%0%dd' % length) % value | |
463 | |
464 | |
465 PATTERN_CHARS = { | |
15 | 466 'G': [1, 2, 3, 4, 5], # era |
467 'y': None, 'Y': None, 'u': None, # year | |
468 'Q': [1, 2, 3, 4], 'q': [1, 2, 3, 4], # quarter | |
469 'M': [1, 2, 3, 4, 5], 'L': [1, 2, 3, 4, 5], # month | |
470 'w': [1, 2], 'W': [1], # week | |
471 'd': [1, 2], 'D': [1, 2, 3], 'F': [1], 'g': None, # day | |
472 'E': [1, 2, 3, 4, 5], 'e': [1, 2, 3, 4, 5], 'c': [1, 3, 4, 5], # week day | |
473 'a': [1], # period | |
474 'h': [1, 2], 'H': [1, 2], 'K': [1, 2], 'k': [1, 2], # hour | |
475 'm': [1, 2], # minute | |
476 's': [1, 2], 'S': None, 'A': None, # second | |
477 'z': [1, 2, 3, 4], 'Z': [1, 2, 3, 4], 'v': [1, 4] # zone | |
1 | 478 } |
479 | |
480 def parse_pattern(pattern): | |
481 """Parse date, time, and datetime format patterns. | |
482 | |
483 >>> parse_pattern("MMMMd").format | |
484 u'%(MMMM)s%(d)s' | |
485 >>> parse_pattern("MMM d, yyyy").format | |
486 u'%(MMM)s %(d)s, %(yyyy)s' | |
16 | 487 |
488 Pattern can contain literal strings in single quotes: | |
489 | |
1 | 490 >>> parse_pattern("H:mm' Uhr 'z").format |
491 u'%(H)s:%(mm)s Uhr %(z)s' | |
492 | |
16 | 493 An actual single quote can be used by using two adjacent single quote |
494 characters: | |
495 | |
496 >>> parse_pattern("hh' o''clock'").format | |
497 u"%(hh)s o'clock" | |
498 | |
1 | 499 :param pattern: the formatting pattern to parse |
500 """ | |
12
e6ba3e878b10
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
501 if type(pattern) is DateTimePattern: |
1 | 502 return pattern |
503 | |
504 result = [] | |
505 quotebuf = None | |
506 charbuf = [] | |
507 fieldchar = [''] | |
508 fieldnum = [0] | |
509 | |
510 def append_chars(): | |
511 result.append(''.join(charbuf).replace('%', '%%')) | |
512 del charbuf[:] | |
513 | |
514 def append_field(): | |
515 limit = PATTERN_CHARS[fieldchar[0]] | |
15 | 516 if limit and fieldnum[0] not in limit: |
1 | 517 raise ValueError('Invalid length for field: %r' |
518 % (fieldchar[0] * fieldnum[0])) | |
519 result.append('%%(%s)s' % (fieldchar[0] * fieldnum[0])) | |
520 fieldchar[0] = '' | |
521 fieldnum[0] = 0 | |
522 | |
16 | 523 for idx, char in enumerate(pattern.replace("''", '\0')): |
1 | 524 if quotebuf is None: |
525 if char == "'": # quote started | |
526 if fieldchar[0]: | |
527 append_field() | |
528 elif charbuf: | |
529 append_chars() | |
530 quotebuf = [] | |
531 elif char in PATTERN_CHARS: | |
532 if charbuf: | |
533 append_chars() | |
534 if char == fieldchar[0]: | |
535 fieldnum[0] += 1 | |
536 else: | |
537 if fieldchar[0]: | |
538 append_field() | |
539 fieldchar[0] = char | |
540 fieldnum[0] = 1 | |
541 else: | |
542 if fieldchar[0]: | |
543 append_field() | |
544 charbuf.append(char) | |
545 | |
546 elif quotebuf is not None: | |
16 | 547 if char == "'": # end of quote |
1 | 548 charbuf.extend(quotebuf) |
549 quotebuf = None | |
550 else: # inside quote | |
551 quotebuf.append(char) | |
552 | |
553 if fieldchar[0]: | |
554 append_field() | |
555 elif charbuf: | |
556 append_chars() | |
557 | |
16 | 558 return DateTimePattern(pattern, u''.join(result).replace('\0', "'")) |