icu4x 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Cargo.lock +2 -2
- data/README.md +1 -1
- data/ext/icu4x/Cargo.toml +1 -1
- data/ext/icu4x/src/datetime_format.rs +68 -68
- data/ext/icu4x_macros/Cargo.toml +1 -1
- data/lib/icu4x/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d20c68d78ce5ac2f944e9f7d1d5439ddcd7cfbf38de6545a3471114d2930be63
|
|
4
|
+
data.tar.gz: e164bb4226ed47018d14ea9642b95bf5b0dc3900e45d0097077a9ad896288654
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a423d9b426a240c82e5d8cd6452b473707093ff5db8c9d28f71ef3607a97178811f3dc3087428ad5e5418cc180a5b0104bb7b59ce4bc058458d2943e048d69f4
|
|
7
|
+
data.tar.gz: f718d99ee81feb48d054ffa2d2943eefb0985af3e31f3e746084b52964143dfc4bddb88fb8044b4d8fb4a5082aeb0ef9ff53e18188da446b7142ee7a778ee727
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.11.0] - 2026-05-18
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Fix `time_style: :long` and `time_style: :full` to include timezone information (#156)
|
|
8
|
+
- Fix `time_style: :short` to omit seconds (#155)
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Update minimum Ruby version requirement to 3.3
|
|
13
|
+
|
|
3
14
|
## [0.10.0] - 2026-05-08
|
|
4
15
|
|
|
5
16
|
### Added
|
data/Cargo.lock
CHANGED
|
@@ -405,7 +405,7 @@ dependencies = [
|
|
|
405
405
|
|
|
406
406
|
[[package]]
|
|
407
407
|
name = "icu4x"
|
|
408
|
-
version = "0.
|
|
408
|
+
version = "0.11.0"
|
|
409
409
|
dependencies = [
|
|
410
410
|
"fixed_decimal",
|
|
411
411
|
"icu",
|
|
@@ -425,7 +425,7 @@ dependencies = [
|
|
|
425
425
|
|
|
426
426
|
[[package]]
|
|
427
427
|
name = "icu4x_macros"
|
|
428
|
-
version = "0.
|
|
428
|
+
version = "0.11.0"
|
|
429
429
|
dependencies = [
|
|
430
430
|
"proc-macro2",
|
|
431
431
|
"quote",
|
data/README.md
CHANGED
|
@@ -24,7 +24,7 @@ No locale data is bundled with the gem. Users generate only the data they need,
|
|
|
24
24
|
|
|
25
25
|
## Requirements
|
|
26
26
|
|
|
27
|
-
- Ruby 3.
|
|
27
|
+
- Ruby 3.3+
|
|
28
28
|
- Rust toolchain (only required when building from source; prebuilt binary gems are available for major platforms)
|
|
29
29
|
|
|
30
30
|
## Setup
|
data/ext/icu4x/Cargo.toml
CHANGED
|
@@ -4,21 +4,20 @@ use crate::parts_collector::{PartsCollector, parts_to_ruby_array};
|
|
|
4
4
|
use icu::calendar::preferences::CalendarAlgorithm;
|
|
5
5
|
use icu::calendar::{AnyCalendarKind, Date, Gregorian};
|
|
6
6
|
use icu::datetime::fieldsets::enums::{
|
|
7
|
-
CalendarPeriodFieldSet, CompositeDateTimeFieldSet,
|
|
8
|
-
TimeFieldSet,
|
|
7
|
+
CalendarPeriodFieldSet, CompositeDateTimeFieldSet, CompositeFieldSet, DateAndTimeFieldSet,
|
|
8
|
+
DateFieldSet, TimeFieldSet,
|
|
9
9
|
};
|
|
10
|
-
use icu::datetime::fieldsets::{self};
|
|
11
|
-
use icu::datetime::options::{Length, YearStyle as IcuYearStyle};
|
|
12
|
-
use icu::datetime::input::DateTime;
|
|
10
|
+
use icu::datetime::fieldsets::{self, zone};
|
|
11
|
+
use icu::datetime::options::{Length, TimePrecision, YearStyle as IcuYearStyle};
|
|
13
12
|
use icu::datetime::parts as dt_parts;
|
|
14
13
|
use icu::datetime::{DateTimeFormatter, DateTimeFormatterPreferences};
|
|
15
14
|
use icu::locale::preferences::extensions::unicode::keywords::HourCycle as IcuHourCycle;
|
|
16
|
-
use icu::time::
|
|
17
|
-
use icu::time::
|
|
15
|
+
use icu::time::zone::{models, IanaParser, UtcOffset, ZoneNameTimestamp};
|
|
16
|
+
use icu::time::{Time, TimeZone, TimeZoneInfo, ZonedDateTime};
|
|
18
17
|
use icu_provider::buf::AsDeserializingBufferProvider;
|
|
19
18
|
use icu4x_macros::RubySymbol;
|
|
20
19
|
use jiff::Timestamp;
|
|
21
|
-
use jiff::tz::TimeZone;
|
|
20
|
+
use jiff::tz::TimeZone as JiffTimeZone;
|
|
22
21
|
use magnus::{Error, RArray, RHash, RModule, Ruby, TryConvert, Value, function, method, prelude::*};
|
|
23
22
|
use writeable::{Part, Writeable};
|
|
24
23
|
|
|
@@ -276,12 +275,12 @@ fn part_to_symbol_name(part: &Part) -> &'static str {
|
|
|
276
275
|
/// Ruby wrapper for ICU4X datetime formatters
|
|
277
276
|
#[magnus::wrap(class = "ICU4X::DateTimeFormat", free_immediately, size)]
|
|
278
277
|
pub struct DateTimeFormat {
|
|
279
|
-
inner: DateTimeFormatter<
|
|
278
|
+
inner: DateTimeFormatter<CompositeFieldSet>,
|
|
280
279
|
locale_str: String,
|
|
281
280
|
date_style: Option<DateStyle>,
|
|
282
281
|
time_style: Option<TimeStyle>,
|
|
283
282
|
time_zone: Option<String>,
|
|
284
|
-
jiff_timezone: Option<
|
|
283
|
+
jiff_timezone: Option<JiffTimeZone>,
|
|
285
284
|
calendar: Calendar,
|
|
286
285
|
hour_cycle: Option<HourCycle>,
|
|
287
286
|
hour12: Option<bool>,
|
|
@@ -382,7 +381,7 @@ impl DateTimeFormat {
|
|
|
382
381
|
));
|
|
383
382
|
}
|
|
384
383
|
// Then create jiff TimeZone for offset calculation
|
|
385
|
-
let jiff_tz =
|
|
384
|
+
let jiff_tz = JiffTimeZone::get(tz_str).map_err(|e| {
|
|
386
385
|
Error::new(
|
|
387
386
|
ruby.exception_arg_error(),
|
|
388
387
|
format!("invalid IANA timezone: {} ({})", tz_str, e),
|
|
@@ -421,6 +420,7 @@ impl DateTimeFormat {
|
|
|
421
420
|
// Create field set based on options
|
|
422
421
|
let field_set = if has_component_options {
|
|
423
422
|
Self::create_field_set_from_components(ruby, &component_options, era)?
|
|
423
|
+
.to_composite_field_set()
|
|
424
424
|
} else {
|
|
425
425
|
Self::create_field_set_from_style(date_style, time_style, era)
|
|
426
426
|
};
|
|
@@ -596,7 +596,7 @@ impl DateTimeFormat {
|
|
|
596
596
|
date_style: Option<DateStyle>,
|
|
597
597
|
time_style: Option<TimeStyle>,
|
|
598
598
|
era: Option<EraStyle>,
|
|
599
|
-
) ->
|
|
599
|
+
) -> CompositeFieldSet {
|
|
600
600
|
match (date_style, time_style) {
|
|
601
601
|
(Some(ds), Some(ts)) => {
|
|
602
602
|
// Both date and time
|
|
@@ -607,6 +607,7 @@ impl DateTimeFormat {
|
|
|
607
607
|
};
|
|
608
608
|
let ymdt = if let Some(s) = era { ymdt.with_year_style(s.to_icu_year_style()) } else { ymdt };
|
|
609
609
|
CompositeDateTimeFieldSet::DateTime(DateAndTimeFieldSet::YMDT(ymdt))
|
|
610
|
+
.to_composite_field_set()
|
|
610
611
|
}
|
|
611
612
|
(Some(ds), None) => {
|
|
612
613
|
// Date only
|
|
@@ -616,16 +617,29 @@ impl DateTimeFormat {
|
|
|
616
617
|
DateStyle::Short => fieldsets::YMD::short(),
|
|
617
618
|
};
|
|
618
619
|
let ymd = if let Some(s) = era { ymd.with_year_style(s.to_icu_year_style()) } else { ymd };
|
|
619
|
-
CompositeDateTimeFieldSet::Date(DateFieldSet::YMD(ymd))
|
|
620
|
+
CompositeDateTimeFieldSet::Date(DateFieldSet::YMD(ymd)).to_composite_field_set()
|
|
620
621
|
}
|
|
621
622
|
(None, Some(ts)) => {
|
|
622
|
-
// Time only
|
|
623
|
-
|
|
624
|
-
TimeStyle::Full
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
623
|
+
// Time only; long/full include timezone per CLDR convention
|
|
624
|
+
match ts {
|
|
625
|
+
TimeStyle::Full => CompositeFieldSet::TimeZone(
|
|
626
|
+
fieldsets::T::long().with_zone(zone::SpecificLong).into_enums(),
|
|
627
|
+
),
|
|
628
|
+
TimeStyle::Long => CompositeFieldSet::TimeZone(
|
|
629
|
+
fieldsets::T::long().with_zone(zone::SpecificShort).into_enums(),
|
|
630
|
+
),
|
|
631
|
+
TimeStyle::Medium => {
|
|
632
|
+
CompositeDateTimeFieldSet::Time(TimeFieldSet::T(fieldsets::T::medium()))
|
|
633
|
+
.to_composite_field_set()
|
|
634
|
+
}
|
|
635
|
+
// short omits seconds to match Intl.DateTimeFormat timeStyle: "short"
|
|
636
|
+
TimeStyle::Short => {
|
|
637
|
+
CompositeDateTimeFieldSet::Time(TimeFieldSet::T(
|
|
638
|
+
fieldsets::T::short().with_time_precision(TimePrecision::Minute),
|
|
639
|
+
))
|
|
640
|
+
.to_composite_field_set()
|
|
641
|
+
}
|
|
642
|
+
}
|
|
629
643
|
}
|
|
630
644
|
(None, None) => {
|
|
631
645
|
// Should not happen due to validation
|
|
@@ -671,8 +685,12 @@ impl DateTimeFormat {
|
|
|
671
685
|
/// Prepare a Ruby Time value for formatting.
|
|
672
686
|
///
|
|
673
687
|
/// Converts objects responding to #to_time, validates the result,
|
|
674
|
-
/// and converts to ICU4X
|
|
675
|
-
fn prepare_datetime(
|
|
688
|
+
/// and converts to ICU4X ZonedDateTime.
|
|
689
|
+
fn prepare_datetime(
|
|
690
|
+
&self,
|
|
691
|
+
ruby: &Ruby,
|
|
692
|
+
time: Value,
|
|
693
|
+
) -> Result<ZonedDateTime<Gregorian, TimeZoneInfo<models::AtTime>>, Error> {
|
|
676
694
|
// Convert to Time if the object responds to #to_time
|
|
677
695
|
let time_value = if time.respond_to("to_time", false)? {
|
|
678
696
|
time.funcall::<_, _, Value>("to_time", ())?
|
|
@@ -689,68 +707,50 @@ impl DateTimeFormat {
|
|
|
689
707
|
));
|
|
690
708
|
}
|
|
691
709
|
|
|
692
|
-
self.
|
|
710
|
+
self.convert_time_to_zoned_datetime(ruby, time_value)
|
|
693
711
|
}
|
|
694
712
|
|
|
695
|
-
/// Convert Ruby Time to ICU4X
|
|
713
|
+
/// Convert Ruby Time to ICU4X ZonedDateTime<Gregorian, TimeZoneInfo<AtTime>>
|
|
696
714
|
///
|
|
697
|
-
/// If time_zone is specified, the
|
|
698
|
-
/// Otherwise,
|
|
699
|
-
fn
|
|
715
|
+
/// If time_zone is specified, the time is represented in that timezone.
|
|
716
|
+
/// Otherwise, UTC is used.
|
|
717
|
+
fn convert_time_to_zoned_datetime(
|
|
700
718
|
&self,
|
|
701
719
|
ruby: &Ruby,
|
|
702
720
|
time: Value,
|
|
703
|
-
) -> Result<
|
|
704
|
-
|
|
705
|
-
let utc_time: Value = time.funcall("getutc", ())?;
|
|
706
|
-
|
|
707
|
-
let utc_year: i32 = utc_time.funcall("year", ())?;
|
|
708
|
-
let utc_month: i32 = utc_time.funcall("month", ())?;
|
|
709
|
-
let utc_day: i32 = utc_time.funcall("day", ())?;
|
|
710
|
-
let utc_hour: i32 = utc_time.funcall("hour", ())?;
|
|
711
|
-
let utc_min: i32 = utc_time.funcall("min", ())?;
|
|
712
|
-
let utc_sec: i32 = utc_time.funcall("sec", ())?;
|
|
713
|
-
|
|
714
|
-
// Get year, month, day, hour, min, sec in the target timezone
|
|
715
|
-
let (year, month, day, hour, min, sec) = if let Some(ref tz) = self.jiff_timezone {
|
|
716
|
-
// Create a jiff Timestamp from UTC components
|
|
717
|
-
let timestamp = Timestamp::from_second(utc_time.funcall::<_, _, i64>("to_i", ())?)
|
|
718
|
-
.map_err(|e| {
|
|
719
|
-
Error::new(
|
|
720
|
-
ruby.exception_arg_error(),
|
|
721
|
-
format!("Invalid timestamp: {}", e),
|
|
722
|
-
)
|
|
723
|
-
})?;
|
|
721
|
+
) -> Result<ZonedDateTime<Gregorian, TimeZoneInfo<models::AtTime>>, Error> {
|
|
722
|
+
let ts_secs: i64 = time.funcall("to_i", ())?;
|
|
724
723
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
dt.day() as i32,
|
|
733
|
-
dt.hour() as i32,
|
|
734
|
-
dt.minute() as i32,
|
|
735
|
-
dt.second() as i32,
|
|
736
|
-
)
|
|
724
|
+
let timestamp = Timestamp::from_second(ts_secs).map_err(|e| {
|
|
725
|
+
Error::new(ruby.exception_arg_error(), format!("Invalid timestamp: {}", e))
|
|
726
|
+
})?;
|
|
727
|
+
|
|
728
|
+
let (jiff_tz, iana_name) = if let Some(ref tz) = self.jiff_timezone {
|
|
729
|
+
let name = tz.iana_name().unwrap_or("UTC").to_owned();
|
|
730
|
+
(tz.clone(), name)
|
|
737
731
|
} else {
|
|
738
|
-
|
|
739
|
-
(utc_year, utc_month, utc_day, utc_hour, utc_min, utc_sec)
|
|
732
|
+
(JiffTimeZone::UTC, "UTC".to_owned())
|
|
740
733
|
};
|
|
741
734
|
|
|
742
|
-
|
|
743
|
-
let
|
|
735
|
+
let zoned = timestamp.to_zoned(jiff_tz);
|
|
736
|
+
let dt = zoned.datetime();
|
|
737
|
+
|
|
738
|
+
let iso_date = Date::try_new_iso(dt.year() as i32, dt.month() as u8, dt.day() as u8)
|
|
744
739
|
.map_err(|e| Error::new(ruby.exception_arg_error(), format!("Invalid date: {}", e)))?;
|
|
745
740
|
let gregorian_date = iso_date.to_calendar(Gregorian);
|
|
746
741
|
|
|
747
|
-
|
|
748
|
-
let time_of_day = Time::try_new(hour as u8, min as u8, sec as u8, 0)
|
|
742
|
+
let icu_time = Time::try_new(dt.hour() as u8, dt.minute() as u8, dt.second() as u8, 0)
|
|
749
743
|
.map_err(|e| Error::new(ruby.exception_arg_error(), format!("Invalid time: {}", e)))?;
|
|
750
744
|
|
|
751
|
-
|
|
745
|
+
let icu_tz: TimeZone = IanaParser::new().parse(&iana_name);
|
|
746
|
+
let utc_offset = UtcOffset::from_seconds_unchecked(zoned.offset().seconds());
|
|
747
|
+
let zone_name_ts = ZoneNameTimestamp::from_epoch_seconds(ts_secs);
|
|
748
|
+
let zone_info = icu_tz.with_offset(Some(utc_offset)).with_zone_name_timestamp(zone_name_ts);
|
|
749
|
+
|
|
750
|
+
Ok(ZonedDateTime {
|
|
752
751
|
date: gregorian_date,
|
|
753
|
-
time:
|
|
752
|
+
time: icu_time,
|
|
753
|
+
zone: zone_info,
|
|
754
754
|
})
|
|
755
755
|
}
|
|
756
756
|
|
data/ext/icu4x_macros/Cargo.toml
CHANGED
data/lib/icu4x/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: icu4x
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- OZAWA Sakuro
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bigdecimal
|