Ticket #9289: django_localflavor_se_r6.diff
File django_localflavor_se_r6.diff, 23.1 KB (added by , 16 years ago) |
---|
-
django/contrib/localflavor/se/se_counties.py
1 # -*- coding: utf-8 -*- 2 """ 3 An alphabetical list of Swedish counties, sorted by codes. 4 5 http://en.wikipedia.org/wiki/Counties_of_Sweden 6 7 This exists in this standalone file so that it's only imported into memory 8 when explicitly needed. 9 10 """ 11 12 from django.utils.translation import ugettext_lazy as _ 13 14 COUNTY_CHOICES = ( 15 ('AB', _(u'Stockholm')), 16 ('AC', _(u'Västerbotten')), 17 ('BD', _(u'Norrbotten')), 18 ('C', _(u'Uppsala')), 19 ('D', _(u'Södermanland')), 20 ('E', _(u'Östergötland')), 21 ('F', _(u'Jönköping')), 22 ('G', _(u'Kronoberg')), 23 ('H', _(u'Kalmar')), 24 ('I', _(u'Gotland')), 25 ('K', _(u'Blekinge')), 26 ('M', _(u'Skåne')), 27 ('N', _(u'Halland')), 28 ('O', _(u'Västra Götaland')), 29 ('S', _(u'Värmland')), 30 ('T', _(u'Örebro')), 31 ('U', _(u'Västmanland')), 32 ('W', _(u'Dalarna')), 33 ('X', _(u'Gävleborg')), 34 ('Y', _(u'Västernorrland')), 35 ('Z', _(u'Jämtland')), 36 ) -
django/contrib/localflavor/se/utils.py
1 import re 2 import datetime 3 4 SWEDISH_ID_NUMBER = re.compile(r'^(?P<century>\d{2})?(?P<year>\d{2})(?P<month>\d{2})(?P<day>\d{2})(?P<sign>[\-+])?(?P<serial>\d{3})(?P<checksum>\d)$') 5 SE_POSTAL_CODE = re.compile(r'^[1-9]\d{2} ?\d{2}$') 6 7 def id_number_checksum(gd): 8 """ 9 Calculates a Swedish ID number checksum, using the 10 "Luhn"-algoritm 11 """ 12 n = s = 0 13 for c in (gd['year'] + gd['month'] + gd['day'] + gd['serial']): 14 tmp = ((n % 2) and 1 or 2) * int(c) 15 16 if tmp > 9: 17 tmp = sum([int(i) for i in str(tmp)]) 18 19 s += tmp 20 n += 1 21 22 if (s % 10) == 0: 23 return 0 24 25 return (((s / 10) + 1) * 10) - s 26 27 def validate_id_birthday(gd, fix_coordination_number_day=True): 28 """ 29 Validates the birth_day and returns the datetime.date object for 30 the birth_day. 31 32 If the date is an invalid birth day, a ValueError will be raised. 33 """ 34 35 today = datetime.date.today() 36 37 day = int(gd['day']) 38 if fix_coordination_number_day and day > 60: 39 day -= 60 40 41 if gd['century'] is None: 42 43 # The century was not specified, and need to be calculated from todays date 44 current_year = today.year 45 year = int(today.strftime('%Y')) - int(today.strftime('%y')) + int(gd['year']) 46 47 if ('%s%s%02d' % (gd['year'], gd['month'], day)) > today.strftime('%y%m%d'): 48 year -= 100 49 50 # If the person is older than 100 years 51 if gd['sign'] == '+': 52 year -= 100 53 else: 54 year = int(gd['century'] + gd['year']) 55 56 # Make sure the year is valid 57 # There are no swedish personal identity numbers where year < 1800 58 if year < 1800: 59 raise ValueError 60 61 # ValueError will be raise for invalid dates 62 birth_day = datetime.date(year, int(gd['month']), day) 63 64 # birth_day must not be in the future 65 if birth_day > today: 66 raise ValueError 67 68 return birth_day 69 70 def format_personal_id_number(birth_day, gd): 71 # birth_day.strftime cannot be used, since it does not support dates < 1900 72 return unicode(str(birth_day.year) + gd['month'] + gd['day'] + gd['serial'] + gd['checksum']) 73 74 def format_organisation_number(gd): 75 if gd['century'] is None: 76 century = '' 77 else: 78 century = gd['century'] 79 80 return unicode(century + gd['year'] + gd['month'] + gd['day'] + gd['serial'] + gd['checksum']) 81 82 def valid_organisation(gd): 83 return gd['century'] in (None, 16) and \ 84 int(gd['month']) >= 20 and \ 85 gd['sign'] in (None, '-') and \ 86 gd['year'][0] in ('2', '5', '7', '8', '9') # group identifier 87 -
django/contrib/localflavor/se/forms.py
1 # -*- coding: utf-8 -*- 2 """ 3 Swedish specific Form helpers 4 """ 5 6 from django import forms 7 from django.utils.translation import ugettext_lazy as _ 8 from django.forms.fields import EMPTY_VALUES 9 10 from utils import * 11 12 __all__ = ('SECountySelect', 'SEOrganisationNumberField', 'SEPersonalIdentityNumberField', 'SEPostalCodeField') 13 14 class SECountySelect(forms.Select): 15 """ 16 A Select form widget that uses a list of the Swedish counties (län) as its 17 choices. 18 19 The cleaned value is the official county code -- see 20 http://en.wikipedia.org/wiki/Counties_of_Sweden for a list. 21 """ 22 23 def __init__(self, attrs=None): 24 from se_counties import COUNTY_CHOICES 25 super(SECountySelect, self).__init__(attrs=attrs, 26 choices=COUNTY_CHOICES) 27 28 class SEOrganisationNumberField(forms.CharField): 29 """ 30 A form field that validates input as a Swedish organisation number 31 (organisationsnummer). 32 33 It accepts the same input as SEPersonalIdentityField (for sole 34 proprietorships (enskild firma). However, co-ordination numbers are not 35 accepted. 36 37 It also accepts ordinary Swedish organisation numbers with the format 38 NNNNNNNNNN. 39 40 The return value will be YYYYMMDDXXXX for sole proprietors, and NNNNNNNNNN 41 for other organisations. 42 """ 43 44 default_error_messages = { 45 'invalid': _('Enter a valid Swedish organisation number.'), 46 } 47 48 def clean(self, value): 49 value = super(SEOrganisationNumberField, self).clean(value) 50 51 if value in EMPTY_VALUES: 52 return u'' 53 54 match = SWEDISH_ID_NUMBER.match(value) 55 if not match: 56 raise forms.ValidationError(self.error_messages['invalid']) 57 58 gd = match.groupdict() 59 60 # Compare the calculated value with the checksum 61 if id_number_checksum(gd) != int(gd['checksum']): 62 raise forms.ValidationError(self.error_messages['invalid']) 63 64 # First: check if this is a real organisation_number 65 if valid_organisation(gd): 66 return format_organisation_number(gd) 67 68 # Is this a single properitor (enskild firma)? 69 try: 70 birth_day = validate_id_birthday(gd, False) 71 return format_personal_id_number(birth_day, gd) 72 except ValueError: 73 raise forms.ValidationError(self.error_messages['invalid']) 74 75 76 class SEPersonalIdentityNumberField(forms.CharField): 77 """ 78 A form field that validates input as a Swedish personal identity number 79 (personnummer). 80 81 The correct formats are YYYYMMDD-XXXX, YYYYMMDDXXXX, YYMMDD-XXXX, 82 YYMMDDXXXX and YYMMDD+XXXX. 83 84 A + indicates that the person is older than 100 years, which will be taken 85 into consideration when the date is validated. 86 87 The checksum will be calculated and checked. The birth date is checked to 88 be a valid date. 89 90 By default, co-ordination numbers (samordningsnummer) will be accepted. To 91 only allow real personal identity numbers, pass the keyword argument 92 coordination_number=False to the constructor. 93 94 The cleaned value will always have the format YYYYMMDDXXXX. 95 """ 96 97 def __init__(self, coordination_number=True, *args, **kwargs): 98 self.coordination_number = coordination_number 99 super(SEPersonalIdentityNumberField, self).__init__(*args, **kwargs) 100 101 default_error_messages = { 102 'invalid': _('Enter a valid Swedish personal identity number.'), 103 'coordination_number': _('Co-ordination numbers are not allowed.'), 104 } 105 106 def clean(self, value): 107 value = super(SEPersonalIdentityNumberField, self).clean(value) 108 109 if value in EMPTY_VALUES: 110 return u'' 111 112 match = SWEDISH_ID_NUMBER.match(value) 113 if match is None: 114 raise forms.ValidationError(self.error_messages['invalid']) 115 116 gd = match.groupdict() 117 118 # compare the calculated value with the checksum 119 if id_number_checksum(gd) != int(gd['checksum']): 120 raise forms.ValidationError(self.error_messages['invalid']) 121 122 # check for valid birthday 123 try: 124 birth_day = validate_id_birthday(gd) 125 except ValueError: 126 raise forms.ValidationError(self.error_messages['invalid']) 127 128 # make sure that co-ordination numbers do not pass if not allowed 129 if not self.coordination_number and int(gd['day']) > 60: 130 raise forms.ValidationError(self.error_messages['coordination_number']) 131 132 return format_personal_id_number(birth_day, gd) 133 134 135 class SEPostalCodeField(forms.RegexField): 136 """ 137 A form field that validates input as a Swedish postal code (postnummer). 138 Valid codes consist of five digits (XXXXX). The number can optionally be 139 formatted with a space after the third digit (XXX XX). 140 141 The cleaned value will never contain the space. 142 """ 143 144 default_error_messages = { 145 'invalid': _('Enter a Swedish postal code in the format XXXXX.'), 146 } 147 148 def __init__(self, *args, **kwargs): 149 super(SEPostalCodeField, self).__init__(SE_POSTAL_CODE, *args, **kwargs) 150 151 def clean(self, value): 152 return super(SEPostalCodeField, self).clean(value).replace(' ', '') -
tests/regressiontests/forms/localflavor/se.py
1 # -*- coding: utf-8 -*- 2 # Tests for the contrib/localflavor/se form fields. 3 4 tests = r""" 5 # Monkey-patch datetime.date 6 >>> import datetime 7 >>> class MockDate(datetime.date): 8 ... def today(cls): 9 ... return datetime.date(2008, 5, 14) 10 ... today = classmethod(today) 11 ... 12 >>> olddate = datetime.date 13 >>> datetime.date = MockDate 14 >>> datetime.date.today() 15 MockDate(2008, 5, 14) 16 17 18 # SECountySelect ##################################################### 19 >>> from django.contrib.localflavor.se.forms import SECountySelect 20 21 >>> w = SECountySelect() 22 >>> w.render('swedish_county', 'E') 23 u'<select name="swedish_county">\n<option value="AB">Stockholm</option>\n<option value="AC">V\xe4sterbotten</option>\n<option value="BD">Norrbotten</option>\n<option value="C">Uppsala</option>\n<option value="D">S\xf6dermanland</option>\n<option value="E" selected="selected">\xd6sterg\xf6tland</option>\n<option value="F">J\xf6nk\xf6ping</option>\n<option value="G">Kronoberg</option>\n<option value="H">Kalmar</option>\n<option value="I">Gotland</option>\n<option value="K">Blekinge</option>\n<option value="M">Sk\xe5ne</option>\n<option value="N">Halland</option>\n<option value="O">V\xe4stra G\xf6taland</option>\n<option value="S">V\xe4rmland</option>\n<option value="T">\xd6rebro</option>\n<option value="U">V\xe4stmanland</option>\n<option value="W">Dalarna</option>\n<option value="X">G\xe4vleborg</option>\n<option value="Y">V\xe4sternorrland</option>\n<option value="Z">J\xe4mtland</option>\n</select>' 24 25 # SEOrganisationNumberField ####################################### 26 27 >>> from django.contrib.localflavor.se.forms import SEOrganisationNumberField 28 29 >>> f = SEOrganisationNumberField() 30 31 # Ordinary personal identity numbers for sole proprietors 32 # The same rules as for SEPersonalIdentityField applies here 33 >>> f.clean('870512-1989') 34 u'198705121989' 35 >>> f.clean('19870512-1989') 36 u'198705121989' 37 >>> f.clean('870512-2128') 38 u'198705122128' 39 >>> f.clean('081015-6315') 40 u'190810156315' 41 >>> f.clean('081015+6315') 42 u'180810156315' 43 >>> f.clean('0810156315') 44 u'190810156315' 45 46 >>> f.clean('081015 6315') 47 Traceback (most recent call last): 48 ... 49 ValidationError: [u'Enter a valid Swedish organisation number.'] 50 >>> f.clean('950231-4496') 51 Traceback (most recent call last): 52 ... 53 ValidationError: [u'Enter a valid Swedish organisation number.'] 54 >>> f.clean('6914104499') 55 Traceback (most recent call last): 56 ... 57 ValidationError: [u'Enter a valid Swedish organisation number.'] 58 >>> f.clean('950d314496') 59 Traceback (most recent call last): 60 ... 61 ValidationError: [u'Enter a valid Swedish organisation number.'] 62 >>> f.clean('invalid!!!') 63 Traceback (most recent call last): 64 ... 65 ValidationError: [u'Enter a valid Swedish organisation number.'] 66 >>> f.clean('870514-1111') 67 Traceback (most recent call last): 68 ... 69 ValidationError: [u'Enter a valid Swedish organisation number.'] 70 71 72 # Empty values 73 >>> f.clean('') 74 Traceback (most recent call last): 75 ... 76 ValidationError: [u'This field is required.'] 77 78 >>> f.clean(None) 79 Traceback (most recent call last): 80 ... 81 ValidationError: [u'This field is required.'] 82 83 # Co-ordination number checking 84 # Co-ordination numbers are not valid organisation numbers 85 >>> f.clean('870574-1315') 86 Traceback (most recent call last): 87 ... 88 ValidationError: [u'Enter a valid Swedish organisation number.'] 89 90 >>> f.clean('870573-1311') 91 Traceback (most recent call last): 92 ... 93 ValidationError: [u'Enter a valid Swedish organisation number.'] 94 95 # Test some different organisation numbers 96 >>> f.clean('556074-7569') # IKEA Linköping 97 u'5560747569' 98 99 >>> f.clean('556074-3089') # Volvo Personvagnar 100 u'5560743089' 101 102 >>> f.clean('822001-5476') # LJS (organisation) 103 u'8220015476' 104 105 >>> f.clean('8220015476') # LJS (organisation) 106 u'8220015476' 107 108 >>> f.clean('2120000449') # Katedralskolan Linköping (school) 109 u'2120000449' 110 111 # Faux organisation number, which tests that the checksum can be 0 112 >>> f.clean('232518-5060') 113 u'2325185060' 114 115 >>> f.clean('556074+3089') # Volvo Personvagnar, bad format 116 Traceback (most recent call last): 117 ... 118 ValidationError: [u'Enter a valid Swedish organisation number.'] 119 120 121 # Invalid checksum 122 >>> f.clean('2120000441') 123 Traceback (most recent call last): 124 ... 125 ValidationError: [u'Enter a valid Swedish organisation number.'] 126 127 # Valid checksum but invalid organisation type 128 f.clean('1120000441') 129 Traceback (most recent call last): 130 ... 131 ValidationError: [u'Enter a valid Swedish organisation number.'] 132 133 # Empty values with required=False 134 >>> f = SEOrganisationNumberField(required=False) 135 136 >>> f.clean(None) 137 u'' 138 139 >>> f.clean('') 140 u'' 141 142 143 # SEPersonalIdentityNumberField ####################################### 144 145 >>> from django.contrib.localflavor.se.forms import SEPersonalIdentityNumberField 146 147 >>> f = SEPersonalIdentityNumberField() 148 149 # Valid id numbers 150 >>> f.clean('870512-1989') 151 u'198705121989' 152 153 >>> f.clean('870512-2128') 154 u'198705122128' 155 156 >>> f.clean('19870512-1989') 157 u'198705121989' 158 159 >>> f.clean('198705121989') 160 u'198705121989' 161 162 >>> f.clean('081015-6315') 163 u'190810156315' 164 165 >>> f.clean('0810156315') 166 u'190810156315' 167 168 # This is a "special-case" in the checksum calculation, 169 # where the sum is divisible by 10 (the checksum digit == 0) 170 >>> f.clean('8705141060') 171 u'198705141060' 172 173 # + means that the person is older than 100 years 174 >>> f.clean('081015+6315') 175 u'180810156315' 176 177 # Bogus values 178 >>> f.clean('081015 6315') 179 Traceback (most recent call last): 180 ... 181 ValidationError: [u'Enter a valid Swedish personal identity number.'] 182 183 >>> f.clean('950d314496') 184 Traceback (most recent call last): 185 ... 186 ValidationError: [u'Enter a valid Swedish personal identity number.'] 187 188 >>> f.clean('invalid!!!') 189 Traceback (most recent call last): 190 ... 191 ValidationError: [u'Enter a valid Swedish personal identity number.'] 192 193 194 # Invalid dates 195 196 # February 31st does not exist 197 >>> f.clean('950231-4496') 198 Traceback (most recent call last): 199 ... 200 ValidationError: [u'Enter a valid Swedish personal identity number.'] 201 202 # Month 14 does not exist 203 >>> f.clean('6914104499') 204 Traceback (most recent call last): 205 ... 206 ValidationError: [u'Enter a valid Swedish personal identity number.'] 207 208 # There are no Swedish personal id numbers where year < 1800 209 >>> f.clean('17430309-7135') 210 Traceback (most recent call last): 211 ... 212 ValidationError: [u'Enter a valid Swedish personal identity number.'] 213 214 # Invalid checksum 215 >>> f.clean('870514-1111') 216 Traceback (most recent call last): 217 ... 218 ValidationError: [u'Enter a valid Swedish personal identity number.'] 219 220 # Empty values 221 >>> f.clean('') 222 Traceback (most recent call last): 223 ... 224 ValidationError: [u'This field is required.'] 225 226 >>> f.clean(None) 227 Traceback (most recent call last): 228 ... 229 ValidationError: [u'This field is required.'] 230 231 # Co-ordination number checking 232 >>> f.clean('870574-1315') 233 u'198705741315' 234 235 >>> f.clean('870574+1315') 236 u'188705741315' 237 238 >>> f.clean('198705741315') 239 u'198705741315' 240 241 # Co-ordination number with bad checksum 242 >>> f.clean('870573-1311') 243 Traceback (most recent call last): 244 ... 245 ValidationError: [u'Enter a valid Swedish personal identity number.'] 246 247 248 # Check valid co-ordination numbers, that should not be accepted 249 # because of coordination_number=False 250 >>> f = SEPersonalIdentityNumberField(coordination_number=False) 251 252 >>> f.clean('870574-1315') 253 Traceback (most recent call last): 254 ... 255 ValidationError: [u'Co-ordination numbers are not allowed.'] 256 257 >>> f.clean('870574+1315') 258 Traceback (most recent call last): 259 ... 260 ValidationError: [u'Co-ordination numbers are not allowed.'] 261 262 >>> f.clean('8705741315') 263 Traceback (most recent call last): 264 ... 265 ValidationError: [u'Co-ordination numbers are not allowed.'] 266 267 # Invalid co-ordination numbers should be treated as invalid, and not 268 # as co-ordination numbers 269 >>> f.clean('870573-1311') 270 Traceback (most recent call last): 271 ... 272 ValidationError: [u'Enter a valid Swedish personal identity number.'] 273 274 # Empty values with required=False 275 >>> f = SEPersonalIdentityNumberField(required=False) 276 277 >>> f.clean(None) 278 u'' 279 280 >>> f.clean('') 281 u'' 282 283 # SEPostalCodeField ############################################### 284 >>> from django.contrib.localflavor.se.forms import SEPostalCodeField 285 >>> f = SEPostalCodeField() 286 >>> 287 Postal codes can have spaces 288 >>> f.clean('589 37') 289 u'58937' 290 291 ... but the dont have to 292 >>> f.clean('58937') 293 u'58937' 294 >>> f.clean('abcasfassadf') 295 Traceback (most recent call last): 296 ... 297 ValidationError: [u'Enter a Swedish postal code in the format XXXXX.'] 298 299 # Only one space is allowed for separation 300 >>> f.clean('589 37') 301 Traceback (most recent call last): 302 ... 303 ValidationError: [u'Enter a Swedish postal code in the format XXXXX.'] 304 305 # The postal code must not start with 0 306 >>> f.clean('01234') 307 Traceback (most recent call last): 308 ... 309 ValidationError: [u'Enter a Swedish postal code in the format XXXXX.'] 310 311 # Empty values 312 >>> f.clean('') 313 Traceback (most recent call last): 314 ... 315 ValidationError: [u'This field is required.'] 316 317 >>> f.clean(None) 318 Traceback (most recent call last): 319 ... 320 ValidationError: [u'This field is required.'] 321 322 # Empty values, required=False 323 >>> f = SEPostalCodeField(required=False) 324 >>> f.clean('') 325 u'' 326 >>> f.clean(None) 327 u'' 328 329 # Revert the monkey patching 330 >>> datetime.date = olddate 331 332 """ -
tests/regressiontests/forms/tests.py
21 21 from localflavor.nl import tests as localflavor_nl_tests 22 22 from localflavor.pl import tests as localflavor_pl_tests 23 23 from localflavor.ro import tests as localflavor_ro_tests 24 from localflavor.se import tests as localflavor_se_tests 24 25 from localflavor.sk import tests as localflavor_sk_tests 25 26 from localflavor.uk import tests as localflavor_uk_tests 26 27 from localflavor.us import tests as localflavor_us_tests … … 54 55 'localflavor_nl_tests': localflavor_nl_tests, 55 56 'localflavor_pl_tests': localflavor_pl_tests, 56 57 'localflavor_ro_tests': localflavor_ro_tests, 58 'localflavor_se_tests': localflavor_se_tests, 57 59 'localflavor_sk_tests': localflavor_sk_tests, 58 60 'localflavor_uk_tests': localflavor_uk_tests, 59 61 'localflavor_us_tests': localflavor_us_tests, -
AUTHORS
300 300 Carlos Eduardo de Paula <carlosedp@gmail.com> 301 301 pavithran s <pavithran.s@gmail.com> 302 302 Barry Pederson <bp@barryp.org> 303 Andreas Pelme <andreas@pelme.se> 303 304 permonik@mesias.brnonet.cz 304 305 peter@mymart.com 305 306 pgross@thoughtworks.com -
docs/ref/contrib/localflavor.txt
60 60 * Slovakia_ 61 61 * `South Africa`_ 62 62 * Spain_ 63 * Sweden_ 63 64 * Switzerland_ 64 65 * `United Kingdom`_ 65 66 * `United States of America`_ … … 99 100 .. _Slovakia: `Slovakia (sk)`_ 100 101 .. _South Africa: `South Africa (za)`_ 101 102 .. _Spain: `Spain (es)`_ 103 .. _Sweden: `Sweden (se)`_ 102 104 .. _Switzerland: `Switzerland (ch)`_ 103 105 .. _United Kingdom: `United Kingdom (uk)`_ 104 106 .. _United States of America: `United States of America (us)`_ … … 573 575 574 576 A ``Select`` widget that uses a list of Spanish regions as its choices. 575 577 578 Sweden (``se``) 579 =============== 580 581 .. class:: se.forms.SECountySelect 582 583 A Select form widget that uses a list of the Swedish counties (län) as its 584 choices. 585 586 The cleaned value is the official county code -- see 587 http://en.wikipedia.org/wiki/Counties_of_Sweden for a list. 588 589 .. class:: se.forms.SEOrganisationNumber 590 591 A form field that validates input as a Swedish organisation number 592 (organisationsnummer). 593 594 It accepts the same input as SEPersonalIdentityField (for sole 595 proprietorships (enskild firma). However, co-ordination numbers are not 596 accepted. 597 598 It also accepts ordinary Swedish organisation numbers with the format 599 NNNNNNNNNN. 600 601 The return value will be YYYYMMDDXXXX for sole proprietors, and NNNNNNNNNN 602 for other organisations. 603 604 .. class:: se.forms.SEPersonalIdentityNumber 605 606 A form field that validates input as a Swedish personal identity number 607 (personnummer). 608 609 The correct formats are YYYYMMDD-XXXX, YYYYMMDDXXXX, YYMMDD-XXXX, 610 YYMMDDXXXX and YYMMDD+XXXX. 611 612 A \+ indicates that the person is older than 100 years, which will be taken 613 into consideration when the date is validated. 614 615 The checksum will be calculated and checked. The birth date is checked 616 to be a valid date. 617 618 By default, co-ordination numbers (samordningsnummer) will be accepted. To 619 only allow real personal identity numbers, pass the keyword argument 620 coordination_number=False to the constructor. 621 622 The cleaned value will always have the format YYYYMMDDXXXX. 623 624 .. class:: se.forms.SEPostalCodeField 625 626 A form field that validates input as a Swedish postal code (postnummer). 627 Valid codes consist of five digits (XXXXX). The number can optionally be 628 formatted with a space after the third digit (XXX XX). 629 630 The cleaned value will never contain the space. 631 576 632 Switzerland (``ch``) 577 633 ==================== 578 634