1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2007-2011 Edgewall Software, 2013-2021 the Babel team
4# All rights reserved.
5#
6# This software is licensed as described in the file LICENSE, 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
14import pytest
15
16from babel import core
17from babel.core import default_locale, Locale
18
19
20def test_locale_provides_access_to_cldr_locale_data():
21    locale = Locale('en', 'US')
22    assert u'English (United States)' == locale.display_name
23    assert u'.' == locale.number_symbols['decimal']
24
25
26def test_locale_repr():
27    assert repr(Locale('en', 'US')) == "Locale('en', territory='US')"
28    assert ("Locale('de', territory='DE')" == repr(Locale('de', 'DE')))
29    assert ("Locale('zh', territory='CN', script='Hans')" ==
30            repr(Locale('zh', 'CN', script='Hans')))
31
32
33def test_locale_comparison():
34    en_US = Locale('en', 'US')
35    en_US_2 = Locale('en', 'US')
36    fi_FI = Locale('fi', 'FI')
37    bad_en_US = Locale('en_US')
38    assert en_US == en_US
39    assert en_US == en_US_2
40    assert en_US != fi_FI
41    assert not (en_US != en_US_2)
42    assert en_US is not None
43    assert en_US != bad_en_US
44    assert fi_FI != bad_en_US
45
46
47def test_can_return_default_locale(os_environ):
48    os_environ['LC_MESSAGES'] = 'fr_FR.UTF-8'
49    assert Locale('fr', 'FR') == Locale.default('LC_MESSAGES')
50
51
52def test_ignore_invalid_locales_in_lc_ctype(os_environ):
53    # This is a regression test specifically for a bad LC_CTYPE setting on
54    # MacOS X 10.6 (#200)
55    os_environ['LC_CTYPE'] = 'UTF-8'
56    # must not throw an exception
57    default_locale('LC_CTYPE')
58
59
60def test_get_global():
61    assert core.get_global('zone_aliases')['GMT'] == 'Etc/GMT'
62    assert core.get_global('zone_aliases')['UTC'] == 'Etc/UTC'
63    assert core.get_global('zone_territories')['Europe/Berlin'] == 'DE'
64
65
66def test_hash():
67    locale_a = Locale('en', 'US')
68    locale_b = Locale('en', 'US')
69    locale_c = Locale('fi', 'FI')
70    assert hash(locale_a) == hash(locale_b)
71    assert hash(locale_a) != hash(locale_c)
72
73
74class TestLocaleClass:
75
76    def test_attributes(self):
77        locale = Locale('en', 'US')
78        assert locale.language == 'en'
79        assert locale.territory == 'US'
80
81    def test_default(self, os_environ):
82        for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
83            os_environ[name] = ''
84        os_environ['LANG'] = 'fr_FR.UTF-8'
85        default = Locale.default('LC_MESSAGES')
86        assert (default.language, default.territory) == ('fr', 'FR')
87
88    def test_negotiate(self):
89        de_DE = Locale.negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
90        assert (de_DE.language, de_DE.territory) == ('de', 'DE')
91        de = Locale.negotiate(['de_DE', 'en_US'], ['en', 'de'])
92        assert (de.language, de.territory) == ('de', None)
93        nothing = Locale.negotiate(['de_DE', 'de'], ['en_US'])
94        assert nothing is None
95
96    def test_negotiate_custom_separator(self):
97        de_DE = Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-')
98        assert (de_DE.language, de_DE.territory) == ('de', 'DE')
99
100    def test_parse(self):
101        l = Locale.parse('de-DE', sep='-')
102        assert l.display_name == 'Deutsch (Deutschland)'
103
104        de_DE = Locale.parse(l)
105        assert (de_DE.language, de_DE.territory) == ('de', 'DE')
106
107    def test_parse_likely_subtags(self):
108        l = Locale.parse('zh-TW', sep='-')
109        assert l.language == 'zh'
110        assert l.territory == 'TW'
111        assert l.script == 'Hant'
112
113        l = Locale.parse('zh_CN')
114        assert l.language == 'zh'
115        assert l.territory == 'CN'
116        assert l.script == 'Hans'
117
118        l = Locale.parse('zh_SG')
119        assert l.language == 'zh'
120        assert l.territory == 'SG'
121        assert l.script == 'Hans'
122
123        l = Locale.parse('und_AT')
124        assert l.language == 'de'
125        assert l.territory == 'AT'
126
127        l = Locale.parse('und_UK')
128        assert l.language == 'en'
129        assert l.territory == 'GB'
130        assert l.script is None
131
132    def test_get_display_name(self):
133        zh_CN = Locale('zh', 'CN', script='Hans')
134        assert zh_CN.get_display_name('en') == 'Chinese (Simplified, China)'
135
136    def test_display_name_property(self):
137        assert Locale('en').display_name == 'English'
138        assert Locale('en', 'US').display_name == 'English (United States)'
139        assert Locale('sv').display_name == 'svenska'
140
141    def test_english_name_property(self):
142        assert Locale('de').english_name == 'German'
143        assert Locale('de', 'DE').english_name == 'German (Germany)'
144
145    def test_languages_property(self):
146        assert Locale('de', 'DE').languages['ja'] == 'Japanisch'
147
148    def test_scripts_property(self):
149        assert Locale('en', 'US').scripts['Hira'] == 'Hiragana'
150
151    def test_territories_property(self):
152        assert Locale('es', 'CO').territories['DE'] == 'Alemania'
153
154    def test_variants_property(self):
155        assert (Locale('de', 'DE').variants['1901'] ==
156                'Alte deutsche Rechtschreibung')
157
158    def test_currencies_property(self):
159        assert Locale('en').currencies['COP'] == 'Colombian Peso'
160        assert Locale('de', 'DE').currencies['COP'] == 'Kolumbianischer Peso'
161
162    def test_currency_symbols_property(self):
163        assert Locale('en', 'US').currency_symbols['USD'] == '$'
164        assert Locale('es', 'CO').currency_symbols['USD'] == 'US$'
165
166    def test_number_symbols_property(self):
167        assert Locale('fr', 'FR').number_symbols['decimal'] == ','
168
169    def test_decimal_formats(self):
170        assert Locale('en', 'US').decimal_formats[None].pattern == '#,##0.###'
171
172    def test_currency_formats_property(self):
173        assert (Locale('en', 'US').currency_formats['standard'].pattern ==
174                u'\xa4#,##0.00')
175        assert (Locale('en', 'US').currency_formats['accounting'].pattern ==
176                u'\xa4#,##0.00;(\xa4#,##0.00)')
177
178    def test_percent_formats_property(self):
179        assert Locale('en', 'US').percent_formats[None].pattern == '#,##0%'
180
181    def test_scientific_formats_property(self):
182        assert Locale('en', 'US').scientific_formats[None].pattern == '#E0'
183
184    def test_periods_property(self):
185        assert Locale('en', 'US').periods['am'] == 'AM'
186
187    def test_days_property(self):
188        assert Locale('de', 'DE').days['format']['wide'][3] == 'Donnerstag'
189
190    def test_months_property(self):
191        assert Locale('de', 'DE').months['format']['wide'][10] == 'Oktober'
192
193    def test_quarters_property(self):
194        assert Locale('de', 'DE').quarters['format']['wide'][1] == '1. Quartal'
195
196    def test_eras_property(self):
197        assert Locale('en', 'US').eras['wide'][1] == 'Anno Domini'
198        assert Locale('en', 'US').eras['abbreviated'][0] == 'BC'
199
200    def test_time_zones_property(self):
201        time_zones = Locale('en', 'US').time_zones
202        assert (time_zones['Europe/London']['long']['daylight'] ==
203                'British Summer Time')
204        assert time_zones['America/St_Johns']['city'] == u'St. John\u2019s'
205
206    def test_meta_zones_property(self):
207        meta_zones = Locale('en', 'US').meta_zones
208        assert (meta_zones['Europe_Central']['long']['daylight'] ==
209                'Central European Summer Time')
210
211    def test_zone_formats_property(self):
212        assert Locale('en', 'US').zone_formats['fallback'] == '%(1)s (%(0)s)'
213        assert Locale('pt', 'BR').zone_formats['region'] == u'Hor\xe1rio %s'
214
215    def test_first_week_day_property(self):
216        assert Locale('de', 'DE').first_week_day == 0
217        assert Locale('en', 'US').first_week_day == 6
218
219    def test_weekend_start_property(self):
220        assert Locale('de', 'DE').weekend_start == 5
221
222    def test_weekend_end_property(self):
223        assert Locale('de', 'DE').weekend_end == 6
224
225    def test_min_week_days_property(self):
226        assert Locale('de', 'DE').min_week_days == 4
227
228    def test_date_formats_property(self):
229        assert Locale('en', 'US').date_formats['short'].pattern == 'M/d/yy'
230        assert Locale('fr', 'FR').date_formats['long'].pattern == 'd MMMM y'
231
232    def test_time_formats_property(self):
233        assert Locale('en', 'US').time_formats['short'].pattern == 'h:mm a'
234        assert Locale('fr', 'FR').time_formats['long'].pattern == 'HH:mm:ss z'
235
236    def test_datetime_formats_property(self):
237        assert Locale('en').datetime_formats['full'] == u"{1} 'at' {0}"
238        assert Locale('th').datetime_formats['medium'] == u'{1} {0}'
239
240    def test_datetime_skeleton_property(self):
241        assert Locale('en').datetime_skeletons['Md'].pattern == u"M/d"
242        assert Locale('th').datetime_skeletons['Md'].pattern == u'd/M'
243
244    def test_plural_form_property(self):
245        assert Locale('en').plural_form(1) == 'one'
246        assert Locale('en').plural_form(0) == 'other'
247        assert Locale('fr').plural_form(0) == 'one'
248        assert Locale('ru').plural_form(100) == 'many'
249
250
251def test_default_locale(os_environ):
252    for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
253        os_environ[name] = ''
254    os_environ['LANG'] = 'fr_FR.UTF-8'
255    assert default_locale('LC_MESSAGES') == 'fr_FR'
256
257    os_environ['LC_MESSAGES'] = 'POSIX'
258    assert default_locale('LC_MESSAGES') == 'en_US_POSIX'
259
260    for value in ['C', 'C.UTF-8', 'POSIX']:
261        os_environ['LANGUAGE'] = value
262        assert default_locale() == 'en_US_POSIX'
263
264
265def test_negotiate_locale():
266    assert (core.negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT']) ==
267            'de_DE')
268    assert core.negotiate_locale(['de_DE', 'en_US'], ['en', 'de']) == 'de'
269    assert (core.negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at']) ==
270            'de_DE')
271    assert (core.negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at']) ==
272            'de_DE')
273    assert (core.negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US']) ==
274            'ja_JP')
275    assert core.negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE']) == 'nb_NO'
276
277
278def test_parse_locale():
279    assert core.parse_locale('zh_CN') == ('zh', 'CN', None, None)
280    assert core.parse_locale('zh_Hans_CN') == ('zh', 'CN', 'Hans', None)
281    assert core.parse_locale('zh-CN', sep='-') == ('zh', 'CN', None, None)
282
283    with pytest.raises(ValueError) as excinfo:
284        core.parse_locale('not_a_LOCALE_String')
285    assert (excinfo.value.args[0] ==
286            "'not_a_LOCALE_String' is not a valid locale identifier")
287
288    assert core.parse_locale('it_IT@euro') == ('it', 'IT', None, None)
289    assert core.parse_locale('en_US.UTF-8') == ('en', 'US', None, None)
290    assert (core.parse_locale('de_DE.iso885915@euro') ==
291            ('de', 'DE', None, None))
292
293
294@pytest.mark.parametrize('filename', [
295    'babel/global.dat',
296    'babel/locale-data/root.dat',
297    'babel/locale-data/en.dat',
298    'babel/locale-data/en_US.dat',
299    'babel/locale-data/en_US_POSIX.dat',
300    'babel/locale-data/zh_Hans_CN.dat',
301    'babel/locale-data/zh_Hant_TW.dat',
302    'babel/locale-data/es_419.dat',
303])
304def test_compatible_classes_in_global_and_localedata(filename):
305    # Use pickle module rather than cPickle since cPickle.Unpickler is a method
306    # on Python 2
307    import pickle
308
309    class Unpickler(pickle.Unpickler):
310
311        def find_class(self, module, name):
312            # *.dat files must have compatible classes between Python 2 and 3
313            if module.split('.')[0] == 'babel':
314                return pickle.Unpickler.find_class(self, module, name)
315            raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
316                                         (module, name))
317
318    with open(filename, 'rb') as f:
319        return Unpickler(f).load()
320
321
322def test_issue_601_no_language_name_but_has_variant():
323    # kw_GB has a variant for Finnish but no actual language name for Finnish,
324    # so `get_display_name()` previously crashed with a TypeError as it attempted
325    # to concatenate " (Finnish)" to None.
326    # Instead, it's better to return None altogether, as we can't reliably format
327    # part of a language name.
328
329    assert Locale.parse('fi_FI').get_display_name('kw_GB') == None
330