Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion doc/dbus/bus/org.opensuse.Agama1.Locale.bus.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,12 @@
* The timezone identifier (e.g., "Europe/Berlin").
* A list containing each part of the name in the language set by the
UILocale property.
* 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.
-->
<method name="ListTimezones">
<arg type="a(sas)" direction="out"/>
<arg type="a(sass)" direction="out"/>
</method>
<method name="Commit">
</method>
Expand Down
5 changes: 4 additions & 1 deletion doc/dbus/org.opensuse.Agama1.Locale.doc.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@
* The timezone identifier (e.g., "Europe/Berlin").
* A list containing each part of the name in the language set by the
UILocale property.
* 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.
-->
<method name="ListTimezones">
<arg type="a(sas)" direction="out"/>
<arg type="a(sass)" direction="out"/>
</method>
<method name="Commit">
</method>
Expand Down
15 changes: 12 additions & 3 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 Expand Up @@ -187,7 +196,7 @@ impl Locale {
};

let mut timezones_db = TimezonesDatabase::new();
timezones_db.read(&locale)?;
timezones_db.read(&ui_locale.language)?;
let mut default_timezone = DEFAULT_TIMEZONE.to_string();
if !timezones_db.exists(&default_timezone) {
default_timezone = timezones_db.entries().get(0).unwrap().code.to_string();
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
28 changes: 25 additions & 3 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.first()?.to_string()))
})
.collect();
Ok(countries)
}

/// Gets list of non-deprecated timezones
pub fn get_timezones() -> Vec<String> {
chrono_tz::TZ_VARIANTS
Expand All @@ -115,21 +137,21 @@ mod tests {
#[test]
fn test_get_languages() {
let result = get_languages().unwrap();
let first = result.language.first().expect("no keyboards");
let first = result.language.first().expect("no languages");
assert_eq!(first.id, "aa")
}

#[test]
fn test_get_territories() {
let result = get_territories().unwrap();
let first = result.territory.first().expect("no keyboards");
let first = result.territory.first().expect("no territories");
assert_eq!(first.id, "001") // looks strange, but it is meta id for whole world
}

#[test]
fn test_get_timezone_parts() {
let result = get_timezone_parts().unwrap();
let first = result.timezone_part.first().expect("no keyboards");
let first = result.timezone_part.first().expect("no timezone parts");
assert_eq!(first.id, "Abidjan")
}

Expand Down
6 changes: 6 additions & 0 deletions rust/package/agama-cli.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Thu Dec 21 11:12:45 UTC 2023 - Ancor Gonzalez Sosa <[email protected]>

- The result of ListTimezones includes the localized country name
for each timezone (gh#openSUSE/agama#946)

-------------------------------------------------------------------
Fri Dec 15 16:29:20 UTC 2023 - Imobach Gonzalez Sosa <[email protected]>

Expand Down
5 changes: 5 additions & 0 deletions web/package/cockpit-agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
-------------------------------------------------------------------
Thu Dec 21 11:18:37 UTC 2023 - Ancor Gonzalez Sosa <[email protected]>

- Display countries at timezone selector (gh#openSUSE/agama#946).

-------------------------------------------------------------------
Mon Dec 18 10:42:53 UTC 2023 - José Iván López González <[email protected]>

Expand Down
2 changes: 1 addition & 1 deletion web/src/assets/styles/blocks.scss
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ ul[data-of="agama/keymaps"] {
ul[data-of="agama/timezones"] {
li {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
grid-template-columns: 2fr 1fr 1fr;

> :last-child {
grid-column: 1 / -1;
Expand Down
7 changes: 4 additions & 3 deletions web/src/client/l10n.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const LOCALE_PATH = "/org/opensuse/Agama1/Locale";
* @typedef {object} Timezone
* @property {string} id - Timezone id (e.g., "Atlantic/Canary").
* @property {Array<string>} parts - Name of the timezone parts (e.g., ["Atlantic", "Canary"]).
* @property {string} country - Name of the country associated to the zone or empty string (e.g., "Spain").
* @property {number} utcOffset - UTC offset.
*/

Expand Down Expand Up @@ -230,13 +231,13 @@ class L10nClient {
/**
* @private
*
* @param {[string, Array<string>]} dbusTimezone
* @param {[string, Array<string>, string]} dbusTimezone
* @returns {Timezone}
*/
buildTimezone([id, parts]) {
buildTimezone([id, parts, country]) {
const utcOffset = timezoneUTCOffset(id);

return ({ id, parts, utcOffset });
return ({ id, parts, country, utcOffset });
}

/**
Expand Down
5 changes: 2 additions & 3 deletions web/src/components/l10n/TimezoneSelector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,12 @@ const timezoneDetails = (timezone) => {
* @param {Date} props.date - Date to show a time.
*/
const TimezoneItem = ({ timezone, date }) => {
const [part1, ...restParts] = timezone.parts;
const time = timezoneTime(timezone.id, { date }) || "";

return (
<>
<div>{part1}</div>
<div>{restParts.join('-')}</div>
<div>{timezone.parts.join('-')}</div>
<div>{timezone.country}</div>
<div>{time || ""}</div>
<div>{timezone.details}</div>
</>
Expand Down