ffi-icu 0.3.0 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e1817bf83d85197ea62937c274ff98c71984ef4a432a6f76fc3cca764bf45dc
4
- data.tar.gz: 2a21f2177dc151831fe89782be8cfe53d2f13a6d1038961eb1efe5c3970a27a0
3
+ metadata.gz: 357d3d218075dcfd56e963cd261c5fbe947d8daeb5d5826aebce8f1bedfe7ffe
4
+ data.tar.gz: afb2c28c1730cf05652433a1a721447385de47fa0fd105cb9782fe7496c55d65
5
5
  SHA512:
6
- metadata.gz: 6fb2d659678226632c485ea24de6e79f7d20ccf7b80b03833f680bb295ae07736957f3a9037ac1d3639b432f6974c279b580767d08315ad005c57cc6be79ed8b
7
- data.tar.gz: 9c49ddef23ae27f88d4eaffbaf0cf42b670e7f07939b7c19b6255a4ac157faae25e8eb5bc973764e767b3b32d40226c4b1cc95772c629fbe4b678d4cff5ebccb
6
+ metadata.gz: a257ab16df440695f42f6db486950e62fa1c08655324ad59d347eeb0e02d46c37581f04845cf035c528c7223a8e3d092a9bac877f7da5da10daee18e701d9aa3
7
+ data.tar.gz: be241d1659c60288c84f7844efaec71d368c1c650b4c1be410cbb0ff99cf6f08f468af30fd02a5ac050d853b18e243250dcc199c81815a5d2f7b1bf9bd783559
data/.travis.yml CHANGED
@@ -1,13 +1,20 @@
1
1
  language: ruby
2
2
  os: linux
3
- dist: xenial
3
+ dist: focal
4
+
5
+ arch:
6
+ - amd64
7
+ - arm64
4
8
 
5
9
  rvm:
6
- - 2.5
7
10
  - 2.6
8
11
  - 2.7
12
+ - 3.0
9
13
  - ruby-head
10
14
 
11
15
  before_script:
12
16
  - sudo apt-get install -y libicu-dev
13
17
 
18
+ jobs:
19
+ allow_failures:
20
+ - arch: arm64
data/README.md CHANGED
@@ -16,7 +16,7 @@ Dependencies
16
16
  ICU.
17
17
 
18
18
  If you get messages that the library or functions are not found, you can
19
- set some environment varibles to tell ffi-icu where to find it, e.g.:
19
+ set some environment variables to tell ffi-icu where to find it, e.g.:
20
20
 
21
21
  $ export FFI_ICU_LIB="icui18n.so"
22
22
  $ export FFI_ICU_VERSION_SUFFIX="_3_8"
@@ -109,14 +109,24 @@ Examples:
109
109
  f #=> "12.11.15 15:21"
110
110
 
111
111
  # reusable formatting objects
112
- formater = ICU::TimeFormatting.create(:locale => 'cs_CZ', :zone => 'Europe/Prague', :date => :long , :time => :none)
113
- formater.format(Time.now) #=> "25. února 2015"
112
+ formatter = ICU::TimeFormatting.create(:locale => 'cs_CZ', :zone => 'Europe/Prague', :date => :long, :time => :none)
113
+ formatter.format(Time.now) #=> "25. února 2015"
114
114
  ```
115
115
 
116
116
  ```ruby
117
117
  # reusable formatting objects
