|  | // Copyright 2019 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | use { | 
|  | fidl_fuchsia_intl as fintl, rust_icu_common as ucommon, rust_icu_sys as usys, | 
|  | rust_icu_ucal as ucal, rust_icu_udat as udat, rust_icu_uloc as uloc, | 
|  | rust_icu_ustring as ustring, std::convert::TryFrom, thiserror::Error, | 
|  | }; | 
|  |  | 
|  | /// All error classes produced in this module. | 
|  | #[derive(Error, Debug)] | 
|  | pub enum Error { | 
|  | /// There was an error in the underlying ICU library. | 
|  | #[error("ICU common error: {}", _0)] | 
|  | Common(ucommon::Error), | 
|  | } | 
|  | impl From<ucommon::Error> for Error { | 
|  | fn from(e: ucommon::Error) -> Self { | 
|  | Error::Common(e) | 
|  | } | 
|  | } | 
|  |  | 
|  | // TODO(fmil): We should use the server host time zone, but the C API for that is only available | 
|  | // starting from as of yet unreleased ICU 65. | 
|  | const UNKNOWN_TIMEZONE: &'static str = "Etc/Unknown"; | 
|  |  | 
|  | // Creates a calendar from the supplied ID and time zone. | 
|  | fn calendar_from(calendar_id: &str, time_zone_id: &str) -> Result<ucal::UCalendar, Error> { | 
|  | // UCAL_DEFAULT means "whatever calendar applies for the locale". | 
|  | ucal::UCalendar::new(time_zone_id, calendar_id, usys::UCalendarType::UCAL_DEFAULT) | 
|  | .map_err(|e| e.into()) | 
|  | } | 
|  |  | 
|  | /// Returns the wise response for the time on the timestamp, in all the specified locales and | 
|  | /// calendars. | 
|  | fn build_response( | 
|  | timestamp_ms: usys::UDate, | 
|  | locale_ids: &Vec<&String>, | 
|  | calendars: &Vec<ucal::UCalendar>, | 
|  | tz_id: &ustring::UChar, | 
|  | ) -> Result<String, Error> { | 
|  | let time_style = usys::UDateFormatStyle::UDAT_FULL; | 
|  | let date_style = usys::UDateFormatStyle::UDAT_FULL; | 
|  |  | 
|  | // TODO(fmil): I18N I18ize this response. | 
|  | let mut response = String::from("\nA wise one knows the time...\n\n"); | 
|  |  | 
|  | for locale in locale_ids { | 
|  | for calendar in calendars { | 
|  | let loc_ref: &str = *locale; | 
|  | let loc = uloc::ULoc::try_from(loc_ref)?; | 
|  | let mut fmt = udat::UDateFormat::new_with_styles(time_style, date_style, &loc, tz_id)?; | 
|  | fmt.set_calendar(calendar); | 
|  | let formatted_date = fmt.format(timestamp_ms)?; | 
|  | response.push_str(&format!("{}\n", formatted_date)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // TODO(fmil): I18N I18ize this response. | 
|  | // Note that this Unicode shenanigan has a space between the two letters "t", but some editors | 
|  | // won't render it.  It's there though. | 
|  | response.push_str("\nBut is it the ๐๐๐๐๐๐๐ time?\n"); | 
|  | Ok(response) | 
|  | } | 
|  |  | 
|  | /// Serves the responses to the "ask for wisdom" requests. | 
|  | pub fn ask_for_wisdom(intl_profile: &fintl::Profile, timestamp_ms: i64) -> Result<String, Error> { | 
|  | // Excavate the identifiers, which are just strings. | 
|  | // | 
|  | // This long statement deserves some explanation, though. | 
|  | // `as_ref()` is needed to treat locales as a reference and avoid a move. | 
|  | // `unwrap()` is needed because locales are for some reason wrapped in Option<> even though | 
|  | // they are required. | 
|  | // `iter()` is neede because unwrapped locales are an array, which by default aren't iterable | 
|  | // without an explicit call to `iter`. | 
|  | // `map()` turns the compound object into a simple string | 
|  | // collect() will produce a vector once all transforms are done. | 
|  | // | 
|  | // Note that all operations that use `&str` reuse references to strings that exist somewhere | 
|  | // inside the intl_profile struct. | 
|  | let locale_ids: Vec<_> = intl_profile.locales.as_ref().unwrap().iter().map(|l| &l.id).collect(); | 
|  |  | 
|  | let time_zone_ids: Vec<_> = | 
|  | intl_profile.time_zones.as_ref().unwrap().iter().map(|t| &t.id).collect(); | 
|  | let time_zone: &str = match time_zone_ids.len() { | 
|  | // If unspecified, use a default timezone. | 
|  | 0 => UNKNOWN_TIMEZONE, | 
|  | // If specified, use the first (most preferred) timezone. | 
|  | _ => &time_zone_ids[0], | 
|  | }; | 
|  |  | 
|  | // Parse the requested calendar IDs, using the first requested time zone, or as fallback the | 
|  | // device time zone. | 
|  | let calendar_ids = intl_profile.calendars.as_ref().unwrap(); | 
|  | let calendars: Vec<_> = match calendar_ids.len() { | 
|  | // If no calendars have been requested explicitly, manufacture one based on the time zone | 
|  | // and the passed locale ID. | 
|  | 0 => vec![calendar_from(locale_ids[0], time_zone)?], | 
|  | // In all other cases, produce calendars from the passed in calendar IDs. | 
|  | _ => calendar_ids | 
|  | .iter() | 
|  | .map(|c| calendar_from(&c.id, time_zone).expect("could not create a calendar")) | 
|  | .collect(), | 
|  | }; | 
|  |  | 
|  | // Now that we prepared all the input parameters (whew!), we build the long response string | 
|  | // ... or I guess fail with an error. | 
|  | let tz_id = ustring::UChar::try_from(time_zone)?; | 
|  | build_response(timestamp_ms as usys::UDate, &locale_ids, &calendars, &tz_id) | 
|  | } | 
|  |  | 
|  | #[cfg(test)] | 
|  | mod tests { | 
|  | use super::*; | 
|  | use icu_data; | 
|  |  | 
|  | #[test] | 
|  | fn basic() -> Result<(), super::Error> { | 
|  | // Keep the ICU data loader live for the duration of the test.  It is never "used" except | 
|  | // for its ability to mark that we want ICU data to be present. | 
|  | let _loader = icu_data::Loader::new().expect("ICU data is loaded"); | 
|  |  | 
|  | struct Test { | 
|  | profile: fintl::Profile, | 
|  | timestamp_ms: i64, | 
|  | result: String, | 
|  | }; | 
|  | let tests = vec![ | 
|  | Test { | 
|  | profile: fintl::Profile { | 
|  | locales: Some(vec![ | 
|  | fintl::LocaleId { id: "sr-RS".to_string() }, | 
|  | fintl::LocaleId { id: "nl-NL".to_string() }, | 
|  | ]), | 
|  | calendars: Some(vec![fintl::CalendarId { | 
|  | id: "und-u-ca-gregorian".to_string(), | 
|  | }]), | 
|  | temperature_unit: Some(fintl::TemperatureUnit::Celsius), | 
|  | time_zones: Some(vec![]), | 
|  | }, | 
|  | timestamp_ms: 0, | 
|  | result: vec![ | 
|  | "\nA wise one knows the time...\n\n", | 
|  | "ัะตัะฒััะฐะบ, 01. ัะฐะฝัะฐั 1970. 00:00:00 GMT\n", | 
|  | "donderdag 1 januari 1970 om 00:00:00 GMT", | 
|  | "\n\nBut is it the ๐๐๐๐๐๐๐ time?\n", | 
|  | ] | 
|  | .concat() | 
|  | .to_string(), | 
|  | }, | 
|  | Test { | 
|  | profile: fintl::Profile { | 
|  | locales: Some(vec![ | 
|  | fintl::LocaleId { id: "sr-RS".to_string() }, | 
|  | fintl::LocaleId { id: "nl-NL".to_string() }, | 
|  | ]), | 
|  | calendars: Some(vec![fintl::CalendarId { | 
|  | id: "und-u-ca-gregorian".to_string(), | 
|  | }]), | 
|  | temperature_unit: Some(fintl::TemperatureUnit::Celsius), | 
|  | time_zones: Some(vec![fintl::TimeZoneId { | 
|  | id: "America/Los_Angeles".to_string(), | 
|  | }]), | 
|  | }, | 
|  | timestamp_ms: 100000000, // About a day after the Unix Epoch | 
|  | result: vec![ | 
|  | "\nA wise one knows the time...\n\n", | 
|  | "ัะตัะฒััะฐะบ, 01. ัะฐะฝัะฐั 1970. 19:46:40 ะกะตะฒะตัะฝะพะฐะผะตัะธัะบะพ ะฟะฐัะธัะธัะบะพ ััะฐะฝะดะฐัะดะฝะพ ะฒัะตะผะต\n", | 
|  | "donderdag 1 januari 1970 om 19:46:40 Pacific-standaardtijd", | 
|  | "\n\nBut is it the ๐๐๐๐๐๐๐ time?\n", | 
|  | ] | 
|  | .concat() | 
|  | .to_string(), | 
|  | }, | 
|  | Test { | 
|  | profile: fintl::Profile { | 
|  | locales: Some(vec![ | 
|  | fintl::LocaleId { id: "en-US".to_string() }, | 
|  | ]), | 
|  | calendars: Some(vec![fintl::CalendarId { | 
|  | id: "und-u-ca-hebrew".to_string(), | 
|  | }]), | 
|  | temperature_unit: Some(fintl::TemperatureUnit::Celsius), | 
|  | time_zones: Some(vec![fintl::TimeZoneId { | 
|  | id: "America/New_York".to_string(), | 
|  | }]), | 
|  | }, | 
|  | timestamp_ms: 100000000, // About a day after the Unix Epoch | 
|  | result: vec![ | 
|  | "\nA wise one knows the time...\n\n", | 
|  | "Thursday, Tevet 23, 5730 at 10:46:40 PM Eastern Standard Time", | 
|  | "\n\nBut is it the ๐๐๐๐๐๐๐ time?\n", | 
|  | ] | 
|  | .concat() | 
|  | .to_string(), | 
|  | }, | 
|  | Test { | 
|  | profile: fintl::Profile { | 
|  | locales: Some(vec![ | 
|  | fintl::LocaleId { id: "ar-AU-u-ca-hebrew-fw-tuesday-nu-traditio-tz-usnyc".to_string() }, | 
|  | ]), | 
|  | calendars: Some(vec![fintl::CalendarId { | 
|  | id: "und-u-ca-islamic".to_string(), | 
|  | }]), | 
|  | temperature_unit: Some(fintl::TemperatureUnit::Celsius), | 
|  | time_zones: Some(vec![fintl::TimeZoneId { | 
|  | id: "America/New_York".to_string(), | 
|  | }]), | 
|  | }, | 
|  | timestamp_ms: 100000000, // About a day after the Unix Epoch | 
|  | result: vec![ | 
|  | "\nA wise one knows the time...\n\n", | 
|  | "ุงูุฎู
ูุณุ ูขูฃ ุดูุงู ูกูฃูจูฉ ูู ูกู :ูคูฆ:ูคู  ู
 ุงูุชูููุช ุงูุฑุณู
ู ุงูุดุฑูู ูุฃู
ุฑููุง ุงูุดู
ุงููุฉ", | 
|  | "\n\nBut is it the ๐๐๐๐๐๐๐ time?\n", | 
|  | ] | 
|  | .concat() | 
|  | .to_string(), | 
|  | }, | 
|  | ]; | 
|  | for t in tests { | 
|  | let result = ask_for_wisdom(&t.profile, t.timestamp_ms)?; | 
|  | assert_eq!(t.result, result, "\nwant: {:?}\ngot : {:?}", t.result, result); | 
|  | } | 
|  | Ok(()) | 
|  | } | 
|  | } |