Mercurial > babel > old > babel-test
annotate babel/dates.py @ 16:ed154241c08d
Handle escape chars in datetime patterns.
author | cmlenz |
---|---|
date | Thu, 31 May 2007 14:37:31 +0000 |
parents | 76985c08a339 |
children | 77a68f88f6bc |
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 | |
24 from datetime import date, datetime, time | |
25 | |
26 from babel.core import Locale | |
27 from babel.util import default_locale | |
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 | |
35 def get_period_names(locale=LC_TIME): | |
36 """Return the names for day periods (AM/PM) used by the locale. | |
37 | |
38 >>> get_period_names(locale='en_US')['am'] | |
39 u'AM' | |
40 | |
41 :param locale: the `Locale` object, or a locale string | |
42 :return: the dictionary of period names | |
43 :rtype: `dict` | |
44 """ | |
45 return Locale.parse(locale).periods | |
46 | |
47 def get_day_names(width='wide', context='format', locale=LC_TIME): | |
48 """Return the day names used by the locale for the specified format. | |
49 | |
50 >>> get_day_names('wide', locale='en_US')[1] | |
15 | 51 u'Tuesday' |
1 | 52 >>> get_day_names('abbreviated', locale='es')[1] |
15 | 53 u'mar' |
1 | 54 >>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1] |
15 | 55 u'D' |
1 | 56 |
57 :param width: the width to use, one of "wide", "abbreviated", or "narrow" | |
58 :param context: the context, either "format" or "stand-alone" | |
59 :param locale: the `Locale` object, or a locale string | |
60 :return: the dictionary of day names | |
61 :rtype: `dict` | |
62 """ | |
63 return Locale.parse(locale).days[context][width] | |
64 | |
65 def get_month_names(width='wide', context='format', locale=LC_TIME): | |
66 """Return the month names used by the locale for the specified format. | |
67 | |
68 >>> get_month_names('wide', locale='en_US')[1] | |
69 u'January' | |
70 >>> get_month_names('abbreviated', locale='es')[1] | |
71 u'ene' | |
72 >>> get_month_names('narrow', context='stand-alone', locale='de_DE')[1] | |
73 u'J' | |
74 | |
75 :param width: the width to use, one of "wide", "abbreviated", or "narrow" | |
76 :param context: the context, either "format" or "stand-alone" | |
77 :param locale: the `Locale` object, or a locale string | |
78 :return: the dictionary of month names | |
79 :rtype: `dict` | |
80 """ | |
81 return Locale.parse(locale).months[context][width] | |
82 | |
83 def get_quarter_names(width='wide', context='format', locale=LC_TIME): | |
84 """Return the quarter names used by the locale for the specified format. | |
85 | |
86 >>> get_quarter_names('wide', locale='en_US')[1] | |
87 u'1st quarter' | |
88 >>> get_quarter_names('abbreviated', locale='de_DE')[1] | |
89 u'Q1' | |
90 | |
91 :param width: the width to use, one of "wide", "abbreviated", or "narrow" | |
92 :param context: the context, either "format" or "stand-alone" | |
93 :param locale: the `Locale` object, or a locale string | |
94 :return: the dictionary of quarter names | |
95 :rtype: `dict` | |
96 """ | |
97 return Locale.parse(locale).quarters[context][width] | |
98 | |
99 def get_era_names(width='wide', locale=LC_TIME): | |
100 """Return the era names used by the locale for the specified format. | |
101 | |
102 >>> get_era_names('wide', locale='en_US')[1] | |
103 u'Anno Domini' | |
104 >>> get_era_names('abbreviated', locale='de_DE')[1] | |
105 u'n. Chr.' | |
106 | |
107 :param width: the width to use, either "wide" or "abbreviated" | |
108 :param locale: the `Locale` object, or a locale string | |
109 :return: the dictionary of era names | |
110 :rtype: `dict` | |
111 """ | |
112 return Locale.parse(locale).eras[width] | |
113 | |
114 def get_date_format(format='medium', locale=LC_TIME): | |
115 """Return the date formatting patterns used by the locale for the specified | |
116 format. | |
117 | |
118 >>> get_date_format(locale='en_US') | |
12
a2c54ef107c2
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
119 <DateTimePattern u'MMM d, yyyy'> |
1 | 120 >>> get_date_format('full', locale='de_DE') |
12
a2c54ef107c2
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
121 <DateTimePattern u'EEEE, d. MMMM yyyy'> |
1 | 122 |
123 :param format: the format to use, one of "full", "long", "medium", or | |
124 "short" | |
125 :param locale: the `Locale` object, or a locale string | |
126 :return: the date format pattern | |
127 :rtype: `dict` | |
128 """ | |
129 return Locale.parse(locale).date_formats[format] | |
130 | |
131 def get_time_format(format='medium', locale=LC_TIME): | |
132 """Return the time formatting patterns used by the locale for the specified | |
133 format. | |
134 | |
135 >>> get_time_format(locale='en_US') | |
12
a2c54ef107c2
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
136 <DateTimePattern u'h:mm:ss a'> |
1 | 137 >>> get_time_format('full', locale='de_DE') |
12
a2c54ef107c2
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
138 <DateTimePattern u"H:mm' Uhr 'z"> |
1 | 139 |
140 :param format: the format to use, one of "full", "long", "medium", or | |
141 "short" | |
142 :param locale: the `Locale` object, or a locale string | |
143 :return: the time format pattern | |
144 :rtype: `dict` | |
145 """ | |
146 return Locale.parse(locale).time_formats[format] | |
147 | |
148 def format_date(date, format='medium', locale=LC_TIME): | |
149 """Returns a date formatted according to the given pattern. | |
150 | |
151 >>> d = date(2007, 04, 01) | |
152 >>> format_date(d, locale='en_US') | |
153 u'Apr 1, 2007' | |
154 >>> format_date(d, format='full', locale='de_DE') | |
155 u'Sonntag, 1. April 2007' | |
156 | |
16 | 157 If you don't want to use the locale default formats, you can specify a |
158 custom date pattern: | |
159 | |
160 >>> format_time(d, "EEE, MMM d, ''yy", locale='en') | |
161 u"Sun, Apr 1, '07" | |
162 | |
1 | 163 :param date: the ``date`` object |
16 | 164 :param format: one of "full", "long", "medium", or "short", or a custom |
165 date/time pattern | |
1 | 166 :param locale: a `Locale` object or a locale string |
167 :rtype: `unicode` | |
168 """ | |
169 locale = Locale.parse(locale) | |
170 if format in ('full', 'long', 'medium', 'short'): | |
171 format = get_date_format(format, locale=locale) | |
172 pattern = parse_pattern(format) | |
173 return parse_pattern(format).apply(date, locale) | |
174 | |
175 def format_datetime(datetime, format='medium', locale=LC_TIME): | |
176 """Returns a date formatted according to the given pattern. | |
177 | |
178 :param datetime: the ``date`` object | |
16 | 179 :param format: one of "full", "long", "medium", or "short", or a custom |
180 date/time pattern | |
1 | 181 :param locale: a `Locale` object or a locale string |
182 :rtype: `unicode` | |
183 """ | |
184 raise NotImplementedError | |
185 | |
186 def format_time(time, format='medium', locale=LC_TIME): | |
187 """Returns a time formatted according to the given pattern. | |
188 | |
189 >>> t = time(15, 30) | |
190 >>> format_time(t, locale='en_US') | |
191 u'3:30:00 PM' | |
192 >>> format_time(t, format='short', locale='de_DE') | |
193 u'15:30' | |
194 | |
16 | 195 If you don't want to use the locale default formats, you can specify a |
196 custom time pattern: | |
197 | |
198 >>> format_time(t, "hh 'o''clock' a", locale='en') | |
199 u"03 o'clock PM" | |
200 | |
1 | 201 :param time: the ``time`` object |
16 | 202 :param format: one of "full", "long", "medium", or "short", or a custom |
203 date/time pattern | |
1 | 204 :param locale: a `Locale` object or a locale string |
205 :rtype: `unicode` | |
206 """ | |
207 locale = Locale.parse(locale) | |
208 if format in ('full', 'long', 'medium', 'short'): | |
209 format = get_time_format(format, locale=locale) | |
210 return parse_pattern(format).apply(time, locale) | |
211 | |
212 def parse_date(string, locale=LC_TIME): | |
213 raise NotImplementedError | |
214 | |
215 def parse_datetime(string, locale=LC_TIME): | |
216 raise NotImplementedError | |
217 | |
218 def parse_time(string, locale=LC_TIME): | |
219 raise NotImplementedError | |
220 | |
221 | |
12
a2c54ef107c2
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
222 class DateTimePattern(object): |
1 | 223 |
224 def __init__(self, pattern, format): | |
225 self.pattern = pattern | |
226 self.format = format | |
227 | |
228 def __repr__(self): | |
229 return '<%s %r>' % (type(self).__name__, self.pattern) | |
230 | |
231 def __unicode__(self): | |
232 return self.pattern | |
233 | |
234 def __mod__(self, other): | |
235 assert type(other) is DateTimeFormat | |
236 return self.format % other | |
237 | |
238 def apply(self, datetime, locale): | |
239 return self % DateTimeFormat(datetime, locale) | |
240 | |
241 | |
242 class DateTimeFormat(object): | |
243 | |
244 def __init__(self, value, locale): | |
245 assert isinstance(value, (date, datetime, time)) | |
246 self.value = value | |
247 self.locale = Locale.parse(locale) | |
248 | |
249 def __getitem__(self, name): | |
250 # TODO: a number of fields missing here | |
15 | 251 char = name[0] |
252 num = len(name) | |
253 if char == 'G': | |
254 return self.format_era(char, num) | |
255 elif char in ('y', 'Y'): | |
256 return self.format_year(char, num) | |
257 elif char in ('Q', 'q'): | |
258 return self.format_quarter(char, num) | |
259 elif char in ('M', 'L'): | |
260 return self.format_month(char, num) | |
261 elif char == 'd': | |
262 return self.format(self.value.day, num) | |
263 elif char in ('E', 'e', 'c'): | |
264 return self.format_weekday(char, num) | |
265 elif char == 'a': | |
266 return self.format_period(char) | |
267 elif char == 'h': | |
268 return self.format(self.value.hour % 12, num) | |
269 elif char == 'H': | |
270 return self.format(self.value.hour, num) | |
271 elif char == 'm': | |
272 return self.format(self.value.minute, num) | |
273 elif char == 's': | |
274 return self.format(self.value.second, num) | |
1 | 275 else: |
15 | 276 raise KeyError('Unsupported date/time field %r' % char) |
1 | 277 |
15 | 278 def format_era(self, char, num): |
1 | 279 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)] |
280 era = int(self.value.year >= 0) | |
281 return get_era_names(width, self.locale)[era] | |
282 | |
15 | 283 def format_year(self, char, num): |
284 if char.islower(): | |
285 value = self.value.year | |
286 else: | |
287 value = self.value.isocalendar()[0] | |
1 | 288 year = self.format(value, num) |
289 if num == 2: | |
290 year = year[-2:] | |
291 return year | |
292 | |
15 | 293 def format_month(self, char, num): |
1 | 294 if num <= 2: |
295 return ('%%0%dd' % num) % self.value.month | |
296 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] | |
15 | 297 context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num] |
1 | 298 return get_month_names(width, context, self.locale)[self.value.month] |
299 | |
15 | 300 def format_weekday(self, char, num): |
301 if num < 3: | |
302 if char.islower(): | |
303 value = 7 - self.locale.first_week_day + self.value.weekday() | |
304 return self.format(value % 7 + 1, num) | |
305 num = 3 | |
306 weekday = self.value.weekday() | |
307 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] | |
308 context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num] | |
1 | 309 return get_day_names(width, context, self.locale)[weekday] |
310 | |
15 | 311 def format_period(self, char): |
1 | 312 period = {0: 'am', 1: 'pm'}[int(self.value.hour > 12)] |
313 return get_period_names(locale=self.locale)[period] | |
314 | |
315 def format(self, value, length): | |
316 return ('%%0%dd' % length) % value | |
317 | |
318 | |
319 PATTERN_CHARS = { | |
15 | 320 'G': [1, 2, 3, 4, 5], # era |
321 'y': None, 'Y': None, 'u': None, # year | |
322 'Q': [1, 2, 3, 4], 'q': [1, 2, 3, 4], # quarter | |
323 'M': [1, 2, 3, 4, 5], 'L': [1, 2, 3, 4, 5], # month | |
324 'w': [1, 2], 'W': [1], # week | |
325 'd': [1, 2], 'D': [1, 2, 3], 'F': [1], 'g': None, # day | |
326 'E': [1, 2, 3, 4, 5], 'e': [1, 2, 3, 4, 5], 'c': [1, 3, 4, 5], # week day | |
327 'a': [1], # period | |
328 'h': [1, 2], 'H': [1, 2], 'K': [1, 2], 'k': [1, 2], # hour | |
329 'm': [1, 2], # minute | |
330 's': [1, 2], 'S': None, 'A': None, # second | |
331 'z': [1, 2, 3, 4], 'Z': [1, 2, 3, 4], 'v': [1, 4] # zone | |
1 | 332 } |
333 | |
334 def parse_pattern(pattern): | |
335 """Parse date, time, and datetime format patterns. | |
336 | |
337 >>> parse_pattern("MMMMd").format | |
338 u'%(MMMM)s%(d)s' | |
339 >>> parse_pattern("MMM d, yyyy").format | |
340 u'%(MMM)s %(d)s, %(yyyy)s' | |
16 | 341 |
342 Pattern can contain literal strings in single quotes: | |
343 | |
1 | 344 >>> parse_pattern("H:mm' Uhr 'z").format |
345 u'%(H)s:%(mm)s Uhr %(z)s' | |
346 | |
16 | 347 An actual single quote can be used by using two adjacent single quote |
348 characters: | |
349 | |
350 >>> parse_pattern("hh' o''clock'").format | |
351 u"%(hh)s o'clock" | |
352 | |
1 | 353 :param pattern: the formatting pattern to parse |
354 """ | |
12
a2c54ef107c2
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
355 if type(pattern) is DateTimePattern: |
1 | 356 return pattern |
357 | |
358 result = [] | |
359 quotebuf = None | |
360 charbuf = [] | |
361 fieldchar = [''] | |
362 fieldnum = [0] | |
363 | |
364 def append_chars(): | |
365 result.append(''.join(charbuf).replace('%', '%%')) | |
366 del charbuf[:] | |
367 | |
368 def append_field(): | |
369 limit = PATTERN_CHARS[fieldchar[0]] | |
15 | 370 if limit and fieldnum[0] not in limit: |
1 | 371 raise ValueError('Invalid length for field: %r' |
372 % (fieldchar[0] * fieldnum[0])) | |
373 result.append('%%(%s)s' % (fieldchar[0] * fieldnum[0])) | |
374 fieldchar[0] = '' | |
375 fieldnum[0] = 0 | |
376 | |
16 | 377 for idx, char in enumerate(pattern.replace("''", '\0')): |
1 | 378 if quotebuf is None: |
379 if char == "'": # quote started | |
380 if fieldchar[0]: | |
381 append_field() | |
382 elif charbuf: | |
383 append_chars() | |
384 quotebuf = [] | |
385 elif char in PATTERN_CHARS: | |
386 if charbuf: | |
387 append_chars() | |
388 if char == fieldchar[0]: | |
389 fieldnum[0] += 1 | |
390 else: | |
391 if fieldchar[0]: | |
392 append_field() | |
393 fieldchar[0] = char | |
394 fieldnum[0] = 1 | |
395 else: | |
396 if fieldchar[0]: | |
397 append_field() | |
398 charbuf.append(char) | |
399 | |
400 elif quotebuf is not None: | |
16 | 401 if char == "'": # end of quote |
1 | 402 charbuf.extend(quotebuf) |
403 quotebuf = None | |
404 else: # inside quote | |
405 quotebuf.append(char) | |
406 | |
407 if fieldchar[0]: | |
408 append_field() | |
409 elif charbuf: | |
410 append_chars() | |
411 | |
16 | 412 return DateTimePattern(pattern, u''.join(result).replace('\0', "'")) |