diff --git a/dll/win32/kernel32/CMakeLists.txt b/dll/win32/kernel32/CMakeLists.txt index 22409a7605e..f2a4e0f0292 100644 --- a/dll/win32/kernel32/CMakeLists.txt +++ b/dll/win32/kernel32/CMakeLists.txt @@ -76,6 +76,7 @@ list(APPEND SOURCE winnls/string/digitmap.c winnls/string/fold.c winnls/string/format_msg.c + winnls/string/japanese.c winnls/string/lang.c winnls/string/lcformat.c winnls/string/lstring.c diff --git a/dll/win32/kernel32/winnls/string/japanese.c b/dll/win32/kernel32/winnls/string/japanese.c new file mode 100644 index 00000000000..c3719f51f2d --- /dev/null +++ b/dll/win32/kernel32/winnls/string/japanese.c @@ -0,0 +1,229 @@ +/* + * PROJECT: ReactOS system libraries + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Japanese era support + * COPYRIGHT: Copyright 2019 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) + */ +#include + +#define NDEBUG +#include +#include "japanese.h" + +#define JAPANESE_ERA_MAX 16 + +/* #define DONT_USE_REGISTRY */ + +static DWORD s_JapaneseEraCount = 0; +static JAPANESE_ERA s_JapaneseEraTable[JAPANESE_ERA_MAX] +#ifdef DONT_USE_REGISTRY += +{ + {1868, 1, 1, {0x660E, 0x6CBB}, {0x660E, 0}, L"Meiji", L"M"}, + {1912, 7, 30, {0x5927, 0x6B63}, {0x5927, 0}, L"Taisho", L"T"}, + {1926, 12, 25, {0x662D, 0x548C}, {0x662D, 0}, L"Showa", L"S"}, + {1989, 1, 8, {0x5E73, 0x6210}, {0x5E73, 0}, L"Heisei", L"H"}, + {2019, 5, 1, {0x4EE4, 0x548C}, {0x4EE4, 0}, L"Reiwa", L"R"}, +} +#endif +; + +HANDLE NLS_RegOpenKey(HANDLE hRootKey, LPCWSTR szKeyName); + +BOOL NLS_RegEnumValue(HANDLE hKey, UINT ulIndex, + LPWSTR szValueName, ULONG valueNameSize, + LPWSTR szValueData, ULONG valueDataSize); + +static INT JapaneseEra_Compare(const void *e1, const void *e2) +{ + PCJAPANESE_ERA pEra1 = (PCJAPANESE_ERA)e1; + PCJAPANESE_ERA pEra2 = (PCJAPANESE_ERA)e2; + if (pEra1->wYear < pEra2->wYear) + return -1; + if (pEra1->wYear > pEra2->wYear) + return 1; + if (pEra1->wMonth < pEra2->wMonth) + return -1; + if (pEra1->wMonth > pEra2->wMonth) + return 1; + if (pEra1->wDay < pEra2->wDay) + return -1; + if (pEra1->wDay > pEra2->wDay) + return 1; + return 0; +} + +/* + * SEE ALSO: + * https://en.wikipedia.org/wiki/Japanese_era_name + * https://docs.microsoft.com/en-us/windows/desktop/Intl/era-handling-for-the-japanese-calendar + */ +static PCJAPANESE_ERA JapaneseEra_Load(DWORD *pdwCount) +{ +#ifndef DONT_USE_REGISTRY + HANDLE KeyHandle = NULL; + DWORD dwIndex; + WCHAR szName[128], szValue[128]; + JAPANESE_ERA *pEntry; + LPWSTR pch1, pch2, pch3, pch4; +#endif + + ASSERT(pdwCount != NULL); + + /* return cache if any */ + if (s_JapaneseEraCount != 0) + { + *pdwCount = s_JapaneseEraCount; + return s_JapaneseEraTable; + } + +#ifdef DONT_USE_REGISTRY + s_JapaneseEraCount = ARRAYSIZE(s_JapaneseEraTable); +#else + /* init */ + *pdwCount = 0; + RtlZeroMemory(&s_JapaneseEraTable, sizeof(s_JapaneseEraTable)); + + /* open registry key */ + KeyHandle = NLS_RegOpenKey(NULL, L"\\Registry\\Machine\\System\\" + L"CurrentControlSet\\Control\\Nls\\Calendars\\Japanese\\Eras"); + if (!KeyHandle) + return NULL; + + /* for all values */ + for (dwIndex = 0; dwIndex < JAPANESE_ERA_MAX; ++dwIndex) + { + pEntry = &s_JapaneseEraTable[dwIndex]; + + /* get name and data */ + if (!NLS_RegEnumValue(KeyHandle, dwIndex, szName, sizeof(szName), + szValue, sizeof(szValue))) + { + break; + } + + /* split fields */ + pch1 = szName; + pch2 = wcschr(pch1, L' '); + if (pch2 == NULL) + { + break; + } + *pch2++ = UNICODE_NULL; + + pch3 = wcschr(pch2, L' '); + if (pch3 == NULL) + { + break; + } + *pch3++ = UNICODE_NULL; + + pEntry->wYear = _wtoi(pch1); + pEntry->wMonth = _wtoi(pch2); + pEntry->wDay = _wtoi(pch3); + if (pEntry->wYear == 0 || pEntry->wMonth == 0 || pEntry->wDay == 0) + { + break; + } + + /* split fields */ + pch1 = szValue; + pch2 = wcschr(pch1, L'_'); + if (pch2 == NULL) + { + break; + } + *pch2++ = UNICODE_NULL; + + pch3 = wcschr(pch2, L'_'); + if (pch3 == NULL) + { + break; + } + *pch3++ = UNICODE_NULL; + + pch4 = wcschr(pch3, L'_'); + if (pch4 == NULL) + { + break; + } + *pch4++ = UNICODE_NULL; + + /* store */ + RtlStringCbCopyW(pEntry->szEraName, sizeof(pEntry->szEraName), pch1); + RtlStringCbCopyW(pEntry->szEraAbbrev, sizeof(pEntry->szEraAbbrev), pch2); + RtlStringCbCopyW(pEntry->szEnglishEraName, sizeof(pEntry->szEnglishEraName), pch3); + RtlStringCbCopyW(pEntry->szEnglishEraAbbrev, sizeof(pEntry->szEnglishEraAbbrev), pch4); + } + + /* close key */ + NtClose(KeyHandle); + + /* sort */ + qsort(s_JapaneseEraTable, s_JapaneseEraCount, sizeof(JAPANESE_ERA), + JapaneseEra_Compare); + + /* make cache */ + s_JapaneseEraCount = dwIndex; +#endif + + *pdwCount = s_JapaneseEraCount; + + return s_JapaneseEraTable; +} + +static BOOL JapaneseEra_ToSystemTime(PCJAPANESE_ERA pEra, LPSYSTEMTIME pst) +{ + ASSERT(pEra != NULL); + ASSERT(pst != NULL); + + ZeroMemory(pst, sizeof(*pst)); + pst->wYear = pEra->wYear; + pst->wMonth = pEra->wMonth; + pst->wDay = pEra->wDay; + return TRUE; +} + +PCJAPANESE_ERA JapaneseEra_Find(const SYSTEMTIME *pst OPTIONAL) +{ + DWORD dwIndex, dwCount = 0; + PCJAPANESE_ERA pTable, pEntry, pPrevEntry = NULL; + SYSTEMTIME st1, st2; + FILETIME ft1, ft2; + LONG nCompare; + + /* pst --> ft1 */ + if (pst == NULL) + { + GetLocalTime(&st1); + pst = &st1; + } + SystemTimeToFileTime(pst, &ft1); + + /* load era table */ + pTable = JapaneseEra_Load(&dwCount); + if (pTable == NULL || dwCount == 0 || dwCount > JAPANESE_ERA_MAX) + { + return NULL; + } + + /* for all eras */ + for (dwIndex = 0; dwIndex < dwCount; dwIndex++) + { + pEntry = &pTable[dwIndex]; + + /* pEntry --> st2 --> ft2 */ + JapaneseEra_ToSystemTime(pEntry, &st2); + SystemTimeToFileTime(&st2, &ft2); + + /* ft1 <=> ft2 */ + nCompare = CompareFileTime(&ft1, &ft2); + if (nCompare == 0) + return pEntry; + if (nCompare < 0) + return pPrevEntry; + pPrevEntry = pEntry; + } + + return pPrevEntry; +} diff --git a/dll/win32/kernel32/winnls/string/japanese.h b/dll/win32/kernel32/winnls/string/japanese.h new file mode 100644 index 00000000000..747ee136ed6 --- /dev/null +++ b/dll/win32/kernel32/winnls/string/japanese.h @@ -0,0 +1,21 @@ +/* + * PROJECT: ReactOS system libraries + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Japanese era support + * COPYRIGHT: Copyright 2019 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) + */ +#define JAPANESE_MAX_TWODIGITYEAR 99 + +typedef struct JAPANESE_ERA +{ + WORD wYear; + WORD wMonth; + WORD wDay; + WCHAR szEraName[16]; + WCHAR szEraAbbrev[5]; + WCHAR szEnglishEraName[24]; + WCHAR szEnglishEraAbbrev[5]; +} JAPANESE_ERA, *PJAPANESE_ERA; +typedef const JAPANESE_ERA *PCJAPANESE_ERA; + +PCJAPANESE_ERA JapaneseEra_Find(const SYSTEMTIME *pst OPTIONAL); diff --git a/dll/win32/kernel32/winnls/string/lang.c b/dll/win32/kernel32/winnls/string/lang.c index 42c57d1aef4..7d139805117 100644 --- a/dll/win32/kernel32/winnls/string/lang.c +++ b/dll/win32/kernel32/winnls/string/lang.c @@ -2366,14 +2366,22 @@ INT WINAPI CompareStringA(LCID lcid, DWORD flags, return ret; } +#ifdef __REACTOS__ +HANDLE NLS_RegOpenKey(HANDLE hRootKey, LPCWSTR szKeyName) +#else static HANDLE NLS_RegOpenKey(HANDLE hRootKey, LPCWSTR szKeyName) +#endif { UNICODE_STRING keyName; OBJECT_ATTRIBUTES attr; HANDLE hkey; RtlInitUnicodeString( &keyName, szKeyName ); +#ifdef __REACTOS__ + InitializeObjectAttributes(&attr, &keyName, OBJ_CASE_INSENSITIVE, hRootKey, NULL); +#else InitializeObjectAttributes(&attr, &keyName, 0, hRootKey, NULL); +#endif if (NtOpenKey( &hkey, KEY_READ, &attr ) != STATUS_SUCCESS) hkey = 0; @@ -2381,9 +2389,15 @@ static HANDLE NLS_RegOpenKey(HANDLE hRootKey, LPCWSTR szKeyName) return hkey; } +#ifdef __REACTOS__ +BOOL NLS_RegEnumValue(HANDLE hKey, UINT ulIndex, + LPWSTR szValueName, ULONG valueNameSize, + LPWSTR szValueData, ULONG valueDataSize) +#else static BOOL NLS_RegEnumValue(HANDLE hKey, UINT ulIndex, LPWSTR szValueName, ULONG valueNameSize, LPWSTR szValueData, ULONG valueDataSize) +#endif { BYTE buffer[80]; KEY_VALUE_FULL_INFORMATION *info = (KEY_VALUE_FULL_INFORMATION *)buffer; diff --git a/dll/win32/kernel32/winnls/string/lcformat.c b/dll/win32/kernel32/winnls/string/lcformat.c index a4088cb9b2e..8d853511905 100644 --- a/dll/win32/kernel32/winnls/string/lcformat.c +++ b/dll/win32/kernel32/winnls/string/lcformat.c @@ -25,6 +25,7 @@ #ifdef __REACTOS__ #include +#include "japanese.h" /* Japanese eras */ #define NDEBUG #include @@ -34,6 +35,12 @@ DEBUG_CHANNEL(nls); #define CRITICAL_SECTION_DEBUG RTL_CRITICAL_SECTION_DEBUG #define CALINFO_MAX_YEAR 2029 +#define IS_LCID_JAPANESE(lcid) PRIMARYLANGID(LANGIDFROMLCID(lcid)) == LANG_JAPANESE + +#ifndef CAL_SABBREVERASTRING + #define CAL_SABBREVERASTRING 0x00000039 +#endif + #else /* __REACTOS__ */ #include "config.h" @@ -372,7 +379,11 @@ BOOL NLS_IsUnicodeOnlyLcid(LCID lcid) #define IsTimeFmtChar(p) (p == 'H'||p == 'h'||p == 'm'||p == 's'||p == 't') /* Only the following flags can be given if a date/time format is specified */ +#ifdef __REACTOS__ +#define DATE_FORMAT_FLAGS (DATE_DATEVARSONLY | DATE_USE_ALT_CALENDAR) +#else #define DATE_FORMAT_FLAGS (DATE_DATEVARSONLY) +#endif #define TIME_FORMAT_FLAGS (TIME_TIMEVARSONLY|TIME_FORCE24HOURFORMAT| \ TIME_NOMINUTESORSECONDS|TIME_NOSECONDS| \ TIME_NOTIMEMARKER) @@ -604,6 +615,24 @@ static INT NLS_GetDateTimeFormatW(LCID lcid, DWORD dwFlags, break; case 'y': +#ifdef __REACTOS__ + if (IS_LCID_JAPANESE(lcid) && (dwFlags & DATE_USE_ALT_CALENDAR)) + { + PCJAPANESE_ERA pEra = JapaneseEra_Find(lpTime); + if (pEra) + { + if (count >= 2) + { + count = 2; + } + dwVal = lpTime->wYear - pEra->wYear + 1; + szAdd = buff; + break; + } + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } +#endif if (count >= 4) { count = 4; @@ -618,6 +647,32 @@ static INT NLS_GetDateTimeFormatW(LCID lcid, DWORD dwFlags, break; case 'g': +#ifdef __REACTOS__ + if (IS_LCID_JAPANESE(lcid)) + { + if (dwFlags & DATE_USE_ALT_CALENDAR) + { + PCJAPANESE_ERA pEra = JapaneseEra_Find(lpTime); + if (pEra) + { + RtlStringCbCopyW(buff, sizeof(buff), pEra->szEraName); + szAdd = buff; + break; + } + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + else + { + /* Seireki */ + buff[0] = 0x897F; + buff[1] = 0x66A6; + buff[2] = 0; + szAdd = buff; + break; + } + } +#endif if (count == 2) { /* FIXME: Our GetCalendarInfo() does not yet support CAL_SERASTRING. @@ -2367,6 +2422,26 @@ int WINAPI GetCalendarInfoA(LCID lcid, CALID Calendar, CALTYPE CalType, { int ret, cchDataW = cchData; LPWSTR lpCalDataW = NULL; +#ifdef __REACTOS__ + DWORD cp = CP_ACP; + if (!(CalType & CAL_USE_CP_ACP)) + { + DWORD dwFlags = ((CalType & CAL_NOUSEROVERRIDE) ? LOCALE_NOUSEROVERRIDE : 0); + const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags); + if (!node) + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + cp = node->dwCodePage; + } + if ((CalType & 0xFFFF) == CAL_SABBREVERASTRING) + { + /* NOTE: CAL_SABBREVERASTRING is not supported in GetCalendarInfoA */ + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } +#endif if (NLS_IsUnicodeOnlyLcid(lcid)) { @@ -2381,7 +2456,11 @@ int WINAPI GetCalendarInfoA(LCID lcid, CALID Calendar, CALTYPE CalType, ret = GetCalendarInfoW(lcid, Calendar, CalType, lpCalDataW, cchDataW, lpValue); if(ret && lpCalDataW && lpCalData) +#ifdef __REACTOS__ + ret = WideCharToMultiByte(cp, 0, lpCalDataW, -1, lpCalData, cchData, NULL, NULL); +#else ret = WideCharToMultiByte(CP_ACP, 0, lpCalDataW, -1, lpCalData, cchData, NULL, NULL); +#endif else if (CalType & CAL_RETURN_NUMBER) ret *= sizeof(WCHAR); HeapFree(GetProcessHeap(), 0, lpCalDataW); @@ -2495,18 +2574,101 @@ int WINAPI GetCalendarInfoW(LCID Locale, CALID Calendar, CALTYPE CalType, switch (calinfo) { case CAL_ICALINTVALUE: +#ifdef __REACTOS__ + if (IS_LCID_JAPANESE(Locale)) + { + if (CalType & CAL_RETURN_NUMBER) + { + *lpValue = CAL_JAPAN; + return sizeof(DWORD) / sizeof(WCHAR); + } + else + { + static const WCHAR fmtW[] = {'%','u',0}; + WCHAR buffer[10]; + int ret = snprintfW( buffer, 10, fmtW, CAL_JAPAN ) + 1; + if (!lpCalData) return ret; + if (ret <= cchData) + { + strcpyW( lpCalData, buffer ); + return ret; + } + SetLastError( ERROR_INSUFFICIENT_BUFFER ); + return 0; + } + } +#endif if (CalType & CAL_RETURN_NUMBER) return GetLocaleInfoW(Locale, LOCALE_RETURN_NUMBER | LOCALE_ICALENDARTYPE, (LPWSTR)lpValue, 2); return GetLocaleInfoW(Locale, LOCALE_ICALENDARTYPE, lpCalData, cchData); case CAL_SCALNAME: +#ifdef __REACTOS__ + if (IS_LCID_JAPANESE(Locale) && Calendar == CAL_JAPAN) + { + // Wareki + lpCalData[0] = 0x548C; + lpCalData[1] = 0x66A6; + lpCalData[2] = 0; + return 3; + } +#endif FIXME("Unimplemented caltype %d\n", calinfo); if (lpCalData) *lpCalData = 0; return 1; case CAL_IYEAROFFSETRANGE: +#ifdef __REACTOS__ + if (IS_LCID_JAPANESE(Locale) && Calendar == CAL_JAPAN) + { + PCJAPANESE_ERA pEra = JapaneseEra_Find(NULL); + if (pEra) + { + if (CalType & CAL_RETURN_NUMBER) + { + *lpValue = pEra->wYear; + return sizeof(DWORD) / sizeof(WCHAR); + } + else + { + static const WCHAR fmtW[] = {'%','u',0}; + WCHAR buffer[10]; + int ret = snprintfW( buffer, 10, fmtW, pEra->wYear ) + 1; + if (!lpCalData) return ret; + if (ret <= cchData) + { + strcpyW( lpCalData, buffer ); + return ret; + } + SetLastError( ERROR_INSUFFICIENT_BUFFER ); + return 0; + } + } + else + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + } +#endif FIXME("Unimplemented caltype %d\n", calinfo); return 0; case CAL_SERASTRING: +#ifdef __REACTOS__ + if (IS_LCID_JAPANESE(Locale) && Calendar == CAL_JAPAN) + { + PCJAPANESE_ERA pEra = JapaneseEra_Find(NULL); + if (pEra) + { + RtlStringCchCopyW(lpCalData, cchData, pEra->szEraName); + return strlenW(lpCalData) + 1; + } + else + { + SetLastError(ERROR_INVALID_PARAMETER); + return 0; + } + } +#endif FIXME("Unimplemented caltype %d\n", calinfo); return 0; case CAL_SSHORTDATE: @@ -2554,6 +2716,30 @@ int WINAPI GetCalendarInfoW(LCID Locale, CALID Calendar, CALTYPE CalType, case CAL_SYEARMONTH: return GetLocaleInfoW(Locale, caltype_lctype_map[calinfo] | localeflags, lpCalData, cchData); case CAL_ITWODIGITYEARMAX: +#ifdef __REACTOS__ + if (IS_LCID_JAPANESE(Locale) && Calendar == CAL_JAPAN) + { + if (CalType & CAL_RETURN_NUMBER) + { + *lpValue = JAPANESE_MAX_TWODIGITYEAR; + return sizeof(DWORD) / sizeof(WCHAR); + } + else + { + static const WCHAR fmtW[] = {'%','u',0}; + WCHAR buffer[10]; + int ret = snprintfW( buffer, 10, fmtW, JAPANESE_MAX_TWODIGITYEAR ) + 1; + if (!lpCalData) return ret; + if (ret <= cchData) + { + strcpyW( lpCalData, buffer ); + return ret; + } + SetLastError( ERROR_INSUFFICIENT_BUFFER ); + return 0; + } + } +#endif if (CalType & CAL_RETURN_NUMBER) { *lpValue = CALINFO_MAX_YEAR; @@ -2574,6 +2760,20 @@ int WINAPI GetCalendarInfoW(LCID Locale, CALID Calendar, CALTYPE CalType, return 0; } break; +#ifdef __REACTOS__ + case CAL_SABBREVERASTRING: + if (IS_LCID_JAPANESE(Locale) && Calendar == CAL_JAPAN) + { + PCJAPANESE_ERA pEra = JapaneseEra_Find(NULL); + if (pEra) + { + RtlStringCchCopyW(lpCalData, cchData, pEra->szEraAbbrev); + return strlenW(lpCalData) + 1; + } + } + SetLastError(ERROR_INVALID_PARAMETER); + return 0; +#endif default: FIXME("Unknown caltype %d\n", calinfo); SetLastError(ERROR_INVALID_FLAGS);