Uses unumf_* to format the individual components of the duration, and
ulistfmt_* to join them. That's roughly analogous with what the C++
MeasureFormatter class does internally.
I asked about some feedback for this approach on the ICU mailing list
too: https://sourceforge.net/p/icu/mailman/message/37645785/
Currently, doing something like `format(time, time: :none, hour_cycle:
'h12')` will give a string like "10/04/2021 PM" - the am/pm marker is
added to the skeleton even though the skeleton has no other time
markers.
The hour cycle can be directly specified to the call to #format, or
inferred from an "@hours=" keyword on the locale.
The algorithm for fixing up the skeletons & patterns is the same as that
used in Firefox:
https://github.com/tc39/ecma402/issues/665#issuecomment-1084833809
The skeleton generator is currently using udat_close to delete objects
created with udatpg_open; it should be using udatpg_close instead.
The C-side implementation of udat_close calls straight into the C++
delete operator:
U_CAPI void U_EXPORT2
udat_close(UDateFormat* format)
{
delete (DateFormat*)format;
}
The destructor on the DateFormat class is virtual, so i'm frankly
astonished this isn't just chasing and calling a pointer to nowhere. I
guess we got "lucky" and DateFormat and DateTimePatternGenerator have
a similar enough layout by chance that this works. However it's entirely
at the whim of the compiler as to whether this keeps working or not, so
we should fix it and call the correct cleanup function.
ICU expects the length of these pointers to be expressed in
_characters_, not _bytes_. This leads to ICU attempting to read past the
end of e.g. a provided pattern.
Because FFI allocates an extra 7 bytes for every malloc call (to
guarantee that it can qword-align everything), this more often than not
leads to ICU reading _unitialized_ memory, rather than _unmapped_
memory, and thus date patterns etc wind up containing garbage.
The current ruby head in CI removes Fixnum and Bignum classes, which has led to broken builds.
This fix attempts to coalesce the passed-in number to an `int64_t`; if that fails, it falls back to calling `unum_format_decimal` - if the versions of ICU does not support `unum_format_decimal`, a `RangeError` will be raised (which should not be an issue for modern versions of ICU)