From f71e1fabebe281246b4b1c3326cbdddcd20bd44a Mon Sep 17 00:00:00 2001 From: Damian Nelson Date: Wed, 24 Mar 2021 23:49:24 -0700 Subject: [PATCH] Add support for skeleton date formats --- lib/ffi-icu/lib.rb | 14 ++++++--- lib/ffi-icu/time_formatting.rb | 56 ++++++++++++++++++++++++++++++---- spec/time_spec.rb | 15 ++++++++- 3 files changed, 73 insertions(+), 12 deletions(-) diff --git a/lib/ffi-icu/lib.rb b/lib/ffi-icu/lib.rb index c83261d..e1ca718 100644 --- a/lib/ffi-icu/lib.rb +++ b/lib/ffi-icu/lib.rb @@ -425,11 +425,12 @@ module ICU attach_function :unum_set_attribute, "unum_setAttribute#{suffix}", [:pointer, :number_format_attribute, :int32_t], :void # date enum :date_format_style, [ - :none, -1, - :full, 0, - :long, 1, - :medium, 2, - :short, 3, + :pattern, -2, + :none, -1, + :full, 0, + :long, 1, + :medium, 2, + :short, 3, ] attach_function :udat_open, "udat_open#{suffix}", [:date_format_style, :date_format_style, :string, :pointer, :int32_t, :pointer, :int32_t, :pointer ], :pointer attach_function :udat_close, "unum_close#{suffix}", [:pointer], :void @@ -437,6 +438,9 @@ module ICU attach_function :udat_parse, "udat_parse#{suffix}", [:pointer, :pointer, :int32_t, :pointer, :pointer], :double attach_function :udat_toPattern, "udat_toPattern#{suffix}", [:pointer, :bool , :pointer, :int32_t , :pointer], :int32_t attach_function :udat_applyPattern, "udat_applyPattern#{suffix}", [:pointer, :bool , :pointer, :int32_t ], :void + # skeleton pattern + attach_function :udatpg_open, "udatpg_open#{suffix}", [:string, :pointer], :pointer + attach_function :udatpg_getBestPattern, "udatpg_getBestPattern#{suffix}", [:pointer, :pointer, :int32_t, :pointer, :int32_t, :pointer], :int32_t # tz attach_function :ucal_setDefaultTimeZone, "ucal_setDefaultTimeZone#{suffix}", [:pointer, :pointer], :int32_t attach_function :ucal_getDefaultTimeZone, "ucal_getDefaultTimeZone#{suffix}", [:pointer, :int32_t, :pointer], :int32_t diff --git a/lib/ffi-icu/time_formatting.rb b/lib/ffi-icu/time_formatting.rb index 89e466c..5449702 100644 --- a/lib/ffi-icu/time_formatting.rb +++ b/lib/ffi-icu/time_formatting.rb @@ -65,21 +65,46 @@ module ICU private - def make_formatter(time_style, date_style, locale, time_zone_str) + def make_formatter(time_style, date_style, locale, time_zone_str, skeleton) time_zone = nil - d_len = 0 + tz_len = 0 if time_zone_str time_zone = UCharPointer.from_string(time_zone_str) - d_len = time_zone_str.size + tz_len = time_zone_str.size else Lib.check_error { | error| i_len = 150 time_zone = UCharPointer.new(i_len) - d_len = Lib.ucal_getDefaultTimeZone(time_zone, i_len, error) + tz_len = Lib.ucal_getDefaultTimeZone(time_zone, i_len, error) } end - ptr = Lib.check_error { | error| Lib.udat_open(time_style, date_style, locale, time_zone, d_len, FFI::MemoryPointer.new(4), -1, error) } + pattern_len = -1 + pattern_ptr = FFI::MemoryPointer.new(4) + + if skeleton + skeleton = UCharPointer.from_string(skeleton) + + pattern_len = 0 + pattern_ptr = UCharPointer.new(pattern_len) + + pg_ptr = Lib.check_error { |error| Lib.udatpg_open(locale, error) } + generator = FFI::AutoPointer.new(pg_ptr, Lib.method(:udat_close)) + + retried = false + begin + Lib.check_error do |error| + pattern_len = Lib.udatpg_getBestPattern(generator, skeleton, skeleton.size, pattern_ptr, pattern_len, error) + end + rescue BufferOverflowError + raise BufferOverflowError, "needed: #{pattern_len}" if retried + pattern_ptr = pattern_ptr.resized_to pattern_len + retried = true + retry + end + end + + ptr = Lib.check_error { | error| Lib.udat_open(time_style, date_style, locale, time_zone, tz_len, pattern_ptr, pattern_len, error) } FFI::AutoPointer.new(ptr, Lib.method(:udat_close)) end end @@ -91,7 +116,9 @@ module ICU locale = options[:locale] || 'C' tz_style = options[:tz_style] time_zone = options[:zone] - @f = make_formatter(time_style, date_style, locale, time_zone) + skeleton = options[:skeleton] + + @f = make_formatter(time_style, date_style, locale, time_zone, skeleton) if tz_style f0 = date_format(true) f1 = update_tz_format(f0, tz_style) @@ -177,6 +204,23 @@ module ICU needed_length = Lib.udat_applyPattern(@f, localized, pattern, pattern_len) end end + + def with_retry(out_ptr) + needed_length = 0 + out_ptr = UCharPointer.new(needed_length) + retried = false + + begin + Lib.check_error do |error| + yield(error) + end + rescue BufferOverflowError + raise BufferOverflowError, "needed: #{needed_length}" if retried + out_ptr = out_ptr.resized_to needed_length + retried = true + retry + end + end end # DateTimeFormatter end # Formatting end # ICU diff --git a/spec/time_spec.rb b/spec/time_spec.rb index 3a3da66..374a064 100644 --- a/spec/time_spec.rb +++ b/spec/time_spec.rb @@ -59,7 +59,7 @@ module ICU expect(f2.format(t8)).to eq("3/29/08#{en_sep} 6:38:24 AM #{en_tz}") end - f3 = TimeFormatting.create(:locale => 'de_DE', :zone => 'Africa/Dakar ', :date => :short , :time => :long) + f3 = TimeFormatting.create(:locale => 'de_DE', :zone => 'Africa/Dakar', :date => :short , :time => :long) ge_sep = "" if cldr_version >= "27.0.1" ge_sep = "," @@ -82,6 +82,19 @@ module ICU expect(f3.format(t7)).to eq("29.03.08#{ge_sep} 02:37:23 GMT") expect(f3.format(t8)).to eq("29.03.08#{ge_sep} 03:38:24 GMT") end + + context 'skeleton pattern' do + f4 = TimeFormatting.create(:locale => 'fr_FR', :date => :pattern , :time => :pattern, :skeleton => 'MMMy') + + it 'check format' do + expect(f4.format(t0)).to eq("nov. 2008") + expect(f4.format(t1)).to eq("oct. 2008") + end + + it 'check date_format' do + expect(f4.date_format(true)).to eq("MMM y") + end + end end end end