Skip to main content

finance_query/constants/enums/
region.rs

1use serde::{Deserialize, Serialize};
2
3/// Supported regions for Yahoo Finance regional APIs
4///
5/// Each region has predefined language and region codes that work together.
6/// Using the Region enum ensures correct lang/region pairing.
7#[non_exhaustive]
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
9pub enum Region {
10    /// Argentina (es-AR, AR)
11    #[serde(rename = "AR")]
12    Argentina,
13    /// Australia (en-AU, AU)
14    #[serde(rename = "AU")]
15    Australia,
16    /// Brazil (pt-BR, BR)
17    #[serde(rename = "BR")]
18    Brazil,
19    /// Canada (en-CA, CA)
20    #[serde(rename = "CA")]
21    Canada,
22    /// China (zh-CN, CN)
23    #[serde(rename = "CN")]
24    China,
25    /// Denmark (da-DK, DK)
26    #[serde(rename = "DK")]
27    Denmark,
28    /// Finland (fi-FI, FI)
29    #[serde(rename = "FI")]
30    Finland,
31    /// France (fr-FR, FR)
32    #[serde(rename = "FR")]
33    France,
34    /// Germany (de-DE, DE)
35    #[serde(rename = "DE")]
36    Germany,
37    /// Greece (el-GR, GR)
38    #[serde(rename = "GR")]
39    Greece,
40    /// Hong Kong (zh-Hant-HK, HK)
41    #[serde(rename = "HK")]
42    HongKong,
43    /// India (en-IN, IN)
44    #[serde(rename = "IN")]
45    India,
46    /// Israel (he-IL, IL)
47    #[serde(rename = "IL")]
48    Israel,
49    /// Italy (it-IT, IT)
50    #[serde(rename = "IT")]
51    Italy,
52    /// Japan (ja-JP, JP)
53    #[serde(rename = "JP")]
54    Japan,
55    /// South Korea (ko-KR, KR)
56    #[serde(rename = "KR")]
57    Korea,
58    /// Malaysia (ms-MY, MY)
59    #[serde(rename = "MY")]
60    Malaysia,
61    /// Mexico (es-MX, MX)
62    #[serde(rename = "MX")]
63    Mexico,
64    /// New Zealand (en-NZ, NZ)
65    #[serde(rename = "NZ")]
66    NewZealand,
67    /// Norway (nb-NO, NO)
68    #[serde(rename = "NO")]
69    Norway,
70    /// Portugal (pt-PT, PT)
71    #[serde(rename = "PT")]
72    Portugal,
73    /// Qatar (ar-QA, QA)
74    #[serde(rename = "QA")]
75    Qatar,
76    /// Russia (ru-RU, RU)
77    #[serde(rename = "RU")]
78    Russia,
79    /// Singapore (en-SG, SG)
80    #[serde(rename = "SG")]
81    Singapore,
82    /// Spain (es-ES, ES)
83    #[serde(rename = "ES")]
84    Spain,
85    /// Sweden (sv-SE, SE)
86    #[serde(rename = "SE")]
87    Sweden,
88    /// Taiwan (zh-TW, TW)
89    #[serde(rename = "TW")]
90    Taiwan,
91    /// Thailand (th-TH, TH)
92    #[serde(rename = "TH")]
93    Thailand,
94    /// Turkey (tr-TR, TR)
95    #[serde(rename = "TR")]
96    Turkey,
97    /// United Kingdom (en-GB, GB)
98    #[serde(rename = "GB", alias = "UK")]
99    UnitedKingdom,
100    /// United States (en-US, US) - Default
101    #[default]
102    #[serde(rename = "US")]
103    UnitedStates,
104    /// Vietnam (vi-VN, VN)
105    #[serde(rename = "VN")]
106    Vietnam,
107}
108
109impl Region {
110    /// Get the language code for this region
111    ///
112    /// # Example
113    ///
114    /// ```
115    /// use finance_query::Region;
116    ///
117    /// assert_eq!(Region::France.lang(), "fr-FR");
118    /// assert_eq!(Region::UnitedStates.lang(), "en-US");
119    /// ```
120    pub fn lang(&self) -> &'static str {
121        match self {
122            Region::Argentina => "es-AR",
123            Region::Australia => "en-AU",
124            Region::Brazil => "pt-BR",
125            Region::Canada => "en-CA",
126            Region::China => "zh-CN",
127            Region::Denmark => "da-DK",
128            Region::Finland => "fi-FI",
129            Region::France => "fr-FR",
130            Region::Germany => "de-DE",
131            Region::Greece => "el-GR",
132            Region::HongKong => "zh-Hant-HK",
133            Region::India => "en-IN",
134            Region::Israel => "he-IL",
135            Region::Italy => "it-IT",
136            Region::Japan => "ja-JP",
137            Region::Korea => "ko-KR",
138            Region::Malaysia => "ms-MY",
139            Region::Mexico => "es-MX",
140            Region::NewZealand => "en-NZ",
141            Region::Norway => "nb-NO",
142            Region::Portugal => "pt-PT",
143            Region::Qatar => "ar-QA",
144            Region::Russia => "ru-RU",
145            Region::Singapore => "en-SG",
146            Region::Spain => "es-ES",
147            Region::Sweden => "sv-SE",
148            Region::Taiwan => "zh-TW",
149            Region::Thailand => "th-TH",
150            Region::Turkey => "tr-TR",
151            Region::UnitedKingdom => "en-GB",
152            Region::UnitedStates => "en-US",
153            Region::Vietnam => "vi-VN",
154        }
155    }
156
157    /// Get the region code for this region
158    ///
159    /// # Example
160    ///
161    /// ```
162    /// use finance_query::Region;
163    ///
164    /// assert_eq!(Region::France.region(), "FR");
165    /// assert_eq!(Region::UnitedStates.region(), "US");
166    /// ```
167    pub fn region(&self) -> &'static str {
168        match self {
169            Region::Argentina => "AR",
170            Region::Australia => "AU",
171            Region::Brazil => "BR",
172            Region::Canada => "CA",
173            Region::China => "CN",
174            Region::Denmark => "DK",
175            Region::Finland => "FI",
176            Region::France => "FR",
177            Region::Germany => "DE",
178            Region::Greece => "GR",
179            Region::HongKong => "HK",
180            Region::India => "IN",
181            Region::Israel => "IL",
182            Region::Italy => "IT",
183            Region::Japan => "JP",
184            Region::Korea => "KR",
185            Region::Malaysia => "MY",
186            Region::Mexico => "MX",
187            Region::NewZealand => "NZ",
188            Region::Norway => "NO",
189            Region::Portugal => "PT",
190            Region::Qatar => "QA",
191            Region::Russia => "RU",
192            Region::Singapore => "SG",
193            Region::Spain => "ES",
194            Region::Sweden => "SE",
195            Region::Taiwan => "TW",
196            Region::Thailand => "TH",
197            Region::Turkey => "TR",
198            Region::UnitedKingdom => "GB",
199            Region::UnitedStates => "US",
200            Region::Vietnam => "VN",
201        }
202    }
203
204    /// UTC offset in seconds for the region's primary exchange.
205    ///
206    /// Returns the standard-time (non-DST) UTC offset of each country's main
207    /// exchange. This is used by the backtesting engine to align higher-timeframe
208    /// resampling bucket boundaries to local calendar weeks and months, preventing
209    /// APAC and other non-UTC exchanges from having bars mis-bucketed into the
210    /// prior week due to UTC midnight falling inside their local trading day.
211    ///
212    /// # Note
213    ///
214    /// DST transitions are not modelled. For exchanges in regions with DST
215    /// (e.g. NYSE, LSE) the boundary shift is at most ±1 hour and affects only
216    /// the transition candles. This is a deliberate simplification — exact DST
217    /// handling would require a timezone database dependency.
218    pub const fn utc_offset_secs(&self) -> i64 {
219        match self {
220            // UTC-5 (NYSE/TSX winter)
221            Region::UnitedStates | Region::Canada => -18_000,
222            // UTC-6 (BMV — Mexico abolished DST for most of the country in 2022)
223            Region::Mexico => -21_600,
224            // UTC-3 (BYMA / B3 winter)
225            Region::Argentina | Region::Brazil => -10_800,
226            // UTC+0 (LSE / Euronext Lisbon)
227            Region::UnitedKingdom | Region::Portugal => 0,
228            // UTC+1 (Euronext Paris/Amsterdam/Milan/Madrid, Oslo, Stockholm, Copenhagen, Helsinki)
229            Region::France
230            | Region::Germany
231            | Region::Italy
232            | Region::Spain
233            | Region::Norway
234            | Region::Sweden
235            | Region::Denmark
236            | Region::Finland => 3_600,
237            // UTC+2 (Athens, Tel Aviv, Moscow — note Russia stays UTC+3 year-round)
238            Region::Greece | Region::Israel => 7_200,
239            // UTC+3 (MOEX — no DST since 2014; Qatar/AST has no DST)
240            Region::Turkey | Region::Russia | Region::Qatar => 10_800,
241            // UTC+5:30 (BSE/NSE — India has no DST)
242            Region::India => 19_800,
243            // UTC+7 (SET Bangkok, HSX Hanoi)
244            Region::Thailand | Region::Vietnam => 25_200,
245            // UTC+8 (SSE/SZSE, HKEX, SGX, Bursa Malaysia, TWSE)
246            Region::China
247            | Region::HongKong
248            | Region::Singapore
249            | Region::Malaysia
250            | Region::Taiwan => 28_800,
251            // UTC+9 (TSE, KRX — neither observes DST)
252            Region::Japan | Region::Korea => 32_400,
253            // UTC+10 (ASX — AEST winter)
254            Region::Australia => 36_000,
255            // UTC+12 (NZX — NZST winter)
256            Region::NewZealand => 43_200,
257        }
258    }
259}
260
261impl std::str::FromStr for Region {
262    type Err = ();
263
264    fn from_str(s: &str) -> Result<Self, Self::Err> {
265        match s.to_uppercase().as_str() {
266            "AR" => Ok(Region::Argentina),
267            "AU" => Ok(Region::Australia),
268            "BR" => Ok(Region::Brazil),
269            "CA" => Ok(Region::Canada),
270            "CN" => Ok(Region::China),
271            "DK" => Ok(Region::Denmark),
272            "FI" => Ok(Region::Finland),
273            "FR" => Ok(Region::France),
274            "DE" => Ok(Region::Germany),
275            "GR" => Ok(Region::Greece),
276            "HK" => Ok(Region::HongKong),
277            "IN" => Ok(Region::India),
278            "IL" => Ok(Region::Israel),
279            "IT" => Ok(Region::Italy),
280            "JP" => Ok(Region::Japan),
281            "KR" => Ok(Region::Korea),
282            "MY" => Ok(Region::Malaysia),
283            "MX" => Ok(Region::Mexico),
284            "NZ" => Ok(Region::NewZealand),
285            "NO" => Ok(Region::Norway),
286            "PT" => Ok(Region::Portugal),
287            "QA" => Ok(Region::Qatar),
288            "RU" => Ok(Region::Russia),
289            "SG" => Ok(Region::Singapore),
290            "ES" => Ok(Region::Spain),
291            "SE" => Ok(Region::Sweden),
292            "TW" => Ok(Region::Taiwan),
293            "TH" => Ok(Region::Thailand),
294            "TR" => Ok(Region::Turkey),
295            "GB" | "UK" => Ok(Region::UnitedKingdom),
296            "US" => Ok(Region::UnitedStates),
297            "VN" => Ok(Region::Vietnam),
298            _ => Err(()),
299        }
300    }
301}
302
303impl From<Region> for String {
304    /// Returns the lowercase two-letter country code used by the Yahoo Finance screener
305    /// (e.g. `"us"`, `"gb"`).
306    fn from(v: Region) -> Self {
307        v.region().to_lowercase()
308    }
309}