AppDateTimeFormat.cpp (8306B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "nsCOMPtr.h" 8 #include "mozilla/intl/AppDateTimeFormat.h" 9 #include "mozilla/intl/DateTimePatternGenerator.h" 10 #include "mozilla/intl/FormatBuffer.h" 11 #include "mozilla/intl/LocaleService.h" 12 #include "OSPreferences.h" 13 #include "mozIOSPreferences.h" 14 #ifdef DEBUG 15 # include "nsThreadManager.h" 16 #endif 17 18 namespace mozilla::intl { 19 20 nsCString* AppDateTimeFormat::sLocale = nullptr; 21 nsTHashMap<nsCStringHashKey, UniquePtr<DateTimeFormat>>* 22 AppDateTimeFormat::sFormatCache; 23 24 static const int32_t DATETIME_FORMAT_INITIAL_LEN = 127; 25 26 /*static*/ 27 nsresult AppDateTimeFormat::Initialize() { 28 MOZ_ASSERT(NS_IsMainThread()); 29 if (sLocale) { 30 return NS_OK; 31 } 32 33 sLocale = new nsCString(); 34 AutoTArray<nsCString, 10> regionalPrefsLocales; 35 LocaleService::GetInstance()->GetRegionalPrefsLocales(regionalPrefsLocales); 36 sLocale->Assign(regionalPrefsLocales[0]); 37 38 return NS_OK; 39 } 40 41 // performs a locale sensitive date formatting operation on the PRTime parameter 42 /*static*/ 43 nsresult AppDateTimeFormat::Format(const DateTimeFormat::StyleBag& aStyle, 44 const PRTime aPrTime, 45 nsAString& aStringOut) { 46 return AppDateTimeFormat::Format( 47 aStyle, (static_cast<double>(aPrTime) / PR_USEC_PER_MSEC), nullptr, 48 aStringOut); 49 } 50 51 // performs a locale sensitive date formatting operation on the PRExplodedTime 52 // parameter 53 /*static*/ 54 nsresult AppDateTimeFormat::Format(const DateTimeFormat::StyleBag& aStyle, 55 const PRExplodedTime* aExplodedTime, 56 nsAString& aStringOut) { 57 return AppDateTimeFormat::Format( 58 aStyle, (PR_ImplodeTime(aExplodedTime) / PR_USEC_PER_MSEC), 59 &(aExplodedTime->tm_params), aStringOut); 60 } 61 62 // performs a locale sensitive date formatting operation on the PRExplodedTime 63 // parameter, using the specified options. 64 /*static*/ 65 nsresult AppDateTimeFormat::Format(const DateTimeFormat::ComponentsBag& aBag, 66 const PRExplodedTime* aExplodedTime, 67 nsAString& aStringOut) { 68 // set up locale data 69 nsresult rv = Initialize(); 70 if (NS_FAILED(rv)) { 71 return rv; 72 } 73 74 aStringOut.Truncate(); 75 76 nsAutoString timeZoneID; 77 BuildTimeZoneString(aExplodedTime->tm_params, timeZoneID); 78 79 auto genResult = DateTimePatternGenerator::TryCreate(sLocale->get()); 80 NS_ENSURE_TRUE(genResult.isOk(), NS_ERROR_FAILURE); 81 auto dateTimePatternGenerator = genResult.unwrap(); 82 83 auto result = DateTimeFormat::TryCreateFromComponents( 84 *sLocale, aBag, dateTimePatternGenerator.get(), 85 Some(Span<const char16_t>(timeZoneID.Data(), timeZoneID.Length()))); 86 NS_ENSURE_TRUE(result.isOk(), NS_ERROR_FAILURE); 87 auto dateTimeFormat = result.unwrap(); 88 89 double unixEpoch = 90 static_cast<float>((PR_ImplodeTime(aExplodedTime) / PR_USEC_PER_MSEC)); 91 92 aStringOut.SetLength(DATETIME_FORMAT_INITIAL_LEN); 93 nsTStringToBufferAdapter buffer(aStringOut); 94 NS_ENSURE_TRUE(dateTimeFormat->TryFormat(unixEpoch, buffer).isOk(), 95 NS_ERROR_FAILURE); 96 97 return rv; 98 } 99 100 /** 101 * An internal utility function to serialize a Maybe<DateTimeFormat::Style> to 102 * an int, to be used as a caching key. 103 */ 104 static int StyleToInt(const Maybe<DateTimeFormat::Style>& aStyle) { 105 if (aStyle.isSome()) { 106 switch (*aStyle) { 107 case DateTimeFormat::Style::Full: 108 return 1; 109 case DateTimeFormat::Style::Long: 110 return 2; 111 case DateTimeFormat::Style::Medium: 112 return 3; 113 case DateTimeFormat::Style::Short: 114 return 4; 115 } 116 } 117 return 0; 118 } 119 120 /*static*/ 121 nsresult AppDateTimeFormat::Format(const DateTimeFormat::StyleBag& aStyle, 122 const double aUnixEpoch, 123 const PRTimeParameters* aTimeParameters, 124 nsAString& aStringOut) { 125 nsresult rv = NS_OK; 126 127 // return, nothing to format 128 if (aStyle.date.isNothing() && aStyle.time.isNothing()) { 129 aStringOut.Truncate(); 130 return NS_OK; 131 } 132 133 // set up locale data 134 rv = Initialize(); 135 136 if (NS_FAILED(rv)) { 137 return rv; 138 } 139 140 nsAutoCString key; 141 key.AppendInt(StyleToInt(aStyle.date)); 142 key.Append(':'); 143 key.AppendInt(StyleToInt(aStyle.time)); 144 if (aTimeParameters) { 145 key.Append(':'); 146 key.AppendInt(aTimeParameters->tp_gmt_offset); 147 key.Append(':'); 148 key.AppendInt(aTimeParameters->tp_dst_offset); 149 } 150 151 if (sFormatCache && sFormatCache->Count() == kMaxCachedFormats) { 152 // Don't allow a pathological page to extend the cache unreasonably. 153 NS_WARNING("flushing DateTimeFormat cache"); 154 DeleteCache(); 155 } 156 if (!sFormatCache) { 157 sFormatCache = new nsTHashMap<nsCStringHashKey, UniquePtr<DateTimeFormat>>( 158 kMaxCachedFormats); 159 } 160 161 UniquePtr<DateTimeFormat>& dateTimeFormat = sFormatCache->LookupOrInsert(key); 162 163 if (!dateTimeFormat) { 164 // We didn't have a cached formatter for this key, so create one. 165 int32_t dateFormatStyle = mozIOSPreferences::dateTimeFormatStyleNone; 166 if (aStyle.date.isSome()) { 167 switch (*aStyle.date) { 168 case DateTimeFormat::Style::Full: 169 case DateTimeFormat::Style::Long: 170 dateFormatStyle = mozIOSPreferences::dateTimeFormatStyleLong; 171 break; 172 case DateTimeFormat::Style::Medium: 173 case DateTimeFormat::Style::Short: 174 dateFormatStyle = mozIOSPreferences::dateTimeFormatStyleShort; 175 break; 176 } 177 } 178 179 int32_t timeFormatStyle = mozIOSPreferences::dateTimeFormatStyleNone; 180 if (aStyle.time.isSome()) { 181 switch (*aStyle.time) { 182 case DateTimeFormat::Style::Full: 183 case DateTimeFormat::Style::Long: 184 timeFormatStyle = mozIOSPreferences::dateTimeFormatStyleLong; 185 break; 186 case DateTimeFormat::Style::Medium: 187 case DateTimeFormat::Style::Short: 188 timeFormatStyle = mozIOSPreferences::dateTimeFormatStyleShort; 189 break; 190 } 191 } 192 193 nsAutoCString str; 194 rv = OSPreferences::GetInstance()->GetDateTimePattern( 195 dateFormatStyle, timeFormatStyle, nsDependentCString(sLocale->get()), 196 str); 197 NS_ENSURE_SUCCESS(rv, rv); 198 nsAutoString pattern = NS_ConvertUTF8toUTF16(str); 199 200 Maybe<Span<const char16_t>> timeZoneOverride = Nothing(); 201 nsAutoString timeZoneID; 202 if (aTimeParameters) { 203 BuildTimeZoneString(*aTimeParameters, timeZoneID); 204 timeZoneOverride = 205 Some(Span<const char16_t>(timeZoneID.Data(), timeZoneID.Length())); 206 } 207 208 auto result = DateTimeFormat::TryCreateFromPattern(*sLocale, pattern, 209 timeZoneOverride); 210 NS_ENSURE_TRUE(result.isOk(), NS_ERROR_FAILURE); 211 dateTimeFormat = result.unwrap(); 212 } 213 214 MOZ_ASSERT(dateTimeFormat); 215 216 aStringOut.SetLength(DATETIME_FORMAT_INITIAL_LEN); 217 nsTStringToBufferAdapter buffer(aStringOut); 218 NS_ENSURE_TRUE(dateTimeFormat->TryFormat(aUnixEpoch, buffer).isOk(), 219 NS_ERROR_FAILURE); 220 221 return rv; 222 } 223 224 /*static*/ 225 void AppDateTimeFormat::BuildTimeZoneString( 226 const PRTimeParameters& aTimeParameters, nsAString& aStringOut) { 227 aStringOut.Truncate(); 228 aStringOut.Append(u"GMT"); 229 int32_t totalOffsetMinutes = 230 (aTimeParameters.tp_gmt_offset + aTimeParameters.tp_dst_offset) / 60; 231 if (totalOffsetMinutes != 0) { 232 char sign = totalOffsetMinutes < 0 ? '-' : '+'; 233 int32_t hours = abs(totalOffsetMinutes) / 60; 234 int32_t minutes = abs(totalOffsetMinutes) % 60; 235 aStringOut.AppendPrintf("%c%02d:%02d", sign, hours, minutes); 236 } 237 } 238 239 /*static*/ 240 void AppDateTimeFormat::DeleteCache() { 241 MOZ_ASSERT(NS_IsMainThread()); 242 if (sFormatCache) { 243 delete sFormatCache; 244 sFormatCache = nullptr; 245 } 246 } 247 248 /*static*/ 249 void AppDateTimeFormat::Shutdown() { 250 MOZ_ASSERT(NS_IsMainThread()); 251 DeleteCache(); 252 delete sLocale; 253 } 254 255 /*static*/ 256 void AppDateTimeFormat::ClearLocaleCache() { 257 MOZ_ASSERT(NS_IsMainThread()); 258 DeleteCache(); 259 delete sLocale; 260 sLocale = nullptr; 261 } 262 263 } // namespace mozilla::intl