summaryrefslogtreecommitdiffstats
path: root/intl/components/src/RelativeTimeFormat.h
blob: 4d4cf2e60efcc22f07c9692bad0c59a6279b9b7b (plain)
1
2
3
4
5
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef intl_components_RelativeTimeFormat_h_
#define intl_components_RelativeTimeFormat_h_

#include "mozilla/Try.h"
#include "mozilla/intl/ICU4CGlue.h"
#include "mozilla/intl/ICUError.h"
#include "mozilla/intl/NumberPart.h"

#include "unicode/ureldatefmt.h"
#include "unicode/utypes.h"

namespace mozilla::intl {

struct RelativeTimeFormatOptions {
  enum class Style { Short, Narrow, Long };
  Style style = Style::Long;

  enum class Numeric {
    /**
     * Only strings with numeric components like `1 day ago`.
     */
    Always,
    /**
     * Natural-language strings like `yesterday` when possible,
     * otherwise strings with numeric components as in `7 months ago`.
     */
    Auto,
  };
  Numeric numeric = Numeric::Always;
};

/**
 * A RelativeTimeFormat implementation that roughly mirrors the API provided by
 * the ECMA-402 Intl.RelativeTimeFormat object.
 *
 * https://tc39.es/ecma402/#relativetimeformat-objects
 */
class RelativeTimeFormat final {
 public:
  /**
   *
   * Initialize a new RelativeTimeFormat for the provided locale and using the
   * provided options.
   *
   * https://tc39.es/ecma402/#sec-InitializeRelativeTimeFormat
   */
  static Result<UniquePtr<RelativeTimeFormat>, ICUError> TryCreate(
      const char* aLocale, const RelativeTimeFormatOptions& aOptions);

  RelativeTimeFormat() = default;

  RelativeTimeFormat(RelativeTimeFormatOptions::Numeric aNumeric,
                     URelativeDateTimeFormatter* aFormatter,
                     UFormattedRelativeDateTime* aFormattedRelativeDateTime);

  RelativeTimeFormat(const RelativeTimeFormat&) = delete;
  RelativeTimeFormat& operator=(const RelativeTimeFormat&) = delete;
  ~RelativeTimeFormat();

  enum class FormatUnit {
    Second,
    Minute,
    Hour,
    Day,
    Week,
    Month,
    Quarter,
    Year
  };

  /**
   * Formats a double to the provider buffer (either utf-8 or utf-16)
   *
   * https://tc39.es/ecma402/#sec-FormatRelativeTime
   */
  template <typename B>
  Result<Ok, ICUError> format(double aNumber, FormatUnit aUnit,
                              B& aBuffer) const {
    static_assert(
        std::is_same_v<typename B::CharType, char> ||
            std::is_same_v<typename B::CharType, char16_t>,
        "The only buffer CharTypes supported by RelativeTimeFormat are char "
        "(for UTF-8 support) and char16_t (for UTF-16 support).");

    auto fmt = mNumeric == RelativeTimeFormatOptions::Numeric::Auto
                   ? ureldatefmt_format
                   : ureldatefmt_formatNumeric;

    if constexpr (std::is_same_v<typename B::CharType, char>) {
      mozilla::Vector<char16_t, StackU16VectorSize> u16Vec;

      MOZ_TRY(FillBufferWithICUCall(
          u16Vec, [this, aNumber, aUnit, fmt](UChar* target, int32_t length,
                                              UErrorCode* status) {
            return fmt(mFormatter, aNumber, ToURelativeDateTimeUnit(aUnit),
                       target, length, status);
          }));

      if (!FillBuffer(u16Vec, aBuffer)) {
        return Err(ICUError::OutOfMemory);
      }
      return Ok{};
    } else {
      static_assert(std::is_same_v<typename B::CharType, char16_t>);

      return FillBufferWithICUCall(
          aBuffer, [this, aNumber, aUnit, fmt](UChar* target, int32_t length,
                                               UErrorCode* status) {
            return fmt(mFormatter, aNumber, ToURelativeDateTimeUnit(aUnit),
                       target, length, status);
          });
    }
  }

  /**
   * Formats the relative time to a utf-16 string, and fills the provided parts
   * vector. The string view is valid until another time is formatted.
   * Accessing the string view after this event is undefined behavior.
   *
   * This is utf-16 only because the only current use case is in
   * SpiderMonkey. Supporting utf-8 would require recalculating the offsets
   * in NumberPartVector from fixed width to variable width, which might be
   * tricky to get right and is work that won't be necessary if we switch to
   * ICU4X (see Bug 1723120).
   *
   * https://tc39.es/ecma402/#sec-FormatRelativeTimeToParts
   */
  Result<Span<const char16_t>, ICUError> formatToParts(
      double aNumber, FormatUnit aUnit, NumberPartVector& aParts) const;

 private:
  RelativeTimeFormatOptions::Numeric mNumeric =
      RelativeTimeFormatOptions::Numeric::Always;
  URelativeDateTimeFormatter* mFormatter = nullptr;
  UFormattedRelativeDateTime* mFormattedRelativeDateTime = nullptr;

  static constexpr size_t StackU16VectorSize = 128;

  URelativeDateTimeUnit ToURelativeDateTimeUnit(FormatUnit unit) const;
};

}  // namespace mozilla::intl

#endif