Google is committed to advancing racial equity for Black communities. See how.

ICU timezone data

The ICU timezone data in Fuchsia is provided dynamically through the ICU data files (icudtl.dat). These are loaded on demand by programs, provided that the program's package is configured to make the file available to the program at runtime.

Section below shows how to do that.

Making the ICU data available to packages

In order for the ICU data files to be visible to a program in Fuchsia, it first needs to be made available in the package that contains the program. This is an example use from the wisdom server.

fuchsia_unittest_package("intl_wisdom_server_rust_tests") {
  deps = [
    # Fuchsia's ICU does not have libicudata.so, and the locale data MUST
    # be loaded from a file instead.
    # This target is implicitly generated by the target
    # ":intl_wisdom_server_rust" above, when it has the setting
    # "with_unit_tests=true".
    ":intl_wisdom_server_rust_test",
    "//src/intl:icudtl",
  ]
}

Note the section that adds a resource for icudtl.dat.

    # Fuchsia's ICU does not have libicudata.so, and the locale data MUST
    # be loaded from a file instead.
    # This target is implicitly generated by the target
    # ":intl_wisdom_server_rust" above, when it has the setting
    # "with_unit_tests=true".
    ":intl_wisdom_server_rust_test",
    "//src/intl:icudtl",
  ]
}


# This group is intentionally named the same as the directory it is in, so that
# the non-test parts of this target can be referred to without looking up the
# contents of this file.  Its dependencies are all non-test things in this
# package, which for rust is in practice just a single library or binary.
#
# The rustc_binary can not be just named "server" due to naming conflicts with
# other Fuchsia binaries, see the remark all the way up top at the
# rustc_binary("...") declaration.  So we must make do with this indirection.
group("server") {
  deps = [ ":intl_wisdom_server_rust" ]
}

# This group contains all tests in this package.  It is intentonally given a
# standardized name "tests" so that it could be referred to as ".../wisdom:tests"
# without needing to look into this file to see what the precise target name is.
group("tests") {
  testonly = true
  deps = [ ":intl_wisdom_server_rust_tests" ]
}

Since the library's footprint can be large, we do not simply import it as a public dependency on any rust program that uses ICU.

Modifying the time zone information on a system

In development you may need to check or set the time zone ID. The program setui_client allows you to do exactly this. See more information about setui_client by running run setui_client.cmx intl --help on a target.

Using the ICU data

You must load the ICU data in your program to make the locale data available. If you do not do that, no locale data will be available, and your ICU code will behave as if the set of i18n data is empty.

The APIs to load the code made available are different per language, so please refer to the pages below for specific examples:

Rust example

In Rust, you should use the icu_data::Loader, which will automatically do the right thing. Here is an example from the ICU data tests showing this approach.

Note that at least one instance of icu_data::Loader must be kept alive for as long as your code needs ICU data. Since it is difficult to predict when this data is needed, and the ICU liveness rules are a bit confusing, it is probably best to simplify things and keep an icu_data::Loader handy for the lifetime of the program.

#[test]
fn initialization() {
    let _loader = Loader::new().expect("loader is constructed with success");
    let _loader2 = Loader::new().expect("loader is just fine with a second initialization");
    let tz: String = uenum::open_time_zones().unwrap().take(1).map(|e| e.unwrap()).collect();
    assert_eq!(tz, "ACT");
    // The library will be cleaned up after the last of the loaders goes out of scope.
}

#[test]
fn you_can_also_clone_loaders() {
    let _loader = Loader::new().expect("loader is constructed with success");
    let _loader2 = Loader::new().expect("loader is just fine with a second initialization");
    let _loader3 = _loader2.clone();
    let tz: String = uenum::open_time_zones().unwrap().take(1).map(|e| e.unwrap()).collect();
    assert_eq!(tz, "ACT");
}

#[test]
fn two_initializations_in_a_row() {
    {
        let _loader = Loader::new().expect("loader is constructed with success");
        let tz: String =
            uenum::open_time_zones().unwrap().take(1).map(|e| e.unwrap()).collect();
        assert_eq!(tz, "ACT");
    }
    {
        let _loader2 = Loader::new().expect("loader is just fine with a second initialization");
        let tz: String =
            uenum::open_time_zones().unwrap().take(1).map(|e| e.unwrap()).collect();
        assert_eq!(tz, "ACT");
    }
}

In code, this amounts to instantiating a icu_data::Loader and keeping at least one instance of it alive for the lifetime of the program. The Loader can be cloned at will and copied around: the ICU data will be loaded before the first time it is needed, it will be unloaded when it is not needed, and will be reloaded again if needed again.

fn main() -> Result<(), Error> {
    // Force the loading of ICU data at the beginning of the program.  Since
    // Fuchsia's ICU does not have `libicudata.so`, we must load the data here
    // so that locales could be used in the server.
    let _loader = icu_data::Loader::new()?;

    let mut executor = fasync::Executor::new().context("error creating executor")?;
    let mut fs = server::ServiceFs::new_local();
    fs.dir("svc").add_fidl_service(move |stream| {
        fasync::Task::local(async move {
            run_service(stream).await.expect("failed to run service");
        })
        .detach();
    });
    fs.take_and_serve_directory_handle()?;
    executor.run_singlethreaded(fs.collect::<()>());
    Ok(())
}

Perhaps a more robust approach to maintaining a live icu_data::Loader is to pass a possibly cloned instance of a Loader into whatever struct requires ICU data. This will ensure that even in face of code refactors, the code that needs live ICU data always has it available:

/// A client implementation that connects to the Wisdom server.
pub struct Client {
    // The proxy for calling into the Wisdom service.
    wisdom: fwisdom::IntlWisdomServer_Proxy,
    // Client requires the ICU data to be loaded for TZ parsing, so we keep
    // a reference to the data alive, even though we don't "use" the code.
    #[allow(dead_code)]
    icu_data_loader: icu_data::Loader,
}

See also