bdarcus / csln

Reimagining CSL
Mozilla Public License 2.0
13 stars 0 forks source link

Add localized EDTF date-time formatting #26

Closed bdarcus closed 1 year ago

bdarcus commented 1 year ago

Update

I think I've decided to ditch the below because of how much of a hassle it is to integrate these different crates.

Seems like ideally someone would fork the EDTF crate and update it, and add a companion edtf-format.

But I don't have the time for that, so will just write my own bit of code for that, using the locales data for month etc formatting.

Vague ideas on how to do that:


I've tried this a few times, and keep getting tripped up on little stupid details, like integer type-casting.

This is high-priority, and I think icu is the right approach. But hopefully someone can submit a PR that magically fixes it all?

Data types problems

The primary stumbling block I ran into was converting the Edtf::Date etc into a form that icu, or even chrono would consume.

There is a to_chrone method in the Edtf crate, but it only works for complete dates. But bibliographic data is often incomplete.

When I manually tried to create these objects, I ran into a brick wall of type errors, and couldn't figure out how to cast the parameters to the correct integer types.

I may just need something like this:

pub fn string_toi32(s: &str) -> i32 {
    s.parse::<i32>().unwrap()
}

pub fn string_to_u8(s: &str) -> u8 {
    s.parse::<u8>().unwrap()
}

pub fn string_to_u32(s: &str) -> u32 {
    s.parse::<u32>().unwrap()
}

impl EdtfString {
    pub fn as_date(&self) -> Option<Date> {
        let edtf = Edtf::parse(&self.0).unwrap();
        match edtf {
            Edtf::Date(date) => Some(date),
            _ => None,
        }
    }
    /// Converts self to a [chrono::NaiveDate], regardless of whether it is a full date.
    pub fn to_chrono(&self) -> Option<NaiveDate> {
        let year = self.as_date().unwrap().year();
        let month = string_to_u32(&self.as_date().unwrap().month().unwrap_or(Component::Value(0)).to_string());
        let day = string_to_u32(&self.as_date().unwrap().day().unwrap_or(Component::Value(0)).to_string());
        NaiveDate::from_ymd_opt(year, month, day)
    }

    pub fn to_iso_datetime(&self) -> icu_DateTime<AnyCalendar> {
        let parsed_date = self.as_date().unwrap();
        let year = parsed_date.year();
        let month = string_to_u8(&self.as_date().unwrap().month().unwrap_or(Component::Value(0)).to_string());
        let day = string_to_u8(&self.as_date().unwrap().day().unwrap_or(Component::Value(0)).to_string());
        let hour = 0;
        let minute = 0;
        let second = 0;
        let iso_date =
            icu_DateTime::try_new_iso_datetime(year, month, day, hour, minute, second).unwrap();
        iso_date.to_any()
    }
}

Background

https://users.rust-lang.org/t/localized-date-time-formatting/94868/7

use icu::calendar::DateTime;
use icu::datetime::{options::length, DateTimeFormatter};
use icu::locid::locale;

let options =
    length::Bag::from_date_time_style(length::Date::Long, length::Time::Medium).into();

let dtf = DateTimeFormatter::try_new_unstable(&icu_testdata::unstable(), &locale!("es").into(), options)
    .expect("Failed to create DateTimeFormatter instance.");

let date = DateTime::try_new_iso_datetime(2020, 9, 12, 12, 35, 0).expect("Failed to parse date.");
let date = date.to_any();

let formatted_date = dtf.format(&date).expect("Formatting failed");
assert_eq!(
    formatted_date.to_string(),
    "12 de septiembre de 2020, 12:35:00"
);

And this is a great thread.

https://www.reddit.com/r/rust/comments/q4xaig/icu_vs_rust_icu/

bdarcus commented 1 year ago

Update: not happy with either chronos or icu yet.

https://gist.github.com/bdarcus/36e3957412c569a891d125bf00fd4c6b

bdarcus commented 1 year ago

I think as a first step, I'll just use chronos, and iterate as needed.

   // parse edtf string
   let edtf_date: Edtf = match Edtf::parse("2022-04-06") {
        Ok(edtf) => edtf,
        Err(error) => panic!("Failed to parse EDTF date: {}", error),
    };
    // convert it to chrono; before doing so, probably want to note qualifiers and such
    let ecdate = edtf_date.as_date().unwrap().to_chrono().unwrap();
   // let chrono localize it
    println!("{}", ecdate.format_localized("%e %B %Y", Locale::en_US));
bdarcus commented 1 year ago

Closed via #74