118
- formater = ICU::TimeFormatting.create(:locale => 'cs_CZ', :zone => 'Europe/Prague', :date => :long , :time => :none)
119
- formater.parse("25. února 2015") #=> Wed Feb 25 00:00:00 +0100 2015
118
+ formatter = ICU::TimeFormatting.create(:locale => 'cs_CZ', :zone => 'Europe/Prague', :date => :long, :time => :none)
119
+ formatter.parse("25. února 2015") #=> Wed Feb 25 00:00:00 +0100 2015
120
+ ```
121
+
122
+ For skeleton formatting, visit the [Unicode date field symbol table](https://unicode-org.github.io/icu/userguide/format_parse/datetime/#date-field-symbol-table) page to help find the pattern characters to use.
123
+
124
+ ```ruby
125
+ formatter = ICU::TimeFormatting.create(:locale => 'cs_CZ', :date => :pattern, :time => :pattern, :skeleton => 'MMMMY')
126
+ formatter.format(Time.now) #=> "únor 2015"
127
+
128
+ formatter = ICU::TimeFormatting.create(:locale => 'cs_CZ', :date => :pattern, :time => :pattern, :skeleton => 'Y')
129
+ formatter.format(Time.now) #=> "2015"
120
130
  ```
121
131
 
122
132
  Tested on:
@@ -28,7 +28,19 @@ module ICU
28
28
  result.read_string(length)
29
29
  end
30
30
 
31
- def self.read_uchar_buffer(length)
31
+ def self.read_uchar_buffer(length, &blk)
32
+ buf, len = read_uchar_buffer_as_ptr_impl(length, &blk)
33
+ buf.string(len)
34
+ end
35
+
36
+ def self.read_uchar_buffer_as_ptr(length, &blk)
37
+ buf, _ = read_uchar_buffer_as_ptr_impl(length, &blk)
38
+ buf
39
+ end
40
+
41
+ private
42
+
43
+ def self.read_uchar_buffer_as_ptr_impl(length)
32
44
  attempts = 0
33
45
 
34
46
  begin
@@ -40,7 +52,7 @@ module ICU
40
52
  raise BufferOverflowError, "needed: #{length}"
41
53
  end
42
54
 
43
- result.string(length)
55
+ [result, length]
44
56
  end
45
57
  end
46
58
  end
data/lib/ffi-icu/lib.rb CHANGED
@@ -19,9 +19,7 @@ module ICU
19
19
  '/usr/local/{lib64,lib}',
20
20
  '/opt/local/{lib64,lib}',
21
21
  '/usr/{lib64,lib}',
22
- '/usr/lib/x86_64-linux-gnu', # for Debian Multiarch http://wiki.debian.org/Multiarch
23
- '/usr/lib/i386-linux-gnu', # for Debian Multiarch
24
- ]
22
+ ] + Dir['/usr/lib/*-linux-gnu'] # for Debian Multiarch http://wiki.debian.org/Multiarch
25
23
  end
26
24
  end
27
25
  end
@@ -39,8 +37,12 @@ module ICU
39
37
  [find_lib("libicui18n.#{FFI::Platform::LIBSUFFIX}.??"),
40
38
  find_lib("libicutu.#{FFI::Platform::LIBSUFFIX}.??")]
41
39
  when :osx
42
- # See https://developer.apple.com/documentation/macos-release-notes/macos-big-sur-11_0_1-release-notes (62986286)
43
- if Gem::Version.new(`sw_vers -productVersion`) >= Gem::Version.new('11')
40
+ if ENV.key?('FFI_ICU_LIB')
41
+ # Ensure we look in the user-supplied override dir for a user-compiled libicu
42
+ [find_lib("libicui18n.??.#{FFI::Platform::LIBSUFFIX}"),
43
+ find_lib("libicutu.??.#{FFI::Platform::LIBSUFFIX}")]
44
+ elsif Gem::Version.new(`sw_vers -productVersion`) >= Gem::Version.new('11')
45
+ # See https://developer.apple.com/documentation/macos-release-notes/macos-big-sur-11_0_1-release-notes (62986286)
44
46
  ["libicucore.#{FFI::Platform::LIBSUFFIX}"]
45
47
  else
46
48
  [find_lib("libicucore.#{FFI::Platform::LIBSUFFIX}")]
@@ -425,11 +427,16 @@ module ICU
425
427
  attach_function :unum_set_attribute, "unum_setAttribute#{suffix}", [:pointer, :number_format_attribute, :int32_t], :void
426
428
  # date
427
429
  enum :date_format_style, [
428
- :none, -1,
429
- :full, 0,
430
- :long, 1,
431
- :medium, 2,
432
- :short, 3,
430
+ :pattern, -2,
431
+ :none, -1,
432
+ :full, 0,
433
+ :long, 1,
434
+ :medium, 2,
435
+ :short, 3,
436
+ ]
437
+ enum :uloc_data_locale_type, [
438
+ :actual_locale, 0,
439
+ :valid_locale, 1,
433
440
  ]
434
441
  attach_function :udat_open, "udat_open#{suffix}", [:date_format_style, :date_format_style, :string, :pointer, :int32_t, :pointer, :int32_t, :pointer ], :pointer
435
442
  attach_function :udat_close, "unum_close#{suffix}", [:pointer], :void
@@ -437,6 +444,11 @@ module ICU
437
444
  attach_function :udat_parse, "udat_parse#{suffix}", [:pointer, :pointer, :int32_t, :pointer, :pointer], :double
438
445
  attach_function :udat_toPattern, "udat_toPattern#{suffix}", [:pointer, :bool , :pointer, :int32_t , :pointer], :int32_t
439
446
  attach_function :udat_applyPattern, "udat_applyPattern#{suffix}", [:pointer, :bool , :pointer, :int32_t ], :void
447
+ # skeleton pattern
448
+ attach_function :udatpg_open, "udatpg_open#{suffix}", [:string, :pointer], :pointer
449
+ attach_function :udatpg_close, "udatpg_close#{suffix}", [:pointer], :void
450
+ attach_function :udatpg_getBestPattern, "udatpg_getBestPattern#{suffix}", [:pointer, :pointer, :int32_t, :pointer, :int32_t, :pointer], :int32_t
451
+ attach_function :udatpg_getSkeleton, "udatpg_getSkeleton#{suffix}", [:pointer, :pointer, :int32_t, :pointer, :int32_t, :pointer], :int32_t
440
452
  # tz
441
453
  attach_function :ucal_setDefaultTimeZone, "ucal_setDefaultTimeZone#{suffix}", [:pointer, :pointer], :int32_t
442
454
  attach_function :ucal_getDefaultTimeZone, "ucal_getDefaultTimeZone#{suffix}", [:pointer, :int32_t, :pointer], :int32_t
@@ -68,8 +68,19 @@ module ICU
68
68
  case number
69
69
  when Float
70
70
  needed_length = Lib.unum_format_double(@f, number, out_ptr, needed_length, nil, error)
71
- when Fixnum
72
- needed_length = Lib.unum_format_int32(@f, number, out_ptr, needed_length, nil, error)
71
+ when Integer
72
+ begin
73
+ # Try doing it fast, for integers that can be marshaled into an int64_t
74
+ needed_length = Lib.unum_format_int64(@f, number, out_ptr, needed_length, nil, error)
75
+ rescue RangeError
76
+ # Fall back to stringifying in Ruby and passing that to ICU
77
+ unless defined? Lib.unum_format_decimal
78
+ raise RangeError,"Number #{number} is too big to fit in int64_t and your "\
79
+ "ICU version is too old to have unum_format_decimal"
80
+ end
81
+ string_version = number.to_s
82
+ needed_length = Lib.unum_format_decimal(@f, string_version, string_version.bytesize, out_ptr, needed_length, nil, error)
83
+ end
73
84
  when BigDecimal
74
85
  string_version = number.to_s('F')
75
86
  if Lib.respond_to? :unum_format_decimal
@@ -77,8 +88,6 @@ module ICU
77
88
  else
78
89
  needed_length = Lib.unum_format_double(@f, number.to_f, out_ptr, needed_length, nil, error)
79
90
  end
80
- when Bignum
81
- needed_length = Lib.unum_format_int64(@f, number, out_ptr, needed_length, nil, error)
82
91
  end
83
92
  end
84
93
  out_ptr.string needed_length
@@ -38,6 +38,14 @@ module ICU
38
38
  # (for example, "Unknown City"), such as Los Angeles
39
39
  # see: http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
40
40
  }
41
+
42
+ HOUR_CYCLE_SYMS = {
43
+ 'h11' => 'K',
44
+ 'h12' => 'h',
45
+ 'h23' => 'H',
46
+ 'h24' => 'k',
47
+ :locale => 'j',
48
+ }
41
49
  @default_options = {}
42
50
 
43
51
  def self.create(options = {})
@@ -65,33 +73,50 @@ module ICU
65
73
 
66
74
  private
67
75
 
68
- def make_formatter(time_style, date_style, locale, time_zone_str)
69
- time_zone = nil
70
- d_len = 0
76
+ def make_formatter(time_style, date_style, locale, time_zone_str, skeleton)
77
+ time_zone = nil
78
+ tz_len = 0
79
+ pattern_len = -1
80
+ pattern_ptr = FFI::MemoryPointer.new(4)
81
+
71
82
  if time_zone_str
72
83
  time_zone = UCharPointer.from_string(time_zone_str)
73
- d_len = time_zone_str.size
84
+ tz_len = time_zone_str.size
74
85
  else
75
86
  Lib.check_error { | error|
76
87
  i_len = 150
77
88
  time_zone = UCharPointer.new(i_len)
78
- d_len = Lib.ucal_getDefaultTimeZone(time_zone, i_len, error)
89
+ tz_len = Lib.ucal_getDefaultTimeZone(time_zone, i_len, error)
79
90
  }
80
91
  end
81
92
 
82
- ptr = Lib.check_error { | error| Lib.udat_open(time_style, date_style, locale, time_zone, d_len, FFI::MemoryPointer.new(4), -1, error) }
93
+ if skeleton
94
+ date_style = :pattern
95
+ time_style = :pattern
96
+
97
+ pattern_len, pattern_ptr = skeleton_format(skeleton, locale)
98
+ end
99
+
100
+ ptr = Lib.check_error { | error| Lib.udat_open(time_style, date_style, locale, time_zone, tz_len, pattern_ptr, pattern_len, error) }
83
101
  FFI::AutoPointer.new(ptr, Lib.method(:udat_close))
84
102
  end
85
103
  end
86
104
 
87
105
  class DateTimeFormatter < BaseFormatter
88
106
  def initialize(options={})
89
- time_style = options[:time] || :short
90
- date_style = options[:date] || :short
91
- locale = options[:locale] || 'C'
92
- tz_style = options[:tz_style]
93
- time_zone = options[:zone]
94
- @f = make_formatter(time_style, date_style, locale, time_zone)
107
+ time_style = options[:time] || :short
108
+ date_style = options[:date] || :short
109
+ @locale = options[:locale] || 'C'
110
+ tz_style = options[:tz_style]
111
+ time_zone = options[:zone]
112
+ skeleton = options[:skeleton]
113
+ @hour_cycle = options[:hour_cycle]
114
+
115
+ if @hour_cycle && !HOUR_CYCLE_SYMS.keys.include?(@hour_cycle)
116
+ raise ICU::Error.new("Unknown hour cycle #{@hour_cycle}")
117
+ end
118
+
119
+ @f = make_formatter(time_style, date_style, @locale, time_zone, skeleton)
95
120
  if tz_style
96
121
  f0 = date_format(true)
97
122
  f1 = update_tz_format(f0, tz_style)
@@ -99,11 +124,13 @@ module ICU
99
124
  set_date_format(true, f1)
100
125
  end
101
126
  end
127
+
128
+ replace_hour_symbol!
102
129
  end
103
130
 
104
131
  def parse(str)
105
132
  str_u = UCharPointer.from_string(str)
106
- str_l = str_u.size
133
+ str_l = str.size
107
134
  Lib.check_error do |error|
108
135
  ret = Lib.udat_parse(@f, str_u, str_l, nil, error)
109
136
  Time.at(ret / 1000.0)
@@ -170,6 +197,128 @@ module ICU
170
197
  end
171
198
 
172
199
  def set_date_format(localized, pattern_str)
200
+ set_date_format_impl(localized, pattern_str)
201
+
202
+ # After setting the date format string, we need to ensure that any hour
203
+ # symbols were properly localised according to @hour_cycle.
204
+ replace_hour_symbol!
205
+ end
206
+
207
+ def skeleton_format(skeleton_pattern_str, locale)
208
+ skeleton_pattern_ptr = UCharPointer.from_string(skeleton_pattern_str)
209
+ skeleton_pattern_len = skeleton_pattern_str.size
210
+
211
+ needed_length = 0
212
+ pattern_ptr = UCharPointer.new(needed_length)
213
+
214
+ udatpg_ptr = Lib.check_error { |error| Lib.udatpg_open(locale, error) }
215
+ generator = FFI::AutoPointer.new(udatpg_ptr, Lib.method(:udatpg_close))
216
+
217
+ retried = false
218
+
219
+ begin
220
+ Lib.check_error do |error|
221
+ needed_length = Lib.udatpg_getBestPattern(generator, skeleton_pattern_ptr, skeleton_pattern_len, pattern_ptr, needed_length, error)
222
+ end
223
+
224
+ return needed_length, pattern_ptr
225
+ rescue BufferOverflowError
226
+ raise BufferOverflowError, "needed: #{needed_length}" if retried
227
+ pattern_ptr = pattern_ptr.resized_to needed_length
228
+ retried = true
229
+ retry
230
+ end
231
+ end
232
+
233
+ private
234
+
235
+ # Converts the current pattern to a pattern that takes the desired hour cycle
236
+ # into account. This is needed because most of the standard patterns in ICU
237
+ # contain either h (12 hour) or H (23 hour) in them, instead of j (locale-
238
+ # specified hour cycle). This means if you use a locale with an @hours=h12
239
+ # keyword in it, for example, it would normally be totally ignored by ICU.
240
+ #
241
+ # This is the same fixup done by Firefox:
242
+ # https://github.com/tc39/ecma402/issues/665#issuecomment-1084833809
243
+ # https://searchfox.org/mozilla-central/rev/625c3d0c8ae46502aed83f33bd530cb93e926e9f/intl/components/src/DateTimeFormat.cpp#282-323
244
+ def replace_hour_symbol!
245
+ # Short circuit this case - nil means "use whatever is in the pattern already", so
246
+ # no need to actually run any of this implementation.
247
+ return unless @hour_cycle
248
+
249
+ # Get the current pattern and convert to a skeleton
250
+ skeleton_str = pattern_to_skeleton_uchar(current_pattern_as_uchar).string
251
+
252
+ # Manipulate the skeleton to make it work with the correct hour cycle.
253
+ skeleton_str.gsub!(/[hHkKjJ]/, HOUR_CYCLE_SYMS[@hour_cycle])
254
+
255
+ # Either ensure the skeleton has, or does not have, am/pm, as appropriate
256
+ if ['h11', 'h12'].include?(@hour_cycle)
257
+ skeleton_str << 'a' unless skeleton_str.include? 'a'
258
+ else
259
+ skeleton_str.gsub!('a', '')
260
+ end
261
+
262
+ # Convert the skeleton back to a pattern
263
+ new_pattern_str = skeleton_to_pattern_uchar(UCharPointer.from_string(skeleton_str)).string
264
+
265
+ # We also need to manipulate the _pattern_, a little bit, because (according to Firefox source):
266
+ #
267
+ # Input skeletons don't differentiate between "K" and "h" resp. "k" and "H".
268
+ #
269
+ # https://searchfox.org/mozilla-central/rev/625c3d0c8ae46502aed83f33bd530cb93e926e9f/intl/components/src/DateTimeFormat.cpp#183
270
+ # So, if we put a skeleton with a k in it into getBestPattern, it comes out with a H (and a
271
+ # skeleton with a K in it comes out with a h). Need to fix this in the generated pattern.
272
+ resolved_hour_cycle = @hour_cycle == :locale ? Locale.new(@locale).keyword('hours') : @hour_cycle
273
+
274
+ if HOUR_CYCLE_SYMS.keys.include?(resolved_hour_cycle)
275
+ new_pattern_str.gsub!(/[hHkK]/, HOUR_CYCLE_SYMS[resolved_hour_cycle])
276
+ end
277
+
278
+ # Finally, set the new pattern onto the date time formatter
279
+ set_date_format_impl(false, new_pattern_str)
280
+ end
281
+
282
+ # Load up the date formatter locale and make a generator
283
+ # Note that we _MUST_ actually use @locale as passed to us, rather than calling
284
+ # udat_getLocaleByType to look it up from @f, because the latter will throw away
285
+ # any @hours specifier in the locale, and we need it.
286
+ def datetime_pattern_generator
287
+ @datetime_pattern_generator ||= FFI::AutoPointer.new(
288
+ Lib.check_error { |error| Lib.udatpg_open(@locale, error) },
289
+ Lib.method(:udatpg_close)
290
+ )
291
+ end
292
+
293
+ def current_pattern_as_uchar
294
+ Lib::Util.read_uchar_buffer_as_ptr(0) do |buf, error|
295
+ Lib.udat_toPattern(@f, false, buf, buf.length_in_uchars, error)
296
+ end
297
+ end
298
+
299
+ def pattern_to_skeleton_uchar(pattern_uchar)
300
+ Lib::Util.read_uchar_buffer_as_ptr(0) do |buf, error|
301
+ Lib.udatpg_getSkeleton(
302
+ datetime_pattern_generator,
303
+ pattern_uchar, pattern_uchar.length_in_uchars,
304
+ buf, buf.length_in_uchars,
305
+ error
306
+ )
307
+ end
308
+ end
309
+
310
+ def skeleton_to_pattern_uchar(skeleton_uchar)
311
+ Lib::Util.read_uchar_buffer_as_ptr(0) do |buf, error|
312
+ Lib.udatpg_getBestPattern(
313
+ datetime_pattern_generator,
314
+ skeleton_uchar, skeleton_uchar.length_in_uchars,
315
+ buf, buf.length_in_uchars,
316
+ error
317
+ )
318
+ end
319
+ end
320
+
321
+ def set_date_format_impl(localized, pattern_str)
173
322
  pattern = UCharPointer.from_string(pattern_str)
174
323
  pattern_len = pattern_str.size
175
324
 
data/lib/ffi-icu/uchar.rb CHANGED
@@ -43,6 +43,10 @@ module ICU
43
43
  wstring.pack("U*")
44
44
  end
45
45
 
46
+ def length_in_uchars
47
+ size / type_size
48
+ end
49
+
46
50
 
47
51
  end # UCharPointer
48
52
  end # ICU
@@ -1,3 +1,3 @@
1
1
  module ICU
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.2"
3
3
  end
@@ -69,6 +69,11 @@ module ICU
69
69
  expect { NumberFormatting.create('en-US', :currency, style: :iso) }.to raise_error(StandardError)
70
70
  end
71
71
  end
72
+
73
+ it 'should format a bignum' do
74
+ str = NumberFormatting.format_number("en", 1_000_000_000_000_000_000_000_000_000_000_000_000_000)
75
+ expect(str).to eq('1,000,000,000,000,000,000,000,000,000,000,000,000,000')
76
+ end
72
77
  end
73
78
  end # NumberFormatting
74
79
  end # ICU
data/spec/time_spec.rb CHANGED
@@ -59,7 +59,7 @@ module ICU
59
59
  expect(f2.format(t8)).to eq("3/29/08#{en_sep} 6:38:24 AM #{en_tz}")
60
60
  end
61
61
 
62
- f3 = TimeFormatting.create(:locale => 'de_DE', :zone => 'Africa/Dakar ', :date => :short , :time => :long)
62
+ f3 = TimeFormatting.create(:locale => 'de_DE', :zone => 'Africa/Dakar', :date => :short , :time => :long)
63
63
  ge_sep = ""
64
64
  if cldr_version >= "27.0.1"
65
65
  ge_sep = ","
@@ -82,6 +82,105 @@ module ICU
82
82
  expect(f3.format(t7)).to eq("29.03.08#{ge_sep} 02:37:23 GMT")
83
83
  expect(f3.format(t8)).to eq("29.03.08#{ge_sep} 03:38:24 GMT")
84
84
  end
85
+
86
+ context 'skeleton pattern' do
87
+ f4 = TimeFormatting.create(:locale => 'fr_FR', :date => :pattern, :time => :pattern, :skeleton => 'MMMy')
88
+
89
+ it 'check format' do
90
+ expect(f4.format(t0)).to eq("nov. 2008")
91
+ expect(f4.format(t1)).to eq("oct. 2008")
92
+ end
93
+
94
+ it 'check date_format' do
95
+ expect(f4.date_format(true)).to eq("MMM y")
96
+ end
97
+ end
98
+
99
+ context 'hour cycle' do
100
+ # en_AU normally is 12 hours, fr_FR is normally 23 hours
101
+ ['en_AU', 'fr_FR', 'zh_CN'].each do |locale_name|
102
+ context "with locale #{locale_name}" do
103
+ it 'works with hour_cycle: h11' do
104
+ t = Time.new(2021, 04, 01, 12, 05, 0, "+00:00")
105
+ str = TimeFormatting.format(t, time: :short, date: :none, locale: locale_name, zone: 'UTC', hour_cycle: 'h11')
106
+ expect(str).to match(/0:05/i)
107
+ expect(str).to match(/(pm|下午)/i)
108
+ end
109
+
110
+ it 'works with hour_cycle: h12' do
111
+ t = Time.new(2021, 04, 01, 12, 05, 0, "+00:00")
112
+ str = TimeFormatting.format(t, time: :short, date: :none, locale: locale_name, zone: 'UTC', hour_cycle: 'h12')
113
+ expect(str).to match(/12:05/i)
114
+ expect(str).to match(/(pm|下午)/i)
115
+ end
116
+
117
+ it 'works with hour_cycle: h23' do
118
+ t = Time.new(2021, 04, 01, 00, 05, 0, "+00:00")
119
+ str = TimeFormatting.format(t, time: :short, date: :none, locale: locale_name, zone: 'UTC', hour_cycle: 'h23')
120
+ expect(str).to match(/0:05/i)
121
+ expect(str).to_not match(/(am|pm)/i)
122
+ end
123
+
124
+ it 'works with hour_cycle: h24' do
125
+ t = Time.new(2021, 04, 01, 00, 05, 0, "+00:00")
126
+ str = TimeFormatting.format(t, time: :short, date: :none, locale: locale_name, zone: 'UTC', hour_cycle: 'h24')
127
+ expect(str).to match(/24:05/i)
128
+ expect(str).to_not match(/(am|pm)/i)
129
+ end
130
+
131
+ context '@hours keyword' do
132
+ before(:each) do
133
+ skip("Only works on ICU >= 67") if Lib.version.to_a[0] < 67
134
+ end
135
+
136
+ it 'works with @hours=h11 keyword' do
137
+ t = Time.new(2021, 04, 01, 12, 05, 0, "+00:00")
138
+ locale = Locale.new(locale_name).with_keyword('hours', 'h11').to_s
139
+ str = TimeFormatting.format(t, time: :short, date: :none, locale: locale, zone: 'UTC', hour_cycle: :locale)
140
+ expect(str).to match(/0:05/i)
141
+ expect(str).to match(/(pm|下午)/i)
142
+ end
143
+ it 'works with @hours=h12 keyword' do
144
+ t = Time.new(2021, 04, 01, 12, 05, 0, "+00:00")
145
+ locale = Locale.new(locale_name).with_keyword('hours', 'h12').to_s
146
+ str = TimeFormatting.format(t, time: :short, date: :none, locale: locale, zone: 'UTC', hour_cycle: :locale)
147
+ expect(str).to match(/12:05/i)
148
+ expect(str).to match(/(pm|下午)/i)
149
+ end
150
+
151
+ it 'works with @hours=h23 keyword' do
152
+ t = Time.new(2021, 04, 01, 00, 05, 0, "+00:00")
153
+ locale = Locale.new(locale_name).with_keyword('hours', 'h23').to_s
154
+ str = TimeFormatting.format(t, time: :short, date: :none, locale: locale, zone: 'UTC', hour_cycle: :locale)
155
+ expect(str).to match(/0:05/i)
156
+ expect(str).to_not match(/(am|pm)/i)
157
+ end
158
+
159
+ it 'works with @hours=h24 keyword' do
160
+ t = Time.new(2021, 04, 01, 00, 05, 0, "+00:00")
161
+ locale = Locale.new(locale_name).with_keyword('hours', 'h24').to_s
162
+ str = TimeFormatting.format(t, time: :short, date: :none, locale: locale, zone: 'UTC', hour_cycle: :locale)
163
+ expect(str).to match(/24:05/i)
164
+ expect(str).to_not match(/(am|pm)/i)
165
+ end
166
+ end
167
+ end
168
+ end
169
+
170
+ it 'works with defaults on a h12 locale' do
171
+ t = Time.new(2021, 04, 01, 13, 05, 0, "+00:00")
172
+ str = TimeFormatting.format(t, time: :short, date: :none, locale: 'en_AU', zone: 'UTC', hour_cycle: :locale)
173
+ expect(str).to match(/1:05/i)
174
+ expect(str).to match(/pm/i)
175
+ end
176
+
177
+ it 'works with defaults on a h23 locale' do
178
+ t = Time.new(2021, 04, 01, 0, 05, 0, "+00:00")
179
+ str = TimeFormatting.format(t, time: :short, date: :none, locale: 'fr_FR', zone: 'UTC', hour_cycle: :locale)
180
+ expect(str).to match(/0:05/i)
181
+ expect(str).to_not match(/(am|pm)/i)
182
+ end
183
+ end
85
184
  end
86
185
  end
87
186
  end
@@ -9,7 +9,7 @@ module ICU
9
9
  [
10
10
  ["Any-Hex", "abcde", "\\u0061\\u0062\\u0063\\u0064\\u0065"],
11
11
  ["Lower", "ABC", "abc"],
12
- ["en", "雙屬性集合之空間分群演算法-應用於地理資料", "shuāng shǔ xìng jí hé zhī kōng jiān fēn qún yǎn suàn fǎ-yīng yòng yú de lǐ zī liào"],
12
+ ["Han-Latin", "雙屬性集合之空間分群演算法-應用於地理資料", "shuāng shǔ xìng jí hé zhī kōng jiān fēn qún yǎn suàn fǎ-yīng yòng yú de lǐ zī liào"],
13
13
  ["Devanagari-Latin", "दौलत", "daulata"]
14
14
  ].each do |id, input, output|
15
15
  it "should transliterate #{id}" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffi-icu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jari Bakken