ffi-icu 0.5.2 → 0.6.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/Gemfile +10 -0
- data/LICENSE +1 -1
- data/README.md +21 -51
- data/Rakefile +4 -5
- data/ffi-icu.gemspec +34 -25
- data/lib/ffi-icu/break_iterator.rb +19 -18
- data/lib/ffi-icu/chardet.rb +12 -13
- data/lib/ffi-icu/collation.rb +62 -59
- data/lib/ffi-icu/duration_formatting.rb +293 -267
- data/lib/ffi-icu/lib/util.rb +10 -10
- data/lib/ffi-icu/lib.rb +273 -202
- data/lib/ffi-icu/locale.rb +14 -10
- data/lib/ffi-icu/normalization.rb +7 -7
- data/lib/ffi-icu/normalizer.rb +14 -8
- data/lib/ffi-icu/number_formatting.rb +41 -27
- data/lib/ffi-icu/time_formatting.rb +116 -93
- data/lib/ffi-icu/transliteration.rb +19 -19
- data/lib/ffi-icu/uchar.rb +14 -17
- data/lib/ffi-icu/version.rb +3 -1
- data/lib/ffi-icu.rb +16 -17
- metadata +35 -71
- data/.document +0 -5
- data/.gitignore +0 -23
- data/.rspec +0 -2
- data/.travis.yml +0 -28
- data/benchmark/detect.rb +0 -14
- data/benchmark/shared.rb +0 -17
- data/build_icu.sh +0 -53
- data/lib/ffi-icu/core_ext/string.rb +0 -9
- data/spec/break_iterator_spec.rb +0 -77
- data/spec/chardet_spec.rb +0 -42
- data/spec/collation_spec.rb +0 -84
- data/spec/duration_formatting_spec.rb +0 -143
- data/spec/lib/version_info_spec.rb +0 -20
- data/spec/lib_spec.rb +0 -63
- data/spec/locale_spec.rb +0 -280
- data/spec/normalization_spec.rb +0 -22
- data/spec/normalizer_spec.rb +0 -57
- data/spec/number_formatting_spec.rb +0 -79
- data/spec/spec_helper.rb +0 -13
- data/spec/time_spec.rb +0 -198
- data/spec/transliteration_spec.rb +0 -36
- data/spec/uchar_spec.rb +0 -34
- data/test.c +0 -56
data/lib/ffi-icu/locale.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ICU
|
|
2
4
|
class Locale
|
|
3
5
|
class << self
|
|
@@ -43,16 +45,16 @@ module ICU
|
|
|
43
45
|
attr_reader :id
|
|
44
46
|
|
|
45
47
|
DISPLAY_CONTEXT = {
|
|
46
|
-
length_full:
|
|
47
|
-
length_short: 513
|
|
48
|
-
}
|
|
48
|
+
length_full: 512, # UDISPCTX_LENGTH_FULL = (UDISPCTX_TYPE_DISPLAY_LENGTH<<8) + 0
|
|
49
|
+
length_short: 513 # UDISPCTX_LENGTH_SHORT = (UDISPCTX_TYPE_DISPLAY_LENGTH<<8) + 1
|
|
50
|
+
}.freeze
|
|
49
51
|
|
|
50
52
|
def initialize(id)
|
|
51
53
|
@id = id.to_s
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
def ==(other)
|
|
55
|
-
other.is_a?(self.class) && other.id ==
|
|
57
|
+
other.is_a?(self.class) && other.id == id
|
|
56
58
|
end
|
|
57
59
|
|
|
58
60
|
def base_name
|
|
@@ -104,9 +106,9 @@ module ICU
|
|
|
104
106
|
def display_name_with_context(locale, contexts = [])
|
|
105
107
|
contexts = DISPLAY_CONTEXT.select { |context| contexts.include?(context) }.values
|
|
106
108
|
|
|
107
|
-
with_locale_display_name(
|
|
109
|
+
with_locale_display_name(locale, contexts) do |locale_display_names|
|
|
108
110
|
Lib::Util.read_uchar_buffer(256) do |buffer, status|
|
|
109
|
-
Lib.uldn_localeDisplayName(locale_display_names,
|
|
111
|
+
Lib.uldn_localeDisplayName(locale_display_names, @id, buffer, buffer.size, status)
|
|
110
112
|
end
|
|
111
113
|
end
|
|
112
114
|
end
|
|
@@ -183,13 +185,13 @@ module ICU
|
|
|
183
185
|
end
|
|
184
186
|
end
|
|
185
187
|
|
|
186
|
-
def to_language_tag(strict = false)
|
|
188
|
+
def to_language_tag(strict = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
187
189
|
Lib::Util.read_string_buffer(64) do |buffer, status|
|
|
188
190
|
Lib.uloc_toLanguageTag(@id, buffer, buffer.size, strict ? 1 : 0, status)
|
|
189
191
|
end
|
|
190
192
|
end
|
|
191
193
|
|
|
192
|
-
|
|
194
|
+
alias to_s id
|
|
193
195
|
|
|
194
196
|
def variant
|
|
195
197
|
Lib::Util.read_string_buffer(64) do |buffer, status|
|
|
@@ -238,9 +240,11 @@ module ICU
|
|
|
238
240
|
|
|
239
241
|
def with_locale_display_name(locale, contexts)
|
|
240
242
|
pointer = FFI::MemoryPointer.new(:int, contexts.length).write_array_of_int(contexts)
|
|
241
|
-
locale_display_names = ICU::Lib.check_error
|
|
243
|
+
locale_display_names = ICU::Lib.check_error do |status|
|
|
244
|
+
ICU::Lib.uldn_openForContext(locale, pointer, contexts.length, status)
|
|
245
|
+
end
|
|
242
246
|
|
|
243
|
-
yield
|
|
247
|
+
yield(locale_display_names)
|
|
244
248
|
ensure
|
|
245
249
|
Lib.uldn_close(locale_display_names) if locale_display_names
|
|
246
250
|
end
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ICU
|
|
2
4
|
module Normalization
|
|
3
|
-
|
|
4
5
|
def self.normalize(input, mode = :default)
|
|
5
|
-
input_length = input.
|
|
6
|
+
input_length = input.size
|
|
6
7
|
needed_length = out_length = options = 0
|
|
7
8
|
in_ptr = UCharPointer.from_string(input)
|
|
8
9
|
out_ptr = UCharPointer.new(out_length)
|
|
@@ -14,9 +15,9 @@ module ICU
|
|
|
14
15
|
needed_length = Lib.unorm_normalize(in_ptr, input_length, mode, options, out_ptr, out_length, error)
|
|
15
16
|
end
|
|
16
17
|
rescue BufferOverflowError
|
|
17
|
-
raise
|
|
18
|
+
raise(BufferOverflowError, "needed: #{needed_length}") if retried
|
|
18
19
|
|
|
19
|
-
out_ptr = out_ptr.resized_to
|
|
20
|
+
out_ptr = out_ptr.resized_to(needed_length)
|
|
20
21
|
out_length = needed_length + 1
|
|
21
22
|
|
|
22
23
|
retried = true
|
|
@@ -25,6 +26,5 @@ module ICU
|
|
|
25
26
|
|
|
26
27
|
out_ptr.string
|
|
27
28
|
end
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end # ICU
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/ffi-icu/normalizer.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ICU
|
|
2
4
|
class Normalizer
|
|
3
5
|
# support for newer ICU normalization API
|
|
@@ -9,7 +11,7 @@ module ICU
|
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
def normalize(input)
|
|
12
|
-
input_length = input.
|
|
14
|
+
input_length = input.size
|
|
13
15
|
in_ptr = UCharPointer.from_string(input)
|
|
14
16
|
needed_length = capacity = 0
|
|
15
17
|
out_ptr = UCharPointer.new(needed_length)
|
|
@@ -20,10 +22,10 @@ module ICU
|
|
|
20
22
|
needed_length = Lib.unorm2_normalize(@instance, in_ptr, input_length, out_ptr, capacity, error)
|
|
21
23
|
end
|
|
22
24
|
rescue BufferOverflowError
|
|
23
|
-
raise
|
|
25
|
+
raise(BufferOverflowError, "needed: #{needed_length}") if retried
|
|
24
26
|
|
|
25
27
|
capacity = needed_length
|
|
26
|
-
out_ptr = out_ptr.resized_to
|
|
28
|
+
out_ptr = out_ptr.resized_to(needed_length)
|
|
27
29
|
|
|
28
30
|
retried = true
|
|
29
31
|
retry
|
|
@@ -32,16 +34,20 @@ module ICU
|
|
|
32
34
|
out_ptr.string
|
|
33
35
|
end
|
|
34
36
|
|
|
35
|
-
def
|
|
36
|
-
input_length = input.
|
|
37
|
+
def normailzed?(input)
|
|
38
|
+
input_length = input.size
|
|
37
39
|
in_ptr = UCharPointer.from_string(input)
|
|
38
40
|
|
|
39
41
|
Lib.check_error do |error|
|
|
40
|
-
|
|
42
|
+
Lib.unorm2_isNormalized(@instance, in_ptr, input_length, error)
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
result
|
|
44
46
|
end
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
def is_normalized?(input) # rubocop:disable Naming/PredicatePrefix
|
|
49
|
+
Warning.warn('is_normalized? is deprecated and will be removed after v0.7. Please use normalized? instead.')
|
|
50
|
+
normalized?(input)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'bigdecimal'
|
|
2
4
|
|
|
3
5
|
module ICU
|
|
4
6
|
module NumberFormatting
|
|
5
7
|
@default_options = {}
|
|
6
|
-
|
|
8
|
+
|
|
7
9
|
def self.create(locale, type = :decimal, options = {})
|
|
8
10
|
case type
|
|
9
11
|
when :currency
|
|
@@ -17,7 +19,7 @@ module ICU
|
|
|
17
19
|
@default_options.clear
|
|
18
20
|
end
|
|
19
21
|
|
|
20
|
-
def self.set_default_options(options)
|
|
22
|
+
def self.set_default_options(options) # rubocop:disable Naming/AccessorMethodName
|
|
21
23
|
@default_options.merge!(options)
|
|
22
24
|
end
|
|
23
25
|
|
|
@@ -38,8 +40,7 @@ module ICU
|
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
class BaseFormatter
|
|
41
|
-
|
|
42
|
-
def set_attributes(options)
|
|
43
|
+
def set_attributes(options) # rubocop:disable Naming/AccessorMethodName
|
|
43
44
|
options.each { |key, value| Lib.unum_set_attribute(@f, key, value) }
|
|
44
45
|
self
|
|
45
46
|
end
|
|
@@ -47,13 +48,17 @@ module ICU
|
|
|
47
48
|
private
|
|
48
49
|
|
|
49
50
|
def make_formatter(type, locale)
|
|
50
|
-
ptr = Lib.check_error
|
|
51
|
+
ptr = Lib.check_error do |error|
|
|
52
|
+
Lib.unum_open(type, FFI::MemoryPointer.new(4), 0, locale, FFI::MemoryPointer.new(4), error)
|
|
53
|
+
end
|
|
51
54
|
FFI::AutoPointer.new(ptr, Lib.method(:unum_close))
|
|
52
55
|
end
|
|
53
56
|
end
|
|
54
57
|
|
|
55
58
|
class NumberFormatter < BaseFormatter
|
|
56
59
|
def initialize(locale, type = :decimal)
|
|
60
|
+
super()
|
|
61
|
+
|
|
57
62
|
@f = make_formatter(type, locale)
|
|
58
63
|
end
|
|
59
64
|
|
|
@@ -75,41 +80,48 @@ module ICU
|
|
|
75
80
|
rescue RangeError
|
|
76
81
|
# Fall back to stringifying in Ruby and passing that to ICU
|
|
77
82
|
unless defined? Lib.unum_format_decimal
|
|
78
|
-
raise
|
|
79
|
-
|
|
83
|
+
raise(RangeError, "Number #{number} is too big to fit in int64_t and your " \
|
|
84
|
+
'ICU version is too old to have unum_format_decimal')
|
|
80
85
|
end
|
|
81
86
|
string_version = number.to_s
|
|
82
|
-
needed_length = Lib.unum_format_decimal(@f, string_version, string_version.
|
|
87
|
+
needed_length = Lib.unum_format_decimal(@f, string_version, string_version.size, out_ptr,
|
|
88
|
+
needed_length, nil, error)
|
|
83
89
|
end
|
|
84
90
|
when BigDecimal
|
|
85
91
|
string_version = number.to_s('F')
|
|
86
|
-
if Lib.respond_to?
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
needed_length = if Lib.respond_to?(:unum_format_decimal)
|
|
93
|
+
Lib.unum_format_decimal(@f, string_version, string_version.size, out_ptr,
|
|
94
|
+
needed_length, nil, error)
|
|
95
|
+
else
|
|
96
|
+
Lib.unum_format_double(@f, number.to_f, out_ptr, needed_length, nil, error)
|
|
97
|
+
end
|
|
91
98
|
end
|
|
92
99
|
end
|
|
93
|
-
out_ptr.string
|
|
100
|
+
out_ptr.string(needed_length)
|
|
94
101
|
rescue BufferOverflowError
|
|
95
|
-
raise
|
|
96
|
-
|
|
102
|
+
raise(BufferOverflowError, "needed: #{needed_length}") if retried
|
|
103
|
+
|
|
104
|
+
out_ptr = out_ptr.resized_to(needed_length)
|
|
97
105
|
retried = true
|
|
98
106
|
retry
|
|
99
107
|
end
|
|
100
108
|
end
|
|
101
|
-
end
|
|
109
|
+
end
|
|
102
110
|
|
|
103
111
|
class CurrencyFormatter < BaseFormatter
|
|
104
112
|
def initialize(locale, style = :default)
|
|
105
|
-
|
|
113
|
+
super()
|
|
114
|
+
|
|
115
|
+
if ['iso', 'plural'].include?((style || '').to_s)
|
|
106
116
|
if Lib.version.to_a.first >= 53
|
|
107
|
-
style = "currency_#{style}"
|
|
117
|
+
style = :"currency_#{style}"
|
|
108
118
|
else
|
|
109
|
-
|
|
119
|
+
raise("Your version of ICU (#{Lib.version.to_a.join('.')}) does not support " \
|
|
120
|
+
"#{style} currency formatting (supported only in version >= 53)")
|
|
110
121
|
end
|
|
111
122
|
elsif style && style.to_sym != :default
|
|
112
|
-
|
|
123
|
+
raise('The ffi-icu ruby gem does not support: ' \
|
|
124
|
+
"#{default} currency formatting (only :default, :iso, and :plural)")
|
|
113
125
|
else
|
|
114
126
|
style = :currency
|
|
115
127
|
end
|
|
@@ -124,16 +136,18 @@ module ICU
|
|
|
124
136
|
|
|
125
137
|
begin
|
|
126
138
|
Lib.check_error do |error|
|
|
127
|
-
needed_length = Lib.unum_format_currency(@f, number, UCharPointer.from_string(currency, 4), out_ptr,
|
|
139
|
+
needed_length = Lib.unum_format_currency(@f, number, UCharPointer.from_string(currency, 4), out_ptr,
|
|
140
|
+
needed_length, nil, error)
|
|
128
141
|
end
|
|
129
142
|
out_ptr.string
|
|
130
143
|
rescue BufferOverflowError
|
|
131
|
-
raise
|
|
132
|
-
|
|
144
|
+
raise(BufferOverflowError, "needed: #{needed_length}") if retried
|
|
145
|
+
|
|
146
|
+
out_ptr = out_ptr.resized_to(needed_length)
|
|
133
147
|
retried = true
|
|
134
148
|
retry
|
|
135
149
|
end
|
|
136
150
|
end
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -1,53 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'date'
|
|
2
4
|
|
|
3
5
|
module ICU
|
|
4
6
|
module TimeFormatting
|
|
5
7
|
TZ_MAP = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
:
|
|
36
|
-
:
|
|
37
|
-
|
|
38
|
-
|
|
8
|
+
# The generic location format.
|
|
9
|
+
# Where that is unavailable, falls back to the long localized GMT format ("OOOO";
|
|
10
|
+
# Note: Fallback is only necessary with a GMT-style Time Zone ID, like Etc/GMT-830.),
|
|
11
|
+
# This is especially useful when presenting possible timezone choices for user selection,
|
|
12
|
+
# since the naming is more uniform than the "v" format.
|
|
13
|
+
# such as "United States Time (New York)", "Italy Time"
|
|
14
|
+
generic_location: 'VVVV',
|
|
15
|
+
# The long generic non-location format.
|
|
16
|
+
# Where that is unavailable, falls back to generic location format ("VVVV")., such as "Eastern Time".
|
|
17
|
+
generic_long: 'vvvv',
|
|
18
|
+
# The short generic non-location format.
|
|
19
|
+
# Where that is unavailable, falls back to the generic location format ("VVVV"),
|
|
20
|
+
# then the short localized GMT format as the final fallback., such as "ET".
|
|
21
|
+
generic_short: 'v',
|
|
22
|
+
# The long specific non-location format.
|
|
23
|
+
# Where that is unavailable, falls back to the long localized GMT format ("OOOO").
|
|
24
|
+
specific_long: 'zzzz',
|
|
25
|
+
# The short specific non-location format.
|
|
26
|
+
# Where that is unavailable, falls back to the short localized GMT format ("O").
|
|
27
|
+
specific_short: 'z',
|
|
28
|
+
# The ISO8601 basic format with hours, minutes and optional seconds fields.
|
|
29
|
+
# The format is equivalent to RFC 822 zone format (when optional seconds field is absent).
|
|
30
|
+
# This is equivalent to the "xxxx" specifier.
|
|
31
|
+
basic: 'Z',
|
|
32
|
+
# The long localized GMT format. This is equivalent to the "OOOO" specifier, such as GMT-8:00
|
|
33
|
+
localized_long: 'ZZZZ',
|
|
34
|
+
# The ISO8601 extended format with hours, minutes and optional seconds fields.
|
|
35
|
+
# The ISO8601 UTC indicator "Z" is used when local time offset is 0.
|
|
36
|
+
# This is equivalent to the "XXXXX" specifier, such as -08:00 -07:52:58
|
|
37
|
+
extended: 'ZZZZZ',
|
|
38
|
+
localized_short: 'O', # The short localized GMT format, such as GMT-8
|
|
39
|
+
localized_longO: 'OOOO', # The long localized GMT format, such as GMT-08:00
|
|
40
|
+
# The short time zone ID. Where that is unavailable,
|
|
41
|
+
# the special short time zone ID unk (Unknown Zone) is used.
|
|
42
|
+
# Note: This specifier was originally used for a variant of the short specific non-location format,
|
|
43
|
+
# but it was deprecated in the later version of this specification. In CLDR 23, the definition
|
|
44
|
+
# of the specifier was changed to designate a short time zone ID, such as uslax
|
|
45
|
+
tz_id_short: 'V',
|
|
46
|
+
tz_id_long: 'VV', # The long time zone ID, such as America/Los_Angeles
|
|
47
|
+
# The exemplar city (location) for the time zone. Where that is unavailable,
|
|
48
|
+
# the localized exemplar city name for the special zone Etc/Unknown is used as the fallback
|
|
49
|
+
# (for example, "Unknown City"), such as Los Angeles
|
|
39
50
|
# see: http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
|
|
40
|
-
|
|
51
|
+
city_location: 'VVV'
|
|
52
|
+
}.freeze
|
|
41
53
|
|
|
42
54
|
HOUR_CYCLE_SYMS = {
|
|
43
55
|
'h11' => 'K',
|
|
44
56
|
'h12' => 'h',
|
|
45
57
|
'h23' => 'H',
|
|
46
58
|
'h24' => 'k',
|
|
47
|
-
:locale => 'j'
|
|
48
|
-
}
|
|
59
|
+
:locale => 'j'
|
|
60
|
+
}.freeze
|
|
49
61
|
@default_options = {}
|
|
50
|
-
|
|
62
|
+
|
|
51
63
|
def self.create(options = {})
|
|
52
64
|
DateTimeFormatter.new(@default_options.merge(options))
|
|
53
65
|
end
|
|
@@ -56,17 +68,16 @@ module ICU
|
|
|
56
68
|
@default_options.clear
|
|
57
69
|
end
|
|
58
70
|
|
|
59
|
-
def self.set_default_options(options)
|
|
71
|
+
def self.set_default_options(options) # rubocop:disable Naming/AccessorMethodName
|
|
60
72
|
@default_options.merge!(options)
|
|
61
73
|
end
|
|
62
74
|
|
|
63
|
-
def self.format(
|
|
64
|
-
create(@default_options.merge(options)).format(
|
|
75
|
+
def self.format(datetime, options = {})
|
|
76
|
+
create(@default_options.merge(options)).format(datetime)
|
|
65
77
|
end
|
|
66
78
|
|
|
67
79
|
class BaseFormatter
|
|
68
|
-
|
|
69
|
-
def set_attributes(options)
|
|
80
|
+
def set_attributes(options) # rubocop:disable Naming/AccessorMethodName
|
|
70
81
|
options.each { |key, value| Lib.unum_set_attribute(@f, key, value) }
|
|
71
82
|
self
|
|
72
83
|
end
|
|
@@ -83,11 +94,11 @@ module ICU
|
|
|
83
94
|
time_zone = UCharPointer.from_string(time_zone_str)
|
|
84
95
|
tz_len = time_zone_str.size
|
|
85
96
|
else
|
|
86
|
-
Lib.check_error
|
|
87
|
-
i_len
|
|
97
|
+
Lib.check_error do |error|
|
|
98
|
+
i_len = 150
|
|
88
99
|
time_zone = UCharPointer.new(i_len)
|
|
89
|
-
tz_len = Lib.ucal_getDefaultTimeZone(time_zone, i_len, error)
|
|
90
|
-
|
|
100
|
+
tz_len = Lib.ucal_getDefaultTimeZone(time_zone, i_len, error)
|
|
101
|
+
end
|
|
91
102
|
end
|
|
92
103
|
|
|
93
104
|
if skeleton
|
|
@@ -97,13 +108,17 @@ module ICU
|
|
|
97
108
|
pattern_len, pattern_ptr = skeleton_format(skeleton, locale)
|
|
98
109
|
end
|
|
99
110
|
|
|
100
|
-
ptr = Lib.check_error
|
|
111
|
+
ptr = Lib.check_error do |error|
|
|
112
|
+
Lib.udat_open(time_style, date_style, locale, time_zone, tz_len, pattern_ptr, pattern_len, error)
|
|
113
|
+
end
|
|
101
114
|
FFI::AutoPointer.new(ptr, Lib.method(:udat_close))
|
|
102
115
|
end
|
|
103
116
|
end
|
|
104
117
|
|
|
105
118
|
class DateTimeFormatter < BaseFormatter
|
|
106
|
-
def initialize(options={})
|
|
119
|
+
def initialize(options = {})
|
|
120
|
+
super()
|
|
121
|
+
|
|
107
122
|
time_style = options[:time] || :short
|
|
108
123
|
date_style = options[:date] || :short
|
|
109
124
|
@locale = options[:locale] || 'C'
|
|
@@ -113,31 +128,29 @@ module ICU
|
|
|
113
128
|
@hour_cycle = options[:hour_cycle]
|
|
114
129
|
|
|
115
130
|
if @hour_cycle && !HOUR_CYCLE_SYMS.keys.include?(@hour_cycle)
|
|
116
|
-
raise
|
|
131
|
+
raise(ICU::Error, "Unknown hour cycle #{@hour_cycle}")
|
|
117
132
|
end
|
|
118
133
|
|
|
119
134
|
@f = make_formatter(time_style, date_style, @locale, time_zone, skeleton)
|
|
120
135
|
if tz_style
|
|
121
136
|
f0 = date_format(true)
|
|
122
|
-
f1 = update_tz_format(f0, tz_style)
|
|
123
|
-
if f1 != f0
|
|
124
|
-
set_date_format(true, f1)
|
|
125
|
-
end
|
|
137
|
+
f1 = update_tz_format(f0, tz_style)
|
|
138
|
+
set_date_format(true, f1) if f1 != f0
|
|
126
139
|
end
|
|
127
140
|
|
|
128
141
|
replace_hour_symbol!
|
|
129
142
|
end
|
|
130
143
|
|
|
131
144
|
def parse(str)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
145
|
+
str_u = UCharPointer.from_string(str)
|
|
146
|
+
str_l = str.size
|
|
147
|
+
Lib.check_error do |error|
|
|
148
|
+
ret = Lib.udat_parse(@f, str_u, str_l, nil, error)
|
|
149
|
+
Time.at(ret / 1000.0)
|
|
150
|
+
end
|
|
138
151
|
end
|
|
139
152
|
|
|
140
|
-
def format(
|
|
153
|
+
def format(datetime)
|
|
141
154
|
needed_length = 0
|
|
142
155
|
out_ptr = UCharPointer.new(needed_length)
|
|
143
156
|
|
|
@@ -145,18 +158,23 @@ module ICU
|
|
|
145
158
|
|
|
146
159
|
begin
|
|
147
160
|
Lib.check_error do |error|
|
|
148
|
-
case
|
|
161
|
+
case datetime
|
|
149
162
|
when Date
|
|
150
|
-
needed_length = Lib.udat_format(
|
|
163
|
+
needed_length = Lib.udat_format(
|
|
164
|
+
@f,
|
|
165
|
+
Time.mktime(datetime.year, datetime.month, datetime.day, 0, 0, 0, 0).to_f * 1000.0,
|
|
166
|
+
out_ptr, needed_length, nil, error
|
|
167
|
+
)
|
|
151
168
|
when Time
|
|
152
|
-
needed_length = Lib.udat_format(@f,
|
|
169
|
+
needed_length = Lib.udat_format(@f, datetime.to_f * 1000.0, out_ptr, needed_length, nil, error)
|
|
153
170
|
end
|
|
154
171
|
end
|
|
155
172
|
|
|
156
173
|
out_ptr.string
|
|
157
174
|
rescue BufferOverflowError
|
|
158
|
-
raise
|
|
159
|
-
|
|
175
|
+
raise(BufferOverflowError, "needed: #{needed_length}") if retried
|
|
176
|
+
|
|
177
|
+
out_ptr = out_ptr.resized_to(needed_length)
|
|
160
178
|
retried = true
|
|
161
179
|
retry
|
|
162
180
|
end
|
|
@@ -164,19 +182,23 @@ module ICU
|
|
|
164
182
|
|
|
165
183
|
# time-zone formating
|
|
166
184
|
def update_tz_format(format, tz_style)
|
|
167
|
-
return format if format !~ /(.*?)(\s*(?:[
|
|
168
|
-
|
|
185
|
+
return format if format !~ /(.*?)(\s*(?:[zZOV]+\s*))(.*?)/
|
|
186
|
+
|
|
187
|
+
pre = ::Regexp.last_match(1)
|
|
188
|
+
tz = ::Regexp.last_match(2)
|
|
189
|
+
suff = ::Regexp.last_match(3)
|
|
169
190
|
if tz_style == :none
|
|
170
|
-
tz = (
|
|
191
|
+
tz = (tz =~ /\s/) && !pre.empty? && !suff.empty? ? ' ' : ''
|
|
171
192
|
else
|
|
172
193
|
repl = TZ_MAP[tz_style]
|
|
173
|
-
raise
|
|
174
|
-
|
|
194
|
+
raise('no such tz_style') unless repl
|
|
195
|
+
|
|
196
|
+
tz.gsub!(/^(\s*)(.*?)(\s*)$/, "\\1#{repl}\\3")
|
|
175
197
|
end
|
|
176
198
|
pre + tz + suff
|
|
177
199
|
end
|
|
178
200
|
|
|
179
|
-
def date_format(localized=true)
|
|
201
|
+
def date_format(localized = true) # rubocop:disable Style/OptionalBooleanParameter
|
|
180
202
|
needed_length = 0
|
|
181
203
|
out_ptr = UCharPointer.new(needed_length)
|
|
182
204
|
|
|
@@ -189,8 +211,9 @@ module ICU
|
|
|
189
211
|
|
|
190
212
|
out_ptr.string
|
|
191
213
|
rescue BufferOverflowError
|
|
192
|
-
raise
|
|
193
|
-
|
|
214
|
+
raise(BufferOverflowError, "needed: #{needed_length}") if retried
|
|
215
|
+
|
|
216
|
+
out_ptr = out_ptr.resized_to(needed_length)
|
|
194
217
|
retried = true
|
|
195
218
|
retry
|
|
196
219
|
end
|
|
@@ -205,26 +228,28 @@ module ICU
|
|
|
205
228
|
end
|
|
206
229
|
|
|
207
230
|
def skeleton_format(skeleton_pattern_str, locale)
|
|
208
|
-
|
|
209
|
-
|
|
231
|
+
skeleton_pattern_ptr = UCharPointer.from_string(skeleton_pattern_str)
|
|
232
|
+
skeleton_pattern_len = skeleton_pattern_str.size
|
|
210
233
|
|
|
211
|
-
|
|
212
|
-
|
|
234
|
+
needed_length = 0
|
|
235
|
+
pattern_ptr = UCharPointer.new(needed_length)
|
|
213
236
|
|
|
214
|
-
|
|
215
|
-
|
|
237
|
+
udatpg_ptr = Lib.check_error { |error| Lib.udatpg_open(locale, error) }
|
|
238
|
+
generator = FFI::AutoPointer.new(udatpg_ptr, Lib.method(:udatpg_close))
|
|
216
239
|
|
|
217
|
-
|
|
240
|
+
retried = false
|
|
218
241
|
|
|
219
242
|
begin
|
|
220
243
|
Lib.check_error do |error|
|
|
221
|
-
needed_length = Lib.udatpg_getBestPattern(generator, skeleton_pattern_ptr, skeleton_pattern_len,
|
|
244
|
+
needed_length = Lib.udatpg_getBestPattern(generator, skeleton_pattern_ptr, skeleton_pattern_len,
|
|
245
|
+
pattern_ptr, needed_length, error)
|
|
222
246
|
end
|
|
223
247
|
|
|
224
|
-
|
|
248
|
+
[needed_length, pattern_ptr]
|
|
225
249
|
rescue BufferOverflowError
|
|
226
|
-
raise
|
|
227
|
-
|
|
250
|
+
raise(BufferOverflowError, "needed: #{needed_length}") if retried
|
|
251
|
+
|
|
252
|
+
pattern_ptr = pattern_ptr.resized_to(needed_length)
|
|
228
253
|
retried = true
|
|
229
254
|
retry
|
|
230
255
|
end
|
|
@@ -255,9 +280,7 @@ module ICU
|
|
|
255
280
|
# Either ensure the skeleton has, or does not have, am/pm, as appropriate
|
|
256
281
|
if ['h11', 'h12'].include?(@hour_cycle)
|
|
257
282
|
# Only actually append 'am/pm' if there is an hour in the format string
|
|
258
|
-
if skeleton_str =~ /[hHkKjJ]/ && !skeleton_str.include?('a')
|
|
259
|
-
skeleton_str << 'a'
|
|
260
|
-
end
|
|
283
|
+
skeleton_str << 'a' if skeleton_str =~ /[hHkKjJ]/ && !skeleton_str.include?('a')
|
|
261
284
|
else
|
|
262
285
|
skeleton_str.gsub!('a', '')
|
|
263
286
|
end
|
|
@@ -275,7 +298,7 @@ module ICU
|
|
|
275
298
|
resolved_hour_cycle = @hour_cycle == :locale ? Locale.new(@locale).keyword('hours') : @hour_cycle
|
|
276
299
|
|
|
277
300
|
if HOUR_CYCLE_SYMS.keys.include?(resolved_hour_cycle)
|
|
278
|
-
new_pattern_str.gsub!(/[hHkK](?=(?:[
|
|
301
|
+
new_pattern_str.gsub!(/[hHkK](?=(?:[^']|'[^']*')*$)/, HOUR_CYCLE_SYMS[resolved_hour_cycle])
|
|
279
302
|
end
|
|
280
303
|
|
|
281
304
|
# Finally, set the new pattern onto the date time formatter
|
|
@@ -325,10 +348,10 @@ module ICU
|
|
|
325
348
|
pattern = UCharPointer.from_string(pattern_str)
|
|
326
349
|
pattern_len = pattern_str.size
|
|
327
350
|
|
|
328
|
-
Lib.check_error do |
|
|
329
|
-
|
|
351
|
+
Lib.check_error do |_error|
|
|
352
|
+
Lib.udat_applyPattern(@f, localized, pattern, pattern_len)
|
|
330
353
|
end
|
|
331
354
|
end
|
|
332
|
-
end
|
|
333
|
-
end
|
|
334
|
-
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
end
|