RelativeTimeFormat.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /*
  2. * Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #define AK_DONT_REPLACE_STD
  7. #include <LibUnicode/ICU.h>
  8. #include <LibUnicode/Locale.h>
  9. #include <LibUnicode/NumberFormat.h>
  10. #include <LibUnicode/PartitionRange.h>
  11. #include <LibUnicode/RelativeTimeFormat.h>
  12. #include <unicode/decimfmt.h>
  13. #include <unicode/numfmt.h>
  14. #include <unicode/reldatefmt.h>
  15. namespace Unicode {
  16. Optional<TimeUnit> time_unit_from_string(StringView time_unit)
  17. {
  18. if (time_unit == "second"sv)
  19. return TimeUnit::Second;
  20. if (time_unit == "minute"sv)
  21. return TimeUnit::Minute;
  22. if (time_unit == "hour"sv)
  23. return TimeUnit::Hour;
  24. if (time_unit == "day"sv)
  25. return TimeUnit::Day;
  26. if (time_unit == "week"sv)
  27. return TimeUnit::Week;
  28. if (time_unit == "month"sv)
  29. return TimeUnit::Month;
  30. if (time_unit == "quarter"sv)
  31. return TimeUnit::Quarter;
  32. if (time_unit == "year"sv)
  33. return TimeUnit::Year;
  34. return {};
  35. }
  36. StringView time_unit_to_string(TimeUnit time_unit)
  37. {
  38. switch (time_unit) {
  39. case TimeUnit::Second:
  40. return "second"sv;
  41. case TimeUnit::Minute:
  42. return "minute"sv;
  43. case TimeUnit::Hour:
  44. return "hour"sv;
  45. case TimeUnit::Day:
  46. return "day"sv;
  47. case TimeUnit::Week:
  48. return "week"sv;
  49. case TimeUnit::Month:
  50. return "month"sv;
  51. case TimeUnit::Quarter:
  52. return "quarter"sv;
  53. case TimeUnit::Year:
  54. return "year"sv;
  55. }
  56. VERIFY_NOT_REACHED();
  57. }
  58. static constexpr URelativeDateTimeUnit icu_time_unit(TimeUnit unit)
  59. {
  60. switch (unit) {
  61. case TimeUnit::Second:
  62. return URelativeDateTimeUnit::UDAT_REL_UNIT_SECOND;
  63. case TimeUnit::Minute:
  64. return URelativeDateTimeUnit::UDAT_REL_UNIT_MINUTE;
  65. case TimeUnit::Hour:
  66. return URelativeDateTimeUnit::UDAT_REL_UNIT_HOUR;
  67. case TimeUnit::Day:
  68. return URelativeDateTimeUnit::UDAT_REL_UNIT_DAY;
  69. case TimeUnit::Week:
  70. return URelativeDateTimeUnit::UDAT_REL_UNIT_WEEK;
  71. case TimeUnit::Month:
  72. return URelativeDateTimeUnit::UDAT_REL_UNIT_MONTH;
  73. case TimeUnit::Quarter:
  74. return URelativeDateTimeUnit::UDAT_REL_UNIT_QUARTER;
  75. case TimeUnit::Year:
  76. return URelativeDateTimeUnit::UDAT_REL_UNIT_YEAR;
  77. }
  78. VERIFY_NOT_REACHED();
  79. }
  80. NumericDisplay numeric_display_from_string(StringView numeric_display)
  81. {
  82. if (numeric_display == "always"sv)
  83. return NumericDisplay::Always;
  84. if (numeric_display == "auto"sv)
  85. return NumericDisplay::Auto;
  86. VERIFY_NOT_REACHED();
  87. }
  88. StringView numeric_display_to_string(NumericDisplay numeric_display)
  89. {
  90. switch (numeric_display) {
  91. case NumericDisplay::Always:
  92. return "always"sv;
  93. case NumericDisplay::Auto:
  94. return "auto"sv;
  95. }
  96. VERIFY_NOT_REACHED();
  97. }
  98. static constexpr UDateRelativeDateTimeFormatterStyle icu_relative_date_time_style(Style unit_display)
  99. {
  100. switch (unit_display) {
  101. case Style::Long:
  102. return UDAT_STYLE_LONG;
  103. case Style::Short:
  104. return UDAT_STYLE_SHORT;
  105. case Style::Narrow:
  106. return UDAT_STYLE_NARROW;
  107. }
  108. VERIFY_NOT_REACHED();
  109. }
  110. static constexpr StringView icu_relative_time_format_field_to_string(i32 field)
  111. {
  112. switch (field) {
  113. case PartitionRange::LITERAL_FIELD:
  114. return "literal"sv;
  115. case UNUM_INTEGER_FIELD:
  116. return "integer"sv;
  117. case UNUM_FRACTION_FIELD:
  118. return "fraction"sv;
  119. case UNUM_DECIMAL_SEPARATOR_FIELD:
  120. return "decimal"sv;
  121. case UNUM_GROUPING_SEPARATOR_FIELD:
  122. return "group"sv;
  123. }
  124. VERIFY_NOT_REACHED();
  125. }
  126. class RelativeTimeFormatImpl : public RelativeTimeFormat {
  127. public:
  128. explicit RelativeTimeFormatImpl(NonnullOwnPtr<icu::RelativeDateTimeFormatter> formatter)
  129. : m_formatter(move(formatter))
  130. {
  131. }
  132. virtual ~RelativeTimeFormatImpl() override = default;
  133. virtual String format(double time, TimeUnit unit, NumericDisplay numeric_display) const override
  134. {
  135. UErrorCode status = U_ZERO_ERROR;
  136. auto formatted = format_impl(time, unit, numeric_display);
  137. auto formatted_time = formatted->toTempString(status);
  138. if (icu_failure(status))
  139. return {};
  140. return icu_string_to_string(formatted_time);
  141. }
  142. virtual Vector<Partition> format_to_parts(double time, TimeUnit unit, NumericDisplay numeric_display) const override
  143. {
  144. UErrorCode status = U_ZERO_ERROR;
  145. auto formatted = format_impl(time, unit, numeric_display);
  146. auto unit_string = time_unit_to_string(unit);
  147. auto formatted_time = formatted->toTempString(status);
  148. if (icu_failure(status))
  149. return {};
  150. Vector<Partition> result;
  151. Vector<PartitionRange> separators;
  152. auto create_partition = [&](i32 field, i32 begin, i32 end, bool is_unit) {
  153. Partition partition;
  154. partition.type = icu_relative_time_format_field_to_string(field);
  155. partition.value = icu_string_to_string(formatted_time.tempSubStringBetween(begin, end));
  156. if (is_unit)
  157. partition.unit = unit_string;
  158. result.append(move(partition));
  159. };
  160. icu::ConstrainedFieldPosition position;
  161. position.constrainCategory(UFIELD_CATEGORY_NUMBER);
  162. i32 previous_end_index = 0;
  163. while (static_cast<bool>(formatted->nextPosition(position, status)) && icu_success(status)) {
  164. if (position.getField() == UNUM_GROUPING_SEPARATOR_FIELD) {
  165. separators.empend(position.getField(), position.getStart(), position.getLimit());
  166. continue;
  167. }
  168. if (previous_end_index < position.getStart())
  169. create_partition(PartitionRange::LITERAL_FIELD, previous_end_index, position.getStart(), false);
  170. auto start = position.getStart();
  171. if (position.getField() == UNUM_INTEGER_FIELD) {
  172. for (auto const& separator : separators) {
  173. if (start >= separator.start)
  174. continue;
  175. create_partition(position.getField(), start, separator.start, true);
  176. create_partition(separator.field, separator.start, separator.end, true);
  177. start = separator.end;
  178. break;
  179. }
  180. }
  181. create_partition(position.getField(), start, position.getLimit(), true);
  182. previous_end_index = position.getLimit();
  183. }
  184. if (previous_end_index < formatted_time.length())
  185. create_partition(PartitionRange::LITERAL_FIELD, previous_end_index, formatted_time.length(), false);
  186. return result;
  187. }
  188. private:
  189. Optional<icu::FormattedRelativeDateTime> format_impl(double time, TimeUnit unit, NumericDisplay numeric_display) const
  190. {
  191. UErrorCode status = U_ZERO_ERROR;
  192. auto formatted = numeric_display == NumericDisplay::Always
  193. ? m_formatter->formatNumericToValue(time, icu_time_unit(unit), status)
  194. : m_formatter->formatToValue(time, icu_time_unit(unit), status);
  195. if (icu_failure(status))
  196. return {};
  197. return formatted;
  198. }
  199. NonnullOwnPtr<icu::RelativeDateTimeFormatter> m_formatter;
  200. };
  201. NonnullOwnPtr<RelativeTimeFormat> RelativeTimeFormat::create(StringView locale, Style style)
  202. {
  203. UErrorCode status = U_ZERO_ERROR;
  204. auto locale_data = LocaleData::for_locale(locale);
  205. VERIFY(locale_data.has_value());
  206. auto* number_formatter = icu::NumberFormat::createInstance(locale_data->locale(), UNUM_DECIMAL, status);
  207. VERIFY(locale_data.has_value());
  208. if (number_formatter->getDynamicClassID() == icu::DecimalFormat::getStaticClassID())
  209. static_cast<icu::DecimalFormat&>(*number_formatter).setMinimumGroupingDigits(UNUM_MINIMUM_GROUPING_DIGITS_AUTO);
  210. auto formatter = make<icu::RelativeDateTimeFormatter>(locale_data->locale(), number_formatter, icu_relative_date_time_style(style), UDISPCTX_CAPITALIZATION_NONE, status);
  211. VERIFY(icu_success(status));
  212. return make<RelativeTimeFormatImpl>(move(formatter));
  213. }
  214. }