22import argparse
33import sys
44import random
5+ from datetime import timedelta , datetime
6+ import json
57
68egn_regions = {
7- 43 : {'bg' : 'Благоевград' , 'en' : 'Blagoevgrad' }, # 000 - 043
8- 93 : {'bg' : 'Бургас' , 'en' : 'Burgas' }, # 044 - 093
9- 139 : {'bg' : 'Варна' , 'en' : 'Varna' }, # 094 - 139
10- 169 : {'bg' : 'Велико Търново' , 'en' : 'Veliko Turnovo' }, # 140 - 169
11- 183 : {'bg' : 'Видин' , 'en' : 'Vidin' }, # 170 - 183
12- 217 : {'bg' : 'Враца' , 'en' : 'Vratza' }, # 184 - 217
13- 233 : {'bg' : 'Габрово' , 'en' : 'Gabrovo' }, # 218 - 233
14- 281 : {'bg' : 'Кърджали' , 'en' : 'Kurdzhali' }, # 234 - 281
15- 301 : {'bg' : 'Кюстендил' , 'en' : 'Kyustendil' }, # 282 - 301
16- 319 : {'bg' : 'Ловеч' , 'en' : 'Lovech' }, # 302 - 319
17- 341 : {'bg' : 'Монтана' , 'en' : 'Montana' }, # 320 - 341
18- 377 : {'bg' : 'Пазарджик' , 'en' : 'Pazardzhik' }, # 342 - 377
19- 395 : {'bg' : 'Перник' , 'en' : 'Pernik' }, # 378 - 395
20- 435 : {'bg' : 'Плевен' , 'en' : 'Pleven' }, # 396 - 435
21- 501 : {'bg' : 'Пловдив' , 'en' : 'Plovdiv' }, # 436 - 501
22- 527 : {'bg' : 'Разград' , 'en' : 'Razgrad' }, # 502 - 527
23- 555 : {'bg' : 'Русе' , 'en' : 'Ruse' }, # 528 - 555
24- 575 : {'bg' : 'Силистра' , 'en' : 'Silistra' }, # 556 - 575
25- 601 : {'bg' : 'Сливен' , 'en' : 'Sliven' }, # 576 - 601
26- 623 : {'bg' : 'Смолян' , 'en' : 'Smolyan' }, # 602 - 623
27- 721 : {'bg' : 'София' , 'en' : 'Sofia' }, # 624 - 721
28- 751 : {'bg' : 'София (окръг)' , 'en' : 'Sofia (county)' }, # 722 - 751
29- 789 : {'bg' : 'Стара Загора' , 'en' : 'Stara Zagora' }, # 752 - 789
30- 821 : {'bg' : 'Добрич' , 'en' : 'Dobrich' }, # 790 - 821
31- 843 : {'bg' : 'Търговище' , 'en' : 'Targovishte' }, # 822 - 843
32- 871 : {'bg' : 'Хасково' , 'en' : 'Haskovo' }, # 844 - 871
33- 903 : {'bg' : 'Шумен' , 'en' : 'Shumen' }, # 872 - 903
34- 925 : {'bg' : 'Ямбол' , 'en' : 'Yambol' }, # 904 - 925
35- 999 : {'bg' : 'Друг' , 'en' : 'Other' }} # 926 - 999
36-
37-
38- def generate (** kwargs ):
39- '''
40- Method to generate numbers.
41- '''
9+ # https://www.iso.org/obp/ui/#iso:code:3166:BG
10+ 43 : {'bg' : 'Благоевград' , 'en' : 'Blagoevgrad' , 'start' : 0 , 'iso' : 'BG-01' }, # 000 - 043
11+ 93 : {'bg' : 'Бургас' , 'en' : 'Burgas' , 'start' : 44 , 'iso' : 'BG-02' }, # 044 - 093
12+ 139 : {'bg' : 'Варна' , 'en' : 'Varna' , 'start' : 94 , 'iso' : 'BG-03' }, # 094 - 139
13+ 169 : {'bg' : 'Велико Търново' , 'en' : 'Veliko Turnovo' , 'start' : 140 , 'iso' : 'BG-04' }, # 140 - 169
14+ 183 : {'bg' : 'Видин' , 'en' : 'Vidin' , 'start' : 170 , 'iso' : 'BG-05' }, # 170 - 183
15+ 217 : {'bg' : 'Враца' , 'en' : 'Vratza' , 'start' : 184 , 'iso' : 'BG-06' }, # 184 - 217
16+ 233 : {'bg' : 'Габрово' , 'en' : 'Gabrovo' , 'start' : 218 , 'iso' : 'BG-07' }, # 218 - 233
17+ 281 : {'bg' : 'Кърджали' , 'en' : 'Kurdzhali' , 'start' : 234 , 'iso' : 'BG-09' }, # 234 - 281
18+ 301 : {'bg' : 'Кюстендил' , 'en' : 'Kyustendil' , 'start' : 282 , 'iso' : 'BG-10' }, # 282 - 301
19+ 319 : {'bg' : 'Ловеч' , 'en' : 'Lovech' , 'start' : 302 , 'iso' : 'BG-11' }, # 302 - 319
20+ 341 : {'bg' : 'Монтана' , 'en' : 'Montana' , 'start' : 320 , 'iso' : 'BG-12' }, # 320 - 341
21+ 377 : {'bg' : 'Пазарджик' , 'en' : 'Pazardzhik' , 'start' : 342 , 'iso' : 'BG-13' }, # 342 - 377
22+ 395 : {'bg' : 'Перник' , 'en' : 'Pernik' , 'start' : 378 , 'iso' : 'BG-14' }, # 378 - 395
23+ 435 : {'bg' : 'Плевен' , 'en' : 'Pleven' , 'start' : 396 , 'iso' : 'BG-15' }, # 396 - 435
24+ 501 : {'bg' : 'Пловдив' , 'en' : 'Plovdiv' , 'start' : 436 , 'iso' : 'BG-16' }, # 436 - 501
25+ 527 : {'bg' : 'Разград' , 'en' : 'Razgrad' , 'start' : 502 , 'iso' : 'BG-17' }, # 502 - 527
26+ 555 : {'bg' : 'Русе' , 'en' : 'Ruse' , 'start' : 528 , 'iso' : 'BG-18' }, # 528 - 555
27+ 575 : {'bg' : 'Силистра' , 'en' : 'Silistra' , 'start' : 556 , 'iso' : 'BG-19' }, # 556 - 575
28+ 601 : {'bg' : 'Сливен' , 'en' : 'Sliven' , 'start' : 576 , 'iso' : 'BG-20' }, # 576 - 601
29+ 623 : {'bg' : 'Смолян' , 'en' : 'Smolyan' , 'start' : 602 , 'iso' : 'BG-21' }, # 602 - 623
30+ 721 : {'bg' : 'София' , 'en' : 'Sofia' , 'start' : 624 , 'iso' : 'BG-22' }, # 624 - 721
31+ 751 : {'bg' : 'София (окръг)' , 'en' : 'Sofia (county)' , 'start' : 722 , 'iso' : 'BG-23' }, # 722 - 751
32+ 789 : {'bg' : 'Стара Загора' , 'en' : 'Stara Zagora' , 'start' : 752 , 'iso' : 'BG-24' }, # 752 - 789
33+ 821 : {'bg' : 'Добрич' , 'en' : 'Dobrich' , 'start' : 790 , 'iso' : 'BG-08' }, # 790 - 821
34+ 843 : {'bg' : 'Търговище' , 'en' : 'Targovishte' , 'start' : 822 , 'iso' : 'BG-25' }, # 822 - 843
35+ 871 : {'bg' : 'Хасково' , 'en' : 'Haskovo' , 'start' : 844 , 'iso' : 'BG-26' }, # 844 - 871
36+ 903 : {'bg' : 'Шумен' , 'en' : 'Shumen' , 'start' : 872 , 'iso' : 'BG-27' }, # 872 - 903
37+ 925 : {'bg' : 'Ямбол' , 'en' : 'Yambol' , 'start' : 904 , 'iso' : 'BG-28' }, # 904 - 925
38+ 999 : {'bg' : 'Друг' , 'en' : 'Other' , 'start' : 926 , 'iso' : 'BG-XX' }} # 926 - 999
39+
40+
41+ def generate_random (gender = None ,
42+ region = None ,
43+ limit = 10 ):
44+ """
45+ Generate a random EGN.
46+ """
47+ egns = []
48+ while len (egns ) < limit :
49+ if region is None :
50+ rr = random .randint (1 , 28 )
51+ rand_region = f"BG-{ rr :02d} "
52+ else :
53+ rand_region = region
54+ year = random .randint (1800 , datetime .today ().year - 1 )
55+ month = random .randint (1 , 12 )
56+ day = random .randint (1 , 28 )
57+ date_from = f"{ year } -{ month } -{ day } "
58+ date_to = datetime .today ().strftime ('%Y-%m-%d' )
59+ rand_egn = generate (date_from = date_from , date_to = date_to , region = rand_region , gender = gender , limit = 1 )
60+ if len (rand_egn ) == 1 :
61+ egns .append (rand_egn [0 ])
62+ return egns
63+
64+
65+ def generate (date_from = "1800-01-01" ,
66+ date_to = datetime .today ().strftime ('%Y-%m-%d' ),
67+ gender = None ,
68+ region = None ,
69+ limit = 10 ):
70+ """
71+ Generate egn within boundaries.
72+ """
73+ def get_region_range (region = "Other" ):
74+ """
75+ Get region code.
76+ Region could be the latin, cyrrilic or ISO 3166 anotation of the region.
77+ """
78+ start = 0
79+ end = 0
80+ for i in egn_regions :
81+ if region .lower () in [egn_regions [i ]['en' ].lower (),
82+ egn_regions [i ]['bg' ].lower (),
83+ egn_regions [i ]['iso' ].lower ()]:
84+ start = egn_regions [i ]['start' ]
85+ end = i
86+ return start , end
87+
88+ def get_dates_range (date_from , date_to , limit = None ):
89+ """
90+ Generate all dates between date range in ENG suitable format.
91+ Params in ISO 8601 format.
92+ """
93+ dates = []
94+ try :
95+ sdate = datetime .strptime (date_from , '%Y-%m-%d' )
96+ edate = datetime .strptime (date_to , '%Y-%m-%d' )
97+ delta = edate - sdate
98+
99+ for i in range (delta .days + 1 ):
100+ if limit is not None and i > limit :
101+ return dates
102+ dday = str (sdate + timedelta (days = i ))
103+ year , month , day = int (dday [0 :4 ]), int (dday [6 :7 ]), int (dday [8 :10 ])
104+ if year >= 2000 :
105+ month += 40
106+ year -= 2000
107+ elif year < 1900 :
108+ month += 20
109+ year -= 1800
110+ else :
111+ # Gregorian calendar adoption: 31/03/1916 > +13 days > 14/04/1916
112+ if year == 1916 and month == 4 and day <= 13 :
113+ continue
114+ year -= 1900
115+ dates .append (f"{ year :02d} { month :02d} { day :02d} " )
116+ return dates
117+ except Exception as e :
118+ print (e )
119+ return None
120+
121+ egns = []
122+ if not region :
123+ regions = range (0 , 999 , 1 )
124+ else :
125+ region_start , region_end = get_region_range (region )
126+ regions = range (region_start , region_end , 1 )
42127
43- return '9941011142'
128+ if gender is not None :
129+ if gender [0 ].lower () == 'm' : # Male
130+ regions = [x for x in regions if x % 2 == 0 ]
131+ else : # Female
132+ regions = [x for x in regions if x % 2 ]
133+
134+ regions = [f"{ x :02d} " for x in regions ]
135+ dates = get_dates_range (date_from , date_to , limit = limit )
136+ counter = 1
137+
138+ # Generate the list of egns
139+ for dt in dates :
140+ for region in regions :
141+ for check_num in range (0 , 9 , 1 ):
142+ egene = f"{ dt } { region } { check_num } "
143+ if validate (egene ):
144+ counter += 1
145+ egns .append (egene )
146+ if counter > limit :
147+ return egns
148+ return egns
44149
45150
46151def get_date (egn ):
@@ -51,7 +156,7 @@ def get_date(egn):
51156 from datetime import datetime
52157 try :
53158 year , month , day = int (egn [0 :2 ]), int (egn [2 :4 ]), int (egn [4 :6 ])
54- except :
159+ except Exception :
55160 return False
56161 if month >= 40 :
57162 month -= 40
@@ -111,6 +216,7 @@ def parse(egn):
111216 '''
112217 Parse the EGN information and return hash with the values
113218 '''
219+ egn = str (egn )
114220 if not validate (egn ):
115221 raise Exception ('Invalid EGN' )
116222 egn_hash = {}
@@ -125,12 +231,15 @@ def parse(egn):
125231 if key > region_num :
126232 egn_hash ['region_bg' ] = value ['bg' ]
127233 egn_hash ['region_en' ] = value ['en' ]
234+ egn_hash ['region_iso' ] = value ['iso' ]
128235 break
129236
130237 # Parse the gender
131- egn_hash ['gender' ] = 'male '
238+ egn_hash ['gender' ] = 'Male '
132239 if int (egn [8 :9 ]) % 2 :
133- egn_hash ['gender' ] = 'female'
240+ egn_hash ['gender' ] = 'Female'
241+
242+ egn_hash ['egn' ] = egn
134243
135244 return egn_hash
136245
@@ -140,41 +249,39 @@ def parse_args(args):
140249 Method to parse the arguments to the program.
141250 '''
142251 parser = argparse .ArgumentParser ()
143-
144- parser .add_argument ("-v" , "--validate" , help = "Validate EGN" ,
145- type = int )
146- parser .add_argument ("-p" , "--parse" , help = "Parse EGN" ,
147- type = int )
148- # Generate
149- parser .add_argument ("-g" , "--generate" , help = "Generate EGN" ,
150- action = "store_true" )
151- parser .add_argument ("-s" , "--sex" , help = "Sex/gender" ,
152- type = str , choices = ['male' , 'female' , 'm' , 'f' ],
153- default = random .choice (['m' , 'f' ]))
154- parser .add_argument ("-y" , "--year" , help = "Year (between 1800-2099)" ,
155- type = int , default = random .randint (1800 , 2099 ))
156- parser .add_argument ("-m" , "--month" , help = "Month (between 1-12)" ,
157- type = int , default = random .randint (1 , 12 ))
158- parser .add_argument ("-d" , "--day" , help = "Day (between 1-31)" ,
159- type = int , default = random .randint (1 , 31 ))
160- parser .add_argument ("-r" , "--region" , help = "Region (pernik/sofia etc.)" ,
161- type = str , default = random .choice (['Pernik' , 'Sofia' ]))
252+ today = format (datetime .today ().strftime ('%Y-%m-%d' ))
253+ parser .add_argument ("-v" , "--validate" , help = "Validate EGN" , type = str )
254+ parser .add_argument ("-p" , "--parse" , help = "Parse EGN" , type = str )
255+ # Generation
256+ parser .add_argument ("-l" , "--limit" , help = "Limit of generated results (default: 1)" , type = int , default = 1 )
257+ parser .add_argument ("-r" , "--random" , help = "Generate random EGN (default limit: 1)" , action = "store_true" )
258+ parser .add_argument ("-g" , "--generate" , help = "Generate EGN" , action = "store_true" )
259+ parser .add_argument ("--gender" , help = "Gender - Male or Female" , type = str , choices = ['m' , 'f' ])
260+ parser .add_argument ("--region" , help = "Region (Latin/Cyrrilic region name or ISO 3166 format)" , type = str )
261+ parser .add_argument ("--from" , help = "Date from generate (Y-m-d) (default: 1800-01-01)" ,
262+ type = str , default = '1800-01-01' )
263+ parser .add_argument ("--to" , type = str , help = f"Date to generate (Y-m-d) (default: { today } )" , default = today )
162264 return vars (parser .parse_args (args ))
163265
164266
165267def calc_args (parser ):
166268 '''
167- Method to parse the argparse values and return output
269+ Method to parse the argparse values and return an output.
168270 '''
169271
170272 if 'parse' in parser and parser ['parse' ]:
171273 validate_egn = str (parser ['parse' ])
172274 if not validate (validate_egn ):
173275 print ("{} is invalid!" .format (validate_egn ))
174276 exit (1 )
175- print (parse (validate_egn ))
277+ validate_egn = parse (validate_egn )
278+ validate_egn .pop ('datetime' )
279+ print (json .dumps (validate_egn ))
280+ elif 'random' in parser and parser ['random' ]:
281+ print ("\n " .join (generate_random (limit = parser ['limit' ], region = parser ['region' ], gender = parser ['gender' ])))
176282 elif 'generate' in parser and parser ['generate' ]:
177- print (generate ())
283+ print ("\n " .join (generate (limit = parser ['limit' ], region = parser ['region' ], gender = parser ['gender' ],
284+ date_from = parser ['from' ], date_to = parser ['to' ])))
178285 elif 'validate' in parser and parser ['validate' ]:
179286 validate_egn = str (parser ['validate' ])
180287 if validate (validate_egn ):
0 commit comments