windtfmt.cpp (13207B)
1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ******************************************************************************** 5 * Copyright (C) 2005-2016, International Business Machines 6 * Corporation and others. All Rights Reserved. 7 ******************************************************************************** 8 * 9 * File WINDTFMT.CPP 10 * 11 ******************************************************************************** 12 */ 13 14 #include "unicode/utypes.h" 15 16 #if U_PLATFORM_USES_ONLY_WIN32_API 17 18 #if !UCONFIG_NO_FORMATTING 19 20 #include "unicode/ures.h" 21 #include "unicode/format.h" 22 #include "unicode/fmtable.h" 23 #include "unicode/datefmt.h" 24 #include "unicode/simpleformatter.h" 25 #include "unicode/calendar.h" 26 #include "unicode/gregocal.h" 27 #include "unicode/locid.h" 28 #include "unicode/unistr.h" 29 #include "unicode/ustring.h" 30 #include "unicode/timezone.h" 31 #include "unicode/utmscale.h" 32 33 #include "charstr.h" 34 #include "cmemory.h" 35 #include "ulocimp.h" 36 #include "uresimp.h" 37 #include "windtfmt.h" 38 #include "wintzimpl.h" 39 40 #ifndef WIN32_LEAN_AND_MEAN 41 # define WIN32_LEAN_AND_MEAN 42 #endif 43 # define VC_EXTRALEAN 44 # define NOUSER 45 # define NOSERVICE 46 # define NOIME 47 # define NOMCX 48 #include <windows.h> 49 #include <typeinfo> 50 51 U_NAMESPACE_BEGIN 52 53 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(Win32DateFormat) 54 55 #define NEW_ARRAY(type,count) (type *) uprv_malloc((count) * sizeof(type)) 56 #define DELETE_ARRAY(array) uprv_free((void *) (array)) 57 58 #define STACK_BUFFER_SIZE 64 59 60 UnicodeString* Win32DateFormat::getTimeDateFormat(const Calendar *cal, const Locale *locale, UErrorCode &status) const 61 { 62 UnicodeString *result = nullptr; 63 const char *type = cal->getType(); 64 const char *base = locale->getBaseName(); 65 UResourceBundle *topBundle = ures_open((char *) 0, base, &status); 66 UResourceBundle *calBundle = ures_getByKey(topBundle, "calendar", nullptr, &status); 67 UResourceBundle *typBundle = ures_getByKeyWithFallback(calBundle, type, nullptr, &status); 68 UResourceBundle *patBundle = ures_getByKeyWithFallback(typBundle, "DateTimePatterns", nullptr, &status); 69 70 if (status == U_MISSING_RESOURCE_ERROR) { 71 status = U_ZERO_ERROR; 72 typBundle = ures_getByKeyWithFallback(calBundle, "gregorian", typBundle, &status); 73 patBundle = ures_getByKeyWithFallback(typBundle, "DateTimePatterns", patBundle, &status); 74 } 75 76 if (U_FAILURE(status)) { 77 static const char16_t defaultPattern[] = {0x007B, 0x0031, 0x007D, 0x0020, 0x007B, 0x0030, 0x007D, 0x0000}; // "{1} {0}" 78 return new UnicodeString(defaultPattern, UPRV_LENGTHOF(defaultPattern)); 79 } 80 81 int32_t resStrLen = 0; 82 int32_t glueIndex = DateFormat::kDateTime; 83 int32_t patSize = ures_getSize(patBundle); 84 if (patSize >= (DateFormat::kDateTimeOffset + DateFormat::kShort + 1)) { 85 // Get proper date time format 86 glueIndex = (int32_t)(DateFormat::kDateTimeOffset + (fDateStyle - DateFormat::kDateOffset)); 87 } 88 const char16_t *resStr = ures_getStringByIndex(patBundle, glueIndex, &resStrLen, &status); 89 90 result = new UnicodeString(true, resStr, resStrLen); 91 92 ures_close(patBundle); 93 ures_close(typBundle); 94 ures_close(calBundle); 95 ures_close(topBundle); 96 97 return result; 98 } 99 100 // TODO: This is copied in both winnmfmt.cpp and windtfmt.cpp, but really should 101 // be factored out into a common helper for both. 102 static UErrorCode GetEquivalentWindowsLocaleName(const Locale& locale, UnicodeString** buffer) 103 { 104 UErrorCode status = U_ZERO_ERROR; 105 106 // Convert from names like "en_CA" and "de_DE@collation=phonebook" to "en-CA" and "de-DE-u-co-phonebk". 107 CharString asciiBCP47Tag = ulocimp_toLanguageTag(locale.getName(), false, status); 108 109 if (U_SUCCESS(status)) 110 { 111 // Need it to be UTF-16, not 8-bit 112 // TODO: This seems like a good thing for a helper 113 wchar_t bcp47Tag[LOCALE_NAME_MAX_LENGTH] = {}; 114 int32_t i; 115 for (i = 0; i < UPRV_LENGTHOF(bcp47Tag); i++) 116 { 117 if (asciiBCP47Tag[i] == '\0') 118 { 119 break; 120 } 121 else 122 { 123 // normally just copy the character 124 bcp47Tag[i] = static_cast<wchar_t>(asciiBCP47Tag[i]); 125 } 126 } 127 128 // Ensure it's null terminated 129 if (i < (UPRV_LENGTHOF(bcp47Tag) - 1)) 130 { 131 bcp47Tag[i] = L'\0'; 132 } 133 else 134 { 135 // Ran out of room. 136 bcp47Tag[UPRV_LENGTHOF(bcp47Tag) - 1] = L'\0'; 137 } 138 139 140 wchar_t windowsLocaleName[LOCALE_NAME_MAX_LENGTH] = {}; 141 142 // Note: On Windows versions below 10, there is no support for locale name aliases. 143 // This means that it will fail for locales where ICU has a completely different 144 // name (like ku vs ckb), and it will also not work for alternate sort locale 145 // names like "de-DE-u-co-phonebk". 146 147 // TODO: We could add some sort of exception table for cases like ku vs ckb. 148 149 int length = ResolveLocaleName(bcp47Tag, windowsLocaleName, UPRV_LENGTHOF(windowsLocaleName)); 150 151 if (length > 0) 152 { 153 *buffer = new UnicodeString(windowsLocaleName); 154 } 155 else 156 { 157 status = U_UNSUPPORTED_ERROR; 158 } 159 } 160 return status; 161 } 162 163 // TODO: Range-check timeStyle, dateStyle 164 Win32DateFormat::Win32DateFormat(DateFormat::EStyle timeStyle, DateFormat::EStyle dateStyle, const Locale &locale, UErrorCode &status) 165 : DateFormat(), fDateTimeMsg(nullptr), fTimeStyle(timeStyle), fDateStyle(dateStyle), fLocale(locale), fZoneID(), fWindowsLocaleName(nullptr) 166 { 167 if (U_SUCCESS(status)) { 168 GetEquivalentWindowsLocaleName(locale, &fWindowsLocaleName); 169 // Note: In the previous code, it would look up the LCID for the locale, and if 170 // the locale was not recognized then it would get an LCID of 0, which is a 171 // synonym for LOCALE_USER_DEFAULT on Windows. 172 // If the above method fails, then fWindowsLocaleName will remain as nullptr, and 173 // then we will pass nullptr to API GetLocaleInfoEx, which is the same as passing 174 // LOCALE_USER_DEFAULT. 175 176 fTZI = NEW_ARRAY(TIME_ZONE_INFORMATION, 1); 177 uprv_memset(fTZI, 0, sizeof(TIME_ZONE_INFORMATION)); 178 adoptCalendar(Calendar::createInstance(locale, status)); 179 } 180 } 181 182 Win32DateFormat::Win32DateFormat(const Win32DateFormat &other) 183 : DateFormat(other) 184 { 185 *this = other; 186 } 187 188 Win32DateFormat::~Win32DateFormat() 189 { 190 // delete fCalendar; 191 uprv_free(fTZI); 192 delete fDateTimeMsg; 193 delete fWindowsLocaleName; 194 } 195 196 Win32DateFormat &Win32DateFormat::operator=(const Win32DateFormat &other) 197 { 198 if (this == &other) { return *this; } // self-assignment: no-op 199 // The following handles fCalendar 200 DateFormat::operator=(other); 201 202 // delete fCalendar; 203 204 this->fDateTimeMsg = other.fDateTimeMsg == nullptr ? nullptr : new UnicodeString(*other.fDateTimeMsg); 205 this->fTimeStyle = other.fTimeStyle; 206 this->fDateStyle = other.fDateStyle; 207 this->fLocale = other.fLocale; 208 // this->fCalendar = other.fCalendar->clone(); 209 this->fZoneID = other.fZoneID; 210 211 this->fTZI = NEW_ARRAY(TIME_ZONE_INFORMATION, 1); 212 *this->fTZI = *other.fTZI; 213 214 this->fWindowsLocaleName = other.fWindowsLocaleName == nullptr ? nullptr : new UnicodeString(*other.fWindowsLocaleName); 215 216 return *this; 217 } 218 219 Win32DateFormat *Win32DateFormat::clone() const 220 { 221 return new Win32DateFormat(*this); 222 } 223 224 // TODO: Is just ignoring pos the right thing? 225 UnicodeString &Win32DateFormat::format(Calendar &cal, UnicodeString &appendTo, FieldPosition & /* pos */) const 226 { 227 FILETIME ft; 228 SYSTEMTIME st_gmt; 229 SYSTEMTIME st_local; 230 TIME_ZONE_INFORMATION tzi = *fTZI; 231 UErrorCode status = U_ZERO_ERROR; 232 const TimeZone &tz = cal.getTimeZone(); 233 int64_t uct, uft; 234 235 setTimeZoneInfo(&tzi, tz); 236 237 uct = utmscale_fromInt64((int64_t) cal.getTime(status), UDTS_ICU4C_TIME, &status); 238 uft = utmscale_toInt64(uct, UDTS_WINDOWS_FILE_TIME, &status); 239 240 ft.dwLowDateTime = (DWORD) (uft & 0xFFFFFFFF); 241 ft.dwHighDateTime = (DWORD) ((uft >> 32) & 0xFFFFFFFF); 242 243 FileTimeToSystemTime(&ft, &st_gmt); 244 SystemTimeToTzSpecificLocalTime(&tzi, &st_gmt, &st_local); 245 246 247 if (fDateStyle != DateFormat::kNone && fTimeStyle != DateFormat::kNone) { 248 UnicodeString date; 249 UnicodeString time; 250 UnicodeString *pattern = fDateTimeMsg; 251 252 formatDate(&st_local, date); 253 formatTime(&st_local, time); 254 255 if (typeid(cal) != typeid(*fCalendar)) { 256 pattern = getTimeDateFormat(&cal, &fLocale, status); 257 } 258 259 SimpleFormatter(*pattern, 2, 2, status).format(time, date, appendTo, status); 260 } else if (fDateStyle != DateFormat::kNone) { 261 formatDate(&st_local, appendTo); 262 } else if (fTimeStyle != DateFormat::kNone) { 263 formatTime(&st_local, appendTo); 264 } 265 266 return appendTo; 267 } 268 269 void Win32DateFormat::parse(const UnicodeString& /* text */, Calendar& /* cal */, ParsePosition& pos) const 270 { 271 pos.setErrorIndex(pos.getIndex()); 272 } 273 274 void Win32DateFormat::adoptCalendar(Calendar *newCalendar) 275 { 276 if (fCalendar == nullptr || typeid(*fCalendar) != typeid(*newCalendar)) { 277 UErrorCode status = U_ZERO_ERROR; 278 279 if (fDateStyle != DateFormat::kNone && fTimeStyle != DateFormat::kNone) { 280 delete fDateTimeMsg; 281 fDateTimeMsg = getTimeDateFormat(newCalendar, &fLocale, status); 282 } 283 } 284 285 delete fCalendar; 286 fCalendar = newCalendar; 287 288 fZoneID = setTimeZoneInfo(fTZI, fCalendar->getTimeZone()); 289 } 290 291 void Win32DateFormat::setCalendar(const Calendar &newCalendar) 292 { 293 adoptCalendar(newCalendar.clone()); 294 } 295 296 void Win32DateFormat::adoptTimeZone(TimeZone *zoneToAdopt) 297 { 298 fZoneID = setTimeZoneInfo(fTZI, *zoneToAdopt); 299 fCalendar->adoptTimeZone(zoneToAdopt); 300 } 301 302 void Win32DateFormat::setTimeZone(const TimeZone& zone) 303 { 304 fZoneID = setTimeZoneInfo(fTZI, zone); 305 fCalendar->setTimeZone(zone); 306 } 307 308 static const DWORD dfFlags[] = {DATE_LONGDATE, DATE_LONGDATE, DATE_SHORTDATE, DATE_SHORTDATE}; 309 310 void Win32DateFormat::formatDate(const SYSTEMTIME *st, UnicodeString &appendTo) const 311 { 312 int result=0; 313 wchar_t stackBuffer[STACK_BUFFER_SIZE]; 314 wchar_t *buffer = stackBuffer; 315 const wchar_t *localeName = nullptr; 316 317 if (fWindowsLocaleName != nullptr) 318 { 319 localeName = reinterpret_cast<const wchar_t*>(toOldUCharPtr(fWindowsLocaleName->getTerminatedBuffer())); 320 } 321 322 result = GetDateFormatEx(localeName, dfFlags[fDateStyle - kDateOffset], st, nullptr, buffer, STACK_BUFFER_SIZE, nullptr); 323 324 if (result == 0) { 325 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { 326 int newLength = GetDateFormatEx(localeName, dfFlags[fDateStyle - kDateOffset], st, nullptr, nullptr, 0, nullptr); 327 328 buffer = NEW_ARRAY(wchar_t, newLength); 329 330 GetDateFormatEx(localeName, dfFlags[fDateStyle - kDateOffset], st, nullptr, buffer, newLength, nullptr); 331 } 332 } 333 334 appendTo.append((const char16_t *)buffer, (int32_t) wcslen(buffer)); 335 336 if (buffer != stackBuffer) { 337 DELETE_ARRAY(buffer); 338 } 339 } 340 341 static const DWORD tfFlags[] = {0, 0, 0, TIME_NOSECONDS}; 342 343 void Win32DateFormat::formatTime(const SYSTEMTIME *st, UnicodeString &appendTo) const 344 { 345 int result; 346 wchar_t stackBuffer[STACK_BUFFER_SIZE]; 347 wchar_t *buffer = stackBuffer; 348 const wchar_t *localeName = nullptr; 349 350 if (fWindowsLocaleName != nullptr) 351 { 352 localeName = reinterpret_cast<const wchar_t*>(toOldUCharPtr(fWindowsLocaleName->getTerminatedBuffer())); 353 } 354 355 result = GetTimeFormatEx(localeName, tfFlags[fTimeStyle], st, nullptr, buffer, STACK_BUFFER_SIZE); 356 357 if (result == 0) { 358 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { 359 int newLength = GetTimeFormatEx(localeName, tfFlags[fTimeStyle], st, nullptr, nullptr, 0); 360 361 buffer = NEW_ARRAY(wchar_t, newLength); 362 363 GetTimeFormatEx(localeName, tfFlags[fTimeStyle], st, nullptr, buffer, newLength); 364 } 365 } 366 367 appendTo.append((const char16_t *)buffer, (int32_t) wcslen(buffer)); 368 369 if (buffer != stackBuffer) { 370 DELETE_ARRAY(buffer); 371 } 372 } 373 374 UnicodeString Win32DateFormat::setTimeZoneInfo(TIME_ZONE_INFORMATION *tzi, const TimeZone &zone) const 375 { 376 UnicodeString zoneID; 377 378 zone.getID(zoneID); 379 380 if (zoneID.compare(fZoneID) != 0) { 381 UnicodeString icuid; 382 383 zone.getID(icuid); 384 if (! uprv_getWindowsTimeZoneInfo(tzi, icuid.getBuffer(), icuid.length())) { 385 UBool found = false; 386 int32_t ec = TimeZone::countEquivalentIDs(icuid); 387 388 for (int z = 0; z < ec; z += 1) { 389 UnicodeString equiv = TimeZone::getEquivalentID(icuid, z); 390 391 found = uprv_getWindowsTimeZoneInfo(tzi, equiv.getBuffer(), equiv.length()); 392 if (found) { 393 break; 394 } 395 } 396 397 if (! found) { 398 GetTimeZoneInformation(tzi); 399 } 400 } 401 } 402 403 return zoneID; 404 } 405 406 U_NAMESPACE_END 407 408 #endif /* #if !UCONFIG_NO_FORMATTING */ 409 410 #endif // U_PLATFORM_USES_ONLY_WIN32_API