Mercurial > babel > old > babel-test
annotate babel/dates.py @ 19:c0c92d11f1ab
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
author | cmlenz |
---|---|
date | Thu, 31 May 2007 17:56:14 +0000 |
parents | 77a68f88f6bc |
children | da1c9610e751 |
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 | |
19
c0c92d11f1ab
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
163 :param date: the ``date`` or ``datetime`` object |
16 | 164 :param format: one of "full", "long", "medium", or "short", or a custom |
165 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
|
166 :param locale: a `Locale` object or a locale identifier |
1 | 167 :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
|
168 |
c0c92d11f1ab
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
169 :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
|
170 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
|
171 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
|
172 as this function automatically converts that to a ``date``. |
1 | 173 """ |
18
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
174 if isinstance(date, datetime): |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
175 date = date.date() |
1 | 176 locale = Locale.parse(locale) |
177 if format in ('full', 'long', 'medium', 'short'): | |
178 format = get_date_format(format, locale=locale) | |
179 pattern = parse_pattern(format) | |
180 return parse_pattern(format).apply(date, locale) | |
181 | |
182 def format_datetime(datetime, format='medium', locale=LC_TIME): | |
183 """Returns a date formatted according to the given pattern. | |
184 | |
185 :param datetime: the ``date`` object | |
16 | 186 :param format: one of "full", "long", "medium", or "short", or a custom |
187 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
|
188 :param locale: a `Locale` object or a locale identifier |
1 | 189 :rtype: `unicode` |
190 """ | |
18
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
191 locale = Locale.parse(locale) |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
192 if format in ('full', 'long', 'medium', 'short'): |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
193 raise NotImplementedError |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
194 pattern = parse_pattern(format) |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
195 return parse_pattern(format).apply(datetime, locale) |
1 | 196 |
197 def format_time(time, format='medium', locale=LC_TIME): | |
198 """Returns a time formatted according to the given pattern. | |
199 | |
200 >>> t = time(15, 30) | |
201 >>> format_time(t, locale='en_US') | |
202 u'3:30:00 PM' | |
203 >>> format_time(t, format='short', locale='de_DE') | |
204 u'15:30' | |
205 | |
16 | 206 If you don't want to use the locale default formats, you can specify a |
207 custom time pattern: | |
208 | |
209 >>> format_time(t, "hh 'o''clock' a", locale='en') | |
210 u"03 o'clock PM" | |
211 | |
19
c0c92d11f1ab
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
212 :param time: the ``time`` or ``datetime`` object |
16 | 213 :param format: one of "full", "long", "medium", or "short", or a custom |
214 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
|
215 :param locale: a `Locale` object or a locale identifier |
1 | 216 :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
|
217 |
c0c92d11f1ab
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
18
diff
changeset
|
218 :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
|
219 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
|
220 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
|
221 as this function automatically converts that to a ``time``. |
1 | 222 """ |
18
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
223 if isinstance(time, (int, long)): |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
224 time = datetime.fromtimestamp(time).time() |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
225 elif isinstance(time, datetime): |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
226 time = time.time() |
1 | 227 locale = Locale.parse(locale) |
228 if format in ('full', 'long', 'medium', 'short'): | |
229 format = get_time_format(format, locale=locale) | |
230 return parse_pattern(format).apply(time, locale) | |
231 | |
232 def parse_date(string, locale=LC_TIME): | |
233 raise NotImplementedError | |
234 | |
235 def parse_datetime(string, locale=LC_TIME): | |
236 raise NotImplementedError | |
237 | |
238 def parse_time(string, locale=LC_TIME): | |
239 raise NotImplementedError | |
240 | |
241 | |
12
a2c54ef107c2
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
242 class DateTimePattern(object): |
1 | 243 |
244 def __init__(self, pattern, format): | |
245 self.pattern = pattern | |
246 self.format = format | |
247 | |
248 def __repr__(self): | |
249 return '<%s %r>' % (type(self).__name__, self.pattern) | |
250 | |
251 def __unicode__(self): | |
252 return self.pattern | |
253 | |
254 def __mod__(self, other): | |
255 assert type(other) is DateTimeFormat | |
256 return self.format % other | |
257 | |
258 def apply(self, datetime, locale): | |
259 return self % DateTimeFormat(datetime, locale) | |
260 | |
261 | |
262 class DateTimeFormat(object): | |
263 | |
264 def __init__(self, value, locale): | |
265 assert isinstance(value, (date, datetime, time)) | |
266 self.value = value | |
267 self.locale = Locale.parse(locale) | |
268 | |
269 def __getitem__(self, name): | |
270 # TODO: a number of fields missing here | |
15 | 271 char = name[0] |
272 num = len(name) | |
273 if char == 'G': | |
274 return self.format_era(char, num) | |
275 elif char in ('y', 'Y'): | |
276 return self.format_year(char, num) | |
277 elif char in ('Q', 'q'): | |
278 return self.format_quarter(char, num) | |
279 elif char in ('M', 'L'): | |
280 return self.format_month(char, num) | |
281 elif char == 'd': | |
282 return self.format(self.value.day, num) | |
283 elif char in ('E', 'e', 'c'): | |
284 return self.format_weekday(char, num) | |
285 elif char == 'a': | |
286 return self.format_period(char) | |
287 elif char == 'h': | |
288 return self.format(self.value.hour % 12, num) | |
289 elif char == 'H': | |
290 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
|
291 elif char == 'K': |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
292 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
|
293 elif char == 'k': |
77a68f88f6bc
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
16
diff
changeset
|
294 return self.format(self.value.hour + 1, num) |
15 | 295 elif char == 'm': |
296 return self.format(self.value.minute, num) | |
297 elif char == 's': | |
298 return self.format(self.value.second, num) | |
1 | 299 else: |
15 | 300 raise KeyError('Unsupported date/time field %r' % char) |
1 | 301 |
15 | 302 def format_era(self, char, num): |
1 | 303 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)] |
304 era = int(self.value.year >= 0) | |
305 return get_era_names(width, self.locale)[era] | |
306 | |
15 | 307 def format_year(self, char, num): |
308 if char.islower(): | |
309 value = self.value.year | |
310 else: | |
311 value = self.value.isocalendar()[0] | |
1 | 312 year = self.format(value, num) |
313 if num == 2: | |
314 year = year[-2:] | |
315 return year | |
316 | |
15 | 317 def format_month(self, char, num): |
1 | 318 if num <= 2: |
319 return ('%%0%dd' % num) % self.value.month | |
320 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] | |
15 | 321 context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num] |
1 | 322 return get_month_names(width, context, self.locale)[self.value.month] |
323 | |
15 | 324 def format_weekday(self, char, num): |
325 if num < 3: | |
326 if char.islower(): | |
327 value = 7 - self.locale.first_week_day + self.value.weekday() | |
328 return self.format(value % 7 + 1, num) | |
329 num = 3 | |
330 weekday = self.value.weekday() | |
331 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] | |
332 context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num] | |
1 | 333 return get_day_names(width, context, self.locale)[weekday] |
334 | |
15 | 335 def format_period(self, char): |
1 | 336 period = {0: 'am', 1: 'pm'}[int(self.value.hour > 12)] |
337 return get_period_names(locale=self.locale)[period] | |
338 | |
339 def format(self, value, length): | |
340 return ('%%0%dd' % length) % value | |
341 | |
342 | |
343 PATTERN_CHARS = { | |
15 | 344 'G': [1, 2, 3, 4, 5], # era |
345 'y': None, 'Y': None, 'u': None, # year | |
346 'Q': [1, 2, 3, 4], 'q': [1, 2, 3, 4], # quarter | |
347 'M': [1, 2, 3, 4, 5], 'L': [1, 2, 3, 4, 5], # month | |
348 'w': [1, 2], 'W': [1], # week | |
349 'd': [1, 2], 'D': [1, 2, 3], 'F': [1], 'g': None, # day | |
350 'E': [1, 2, 3, 4, 5], 'e': [1, 2, 3, 4, 5], 'c': [1, 3, 4, 5], # week day | |
351 'a': [1], # period | |
352 'h': [1, 2], 'H': [1, 2], 'K': [1, 2], 'k': [1, 2], # hour | |
353 'm': [1, 2], # minute | |
354 's': [1, 2], 'S': None, 'A': None, # second | |
355 'z': [1, 2, 3, 4], 'Z': [1, 2, 3, 4], 'v': [1, 4] # zone | |
1 | 356 } |
357 | |
358 def parse_pattern(pattern): | |
359 """Parse date, time, and datetime format patterns. | |
360 | |
361 >>> parse_pattern("MMMMd").format | |
362 u'%(MMMM)s%(d)s' | |
363 >>> parse_pattern("MMM d, yyyy").format | |
364 u'%(MMM)s %(d)s, %(yyyy)s' | |
16 | 365 |
366 Pattern can contain literal strings in single quotes: | |
367 | |
1 | 368 >>> parse_pattern("H:mm' Uhr 'z").format |
369 u'%(H)s:%(mm)s Uhr %(z)s' | |
370 | |
16 | 371 An actual single quote can be used by using two adjacent single quote |
372 characters: | |
373 | |
374 >>> parse_pattern("hh' o''clock'").format | |
375 u"%(hh)s o'clock" | |
376 | |
1 | 377 :param pattern: the formatting pattern to parse |
378 """ | |
12
a2c54ef107c2
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
8
diff
changeset
|
379 if type(pattern) is DateTimePattern: |
1 | 380 return pattern |
381 | |
382 result = [] | |
383 quotebuf = None | |
384 charbuf = [] | |
385 fieldchar = [''] | |
386 fieldnum = [0] | |
387 | |
388 def append_chars(): | |
389 result.append(''.join(charbuf).replace('%', '%%')) | |
390 del charbuf[:] | |
391 | |
392 def append_field(): | |
393 limit = PATTERN_CHARS[fieldchar[0]] | |
15 | 394 if limit and fieldnum[0] not in limit: |
1 | 395 raise ValueError('Invalid length for field: %r' |
396 % (fieldchar[0] * fieldnum[0])) | |
397 result.append('%%(%s)s' % (fieldchar[0] * fieldnum[0])) | |
398 fieldchar[0] = '' | |
399 fieldnum[0] = 0 | |
400 | |
16 | 401 for idx, char in enumerate(pattern.replace("''", '\0')): |
1 | 402 if quotebuf is None: |
403 if char == "'": # quote started | |
404 if fieldchar[0]: | |
405 append_field() | |
406 elif charbuf: | |
407 append_chars() | |
408 quotebuf = [] | |
409 elif char in PATTERN_CHARS: | |
410 if charbuf: | |
411 append_chars() | |
412 if char == fieldchar[0]: | |
413 fieldnum[0] += 1 | |
414 else: | |
415 if fieldchar[0]: | |
416 append_field() | |
417 fieldchar[0] = char | |
418 fieldnum[0] = 1 | |
419 else: | |
420 if fieldchar[0]: | |
421 append_field() | |
422 charbuf.append(char) | |
423 | |
424 elif quotebuf is not None: | |
16 | 425 if char == "'": # end of quote |
1 | 426 charbuf.extend(quotebuf) |
427 quotebuf = None | |
428 else: # inside quote | |
429 quotebuf.append(char) | |
430 | |
431 if fieldchar[0]: | |
432 append_field() | |
433 elif charbuf: | |
434 append_chars() | |
435 | |
16 | 436 return DateTimePattern(pattern, u''.join(result).replace('\0', "'")) |