Mercurial > babel > old > mirror
annotate babel/core.py @ 17:aa33ad077d24
Minor date formatting improvements.
author | cmlenz |
---|---|
date | Thu, 31 May 2007 14:20:04 +0000 |
parents | 29ef15a6fd75 |
children | 6c2c9fc7d787 |
rev | line source |
---|---|
3 | 1 # -*- coding: utf-8 -*- |
2 # | |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
3 # Copyright (C) 2007 Edgewall Software |
3 | 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 """Core locale representation and locale data access gateway.""" | |
15 | |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
16 import os |
3 | 17 import pickle |
18 try: | |
19 import threading | |
20 except ImportError: | |
21 import dummy_threading as threading | |
22 | |
23 __all__ = ['Locale', 'negotiate', 'parse'] | |
24 __docformat__ = 'restructuredtext en' | |
25 | |
26 | |
27 class Locale(object): | |
28 """Representation of a specific locale. | |
29 | |
30 >>> locale = Locale('en', territory='US') | |
31 >>> repr(locale) | |
32 '<Locale "en_US">' | |
33 >>> locale.display_name | |
34 u'English (United States)' | |
35 | |
36 A `Locale` object can also be instantiated from a raw locale string: | |
37 | |
38 >>> locale = Locale.parse('en-US', sep='-') | |
39 >>> repr(locale) | |
40 '<Locale "en_US">' | |
41 | |
42 `Locale` objects provide access to a collection of locale data, such as | |
43 territory and language names, number and date format patterns, and more: | |
44 | |
45 >>> locale.number_symbols['decimal'] | |
46 u'.' | |
47 | |
48 :see: `IETF RFC 3066 <http://www.ietf.org/rfc/rfc3066.txt>`_ | |
49 """ | |
50 _cache = {} | |
51 _cache_lock = threading.Lock() | |
52 | |
53 def __new__(cls, language, territory=None, variant=None): | |
54 """Create new locale object, or load it from the cache if it had already | |
55 been instantiated. | |
56 | |
57 >>> l1 = Locale('en') | |
58 >>> l2 = Locale('en') | |
59 >>> l1 is l2 | |
60 True | |
61 | |
62 :param language: the language code | |
63 :param territory: the territory (country or region) code | |
64 :param variant: the variant code | |
65 :return: new or existing `Locale` instance | |
66 :rtype: `Locale` | |
67 """ | |
68 key = (language, territory, variant) | |
69 cls._cache_lock.acquire() | |
70 try: | |
71 self = cls._cache.get(key) | |
72 if self is None: | |
73 self = super(Locale, cls).__new__(cls, language, territory, | |
74 variant) | |
75 cls._cache[key] = self | |
76 return self | |
77 finally: | |
78 self._cache_lock.release() | |
79 | |
80 def __init__(self, language, territory=None, variant=None): | |
81 """Initialize the locale object from the given identifier components. | |
82 | |
83 >>> locale = Locale('en', 'US') | |
84 >>> locale.language | |
85 'en' | |
86 >>> locale.territory | |
87 'US' | |
88 | |
89 :param language: the language code | |
90 :param territory: the territory (country or region) code | |
91 :param variant: the variant code | |
92 """ | |
93 self.language = language | |
94 self.territory = territory | |
95 self.variant = variant | |
96 self.__data = None | |
97 | |
98 def parse(cls, identifier, sep='_'): | |
99 """Create a `Locale` instance for the given locale identifier. | |
100 | |
101 >>> l = Locale.parse('de-DE', sep='-') | |
102 >>> l.display_name | |
103 u'Deutsch (Deutschland)' | |
104 | |
105 If the `identifier` parameter is not a string, but actually a `Locale` | |
106 object, that object is returned: | |
107 | |
108 >>> Locale.parse(l) | |
109 <Locale "de_DE"> | |
110 | |
111 :param identifier: the locale identifier string | |
112 :param sep: optional component separator | |
113 :return: a corresponding `Locale` instance | |
114 :rtype: `Locale` | |
115 :raise `ValueError`: if the string does not appear to be a valid locale | |
116 identifier | |
117 """ | |
118 if type(identifier) is cls: | |
119 return identifier | |
120 return cls(*parse(identifier, sep=sep)) | |
121 parse = classmethod(parse) | |
122 | |
123 def __repr__(self): | |
124 return '<Locale "%s">' % str(self) | |
125 | |
126 def __str__(self): | |
127 return '_'.join(filter(None, [self.language, self.territory, | |
128 self.variant])) | |
129 | |
130 def _data(self): | |
131 if self.__data is None: | |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
132 filename = os.path.join(os.path.dirname(__file__), |
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
133 'localedata/%s.dat' % self) |
3 | 134 fileobj = open(filename, 'rb') |
135 try: | |
136 self.__data = pickle.load(fileobj) | |
137 finally: | |
138 fileobj.close() | |
139 return self.__data | |
140 _data = property(_data) | |
141 | |
142 def display_name(self): | |
143 retval = self.languages.get(self.language) | |
144 if self.territory: | |
145 variant = '' | |
146 if self.variant: | |
147 variant = ', %s' % self.variants.get(self.variant) | |
148 retval += ' (%s%s)' % (self.territories.get(self.territory), variant) | |
149 return retval | |
150 display_name = property(display_name, doc="""\ | |
151 The localized display name of the locale. | |
152 | |
153 >>> Locale('en').display_name | |
154 u'English' | |
155 >>> Locale('en', 'US').display_name | |
156 u'English (United States)' | |
157 | |
158 :type: `unicode` | |
159 """) | |
160 | |
10
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
161 #{ General Locale Display Names |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
162 |
3 | 163 def languages(self): |
164 return self._data['languages'] | |
165 languages = property(languages, doc="""\ | |
166 Mapping of language codes to translated language names. | |
167 | |
168 >>> Locale('de', 'DE').languages['ja'] | |
169 u'Japanisch' | |
170 | |
171 :type: `dict` | |
172 :see: `ISO 639 <http://www.loc.gov/standards/iso639-2/>`_ | |
173 """) | |
174 | |
175 def scripts(self): | |
176 return self._data['scripts'] | |
177 scripts = property(scripts, doc="""\ | |
178 Mapping of script codes to translated script names. | |
179 | |
180 >>> Locale('en', 'US').scripts['Hira'] | |
181 u'Hiragana' | |
182 | |
183 :type: `dict` | |
184 :see: `ISO 15924 <http://www.evertype.com/standards/iso15924/>`_ | |
185 """) | |
186 | |
187 def territories(self): | |
188 return self._data['territories'] | |
189 territories = property(territories, doc="""\ | |
190 Mapping of script codes to translated script names. | |
191 | |
192 >>> Locale('es', 'CO').territories['DE'] | |
193 u'Alemania' | |
194 | |
195 :type: `dict` | |
196 :see: `ISO 3166 <http://www.iso.org/iso/en/prods-services/iso3166ma/>`_ | |
197 """) | |
198 | |
199 def variants(self): | |
200 return self._data['variants'] | |
201 variants = property(variants, doc="""\ | |
202 Mapping of script codes to translated script names. | |
203 | |
204 >>> Locale('de', 'DE').variants['1901'] | |
205 u'alte deutsche Rechtschreibung' | |
206 | |
207 :type: `dict` | |
208 """) | |
209 | |
10
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
210 #{ Number Formatting |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
211 |
3 | 212 def number_symbols(self): |
213 return self._data['number_symbols'] | |
214 number_symbols = property(number_symbols, doc="""\ | |
215 Symbols used in number formatting. | |
216 | |
217 >>> Locale('fr', 'FR').number_symbols['decimal'] | |
218 u',' | |
219 | |
220 :type: `dict` | |
221 """) | |
222 | |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
223 def decimal_formats(self): |
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
224 return self._data['decimal_formats'] |
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
225 decimal_formats = property(decimal_formats, doc="""\ |
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
226 Locale patterns for decimal number formatting. |
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
227 |
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
228 >>> Locale('en', 'US').decimal_formats[None] |
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
229 <NumberPattern u'#,##0.###'> |
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
230 |
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
231 :type: `dict` |
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
232 """) |
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
233 |
10
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
234 #{ Calendar Information and Date Formatting |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
235 |
3 | 236 def periods(self): |
237 return self._data['periods'] | |
238 periods = property(periods, doc="""\ | |
239 Locale display names for day periods (AM/PM). | |
240 | |
241 >>> Locale('en', 'US').periods['am'] | |
242 u'AM' | |
243 | |
244 :type: `dict` | |
245 """) | |
246 | |
247 def days(self): | |
248 return self._data['days'] | |
249 days = property(days, doc="""\ | |
250 Locale display names for weekdays. | |
251 | |
17 | 252 >>> Locale('de', 'DE').days['format']['wide'][3] |
3 | 253 u'Donnerstag' |
254 | |
255 :type: `dict` | |
256 """) | |
257 | |
258 def months(self): | |
259 return self._data['months'] | |
260 months = property(months, doc="""\ | |
261 Locale display names for months. | |
262 | |
263 >>> Locale('de', 'DE').months['format']['wide'][10] | |
264 u'Oktober' | |
265 | |
266 :type: `dict` | |
267 """) | |
268 | |
269 def quarters(self): | |
270 return self._data['quarters'] | |
271 quarters = property(quarters, doc="""\ | |
272 Locale display names for quarters. | |
273 | |
274 >>> Locale('de', 'DE').quarters['format']['wide'][1] | |
275 u'1. Quartal' | |
276 | |
277 :type: `dict` | |
278 """) | |
279 | |
280 def eras(self): | |
281 return self._data['eras'] | |
282 eras = property(eras, doc="""\ | |
283 Locale display names for eras. | |
284 | |
285 >>> Locale('en', 'US').eras['wide'][1] | |
286 u'Anno Domini' | |
287 >>> Locale('en', 'US').eras['abbreviated'][0] | |
288 u'BC' | |
289 | |
290 :type: `dict` | |
291 """) | |
292 | |
10
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
293 def first_week_day(self): |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
294 return self._data['week_data']['first_day'] |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
295 first_week_day = property(first_week_day, doc="""\ |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
296 The first day of a week. |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
297 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
298 >>> Locale('de', 'DE').first_week_day |
17 | 299 0 |
10
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
300 >>> Locale('en', 'US').first_week_day |
17 | 301 6 |
10
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
302 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
303 :type: `int` |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
304 """) |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
305 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
306 def weekend_start(self): |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
307 return self._data['week_data']['weekend_start'] |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
308 weekend_start = property(weekend_start, doc="""\ |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
309 The day the weekend starts. |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
310 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
311 >>> Locale('de', 'DE').weekend_start |
17 | 312 5 |
10
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
313 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
314 :type: `int` |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
315 """) |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
316 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
317 def weekend_end(self): |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
318 return self._data['week_data']['weekend_end'] |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
319 weekend_end = property(weekend_end, doc="""\ |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
320 The day the weekend ends. |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
321 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
322 >>> Locale('de', 'DE').weekend_end |
17 | 323 6 |
10
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
324 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
325 :type: `int` |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
326 """) |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
327 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
328 def min_week_days(self): |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
329 return self._data['week_data']['min_days'] |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
330 min_week_days = property(min_week_days, doc="""\ |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
331 The minimum number of days in a week so that the week is counted as the |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
332 first week of a year or month. |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
333 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
334 >>> Locale('de', 'DE').min_week_days |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
335 4 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
336 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
337 :type: `int` |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
338 """) |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
339 |
3 | 340 def date_formats(self): |
341 return self._data['date_formats'] | |
342 date_formats = property(date_formats, doc="""\ | |
343 Locale patterns for date formatting. | |
344 | |
345 >>> Locale('en', 'US').date_formats['short'] | |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
346 <DateTimePattern u'M/d/yy'> |
3 | 347 >>> Locale('fr', 'FR').date_formats['long'] |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
348 <DateTimePattern u'd MMMM yyyy'> |
3 | 349 |
350 :type: `dict` | |
351 """) | |
352 | |
353 def time_formats(self): | |
354 return self._data['time_formats'] | |
355 time_formats = property(time_formats, doc="""\ | |
356 Locale patterns for time formatting. | |
357 | |
358 >>> Locale('en', 'US').time_formats['short'] | |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
359 <DateTimePattern u'h:mm a'> |
3 | 360 >>> Locale('fr', 'FR').time_formats['long'] |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
11
diff
changeset
|
361 <DateTimePattern u'HH:mm:ss z'> |
11 | 362 |
363 :type: `dict` | |
364 """) | |
365 | |
3 | 366 |
367 def negotiate(preferred, available): | |
368 """Find the best match between available and requested locale strings. | |
369 | |
370 >>> negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT']) | |
371 'de_DE' | |
372 >>> negotiate(['de_DE', 'en_US'], ['en', 'de']) | |
373 'de' | |
374 | |
375 :param preferred: the list of locale strings preferred by the user | |
376 :param available: the list of locale strings available | |
377 :return: the locale identifier for the best match, or `None` if no match | |
378 was found | |
379 :rtype: `str` | |
380 """ | |
381 for locale in preferred: | |
382 if locale in available: | |
383 return locale | |
384 parts = locale.split('_') | |
385 if len(parts) > 1 and parts[0] in available: | |
386 return parts[0] | |
387 return None | |
388 | |
389 def parse(identifier, sep='_'): | |
390 """Parse a locale identifier into a ``(language, territory, variant)`` | |
391 tuple. | |
392 | |
393 >>> parse('zh_CN') | |
394 ('zh', 'CN', None) | |
395 | |
396 The default component separator is "_", but a different separator can be | |
397 specified using the `sep` parameter: | |
398 | |
399 >>> parse('zh-CN', sep='-') | |
400 ('zh', 'CN', None) | |
401 | |
402 :param identifier: the locale identifier string | |
403 :param sep: character that separates the different parts of the locale | |
404 string | |
405 :return: the ``(language, territory, variant)`` tuple | |
406 :rtype: `tuple` | |
407 :raise `ValueError`: if the string does not appear to be a valid locale | |
408 identifier | |
409 | |
410 :see: `IETF RFC 3066 <http://www.ietf.org/rfc/rfc3066.txt>`_ | |
411 """ | |
412 parts = identifier.split(sep) | |
413 lang, territory, variant = parts[0].lower(), None, None | |
414 if not lang.isalpha(): | |
415 raise ValueError('expected only letters, got %r' % lang) | |
416 if len(parts) > 1: | |
417 territory = parts[1].upper().split('.', 1)[0] | |
418 if not territory.isalpha(): | |
419 raise ValueError('expected only letters, got %r' % territory) | |
420 if len(parts) > 2: | |
421 variant = parts[2].upper().split('.', 1)[0] | |
422 return lang, territory, variant |