1use crate::gen::helpers::handle::{pick_archetype, unique_tag, HandleArchetype};
2use crate::gen::helpers::nickname::{build_nickname, Nickname};
3use crate::gen::{ascii_lower, pick_locale};
4use crate::locale::shared;
5use crate::locale::{Locale, NameOrder};
6use crate::rng::Rng;
7
8pub struct Identity {
10 pub locale_code: &'static str,
11 pub name_order: NameOrder,
12 pub first_name: String,
13 pub last_name: String,
14 pub last_name2: String,
15 pub first_ascii: String,
16 pub last_ascii: String,
17 pub archetype: HandleArchetype,
18 pub birth_year: i64,
20 pub birth_month: u8,
22 pub birth_day: u8,
24 pub nickname: Option<Nickname>,
26 pub city: String,
27 pub region: String,
28 pub postal: String,
29 pub lat: f64,
30 pub lon: f64,
31 pub tz: &'static str,
32}
33
34pub fn weighted_birth_year(rng: &mut Rng, since: i64, until: i64) -> i64 {
37 let ref_year = until;
38 let buckets: [(i64, i64, i64); 8] = [
40 (18, 25, 250),
41 (26, 35, 250),
42 (36, 45, 200),
43 (46, 55, 130),
44 (56, 65, 80),
45 (66, 75, 50),
46 (76, 85, 25),
47 (86, 100, 15),
48 ];
49 let roll = rng.range(0, 999);
50 let mut acc = 0;
51 for (age_lo, age_hi, weight) in buckets {
52 acc += weight;
53 if roll < acc {
54 let birth_hi = (ref_year - age_lo).min(until);
55 let birth_lo = (ref_year - age_hi).max(since);
56 if birth_lo < birth_hi {
57 return rng.range(birth_lo, birth_hi);
58 }
59 }
60 }
61 let lo = (ref_year - 40).max(since);
62 let hi = (ref_year - 18).min(until);
63 if lo < hi {
64 rng.range(lo, hi)
65 } else {
66 since
67 }
68}
69
70impl Identity {
71 pub fn new(
72 rng: &mut Rng,
73 locales: &[&Locale],
74 birth_year_range: Option<(i64, i64)>,
75 since: i64,
76 until: i64,
77 ) -> Self {
78 let loc = pick_locale(rng, locales);
79 let first_name = if rng.urange(0, 99) < 5 {
81 shared::INTL_FIRST_NAMES[rng.urange(0, shared::INTL_FIRST_NAMES.len() - 1)].to_string()
82 } else {
83 shared::weighted_choice(rng, loc.first_names, loc.first_names_common).to_string()
84 };
85 let last_name =
86 shared::weighted_choice(rng, loc.last_names, loc.last_names_common).to_string();
87 let last_name2 = match loc.name_order {
88 NameOrder::DoubleSurname => (*rng.choice(loc.last_names)).to_string(),
89 NameOrder::Patronymic { .. } | NameOrder::PatronymicMiddle => {
90 (*rng.choice(loc.first_names)).to_string()
91 }
92 _ => String::new(),
93 };
94 let first_ascii = ascii_lower(rng, &first_name);
95 let last_ascii = ascii_lower(rng, &last_name);
96 let archetype = pick_archetype(rng);
97 let birth_year = if let Some((from, to)) = birth_year_range {
98 let yf = crate::temporal::epoch_to_year(from);
99 let yt = crate::temporal::epoch_to_year(to.saturating_sub(1)).max(yf);
100 rng.range(yf, yt)
101 } else {
102 let yf = crate::temporal::epoch_to_year(since);
103 let yt = crate::temporal::epoch_to_year(until.saturating_sub(1)).max(yf);
104 weighted_birth_year(rng, yf, yt)
105 };
106 let birth_month = rng.range(1, 12) as u8;
107 let birth_day = rng.range(1, 28) as u8;
108 let nickname = if archetype == HandleArchetype::NameOnly {
109 None
110 } else {
111 let nick_tag = unique_tag(rng.record(), 0xDEAD);
112 Some(build_nickname(nick_tag, rng))
113 };
114 let city = rng.choice(loc.cities);
115 Self {
116 locale_code: loc.code,
117 name_order: loc.name_order,
118 first_name,
119 last_name,
120 last_name2,
121 first_ascii,
122 last_ascii,
123 archetype,
124 birth_year,
125 birth_month,
126 birth_day,
127 nickname,
128 city: city.name.to_string(),
129 region: city.region.to_string(),
130 postal: city.postal.to_string(),
131 lat: city.lat,
132 lon: city.lon,
133 tz: city.tz,
134 }
135 }
136}
137
138pub struct GenContext<'a> {
140 pub rng: Rng,
141 pub locales: &'a [&'a Locale],
142 pub modifier: &'a str,
143 pub identity: Option<&'a Identity>,
144 pub tz_offset_minutes: i32,
145 pub since: i64,
146 pub until: i64,
147 pub range: Option<(i64, i64)>,
149 pub ordering: crate::field::Ordering,
151 pub zipf: Option<crate::field::ZipfSpec>,
153 pub numeric: Option<f64>,
155}
156
157impl<'a> GenContext<'a> {
158 pub fn locale(&mut self) -> &'a Locale {
160 assert!(!self.locales.is_empty(), "GenContext requires non-empty locales");
161 if let Some(id) = self.identity {
162 crate::locale::get(id.locale_code).unwrap_or(self.locales[0])
163 } else {
164 self.locales[self.rng.urange(0, self.locales.len() - 1)]
165 }
166 }
167
168 pub fn pick_locale(&mut self) -> &'a Locale {
170 assert!(!self.locales.is_empty(), "GenContext requires non-empty locales");
171 self.locales[self.rng.urange(0, self.locales.len() - 1)]
172 }
173
174 pub fn tz_log(&self, buf: &mut String) {
176 let m = self.tz_offset_minutes;
177 buf.push(if m < 0 { '-' } else { '+' });
178 let abs = m.unsigned_abs();
179 super::gen::date::push_pad2(buf, i64::from(abs / 60));
180 super::gen::date::push_pad2(buf, i64::from(abs % 60));
181 }
182
183 pub fn tz_iso(&self, buf: &mut String) {
185 let m = self.tz_offset_minutes;
186 if m == 0 {
187 buf.push('Z');
188 return;
189 }
190 buf.push(if m < 0 { '-' } else { '+' });
191 let abs = m.unsigned_abs();
192 super::gen::date::push_pad2(buf, i64::from(abs / 60));
193 buf.push(':');
194 super::gen::date::push_pad2(buf, i64::from(abs % 60));
195 }
196}