RelativeTimeFormat.cpp 8.0 KB

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