Mercurial > babel > old > babel-test
annotate babel/dates.py @ 33:0740b6d31799
* Import datetime patterns from CLDR.
* Make the date/time arguments to the formatting functions optional, defaulting to the current date/time.
author | cmlenz |
---|---|
date | Mon, 04 Jun 2007 14:28:54 +0000 |
parents | da1c9610e751 |
children | 3666f3d3df15 |
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
a2c54ef107c2
* 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
a2c54ef107c2
* 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
a2c54ef107c2
* 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
a2c54ef107c2
* 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
c0c92d11f1ab
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
c0c92d11f1ab
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
192 |
c0c92d11f1ab
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 |
c0c92d11f1ab
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 |
c0c92d11f1ab
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, |
c0c92d11f1ab
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
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
201 date = date.date() |
1 | 202 locale = Locale.parse(locale) |
203 if format in ('full', 'long', 'medium', 'short'): | |
204 format = get_date_format(format, locale=locale) | |
205 pattern = parse_pattern(format) | |
206 return parse_pattern(format).apply(date, locale) | |
207 | |
33 | 208 def format_datetime(datetime=None, format='medium', tzinfo=UTC, locale=LC_TIME): |
1 | 209 """Returns a date formatted according to the given pattern. |
210 | |
33 | 211 >>> dt = datetime(2007, 04, 01, 15, 30) |
212 >>> format_datetime(dt, locale='en_US') | |
213 u'Apr 1, 2007 3:30:00 PM' | |
214 | |
215 :param datetime: the `datetime` object; if `None`, the current date and | |
216 time is used | |
16 | 217 :param format: one of "full", "long", "medium", or "short", or a custom |
218 date/time pattern | |
29 | 219 :param tzinfo: the timezone to apply to the time for display |
19
c0c92d11f1ab
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
220 :param locale: a `Locale` object or a locale identifier |
1 | 221 :rtype: `unicode` |
222 """ | |
33 | 223 if datetime is None: |
224 datetime = datetime_.now() | |
18
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
225 locale = Locale.parse(locale) |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
226 if format in ('full', 'long', 'medium', 'short'): |
33 | 227 return get_datetime_format(format, locale=locale) \ |
228 .replace('{0}', format_time(datetime, format, tzinfo=tzinfo, | |
229 locale=locale)) \ | |
230 .replace('{1}', format_date(datetime, format, locale=locale)) | |
231 else: | |
232 return parse_pattern(format).apply(datetime, locale) | |
1 | 233 |
33 | 234 def format_time(time=None, format='medium', tzinfo=UTC, locale=LC_TIME): |
1 | 235 """Returns a time formatted according to the given pattern. |
236 | |
237 >>> t = time(15, 30) | |
238 >>> format_time(t, locale='en_US') | |
239 u'3:30:00 PM' | |
240 >>> format_time(t, format='short', locale='de_DE') | |
241 u'15:30' | |
242 | |
16 | 243 If you don't want to use the locale default formats, you can specify a |
244 custom time pattern: | |
245 | |
246 >>> format_time(t, "hh 'o''clock' a", locale='en') | |
247 u"03 o'clock PM" | |
248 | |
29 | 249 For any pattern requiring the display of the time-zone, the third-party |
250 ``pytz`` package is needed to explicitly specify the time-zone: | |
251 | |
252 >>> from pytz import timezone | |
253 >>> cet = timezone('Europe/Berlin') | |
254 >>> format_time(t, format='full', tzinfo=cet, locale='de_DE') | |
255 u'15:30 Uhr MEZ' | |
256 | |
33 | 257 :param time: the ``time`` or ``datetime`` object; if `None`, the current |
258 time is used | |
16 | 259 :param format: one of "full", "long", "medium", or "short", or a custom |
260 date/time pattern | |
29 | 261 :param tzinfo: the time-zone to apply to the time for display |
19
c0c92d11f1ab
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
262 :param locale: a `Locale` object or a locale identifier |
1 | 263 :rtype: `unicode` |
19
c0c92d11f1ab
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
264 |
c0c92d11f1ab
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
265 :note: If the pattern contains date fields, an `AttributeError` will be |
c0c92d11f1ab
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
266 raised when trying to apply the formatting. This is also true if |
c0c92d11f1ab
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
267 the value of ``time`` parameter is actually a ``datetime`` object, |
c0c92d11f1ab
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
268 as this function automatically converts that to a ``time``. |
1 | 269 """ |
33 | 270 if time is None: |
271 time = datetime.now().time() | |
272 elif isinstance(time, (int, long)): | |
18
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
273 time = datetime.fromtimestamp(time).time() |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
274 elif isinstance(time, datetime): |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
275 time = time.time() |
29 | 276 if time.tzinfo is None: |
277 time = time.replace(tzinfo=tzinfo) | |
1 | 278 locale = Locale.parse(locale) |
279 if format in ('full', 'long', 'medium', 'short'): | |
280 format = get_time_format(format, locale=locale) | |
281 return parse_pattern(format).apply(time, locale) | |
282 | |
283 def parse_date(string, locale=LC_TIME): | |
284 raise NotImplementedError | |
285 | |
286 def parse_datetime(string, locale=LC_TIME): | |
287 raise NotImplementedError | |
288 | |
289 def parse_time(string, locale=LC_TIME): | |
290 raise NotImplementedError | |
291 | |
292 | |
12
a2c54ef107c2
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
293 class DateTimePattern(object): |
1 | 294 |
295 def __init__(self, pattern, format): | |
296 self.pattern = pattern | |
297 self.format = format | |
298 | |
299 def __repr__(self): | |
300 return '<%s %r>' % (type(self).__name__, self.pattern) | |
301 | |
302 def __unicode__(self): | |
303 return self.pattern | |
304 | |
305 def __mod__(self, other): | |
306 assert type(other) is DateTimeFormat | |
307 return self.format % other | |
308 | |
309 def apply(self, datetime, locale): | |
310 return self % DateTimeFormat(datetime, locale) | |
311 | |
312 | |
313 class DateTimeFormat(object): | |
314 | |
315 def __init__(self, value, locale): | |
316 assert isinstance(value, (date, datetime, time)) | |
29 | 317 if isinstance(value, (datetime, time)) and value.tzinfo is None: |
318 value = value.replace(tzinfo=UTC) | |
1 | 319 self.value = value |
320 self.locale = Locale.parse(locale) | |
321 | |
322 def __getitem__(self, name): | |
323 # TODO: a number of fields missing here | |
15 | 324 char = name[0] |
325 num = len(name) | |
326 if char == 'G': | |
327 return self.format_era(char, num) | |
328 elif char in ('y', 'Y'): | |
329 return self.format_year(char, num) | |
330 elif char in ('Q', 'q'): | |
331 return self.format_quarter(char, num) | |
332 elif char in ('M', 'L'): | |
333 return self.format_month(char, num) | |
334 elif char == 'd': | |
335 return self.format(self.value.day, num) | |
336 elif char in ('E', 'e', 'c'): | |
337 return self.format_weekday(char, num) | |
338 elif char == 'a': | |
339 return self.format_period(char) | |
340 elif char == 'h': | |
341 return self.format(self.value.hour % 12, num) | |
342 elif char == 'H': | |
343 return self.format(self.value.hour, num) | |
18
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
344 elif char == 'K': |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
345 return self.format(self.value.hour % 12 - 1, num) |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
346 elif char == 'k': |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
347 return self.format(self.value.hour + 1, num) |
15 | 348 elif char == 'm': |
349 return self.format(self.value.minute, num) | |
350 elif char == 's': | |
351 return self.format(self.value.second, num) | |
29 | 352 elif char in ('z', 'Z', 'v'): |
353 return self.format_timezone(char, num) | |
1 | 354 else: |
15 | 355 raise KeyError('Unsupported date/time field %r' % char) |
1 | 356 |
15 | 357 def format_era(self, char, num): |
1 | 358 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)] |
359 era = int(self.value.year >= 0) | |
360 return get_era_names(width, self.locale)[era] | |
361 | |
15 | 362 def format_year(self, char, num): |
363 if char.islower(): | |
364 value = self.value.year | |
365 else: | |
366 value = self.value.isocalendar()[0] | |
1 | 367 year = self.format(value, num) |
368 if num == 2: | |
369 year = year[-2:] | |
370 return year | |
371 | |
15 | 372 def format_month(self, char, num): |
1 | 373 if num <= 2: |
374 return ('%%0%dd' % num) % self.value.month | |
375 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] | |
15 | 376 context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num] |
1 | 377 return get_month_names(width, context, self.locale)[self.value.month] |
378 | |
15 | 379 def format_weekday(self, char, num): |
380 if num < 3: | |
381 if char.islower(): | |
382 value = 7 - self.locale.first_week_day + self.value.weekday() | |
383 return self.format(value % 7 + 1, num) | |
384 num = 3 | |
385 weekday = self.value.weekday() | |
386 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] | |
387 context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num] | |
1 | 388 return get_day_names(width, context, self.locale)[weekday] |
389 | |
15 | 390 def format_period(self, char): |
1 | 391 period = {0: 'am', 1: 'pm'}[int(self.value.hour > 12)] |
392 return get_period_names(locale=self.locale)[period] | |
393 | |
29 | 394 def format_timezone(self, char, num): |
395 if char == 'z': | |
396 zone = self.value.tzinfo.zone | |
397 if num < 4: | |
398 return self.locale.time_zones[zone]['short'][ | |
399 self.value.dst() and 'daylight' or 'standard' | |
400 ] | |
401 else: | |
402 return self.locale.time_zones[zone]['long'][ | |
403 self.value.dst() and 'daylight' or 'standard' | |
404 ] | |
405 | |
406 elif char == 'Z': | |
407 offset = self.value.utcoffset() | |
408 hours, seconds = divmod(offset.seconds, 3600) | |
409 minutes = seconds // 60 | |
410 sign = '+' | |
411 if offset.seconds < 0: | |
412 sign = '-' | |
413 pattern = {3: '%s%02d%02d', 4: 'GMT %s%02d:%02d'}[max(3, num)] | |
414 return pattern % (sign, hours, minutes) | |
415 | |
416 elif char == 'v': | |
417 raise NotImplementedError | |
418 | |
1 | 419 def format(self, value, length): |
420 return ('%%0%dd' % length) % value | |
421 | |
422 | |
423 PATTERN_CHARS = { | |
15 | 424 'G': [1, 2, 3, 4, 5], # era |
425 'y': None, 'Y': None, 'u': None, # year | |
426 'Q': [1, 2, 3, 4], 'q': [1, 2, 3, 4], # quarter | |
427 'M': [1, 2, 3, 4, 5], 'L': [1, 2, 3, 4, 5], # month | |
428 'w': [1, 2], 'W': [1], # week | |
429 'd': [1, 2], 'D': [1, 2, 3], 'F': [1], 'g': None, # day | |
430 'E': [1, 2, 3, 4, 5], 'e': [1, 2, 3, 4, 5], 'c': [1, 3, 4, 5], # week day | |
431 'a': [1], # period | |
432 'h': [1, 2], 'H': [1, 2], 'K': [1, 2], 'k': [1, 2], # hour | |
433 'm': [1, 2], # minute | |
434 's': [1, 2], 'S': None, 'A': None, # second | |
435 'z': [1, 2, 3, 4], 'Z': [1, 2, 3, 4], 'v': [1, 4] # zone | |
1 | 436 } |
437 | |
438 def parse_pattern(pattern): | |
439 """Parse date, time, and datetime format patterns. | |
440 | |
441 >>> parse_pattern("MMMMd").format | |
442 u'%(MMMM)s%(d)s' | |
443 >>> parse_pattern("MMM d, yyyy").format | |
444 u'%(MMM)s %(d)s, %(yyyy)s' | |
16 | 445 |
446 Pattern can contain literal strings in single quotes: | |
447 | |
1 | 448 >>> parse_pattern("H:mm' Uhr 'z").format |
449 u'%(H)s:%(mm)s Uhr %(z)s' | |
450 | |
16 | 451 An actual single quote can be used by using two adjacent single quote |
452 characters: | |
453 | |
454 >>> parse_pattern("hh' o''clock'").format | |
455 u"%(hh)s o'clock" | |
456 | |
1 | 457 :param pattern: the formatting pattern to parse |
458 """ | |
12
a2c54ef107c2
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
459 if type(pattern) is DateTimePattern: |
1 | 460 return pattern |
461 | |
462 result = [] | |
463 quotebuf = None | |
464 charbuf = [] | |
465 fieldchar = [''] | |
466 fieldnum = [0] | |
467 | |
468 def append_chars(): | |
469 result.append(''.join(charbuf).replace('%', '%%')) | |
470 del charbuf[:] | |
471 | |
472 def append_field(): | |
473 limit = PATTERN_CHARS[fieldchar[0]] | |
15 | 474 if limit and fieldnum[0] not in limit: |
1 | 475 raise ValueError('Invalid length for field: %r' |
476 % (fieldchar[0] * fieldnum[0])) | |
477 result.append('%%(%s)s' % (fieldchar[0] * fieldnum[0])) | |
478 fieldchar[0] = '' | |
479 fieldnum[0] = 0 | |
480 | |
16 | 481 for idx, char in enumerate(pattern.replace("''", '\0')): |
1 | 482 if quotebuf is None: |
483 if char == "'": # quote started | |
484 if fieldchar[0]: | |
485 append_field() | |
486 elif charbuf: | |
487 append_chars() | |
488 quotebuf = [] | |
489 elif char in PATTERN_CHARS: | |
490 if charbuf: | |
491 append_chars() | |
492 if char == fieldchar[0]: | |
493 fieldnum[0] += 1 | |
494 else: | |
495 if fieldchar[0]: | |
496 append_field() | |
497 fieldchar[0] = char | |
498 fieldnum[0] = 1 | |
499 else: | |
500 if fieldchar[0]: | |
501 append_field() | |
502 charbuf.append(char) | |
503 | |
504 elif quotebuf is not None: | |
16 | 505 if char == "'": # end of quote |
1 | 506 charbuf.extend(quotebuf) |
507 quotebuf = None | |
508 else: # inside quote | |
509 quotebuf.append(char) | |
510 | |
511 if fieldchar[0]: | |
512 append_field() | |
513 elif charbuf: | |
514 append_chars() | |
515 | |
16 | 516 return DateTimePattern(pattern, u''.join(result).replace('\0', "'")) |