Skip to content

Commit

Permalink
[rust] Add most representative country to the timezones
Browse files Browse the repository at this point in the history
  • Loading branch information
ancorgs committed Dec 21, 2023
1 parent abb19f7 commit c77e7d3
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 5 deletions.
13 changes: 11 additions & 2 deletions rust/agama-dbus-server/src/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,21 @@ impl Locale {
/// * The timezone identifier (e.g., "Europe/Berlin").
/// * A list containing each part of the name in the language set by the
/// UILocale property.
fn list_timezones(&self) -> Result<Vec<(String, Vec<String>)>, Error> {
/// * The name, in the language set by UILocale, of the main country
/// associated to the timezone (typically, the name of the city that is
/// part of the identifier) or empty string if there is no country.
fn list_timezones(&self) -> Result<Vec<(String, Vec<String>, String)>, Error> {
let timezones: Vec<_> = self
.timezones_db
.entries()
.iter()
.map(|tz| (tz.code.to_string(), tz.parts.clone()))
.map(|tz| {
(
tz.code.to_string(),
tz.parts.clone(),
tz.country.clone().unwrap_or_default(),
)
})
.collect();
Ok(timezones)
}
Expand Down
65 changes: 62 additions & 3 deletions rust/agama-dbus-server/src/locale/timezone.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! This module provides support for reading the timezones database.

use crate::error::Error;
use agama_locale_data::territory::Territories;
use agama_locale_data::timezone_part::TimezoneIdParts;
use std::collections::HashMap;

/// Represents a timezone, including each part as localized.
#[derive(Debug)]
Expand All @@ -10,6 +12,8 @@ pub struct TimezoneEntry {
pub code: String,
/// Localized parts (e.g., "Atlántico", "Canarias").
pub parts: Vec<String>,
/// Localized name of the territory this timezone is associated to
pub country: Option<String>,
}

#[derive(Default)]
Expand Down Expand Up @@ -49,13 +53,26 @@ impl TimezonesDatabase {
fn get_timezones(&self, ui_language: &str) -> Result<Vec<TimezoneEntry>, Error> {
let timezones = agama_locale_data::get_timezones();
let tz_parts = agama_locale_data::get_timezone_parts()?;
let territories = agama_locale_data::get_territories()?;
let tz_countries = agama_locale_data::get_timezone_countries()?;
const COUNTRYLESS: [&str; 2] = ["UTC", "Antarctica/South_Pole"];

let ret = timezones
.into_iter()
.map(|tz| {
.filter_map(|tz| {
let parts = translate_parts(&tz, &ui_language, &tz_parts);
TimezoneEntry { code: tz, parts }
let country = translate_country(&tz, &ui_language, &tz_countries, &territories);
match country {
None if !COUNTRYLESS.contains(&tz.as_str()) => None,
_ => Some(TimezoneEntry {
code: tz,
parts,
country,
}),
}
})
.collect();

Ok(ret)
}
}
Expand All @@ -71,6 +88,23 @@ fn translate_parts(timezone: &str, ui_language: &str, tz_parts: &TimezoneIdParts
.collect()
}

fn translate_country(
timezone: &str,
lang: &str,
countries: &HashMap<String, String>,
territories: &Territories,
) -> Option<String> {
let tz = match timezone {
"Asia/Rangoon" => "Asia/Yangon",
"Europe/Kiev" => "Europe/Kyiv",
_ => timezone,
};
let country_id = countries.get(tz)?;
let territory = territories.find_by_id(&country_id)?;
let name = territory.names.name_for(&lang)?;
Some(name)
}

#[cfg(test)]
mod tests {
use super::TimezonesDatabase;
Expand All @@ -89,7 +123,32 @@ mod tests {
assert_eq!(
found.parts,
vec!["Europa".to_string(), "Berlín".to_string()]
)
);
assert_eq!(found.country, Some("Alemania".to_string()));
}

#[test]
fn test_read_timezone_without_country() {
let mut db = TimezonesDatabase::new();
db.read("es").unwrap();
let timezone = db
.entries()
.into_iter()
.find(|tz| tz.code == "UTC")
.unwrap();
assert_eq!(timezone.country, None);
}

#[test]
fn test_read_kiev_country() {
let mut db = TimezonesDatabase::new();
db.read("en").unwrap();
let timezone = db
.entries()
.into_iter()
.find(|tz| tz.code == "Europe/Kiev")
.unwrap();
assert_eq!(timezone.country, Some("Ukraine".to_string()));
}

#[test]
Expand Down
22 changes: 22 additions & 0 deletions rust/agama-locale-data/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use anyhow::Context;
use flate2::bufread::GzDecoder;
use quick_xml::de::Deserializer;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
Expand Down Expand Up @@ -92,6 +93,27 @@ pub fn get_timezone_parts() -> anyhow::Result<timezone_part::TimezoneIdParts> {
Ok(ret)
}

/// Returns a hash mapping timezones to its main country (typically, the country of
/// the city that is used to name the timezone). The information is read from the
/// file /usr/share/zoneinfo/zone.tab.
pub fn get_timezone_countries() -> anyhow::Result<HashMap<String, String>> {
const FILE_PATH: &str = "/usr/share/zoneinfo/zone.tab";
let content = std::fs::read_to_string(FILE_PATH)
.with_context(|| format!("Failed to read {}", FILE_PATH))?;

let countries = content
.lines()
.filter_map(|line| {
if line.starts_with('#') {
return None;
}
let fields: Vec<&str> = line.split("\t").collect();
Some((fields.get(2)?.to_string(), fields.get(0)?.to_string()))
})
.collect();
Ok(countries)
}

/// Gets list of non-deprecated timezones
pub fn get_timezones() -> Vec<String> {
chrono_tz::TZ_VARIANTS
Expand Down

0 comments on commit c77e7d3

Please sign in to comment.