1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 #ifndef intl_components_DateTimePatternGenerator_h_
5 #define intl_components_DateTimePatternGenerator_h_
6 
7 #include "unicode/udatpg.h"
8 #include "mozilla/EnumSet.h"
9 #include "mozilla/Result.h"
10 #include "mozilla/Span.h"
11 #include "mozilla/UniquePtr.h"
12 #include "mozilla/intl/ICU4CGlue.h"
13 #include "mozilla/intl/ICUError.h"
14 
15 namespace mozilla::intl {
16 
17 class DisplayNames;
18 
19 /**
20  * The DateTimePatternGenerator is the machinery used to work with DateTime
21  * pattern manipulation. It is expensive to create one, and so generally it is
22  * created once and then cached. It may be needed to be passed in as an argument
23  * for different mozilla::intl APIs.
24  */
25 class DateTimePatternGenerator final {
26  public:
DateTimePatternGenerator(UDateTimePatternGenerator * aGenerator)27   explicit DateTimePatternGenerator(UDateTimePatternGenerator* aGenerator)
28       : mGenerator(aGenerator) {
29     MOZ_ASSERT(aGenerator);
30   };
31 
32   // Transfer ownership of the UDateTimePatternGenerator in the move
33   // constructor.
34   DateTimePatternGenerator(DateTimePatternGenerator&& other) noexcept;
35 
36   // Transfer ownership of the UEnumeration in the move assignment operator.
37   DateTimePatternGenerator& operator=(
38       DateTimePatternGenerator&& other) noexcept;
39 
40   // Disallow copy.
41   DateTimePatternGenerator(const DateTimePatternGenerator&) = delete;
42   DateTimePatternGenerator& operator=(const DateTimePatternGenerator&) = delete;
43 
44   ~DateTimePatternGenerator();
45 
46   static Result<UniquePtr<DateTimePatternGenerator>, ICUError> TryCreate(
47       const char* aLocale);
48 
49   enum class PatternMatchOption {
50     /**
51      * Adjust the 'hour' field in the resolved pattern to match the input
52      * skeleton width.
53      */
54     HourField,
55 
56     /**
57      * Adjust the 'minute' field in the resolved pattern to match the input
58      * skeleton width.
59      */
60     MinuteField,
61 
62     /**
63      * Adjust the 'second' field in the resolved pattern to match the input
64      * skeleton width.
65      */
66     SecondField,
67   };
68 
69   /**
70    * Given a skeleton (a string with unordered datetime fields), get a best
71    * pattern that will fit for that locale. This pattern will be filled into the
72    * buffer. e.g. The skeleton "yMd" would return the pattern "M/d/y" for en-US,
73    * or "dd/MM/y" for en-GB.
74    */
75   template <typename B>
76   ICUResult GetBestPattern(Span<const char16_t> aSkeleton, B& aBuffer,
77                            EnumSet<PatternMatchOption> options = {}) {
78     return FillBufferWithICUCall(
79         aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) {
80           return udatpg_getBestPatternWithOptions(
81               mGenerator.GetMut(), aSkeleton.data(),
82               static_cast<int32_t>(aSkeleton.Length()),
83               toUDateTimePatternMatchOptions(options), target, length, status);
84         });
85   }
86 
87   /**
88    * Get a skeleton (a string with unordered datetime fields) from a pattern.
89    * For example, both "MMM-dd" and "dd/MMM" produce the skeleton "MMMdd".
90    */
91   template <typename B>
GetSkeleton(Span<const char16_t> aPattern,B & aBuffer)92   static ICUResult GetSkeleton(Span<const char16_t> aPattern, B& aBuffer) {
93     // At one time udatpg_getSkeleton required a UDateTimePatternGenerator*, but
94     // now it is valid to pass in a nullptr.
95     return FillBufferWithICUCall(
96         aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) {
97           return udatpg_getSkeleton(nullptr, aPattern.data(),
98                                     static_cast<int32_t>(aPattern.Length()),
99                                     target, length, status);
100         });
101   }
102 
103   /**
104    * Get a pattern of the form "{1} {0}" to combine separate date and time
105    * patterns into a single pattern. The "{0}" part is the placeholder for the
106    * time pattern and "{1}" is the placeholder for the date pattern.
107    *
108    * See dateTimeFormat from
109    * https://unicode.org/reports/tr35/tr35-dates.html#dateTimeFormat
110    *
111    * Note:
112    * In CLDR, it's called Date-Time Combined Format
113    * https://cldr.unicode.org/translation/date-time/datetime-patterns#h.x7ca7qwzh4m
114    *
115    * The naming 'placeholder pattern' is from ICU4X.
116    * https://unicode-org.github.io/icu4x-docs/doc/icu_pattern/index.html
117    */
GetPlaceholderPattern()118   Span<const char16_t> GetPlaceholderPattern() const {
119     int32_t length;
120     const char16_t* combined =
121         udatpg_getDateTimeFormat(mGenerator.GetConst(), &length);
122     return Span{combined, static_cast<size_t>(length)};
123   }
124 
125  private:
126   // Allow other mozilla::intl components to access the underlying
127   // UDateTimePatternGenerator.
128   friend class DisplayNames;
129 
GetUDateTimePatternGenerator()130   UDateTimePatternGenerator* GetUDateTimePatternGenerator() {
131     return mGenerator.GetMut();
132   }
133 
134   ICUPointer<UDateTimePatternGenerator> mGenerator =
135       ICUPointer<UDateTimePatternGenerator>(nullptr);
136 
toUDateTimePatternMatchOptions(EnumSet<PatternMatchOption> options)137   static UDateTimePatternMatchOptions toUDateTimePatternMatchOptions(
138       EnumSet<PatternMatchOption> options) {
139     struct OptionMap {
140       PatternMatchOption from;
141       UDateTimePatternMatchOptions to;
142     } static constexpr map[] = {
143         {PatternMatchOption::HourField, UDATPG_MATCH_HOUR_FIELD_LENGTH},
144 #ifndef U_HIDE_INTERNAL_API
145         {PatternMatchOption::MinuteField, UDATPG_MATCH_MINUTE_FIELD_LENGTH},
146         {PatternMatchOption::SecondField, UDATPG_MATCH_SECOND_FIELD_LENGTH},
147 #endif
148     };
149 
150     UDateTimePatternMatchOptions result = UDATPG_MATCH_NO_OPTIONS;
151     for (const auto& entry : map) {
152       if (options.contains(entry.from)) {
153         result = UDateTimePatternMatchOptions(result | entry.to);
154       }
155     }
156     return result;
157   }
158 };
159 
160 }  // namespace mozilla::intl
161 #endif
162