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 "bytesinkutil.h" 34#include "charstr.h" 35#include "cmemory.h" 36#include "ulocimp.h" 37#include "uresimp.h" 38#include "windtfmt.h" 39#include "wintzimpl.h" 40 41#ifndef WIN32_LEAN_AND_MEAN 42# define WIN32_LEAN_AND_MEAN 43#endif 44# define VC_EXTRALEAN 45# define NOUSER 46# define NOSERVICE 47# define NOIME 48# define NOMCX 49#include <windows.h> 50 51U_NAMESPACE_BEGIN 52 53UOBJECT_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 60UnicodeString* 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. 102static 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; 108 { 109 CharStringByteSink sink(&asciiBCP47Tag); 110 ulocimp_toLanguageTag(locale.getName(), sink, false, &status); 111 } 112 113 if (U_SUCCESS(status)) 114 { 115 // Need it to be UTF-16, not 8-bit 116 // TODO: This seems like a good thing for a helper 117 wchar_t bcp47Tag[LOCALE_NAME_MAX_LENGTH] = {}; 118 int32_t i; 119 for (i = 0; i < UPRV_LENGTHOF(bcp47Tag); i++) 120 { 121 if (asciiBCP47Tag[i] == '\0') 122 { 123 break; 124 } 125 else 126 { 127 // normally just copy the character 128 bcp47Tag[i] = static_cast<wchar_t>(asciiBCP47Tag[i]); 129 } 130 } 131 132 // Ensure it's null terminated 133 if (i < (UPRV_LENGTHOF(bcp47Tag) - 1)) 134 { 135 bcp47Tag[i] = L'\0'; 136 } 137 else 138 { 139 // Ran out of room. 140 bcp47Tag[UPRV_LENGTHOF(bcp47Tag) - 1] = L'\0'; 141 } 142 143 144 wchar_t windowsLocaleName[LOCALE_NAME_MAX_LENGTH] = {}; 145 146 // Note: On Windows versions below 10, there is no support for locale name aliases. 147 // This means that it will fail for locales where ICU has a completely different 148 // name (like ku vs ckb), and it will also not work for alternate sort locale 149 // names like "de-DE-u-co-phonebk". 150 151 // TODO: We could add some sort of exception table for cases like ku vs ckb. 152 153 int length = ResolveLocaleName(bcp47Tag, windowsLocaleName, UPRV_LENGTHOF(windowsLocaleName)); 154 155 if (length > 0) 156 { 157 *buffer = new UnicodeString(windowsLocaleName); 158 } 159 else 160 { 161 status = U_UNSUPPORTED_ERROR; 162 } 163 } 164 return status; 165} 166 167// TODO: Range-check timeStyle, dateStyle 168Win32DateFormat::Win32DateFormat(DateFormat::EStyle timeStyle, DateFormat::EStyle dateStyle, const Locale &locale, UErrorCode &status) 169 : DateFormat(), fDateTimeMsg(nullptr), fTimeStyle(timeStyle), fDateStyle(dateStyle), fLocale(locale), fZoneID(), fWindowsLocaleName(nullptr) 170{ 171 if (U_SUCCESS(status)) { 172 GetEquivalentWindowsLocaleName(locale, &fWindowsLocaleName); 173 // Note: In the previous code, it would look up the LCID for the locale, and if 174 // the locale was not recognized then it would get an LCID of 0, which is a 175 // synonym for LOCALE_USER_DEFAULT on Windows. 176 // If the above method fails, then fWindowsLocaleName will remain as nullptr, and 177 // then we will pass nullptr to API GetLocaleInfoEx, which is the same as passing 178 // LOCALE_USER_DEFAULT. 179 180 fTZI = NEW_ARRAY(TIME_ZONE_INFORMATION, 1); 181 uprv_memset(fTZI, 0, sizeof(TIME_ZONE_INFORMATION)); 182 adoptCalendar(Calendar::createInstance(locale, status)); 183 } 184} 185 186Win32DateFormat::Win32DateFormat(const Win32DateFormat &other) 187 : DateFormat(other) 188{ 189 *this = other; 190} 191 192Win32DateFormat::~Win32DateFormat() 193{ 194// delete fCalendar; 195 uprv_free(fTZI); 196 delete fDateTimeMsg; 197 delete fWindowsLocaleName; 198} 199 200Win32DateFormat &Win32DateFormat::operator=(const Win32DateFormat &other) 201{ 202 if (this == &other) { return *this; } // self-assignment: no-op 203 // The following handles fCalendar 204 DateFormat::operator=(other); 205 206// delete fCalendar; 207 208 this->fDateTimeMsg = other.fDateTimeMsg == nullptr ? nullptr : new UnicodeString(*other.fDateTimeMsg); 209 this->fTimeStyle = other.fTimeStyle; 210 this->fDateStyle = other.fDateStyle; 211 this->fLocale = other.fLocale; 212// this->fCalendar = other.fCalendar->clone(); 213 this->fZoneID = other.fZoneID; 214 215 this->fTZI = NEW_ARRAY(TIME_ZONE_INFORMATION, 1); 216 *this->fTZI = *other.fTZI; 217 218 this->fWindowsLocaleName = other.fWindowsLocaleName == nullptr ? nullptr : new UnicodeString(*other.fWindowsLocaleName); 219 220 return *this; 221} 222 223Win32DateFormat *Win32DateFormat::clone() const 224{ 225 return new Win32DateFormat(*this); 226} 227 228// TODO: Is just ignoring pos the right thing? 229UnicodeString &Win32DateFormat::format(Calendar &cal, UnicodeString &appendTo, FieldPosition & /* pos */) const 230{ 231 FILETIME ft; 232 SYSTEMTIME st_gmt; 233 SYSTEMTIME st_local; 234 TIME_ZONE_INFORMATION tzi = *fTZI; 235 UErrorCode status = U_ZERO_ERROR; 236 const TimeZone &tz = cal.getTimeZone(); 237 int64_t uct, uft; 238 239 setTimeZoneInfo(&tzi, tz); 240 241 uct = utmscale_fromInt64((int64_t) cal.getTime(status), UDTS_ICU4C_TIME, &status); 242 uft = utmscale_toInt64(uct, UDTS_WINDOWS_FILE_TIME, &status); 243 244 ft.dwLowDateTime = (DWORD) (uft & 0xFFFFFFFF); 245 ft.dwHighDateTime = (DWORD) ((uft >> 32) & 0xFFFFFFFF); 246 247 FileTimeToSystemTime(&ft, &st_gmt); 248 SystemTimeToTzSpecificLocalTime(&tzi, &st_gmt, &st_local); 249 250 251 if (fDateStyle != DateFormat::kNone && fTimeStyle != DateFormat::kNone) { 252 UnicodeString date; 253 UnicodeString time; 254 UnicodeString *pattern = fDateTimeMsg; 255 256 formatDate(&st_local, date); 257 formatTime(&st_local, time); 258 259 if (strcmp(fCalendar->getType(), cal.getType()) != 0) { 260 pattern = getTimeDateFormat(&cal, &fLocale, status); 261 } 262 263 SimpleFormatter(*pattern, 2, 2, status).format(time, date, appendTo, status); 264 } else if (fDateStyle != DateFormat::kNone) { 265 formatDate(&st_local, appendTo); 266 } else if (fTimeStyle != DateFormat::kNone) { 267 formatTime(&st_local, appendTo); 268 } 269 270 return appendTo; 271} 272 273void Win32DateFormat::parse(const UnicodeString& /* text */, Calendar& /* cal */, ParsePosition& pos) const 274{ 275 pos.setErrorIndex(pos.getIndex()); 276} 277 278void Win32DateFormat::adoptCalendar(Calendar *newCalendar) 279{ 280 if (fCalendar == nullptr || strcmp(fCalendar->getType(), newCalendar->getType()) != 0) { 281 UErrorCode status = U_ZERO_ERROR; 282 283 if (fDateStyle != DateFormat::kNone && fTimeStyle != DateFormat::kNone) { 284 delete fDateTimeMsg; 285 fDateTimeMsg = getTimeDateFormat(newCalendar, &fLocale, status); 286 } 287 } 288 289 delete fCalendar; 290 fCalendar = newCalendar; 291 292 fZoneID = setTimeZoneInfo(fTZI, fCalendar->getTimeZone()); 293} 294 295void Win32DateFormat::setCalendar(const Calendar &newCalendar) 296{ 297 adoptCalendar(newCalendar.clone()); 298} 299 300void Win32DateFormat::adoptTimeZone(TimeZone *zoneToAdopt) 301{ 302 fZoneID = setTimeZoneInfo(fTZI, *zoneToAdopt); 303 fCalendar->adoptTimeZone(zoneToAdopt); 304} 305 306void Win32DateFormat::setTimeZone(const TimeZone& zone) 307{ 308 fZoneID = setTimeZoneInfo(fTZI, zone); 309 fCalendar->setTimeZone(zone); 310} 311 312static const DWORD dfFlags[] = {DATE_LONGDATE, DATE_LONGDATE, DATE_SHORTDATE, DATE_SHORTDATE}; 313 314void Win32DateFormat::formatDate(const SYSTEMTIME *st, UnicodeString &appendTo) const 315{ 316 int result=0; 317 wchar_t stackBuffer[STACK_BUFFER_SIZE]; 318 wchar_t *buffer = stackBuffer; 319 const wchar_t *localeName = nullptr; 320 321 if (fWindowsLocaleName != nullptr) 322 { 323 localeName = reinterpret_cast<const wchar_t*>(toOldUCharPtr(fWindowsLocaleName->getTerminatedBuffer())); 324 } 325 326 result = GetDateFormatEx(localeName, dfFlags[fDateStyle - kDateOffset], st, nullptr, buffer, STACK_BUFFER_SIZE, nullptr); 327 328 if (result == 0) { 329 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { 330 int newLength = GetDateFormatEx(localeName, dfFlags[fDateStyle - kDateOffset], st, nullptr, nullptr, 0, nullptr); 331 332 buffer = NEW_ARRAY(wchar_t, newLength); 333 334 GetDateFormatEx(localeName, dfFlags[fDateStyle - kDateOffset], st, nullptr, buffer, newLength, nullptr); 335 } 336 } 337 338 appendTo.append((const char16_t *)buffer, (int32_t) wcslen(buffer)); 339 340 if (buffer != stackBuffer) { 341 DELETE_ARRAY(buffer); 342 } 343} 344 345static const DWORD tfFlags[] = {0, 0, 0, TIME_NOSECONDS}; 346 347void Win32DateFormat::formatTime(const SYSTEMTIME *st, UnicodeString &appendTo) const 348{ 349 int result; 350 wchar_t stackBuffer[STACK_BUFFER_SIZE]; 351 wchar_t *buffer = stackBuffer; 352 const wchar_t *localeName = nullptr; 353 354 if (fWindowsLocaleName != nullptr) 355 { 356 localeName = reinterpret_cast<const wchar_t*>(toOldUCharPtr(fWindowsLocaleName->getTerminatedBuffer())); 357 } 358 359 result = GetTimeFormatEx(localeName, tfFlags[fTimeStyle], st, nullptr, buffer, STACK_BUFFER_SIZE); 360 361 if (result == 0) { 362 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { 363 int newLength = GetTimeFormatEx(localeName, tfFlags[fTimeStyle], st, nullptr, nullptr, 0); 364 365 buffer = NEW_ARRAY(wchar_t, newLength); 366 367 GetTimeFormatEx(localeName, tfFlags[fTimeStyle], st, nullptr, buffer, newLength); 368 } 369 } 370 371 appendTo.append((const char16_t *)buffer, (int32_t) wcslen(buffer)); 372 373 if (buffer != stackBuffer) { 374 DELETE_ARRAY(buffer); 375 } 376} 377 378UnicodeString Win32DateFormat::setTimeZoneInfo(TIME_ZONE_INFORMATION *tzi, const TimeZone &zone) const 379{ 380 UnicodeString zoneID; 381 382 zone.getID(zoneID); 383 384 if (zoneID.compare(fZoneID) != 0) { 385 UnicodeString icuid; 386 387 zone.getID(icuid); 388 if (! uprv_getWindowsTimeZoneInfo(tzi, icuid.getBuffer(), icuid.length())) { 389 UBool found = false; 390 int32_t ec = TimeZone::countEquivalentIDs(icuid); 391 392 for (int z = 0; z < ec; z += 1) { 393 UnicodeString equiv = TimeZone::getEquivalentID(icuid, z); 394 395 found = uprv_getWindowsTimeZoneInfo(tzi, equiv.getBuffer(), equiv.length()); 396 if (found) { 397 break; 398 } 399 } 400 401 if (! found) { 402 GetTimeZoneInformation(tzi); 403 } 404 } 405 } 406 407 return zoneID; 408} 409 410U_NAMESPACE_END 411 412#endif /* #if !UCONFIG_NO_FORMATTING */ 413 414#endif // U_PLATFORM_USES_ONLY_WIN32_API 415 416