Commit 31f1a801 authored by Jon Griffiths's avatar Jon Griffiths Committed by Alexandre Julliard

Re-implement and optimise the NLS formatting functions (many fixes).

Documentation updates.
parent 223a442d
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
* Copyright 1995 Martin von Loewis * Copyright 1995 Martin von Loewis
* Copyright 1998 David Lee Lambert * Copyright 1998 David Lee Lambert
* Copyright 2000 Julio César Gázquez * Copyright 2000 Julio César Gázquez
* Copyright 2003 Jon Griffiths
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * modify it under the terms of the GNU Lesser General Public
...@@ -32,515 +33,1552 @@ ...@@ -32,515 +33,1552 @@
#include "winbase.h" #include "winbase.h"
#include "wine/unicode.h" #include "wine/unicode.h"
#include "wine/debug.h" #include "wine/debug.h"
#include "winreg.h"
#include "winternl.h"
WINE_DEFAULT_DEBUG_CHANNEL(nls); WINE_DEFAULT_DEBUG_CHANNEL(nls);
#define DATE_DATEVARSONLY 0x0100 /* only date stuff: yMdg */
#define TIME_TIMEVARSONLY 0x0200 /* only time stuff: hHmst */
/****************************************************************************** /* Since calculating the formatting data for each locale is time-consuming,
* get_date_time_formatW [Internal] * we get the format data for each locale only once and cache it in memory.
* * We cache both the system default and user overridden data, after converting
* dateformat is set TRUE if being called for a date, false for a time * them into the formats that the functions here expect. Since these functions
* will typically be called with only a small number of the total locales
* installed, the memory overhead is minimal while the speedup is significant.
* *
* This function implements stuff for GetDateFormat() and * Our cache takes the form of a singly linked list, whose node is below:
* GetTimeFormat(). */
* #define NLS_NUM_CACHED_STRINGS 45
* d single-digit (no leading zero) day (of month)
* dd two-digit day (of month) typedef struct _NLS_FORMAT_NODE
* ddd short day-of-week name {
* dddd long day-of-week name LCID lcid; /* Locale Id */
* M single-digit month DWORD dwFlags; /* 0 or LOCALE_NOUSEROVERRIDE */
* MM two-digit month DWORD dwCodePage; /* Default code page (if LOCALE_USE_ANSI_CP not given) */
* MMM short month name NUMBERFMTW fmt; /* Default format for numbers */
* MMMM full month name CURRENCYFMTW cyfmt; /* Default format for currencies */
* y two-digit year, no leading 0 LPWSTR lppszStrings[NLS_NUM_CACHED_STRINGS]; /* Default formats,day/month names */
* yy two-digit year WCHAR szShortAM[2]; /* Short 'AM' marker */
* yyyy four-digit year WCHAR szShortPM[2]; /* Short 'PM' marker */
* gg era string struct _NLS_FORMAT_NODE *next;
* h hours with no leading zero (12-hour) } NLS_FORMAT_NODE;
* hh hours with full two digits
* H hours with no leading zero (24-hour) /* Macros to get particular data strings from a format node */
* HH hours with full two digits #define GetNegative(fmt) fmt->lppszStrings[0]
* m minutes with no leading zero #define GetLongDate(fmt) fmt->lppszStrings[1]
* mm minutes with full two digits #define GetShortDate(fmt) fmt->lppszStrings[2]
* s seconds with no leading zero #define GetTime(fmt) fmt->lppszStrings[3]
* ss seconds with full two digits #define GetAM(fmt) fmt->lppszStrings[42]
* t time marker (A or P) #define GetPM(fmt) fmt->lppszStrings[43]
* tt time marker (AM, PM) #define GetYearMonth(fmt) fmt->lppszStrings[44]
* '' used to quote literal characters
* '' (within a quoted string) indicates a literal ' #define GetLongDay(fmt,day) fmt->lppszStrings[4 + day]
#define GetShortDay(fmt,day) fmt->lppszStrings[11 + day]
#define GetLongMonth(fmt,mth) fmt->lppszStrings[18 + mth]
#define GetShortMonth(fmt,mth) fmt->lppszStrings[30 + mth]
/* Write access to the cache is protected by this critical section */
static CRITICAL_SECTION NLS_FormatsCS;
static CRITICAL_SECTION_DEBUG NLS_FormatsCS_debug =
{
0, 0, &NLS_FormatsCS,
{ &NLS_FormatsCS_debug.ProcessLocksList,
&NLS_FormatsCS_debug.ProcessLocksList },
0, 0, { 0, (DWORD)(__FILE__ ": NLS_Formats") }
};
static CRITICAL_SECTION NLS_FormatsCS = { &NLS_FormatsCS_debug, -1, 0, 0, 0, 0 };
/**************************************************************************
* NLS_GetLocaleNumber <internal>
* *
* If TIME_NOMINUTESORSECONDS or TIME_NOSECONDS is specified, the function * Get a numeric locale format value.
* removes the separator(s) preceding the minutes and/or seconds element(s). */
static WINAPI DWORD NLS_GetLocaleNumber(LCID lcid, DWORD dwFlags)
{
WCHAR szBuff[80];
DWORD dwVal = 0;
szBuff[0] = '\0';
GetLocaleInfoW(lcid, dwFlags, szBuff, sizeof(szBuff) / sizeof(WCHAR));
if (szBuff[0] && szBuff[1] == ';' && szBuff[2] != '0')
dwVal = (szBuff[0] - '0') * 10 + (szBuff[2] - '0');
else
{
const WCHAR* iter = szBuff;
dwVal = 0;
while(*iter >= '0' && *iter <= '9')
dwVal = dwVal * 10 + (*iter++ - '0');
}
return dwVal;
}
/**************************************************************************
* NLS_GetLocaleString <internal>
* *
* If TIME_NOTIMEMARKER is specified, the function removes the separator(s) * Get a string locale format value.
* preceding and following the time marker. */
static WINAPI WCHAR* NLS_GetLocaleString(LCID lcid, DWORD dwFlags)
{
WCHAR szBuff[80], *str;
DWORD dwLen;
szBuff[0] = '\0';
GetLocaleInfoW(lcid, dwFlags, szBuff, sizeof(szBuff) / sizeof(WCHAR));
dwLen = strlenW(szBuff) + 1;
str = HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
if (str)
memcpy(str, szBuff, dwLen * sizeof(WCHAR));
return str;
}
#define GET_LOCALE_NUMBER(num, type) num = NLS_GetLocaleNumber(lcid, type|dwFlags); \
TRACE( #type ": %ld (%08lx)\n", (DWORD)num, (DWORD)num)
#define GET_LOCALE_STRING(str, type) str = NLS_GetLocaleString(lcid, type|dwFlags); \
TRACE( #type ": '%s'\n", debugstr_w(str))
/**************************************************************************
* NLS_GetFormats <internal>
* *
* If TIME_FORCE24HOURFORMAT is specified, the function displays any existing * Calculate (and cache) the number formats for a locale.
* time marker, unless the TIME_NOTIMEMARKER flag is also set. */
static WINAPI const NLS_FORMAT_NODE *NLS_GetFormats(LCID lcid, DWORD dwFlags)
{
/* GetLocaleInfo() identifiers for cached formatting strings */
static const USHORT NLS_LocaleIndices[] = {
LOCALE_SNEGATIVESIGN,
LOCALE_SLONGDATE, LOCALE_SSHORTDATE,
LOCALE_STIMEFORMAT,
LOCALE_SDAYNAME1, LOCALE_SDAYNAME2, LOCALE_SDAYNAME3,
LOCALE_SDAYNAME4, LOCALE_SDAYNAME5, LOCALE_SDAYNAME6, LOCALE_SDAYNAME7,
LOCALE_SABBREVDAYNAME1, LOCALE_SABBREVDAYNAME2, LOCALE_SABBREVDAYNAME3,
LOCALE_SABBREVDAYNAME4, LOCALE_SABBREVDAYNAME5, LOCALE_SABBREVDAYNAME6,
LOCALE_SABBREVDAYNAME7,
LOCALE_SMONTHNAME1, LOCALE_SMONTHNAME2, LOCALE_SMONTHNAME3,
LOCALE_SMONTHNAME4, LOCALE_SMONTHNAME5, LOCALE_SMONTHNAME6,
LOCALE_SMONTHNAME7, LOCALE_SMONTHNAME8, LOCALE_SMONTHNAME9,
LOCALE_SMONTHNAME10, LOCALE_SMONTHNAME11, LOCALE_SMONTHNAME12,
LOCALE_SABBREVMONTHNAME1, LOCALE_SABBREVMONTHNAME2, LOCALE_SABBREVMONTHNAME3,
LOCALE_SABBREVMONTHNAME4, LOCALE_SABBREVMONTHNAME5, LOCALE_SABBREVMONTHNAME6,
LOCALE_SABBREVMONTHNAME7, LOCALE_SABBREVMONTHNAME8, LOCALE_SABBREVMONTHNAME9,
LOCALE_SABBREVMONTHNAME10, LOCALE_SABBREVMONTHNAME11, LOCALE_SABBREVMONTHNAME12,
LOCALE_S1159, LOCALE_S2359,
LOCALE_SYEARMONTH
};
static NLS_FORMAT_NODE *NLS_CachedFormats = NULL;
NLS_FORMAT_NODE *node = NLS_CachedFormats;
dwFlags &= LOCALE_NOUSEROVERRIDE;
TRACE("(0x%04lx,0x%08lx)\n", lcid, dwFlags);
/* See if we have already cached the locales number format */
while (node && (node->lcid != lcid || node->dwFlags != dwFlags) && node->next)
node = node->next;
if (!node || node->lcid != lcid || node->dwFlags != dwFlags)
{
NLS_FORMAT_NODE *new_node;
DWORD i;
TRACE("Creating new cache entry\n");
if (!(new_node = HeapAlloc(GetProcessHeap(), 0, sizeof(NLS_FORMAT_NODE))))
return NULL;
GET_LOCALE_NUMBER(new_node->dwCodePage, LOCALE_IDEFAULTANSICODEPAGE);
/* Number Format */
new_node->lcid = lcid;
new_node->dwFlags = dwFlags;
new_node->next = NULL;
GET_LOCALE_NUMBER(new_node->fmt.NumDigits, LOCALE_IDIGITS);
GET_LOCALE_NUMBER(new_node->fmt.LeadingZero, LOCALE_ILZERO);
GET_LOCALE_NUMBER(new_node->fmt.NegativeOrder, LOCALE_INEGNUMBER);
GET_LOCALE_NUMBER(new_node->fmt.Grouping, LOCALE_SGROUPING);
if (new_node->fmt.Grouping > 9 && new_node->fmt.Grouping != 32)
{
WARN("LOCALE_SGROUPING (%d) unhandled, please report!\n",
new_node->fmt.Grouping);
new_node->fmt.Grouping = 0;
}
GET_LOCALE_STRING(new_node->fmt.lpDecimalSep, LOCALE_SDECIMAL);
GET_LOCALE_STRING(new_node->fmt.lpThousandSep, LOCALE_STHOUSAND);
/* Currency Format */
new_node->cyfmt.NumDigits = new_node->fmt.NumDigits;
new_node->cyfmt.LeadingZero = new_node->fmt.LeadingZero;
GET_LOCALE_NUMBER(new_node->cyfmt.Grouping, LOCALE_SGROUPING);
if (new_node->cyfmt.Grouping > 9)
{
WARN("LOCALE_SMONGROUPING (%d) unhandled, please report!\n",
new_node->cyfmt.Grouping);
new_node->cyfmt.Grouping = 0;
}
GET_LOCALE_NUMBER(new_node->cyfmt.NegativeOrder, LOCALE_INEGCURR);
if (new_node->cyfmt.NegativeOrder > 15)
{
WARN("LOCALE_INEGCURR (%d) unhandled, please report!\n",
new_node->cyfmt.NegativeOrder);
new_node->cyfmt.NegativeOrder = 0;
}
GET_LOCALE_NUMBER(new_node->cyfmt.PositiveOrder, LOCALE_ICURRENCY);
if (new_node->cyfmt.PositiveOrder > 3)
{
WARN("LOCALE_IPOSCURR (%d) unhandled,please report!\n",
new_node->cyfmt.PositiveOrder);
new_node->cyfmt.PositiveOrder = 0;
}
GET_LOCALE_STRING(new_node->cyfmt.lpDecimalSep, LOCALE_SMONDECIMALSEP);
GET_LOCALE_STRING(new_node->cyfmt.lpThousandSep, LOCALE_SMONTHOUSANDSEP);
GET_LOCALE_STRING(new_node->cyfmt.lpCurrencySymbol, LOCALE_SCURRENCY);
/* Date/Time Format info, negative character, etc */
for (i = 0; i < sizeof(NLS_LocaleIndices)/sizeof(NLS_LocaleIndices[0]); i++)
{
GET_LOCALE_STRING(new_node->lppszStrings[i], NLS_LocaleIndices[i]);
}
new_node->szShortAM[0] = GetAM(new_node)[0]; new_node->szShortAM[1] = '\0';
new_node->szShortPM[0] = GetPM(new_node)[0]; new_node->szShortPM[1] = '\0';
/* Now add the computed format to the cache */
RtlEnterCriticalSection(&NLS_FormatsCS);
/* Search again: We may have raced to add the node */
node = NLS_CachedFormats;
while (node && (node->lcid != lcid || node->dwFlags != dwFlags) && node->next)
node = node->next;
if (!node)
{
node = NLS_CachedFormats = new_node; /* Empty list */
new_node = NULL;
}
else if (node->lcid != lcid || node->dwFlags != dwFlags)
{
node->next = new_node; /* Not in the list, add to end */
node = new_node;
new_node = NULL;
}
RtlLeaveCriticalSection(&NLS_FormatsCS);
if (new_node)
{
/* We raced and lost: The node was already added by another thread.
* node points to the currently cached node, so free new_node.
*/
for (i = 0; i < sizeof(NLS_LocaleIndices)/sizeof(NLS_LocaleIndices[0]); i++)
HeapFree(GetProcessHeap(), 0, new_node->lppszStrings[i]);
HeapFree(GetProcessHeap(), 0, new_node->fmt.lpDecimalSep);
HeapFree(GetProcessHeap(), 0, new_node->fmt.lpThousandSep);
HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpDecimalSep);
HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpThousandSep);
HeapFree(GetProcessHeap(), 0, new_node->cyfmt.lpCurrencySymbol);
HeapFree(GetProcessHeap(), 0, new_node);
}
}
return node;
}
/**************************************************************************
* NLS_IsUnicodeOnlyLcid <internal>
* *
* These functions REQUIRE valid locale, date, and format. * Determine if a locale is Unicode only, and thus invalid in ASCII calls.
*/
BOOL WINAPI NLS_IsUnicodeOnlyLcid(LCID lcid)
{
switch (PRIMARYLANGID(lcid))
{
case LANG_ARMENIAN:
case LANG_DIVEHI:
case LANG_GEORGIAN:
case LANG_GUJARATI:
case LANG_HINDI:
case LANG_KANNADA:
case LANG_KONKANI:
case LANG_MARATHI:
case LANG_PUNJABI:
case LANG_SANSKRIT:
TRACE("lcid 0x%08lx: langid 0x%4x is Unicode Only\n", lcid, PRIMARYLANGID(lcid));
return TRUE;
default:
return FALSE;
}
}
/*
* Formatting of dates, times, numbers and currencies.
*/
#define IsLiteralMarker(p) (p == '\'')
#define IsDateFmtChar(p) (p == 'd'||p == 'M'||p == 'y'||p == 'g')
#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 */
#define DATE_FORMAT_FLAGS (DATE_DATEVARSONLY|LOCALE_NOUSEROVERRIDE)
#define TIME_FORMAT_FLAGS (TIME_TIMEVARSONLY|TIME_FORCE24HOURFORMAT| \
TIME_NOMINUTESORSECONDS|TIME_NOSECONDS| \
TIME_NOTIMEMARKER|LOCALE_NOUSEROVERRIDE)
/******************************************************************************
* NLS_GetDateTimeFormatW <internal>
* *
* If the time or date is invalid, return 0 and set ERROR_INVALID_PARAMETER * Performs the formatting for GetDateFormatW/GetTimeFormatW.
* *
* Return value is the number of characters written, or if outlen is zero * FIXME
* it is the number of characters required for the output including * DATE_USE_ALT_CALENDAR - Requires GetCalendarInfo to work first.
* the terminating null. * DATE_LTRREADING/DATE_RTLREADING - Not yet implemented.
*/ */
static INT get_date_time_formatW(LCID locale, DWORD flags, DWORD tflags, static INT NLS_GetDateTimeFormatW(LCID lcid, DWORD dwFlags,
const SYSTEMTIME* xtime, LPCWSTR format, const SYSTEMTIME* lpTime, LPCWSTR lpFormat,
LPWSTR output, INT outlen, int dateformat) LPWSTR lpStr, INT cchOut)
{ {
INT outpos; const NLS_FORMAT_NODE *node;
INT lastFormatPos; /* the position in the output buffer of */ SYSTEMTIME st;
/* the end of the output from the last formatting */ INT cchWritten = 0;
/* character */ INT lastFormatPos = 0;
BOOL dropUntilNextFormattingChar = FALSE; /* TIME_NOTIMEMARKER drops BOOL bSkipping = FALSE; /* Skipping text around marker? */
all of the text around the dropped marker,
eg. "h@!t@!m" becomes "hm" */ /* Verify our arguments */
if ((cchOut && !lpStr) || !(node = NLS_GetFormats(lcid, dwFlags)))
/* make a debug report */ {
TRACE("args: 0x%lx, 0x%lx, 0x%lx, time(d=%d,h=%d,m=%d,s=%d), fmt:%s (at %p), " NLS_GetDateTimeFormatW_InvalidParameter:
"%p with max len %d\n", SetLastError(ERROR_INVALID_PARAMETER);
locale, flags, tflags, return 0;
xtime->wDay, xtime->wHour, xtime->wMinute, xtime->wSecond, }
debugstr_w(format), format, output, outlen);
if (dwFlags & ~(DATE_DATEVARSONLY|TIME_TIMEVARSONLY))
/* initialize state variables */ {
outpos = 0; if (lpFormat &&
lastFormatPos = 0; ((dwFlags & DATE_DATEVARSONLY && dwFlags & ~DATE_FORMAT_FLAGS) ||
(dwFlags & TIME_TIMEVARSONLY && dwFlags & ~TIME_FORMAT_FLAGS)))
while (*format) { {
/* Literal string: Maybe terminated early by a \0 */ NLS_GetDateTimeFormatW_InvalidFlags:
if (*format == (WCHAR) '\'') SetLastError(ERROR_INVALID_FLAGS);
{ return 0;
format++; }
/* We loop while we haven't reached the end of the format string */ if (dwFlags & DATE_DATEVARSONLY)
/* and until we haven't found another "'" character */ {
while (*format) if ((dwFlags & (DATE_LTRREADING|DATE_RTLREADING)) == (DATE_LTRREADING|DATE_RTLREADING))
{ goto NLS_GetDateTimeFormatW_InvalidFlags;
/* we found what might be the close single quote mark */ else if (dwFlags & (DATE_LTRREADING|DATE_RTLREADING))
/* we need to make sure there isn't another single quote */ FIXME("Unsupported flags: DATE_LTRREADING/DATE_RTLREADING\n");
/* after it, if there is we need to skip over this quote mark */
/* as the user is trying to put a single quote mark in their output */ switch (dwFlags & (DATE_SHORTDATE|DATE_LONGDATE|DATE_YEARMONTH))
if (*format == (WCHAR) '\'') {
{ case 0:
format++; break;
if (*format != '\'') case DATE_SHORTDATE:
{ case DATE_LONGDATE:
break; /* It was a terminating quote */ case DATE_YEARMONTH:
} if (lpFormat)
} goto NLS_GetDateTimeFormatW_InvalidFlags;
break;
/* if outlen is zero then we are couting the number of */ default:
/* characters of space we need to output this text, don't */ goto NLS_GetDateTimeFormatW_InvalidFlags;
/* modify the output buffer */ }
if (!outlen) }
{ }
outpos++; /* We are counting */;
} else if (outpos >= outlen) if (!lpFormat)
{ {
goto too_short; /* Use the appropriate default format */
} else if (dwFlags & DATE_DATEVARSONLY)
{ {
/* even drop literal strings */ if (dwFlags & DATE_YEARMONTH)
if(!dropUntilNextFormattingChar) lpFormat = GetYearMonth(node);
{ else if (dwFlags & DATE_LONGDATE)
output[outpos] = *format; lpFormat = GetLongDate(node);
outpos++; else
} lpFormat = GetShortDate(node);
} }
format++; else
} lpFormat = GetTime(node);
} else if ( (dateformat && (*format=='d' || }
*format=='M' ||
*format=='y' || if (!lpTime)
*format=='g') ) || {
(!dateformat && (*format=='H' || GetSystemTime(&st); /* Default to current time */
*format=='h' || lpTime = &st;
*format=='m' || }
*format=='s' || else
*format=='t') ) ) {
/* if processing a date and we have a date formatting character, OR */ if (dwFlags & DATE_DATEVARSONLY)
/* if we are processing a time and we have a time formatting character */ {
{ FILETIME ftTmp;
int type, count;
char tmp[16]; /* Verify the date and correct the D.O.W. if needed */
WCHAR buf[40]; memset(&st, 0, sizeof(st));
int buflen=0; st.wYear = lpTime->wYear;
type = *format; st.wMonth = lpTime->wMonth;
format++; st.wDay = lpTime->wDay;
/* clear out the drop text flag if we are in here */ if (st.wDay > 31 || st.wMonth > 12 || !SystemTimeToFileTime(&st, &ftTmp))
dropUntilNextFormattingChar = FALSE; goto NLS_GetDateTimeFormatW_InvalidParameter;
/* count up the number of the same letter values in a row that */ FileTimeToSystemTime(&ftTmp, &st);
/* we get, this lets us distinguish between "s" and "ss" and it */ lpTime = &st;
/* eliminates the duplicate to simplify the below case statement */ }
for (count = 1; *format == type; format++)
count++; if (dwFlags & TIME_TIMEVARSONLY)
{
/* Verify the time */
if (lpTime->wHour > 24 || lpTime->wMinute > 59 || lpTime->wSecond > 59)
goto NLS_GetDateTimeFormatW_InvalidParameter;
}
}
/* Format the output */
while (*lpFormat)
{
if (IsLiteralMarker(*lpFormat))
{
/* Start of a literal string */
lpFormat++;
/* Loop until the end of the literal marker or end of the string */
while (*lpFormat)
{
if (IsLiteralMarker(*lpFormat))
{
lpFormat++;
if (!IsLiteralMarker(*lpFormat))
break; /* Terminating literal marker */
}
if (!cchOut)
cchWritten++; /* Count size only */
else if (cchWritten >= cchOut)
goto NLS_GetDateTimeFormatW_Overrun;
else if (!bSkipping)
{
lpStr[cchWritten] = *lpFormat;
cchWritten++;
}
lpFormat++;
}
}
else if ((dwFlags & DATE_DATEVARSONLY && IsDateFmtChar(*lpFormat)) ||
(dwFlags & TIME_TIMEVARSONLY && IsTimeFmtChar(*lpFormat)))
{
char buffA[32];
WCHAR buff[32], fmtChar;
LPCWSTR szAdd = NULL;
DWORD dwVal = 0;
int count = 0, dwLen;
buf[0] = 0; /* always null terminate the buffer */ bSkipping = FALSE;
switch(type) fmtChar = *lpFormat;
while (*lpFormat == fmtChar)
{
count++;
lpFormat++;
}
buff[0] = '\0';
switch(fmtChar)
{ {
case 'd': case 'd':
if (count >= 4) { if (count >= 4)
GetLocaleInfoW(locale, szAdd = GetLongDay(node, (lpTime->wDayOfWeek + 6) % 7);
LOCALE_SDAYNAME1 + (xtime->wDayOfWeek +6)%7, else if (count == 3)
buf, sizeof(buf)/sizeof(WCHAR) ); szAdd = GetShortDay(node, (lpTime->wDayOfWeek + 6) % 7);
} else if (count == 3) { else
GetLocaleInfoW(locale, {
LOCALE_SABBREVDAYNAME1 + dwVal = lpTime->wDay;
(xtime->wDayOfWeek +6)%7, szAdd = buff;
buf, sizeof(buf)/sizeof(WCHAR) );
} else {
sprintf( tmp, "%.*d", count, xtime->wDay );
MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) );
} }
break; break;
case 'M': case 'M':
if (count >= 4) { if (count >= 4)
GetLocaleInfoW(locale, LOCALE_SMONTHNAME1 + szAdd = GetLongMonth(node, lpTime->wMonth - 1);
xtime->wMonth -1, buf, else if (count == 3)
sizeof(buf)/sizeof(WCHAR) ); szAdd = GetShortMonth(node, lpTime->wMonth - 1);
} else if (count == 3) { else
GetLocaleInfoW(locale, LOCALE_SABBREVMONTHNAME1 + {
xtime->wMonth -1, buf, dwVal = lpTime->wMonth;
sizeof(buf)/sizeof(WCHAR) ); szAdd = buff;
} else {
sprintf( tmp, "%.*d", count, xtime->wMonth );
MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) );
} }
break; break;
case 'y': case 'y':
if (count >= 4) { if (count >= 4)
sprintf( tmp, "%d", xtime->wYear ); {
} else { count = 4;
sprintf( tmp, "%.*d", count > 2 ? 2 : count, xtime->wYear % 100 ); dwVal = lpTime->wYear;
}
else
{
count = count > 2 ? 2 : count;
dwVal = lpTime->wYear % 100;
} }
MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); szAdd = buff;
break; break;
case 'g': case 'g':
if (count == 2) { if (count == 2)
FIXME("LOCALE_ICALENDARTYPE unimplemented\n"); {
strcpy( tmp, "AD" ); /* FIXME: Our GetCalendarInfo() does not yet support CAL_SERASTRING.
} else { * When it is fixed, this string should be cached in 'node'.
/* Win API sez we copy it verbatim */ */
strcpy( tmp, "g" ); FIXME("Should be using GetCalendarInfo(CAL_SERASTRING), defaulting to 'AD'\n");
} buff[0] = 'A'; buff[1] = 'D'; buff[2] = '\0';
MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); }
else
{
buff[0] = 'g'; buff[1] = '\0'; /* Add a literal 'g' */
}
szAdd = buff;
break; break;
case 'h': case 'h':
/* fallthrough if we are forced to output in 24 hour format */ if (!(dwFlags & TIME_FORCE24HOURFORMAT))
if(!(tflags & TIME_FORCE24HOURFORMAT)) {
{ count = count > 2 ? 2 : count;
/* hours 1:00-12:00 --- is this right? */ dwVal = lpTime->wHour == 0 ? 12 : (lpTime->wHour - 1) % 12 + 1;
/* NOTE: 0000 hours is also 12 midnight */ szAdd = buff;
sprintf( tmp, "%.*d", count > 2 ? 2 : count,
xtime->wHour == 0 ? 12 : (xtime->wHour-1)%12 +1);
MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) );
break; break;
} }
/* .. fall through if we are forced to output in 24 hour format */
case 'H': case 'H':
sprintf( tmp, "%.*d", count > 2 ? 2 : count, xtime->wHour ); count = count > 2 ? 2 : count;
MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); dwVal = lpTime->wHour;
szAdd = buff;
break; break;
case 'm': case 'm':
/* if TIME_NOMINUTESORSECONDS don't display minutes */ if (dwFlags & TIME_NOMINUTESORSECONDS)
if(!(tflags & TIME_NOMINUTESORSECONDS))
{ {
sprintf( tmp, "%.*d", count > 2 ? 2 : count, xtime->wMinute ); cchWritten = lastFormatPos; /* Skip */
MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); bSkipping = TRUE;
} else }
else
{ {
outpos = lastFormatPos; count = count > 2 ? 2 : count;
dwVal = lpTime->wMinute;
szAdd = buff;
} }
break; break;
case 's': case 's':
/* if we have a TIME_NOSECONDS or TIME_NOMINUTESORSECONDS if (dwFlags & (TIME_NOSECONDS|TIME_NOMINUTESORSECONDS))
flag then don't display seconds */
if(!(tflags & TIME_NOSECONDS) && !(tflags &
TIME_NOMINUTESORSECONDS))
{ {
sprintf( tmp, "%.*d", count > 2 ? 2 : count, xtime->wSecond ); cchWritten = lastFormatPos; /* Skip */
MultiByteToWideChar( CP_ACP, 0, tmp, -1, buf, sizeof(buf)/sizeof(WCHAR) ); bSkipping = TRUE;
} else }
else
{ {
outpos = lastFormatPos; count = count > 2 ? 2 : count;
dwVal = lpTime->wSecond;
szAdd = buff;
} }
break; break;
case 't': case 't':
if(!(tflags & TIME_NOTIMEMARKER)) if (dwFlags & TIME_NOTIMEMARKER)
{
GetLocaleInfoW(locale, (xtime->wHour < 12) ?
LOCALE_S1159 : LOCALE_S2359,
buf, sizeof(buf) );
if (count == 1)
{ {
buf[1] = 0; cchWritten = lastFormatPos; /* Skip */
bSkipping = TRUE;
} }
} else else
{ {
outpos = lastFormatPos; /* remove any prior text up until if (count == 1)
the output due to formatting characters */ szAdd = lpTime->wHour < 12 ? node->szShortAM : node->szShortPM;
dropUntilNextFormattingChar = TRUE; /* drop everything else
until we hit the next formatting character */ szAdd = lpTime->wHour < 12 ? GetAM(node) : GetPM(node);
} }
break; break;
} }
/* cat buf onto the output */ if (szAdd == buff && buff[0] == '\0')
buflen = strlenW(buf); {
/* We have a numeric value to add */
/* we are counting how many characters we need for output */ sprintf(buffA, "%.*ld", count, dwVal);
/* don't modify the output buffer... */ MultiByteToWideChar(CP_ACP, 0, buffA, -1, buff, sizeof(buff)/sizeof(WCHAR));
if (!outlen)
/* We are counting */;
else if (outpos + buflen < outlen) {
strcpyW( output + outpos, buf );
} else {
lstrcpynW( output + outpos, buf, outlen - outpos );
/* Is this an undocumented feature we are supporting? */
goto too_short;
} }
outpos += buflen;
lastFormatPos = outpos; /* record the end of the formatting text we just output */ dwLen = szAdd ? strlenW(szAdd) : 0;
} else /* we are processing a NON formatting character */
if (cchOut && dwLen)
{ {
/* a literal character */ if (cchWritten + dwLen < cchOut)
if (!outlen) memcpy(lpStr + cchWritten, szAdd, dwLen * sizeof(WCHAR));
else
{ {
outpos++; /* We are counting */; memcpy(lpStr + cchWritten, szAdd, (cchOut - cchWritten) * sizeof(WCHAR));
goto NLS_GetDateTimeFormatW_Overrun;
} }
else if (outpos >= outlen)
{
goto too_short;
} }
else /* just copy the character into the output buffer */ cchWritten += dwLen;
lastFormatPos = cchWritten; /* Save position of last output format text */
}
else
{ {
/* unless we are dropping characters */ /* Literal character */
if(!dropUntilNextFormattingChar) if (!cchOut)
cchWritten++; /* Count size only */
else if (cchWritten >= cchOut)
goto NLS_GetDateTimeFormatW_Overrun;
else if (!bSkipping || *lpFormat == ' ')
{ {
output[outpos] = *format; lpStr[cchWritten] = *lpFormat;
outpos++; cchWritten++;
} }
} lpFormat++;
format++;
} }
} }
/* final string terminator and sanity check */ /* Final string terminator and sanity check */
if (!outlen) if (cchOut)
/* We are counting */; {
else if (outpos >= outlen) if (cchWritten >= cchOut)
goto too_short; goto NLS_GetDateTimeFormatW_Overrun;
else else
output[outpos] = '\0'; lpStr[cchWritten] = '\0';
}
outpos++; /* add one for the terminating null character */ cchWritten++; /* Include terminating NUL */
TRACE(" returning %d %s\n", outpos, debugstr_w(output)); TRACE("returning length=%d, ouput='%s'\n", cchWritten, debugstr_w(lpStr));
return outpos; return cchWritten;
too_short: NLS_GetDateTimeFormatW_Overrun:
TRACE("returning 0, (ERROR_INSUFFICIENT_BUFFER)\n");
SetLastError(ERROR_INSUFFICIENT_BUFFER); SetLastError(ERROR_INSUFFICIENT_BUFFER);
WARN(" buffer overflow\n");
return 0; return 0;
} }
/****************************************************************************** /******************************************************************************
* GetDateFormatA [KERNEL32.@] * NLS_GetDateTimeFormatA <internal>
* Makes an ASCII string of the date
* *
* Acts the same as GetDateFormatW(), except that it's ASCII. * ASCII wrapper for GetDateFormatA/GetTimeFormatA.
*/ */
INT WINAPI GetDateFormatA( LCID locale, DWORD flags, const SYSTEMTIME* xtime, static INT WINAPI NLS_GetDateTimeFormatA(LCID lcid, DWORD dwFlags,
LPCSTR format, LPSTR date, INT datelen ) const SYSTEMTIME* lpTime,
LPCSTR lpFormat, LPSTR lpStr, INT cchOut)
{ {
INT ret; DWORD cp = CP_ACP;
LPWSTR wformat = NULL; WCHAR szFormat[128], szOut[128];
LPWSTR wdate = NULL; INT iRet;
if (format) TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
{ debugstr_a(lpFormat), lpStr, cchOut);
wformat = HeapAlloc(GetProcessHeap(), 0,
(strlen(format) + 1) * sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, 0, format, -1, wformat, strlen(format) + 1); if (NLS_IsUnicodeOnlyLcid(lcid))
{
GetDateTimeFormatA_InvalidParameter:
SetLastError(ERROR_INVALID_PARAMETER);
return 0;
} }
if (date && datelen) if (!(dwFlags & LOCALE_USE_CP_ACP))
{ {
wdate = HeapAlloc(GetProcessHeap(), 0, const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
(datelen + 1) * sizeof(wchar_t)); if (!node)
goto GetDateTimeFormatA_InvalidParameter;
cp = node->dwCodePage;
} }
ret = GetDateFormatW(locale, flags, xtime, wformat, wdate, datelen); if (lpFormat)
MultiByteToWideChar(cp, 0, lpFormat, -1, szFormat, sizeof(szFormat)/sizeof(WCHAR));
if (wdate) if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
{ cchOut = sizeof(szOut)/sizeof(WCHAR);
WideCharToMultiByte(CP_ACP, 0, wdate, ret, date, datelen, NULL, NULL);
HeapFree(GetProcessHeap(), 0, wdate); szOut[0] = '\0';
}
iRet = NLS_GetDateTimeFormatW(lcid, dwFlags, lpTime, lpFormat ? szFormat : NULL,
lpStr ? szOut : NULL, cchOut);
if (wformat) if (lpStr)
{ {
HeapFree(GetProcessHeap(), 0, wformat); if (szOut[0])
WideCharToMultiByte(cp, 0, szOut, -1, lpStr, cchOut, 0, 0);
else if (cchOut && iRet)
*lpStr = '\0';
} }
return iRet;
}
/******************************************************************************
* GetDateFormatA [KERNEL32.@]
*
* Format a date for a given locale.
*
* PARAMS
* lcid [I] Locale to format for
* dwFlags [I] LOCALE_ and DATE_ flags from "winnls.h"
* lpTime [I] Date to format
* lpFormat [I] Format string, or NULL to use the system defaults
* lpDateStr [O] Destination for formatted string
* cchOut [I] Size of lpDateStr, or 0 to calculate the resulting size
*
* NOTES
* - If lpFormat is NULL, lpszValue will be formatted according to the format
* details returned by GetLocaleInfoA() and modified by dwFlags.
* - lpFormat is a string of characters and formatting tokens. Any characters
* in the string are copied verbatim to lpDateStr, with tokens being replaced
* by the date values they represent.
* - The following tokens have special meanings in a date format string:
*| Token Meaning
*| ----- -------
*| d Single digit day of the month (no leading 0)
*| dd Double digit day of the month
*| ddd Short name for the day of the week
*| dddd Long name for the day of the week
*| M Single digit month of the year (no leading 0)
*| MM Double digit month of the year
*| MMM Short name for the month of the year
*| MMMM Long name for the month of the year
*| y Double digit year number (no leading 0)
*| yy Double digit year number
*| yyyy Four digit year number
*| gg Era string, for example 'AD'.
* - To output any literal character that could be misidentified as a token,
* enclose it in single quotes.
* - The Ascii version of this function fails if lcid is Unicode only.
*
* RETURNS
* Success: The number of character written to lpDateStr, or that would
* have been written, if cchOut is 0.
* Failure: 0. Use GetLastError() to determine the cause.
*/
INT WINAPI GetDateFormatA( LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
LPCSTR lpFormat, LPSTR lpDateStr, INT cchOut)
{
TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
debugstr_a(lpFormat), lpDateStr, cchOut);
return ret; return NLS_GetDateTimeFormatA(lcid, dwFlags | DATE_DATEVARSONLY, lpTime,
lpFormat, lpDateStr, cchOut);
} }
/****************************************************************************** /******************************************************************************
* GetDateFormatW [KERNEL32.@] * GetDateFormatW [KERNEL32.@]
* Makes a Unicode string of the date
* *
* This function uses format to format the date, or, if format * See GetDateFormatA.
* is NULL, uses the default for the locale. format is a string */
* of literal fields and characters as follows: INT WINAPI GetDateFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
LPCWSTR lpFormat, LPWSTR lpDateStr, INT cchOut)
{
TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n", lcid, dwFlags, lpTime,
debugstr_w(lpFormat), lpDateStr, cchOut);
return NLS_GetDateTimeFormatW(lcid, dwFlags|DATE_DATEVARSONLY, lpTime,
lpFormat, lpDateStr, cchOut);
}
/******************************************************************************
* GetTimeFormatA [KERNEL32.@]
*
* Format a time for a given locale.
* *
* - d single-digit (no leading zero) day (of month) * PARAMS
* - dd two-digit day (of month) * lcid [I] Locale to format for
* - ddd short day-of-week name * dwFlags [I] LOCALE_ and TIME_ flags from "winnls.h"
* - dddd long day-of-week name * lpTime [I] Time to format
* - M single-digit month * lpFormat [I] Formatting overrides
* - MM two-digit month * lpTimeStr [O] Destination for formatted string
* - MMM short month name * cchOut [I] Size of lpTimeStr, or 0 to calculate the resulting size
* - MMMM full month name
* - y two-digit year, no leading 0
* - yy two-digit year
* - yyyy four-digit year
* - gg era string
* *
* Accepts & returns sizes as counts of Unicode characters. * NOTES
* - If lpFormat is NULL, lpszValue will be formatted according to the format
* details returned by GetLocaleInfoA() and modified by dwFlags.
* - lpFormat is a string of characters and formatting tokens. Any characters
* in the string are copied verbatim to lpTimeStr, with tokens being replaced
* by the time values they represent.
* - The following tokens have special meanings in a time format string:
*| Token Meaning
*| ----- -------
*| h Hours with no leading zero (12-hour clock)
*| hh Hours with full two digits (12-hour clock)
*| H Hours with no leading zero (24-hour clock)
*| HH Hours with full two digits (24-hour clock)
*| m Minutes with no leading zero
*| mm Minutes with full two digits
*| s Seconds with no leading zero
*| ss Seconds with full two digits
*| t Short time marker (e.g. "A" or "P")
*| tt Long time marker (e.g. "AM", "PM")
* - To output any literal character that could be misidentified as a token,
* enclose it in single quotes.
* - The Ascii version of this function fails if lcid is Unicode only.
*
* RETURNS
* Success: The number of character written to lpTimeStr, or that would
* have been written, if cchOut is 0.
* Failure: 0. Use GetLastError() to determine the cause.
*/ */
INT WINAPI GetDateFormatW( LCID locale, DWORD flags, const SYSTEMTIME* xtime, INT WINAPI GetTimeFormatA(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
LPCWSTR format, LPWSTR date, INT datelen) LPCSTR lpFormat, LPSTR lpTimeStr, INT cchOut)
{ {
WCHAR format_buf[40]; TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
LPCWSTR thisformat; debugstr_a(lpFormat), lpTimeStr, cchOut);
SYSTEMTIME t;
LCID thislocale;
INT ret;
FILETIME ft;
BOOL res;
TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n", return NLS_GetDateTimeFormatA(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
locale,flags,xtime,debugstr_w(format),date,datelen); lpFormat, lpTimeStr, cchOut);
}
/* Tests */ /******************************************************************************
if (flags && format) /* if lpFormat is non-null then flags must be zero */ * GetTimeFormatW [KERNEL32.@]
{ *
SetLastError (ERROR_INVALID_FLAGS); * See GetTimeFormatA.
return 0; */
} INT WINAPI GetTimeFormatW(LCID lcid, DWORD dwFlags, const SYSTEMTIME* lpTime,
if (datelen && !date) LPCWSTR lpFormat, LPWSTR lpTimeStr, INT cchOut)
{ {
SetLastError (ERROR_INVALID_PARAMETER); TRACE("(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",lcid, dwFlags, lpTime,
return 0; debugstr_w(lpFormat), lpTimeStr, cchOut);
}
if (!locale)
{
locale = LOCALE_SYSTEM_DEFAULT;
}
if (locale == LOCALE_SYSTEM_DEFAULT) return NLS_GetDateTimeFormatW(lcid, dwFlags|TIME_TIMEVARSONLY, lpTime,
{ lpFormat, lpTimeStr, cchOut);
thislocale = GetSystemDefaultLCID(); }
} else if (locale == LOCALE_USER_DEFAULT)
{ /**************************************************************************
thislocale = GetUserDefaultLCID(); * GetNumberFormatA (KERNEL32.@)
} else *
{ * Format a number string for a given locale.
thislocale = locale; *
} * PARAMS
* lcid [I] Locale to format for
* dwFlags [I] LOCALE_ flags from "winnls.h"
* lpszValue [I] String to format
* lpFormat [I] Formatting overrides
* lpNumberStr [O] Destination for formatted string
* cchOut [I] Size of lpNumberStr, or 0 to calculate the resulting size
*
* NOTES
* - lpszValue can contain only '0' - '9', '-' and '.'.
* - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
* be formatted according to the format details returned by GetLocaleInfoA().
* - This function rounds the number string if the number of decimals exceeds the
* locales normal number of decimal places.
* - If cchOut is 0, this function does not write to lpNumberStr.
* - The Ascii version of this function fails if lcid is Unicode only.
*
* RETURNS
* Success: The number of character written to lpNumberStr, or that would
* have been written, if cchOut is 0.
* Failure: 0. Use GetLastError() to determine the cause.
*/
INT WINAPI GetNumberFormatA(LCID lcid, DWORD dwFlags,
LPCSTR lpszValue, const NUMBERFMTA *lpFormat,
LPSTR lpNumberStr, int cchOut)
{
DWORD cp = CP_ACP;
WCHAR szDec[8], szGrp[8], szIn[128], szOut[128];
NUMBERFMTW fmt;
const NUMBERFMTW *pfmt = NULL;
INT iRet;
TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
lpFormat, lpNumberStr, cchOut);
/* check for invalid flag combinations */ if (NLS_IsUnicodeOnlyLcid(lcid))
if((flags & DATE_LTRREADING) && (flags & DATE_RTLREADING))
{ {
SetLastError (ERROR_INVALID_FLAGS); GetNumberFormatA_InvalidParameter:
SetLastError(ERROR_INVALID_PARAMETER);
return 0; return 0;
} }
/* DATE_SHORTDATE, DATE_LONGDATE and DATE_YEARMONTH are mutually */ if (!(dwFlags & LOCALE_USE_CP_ACP))
/* exclusive */
if((flags & (DATE_SHORTDATE|DATE_LONGDATE|DATE_YEARMONTH))
&& !((flags & DATE_SHORTDATE) ^ (flags &
DATE_LONGDATE) ^ (flags & DATE_YEARMONTH)))
{ {
SetLastError (ERROR_INVALID_FLAGS); const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
return 0; if (!node)
goto GetNumberFormatA_InvalidParameter;
cp = node->dwCodePage;
} }
/* if the user didn't pass in a pointer to the current time we read it */ if (lpFormat)
/* here */
if (xtime == NULL)
{ {
GetSystemTime(&t); memcpy(&fmt, lpFormat, sizeof(fmt));
} else pfmt = &fmt;
if (lpFormat->lpDecimalSep)
{ {
/* NOTE: check here before we perform the SystemTimeToFileTime conversion */ MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
/* because this conversion will fix invalid time values */ fmt.lpDecimalSep = szDec;
/* check to see if the time/date is valid */
/* set ERROR_INVALID_PARAMETER and return 0 if invalid */
if((xtime->wDay > 31) || (xtime->wMonth > 12))
{
SetLastError(ERROR_INVALID_PARAMETER);
return 0;
} }
/* For all we know the day of week and the time may be absolute if (lpFormat->lpThousandSep)
* rubbish. Therefore if we are going to use conversion through
* FileTime we had better use a clean time (and hopefully we won't
* fall over any timezone complications).
* If we go with an alternative method of correcting the day of week
* (e.g. Zeller's congruence) then we won't need to, but we will need
* to check the date.
*/
memset (&t, 0, sizeof(t));
t.wYear = xtime->wYear;
t.wMonth = xtime->wMonth;
t.wDay = xtime->wDay;
/* Silently correct wDayOfWeek by transforming to FileTime and back again */
res=SystemTimeToFileTime(&t,&ft);
/* Check year(?)/month and date for range and set ERROR_INVALID_PARAMETER on error */
if(!res)
{ {
SetLastError(ERROR_INVALID_PARAMETER); MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
return 0; fmt.lpThousandSep = szGrp;
} }
FileTimeToSystemTime(&ft,&t);
} }
if (format == NULL) if (lpszValue)
{ MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
GetLocaleInfoW(thislocale, ((flags&DATE_LONGDATE)
? LOCALE_SLONGDATE if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
: LOCALE_SSHORTDATE), cchOut = sizeof(szOut)/sizeof(WCHAR);
format_buf, sizeof(format_buf)/sizeof(*format_buf));
thisformat = format_buf; szOut[0] = '\0';
} else
{ iRet = GetNumberFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
thisformat = format; lpNumberStr ? szOut : NULL, cchOut);
if (szOut[0] && lpNumberStr)
WideCharToMultiByte(cp, 0, szOut, -1, lpNumberStr, cchOut, 0, 0);
return iRet;
}
/* Number parsing state flags */
#define NF_ISNEGATIVE 0x1 /* '-' found */
#define NF_ISREAL 0x2 /* '.' found */
#define NF_DIGITS 0x4 /* '0'-'9' found */
#define NF_DIGITS_OUT 0x8 /* Digits before the '.' found */
#define NF_ROUND 0x10 /* Number needs to be rounded */
/* Formatting options for Numbers */
#define NLS_NEG_PARENS 0 /* "(1.1)" */
#define NLS_NEG_LEFT 1 /* "-1.1" */
#define NLS_NEG_LEFT_SPACE 2 /* "- 1.1" */
#define NLS_NEG_RIGHT 3 /* "1.1-" */
#define NLS_NEG_RIGHT_SPACE 4 /* "1.1 -" */
/**************************************************************************
* GetNumberFormatW (KERNEL32.@)
*
* See GetNumberFormatA.
*/
INT WINAPI GetNumberFormatW(LCID lcid, DWORD dwFlags,
LPCWSTR lpszValue, const NUMBERFMTW *lpFormat,
LPWSTR lpNumberStr, int cchOut)
{
WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
WCHAR szNegBuff[8];
const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc;
DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0;
INT iRet;
TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
lpFormat, lpNumberStr, cchOut);
if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpNumberStr) ||
!IsValidLocale(lcid, 0) ||
(lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep)))
{
GetNumberFormatW_Error:
SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
return 0;
}
if (!lpFormat)
{
const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
if (!node)
goto GetNumberFormatW_Error;
lpFormat = &node->fmt;
lpszNegStart = lpszNeg = GetNegative(node);
}
else
{
GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
lpszNegStart = lpszNeg = szNegBuff;
}
lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
/* Format the number backwards into a temporary buffer */
szSrc = lpszValue;
*szOut-- = '\0';
/* Check the number for validity */
while (*szSrc)
{
if (*szSrc >= '0' && *szSrc <= '9')
{
dwState |= NF_DIGITS;
if (dwState & NF_ISREAL)
dwDecimals++;
}
else if (*szSrc == '-')
{
if (dwState)
goto GetNumberFormatW_Error; /* '-' not first character */
dwState |= NF_ISNEGATIVE;
}
else if (*szSrc == '.')
{
if (dwState & NF_ISREAL)
goto GetNumberFormatW_Error; /* More than one '.' */
dwState |= NF_ISREAL;
}
else
goto GetNumberFormatW_Error; /* Invalid char */
szSrc++;
}
szSrc--; /* Point to last character */
if (!(dwState & NF_DIGITS))
goto GetNumberFormatW_Error; /* No digits */
/* Add any trailing negative sign */
if (dwState & NF_ISNEGATIVE)
{
switch (lpFormat->NegativeOrder)
{
case NLS_NEG_PARENS:
*szOut-- = ')';
break;
case NLS_NEG_RIGHT:
case NLS_NEG_RIGHT_SPACE:
while (lpszNeg >= lpszNegStart)
*szOut-- = *lpszNeg--;
if (lpFormat->NegativeOrder == NLS_NEG_RIGHT_SPACE)
*szOut-- = ' ';
break;
}
}
/* Copy all digits up to the decimal point */
if (!lpFormat->NumDigits)
{
if (dwState & NF_ISREAL)
{
while (*szSrc != '.') /* Don't write any decimals or a seperator */
{
if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
dwState |= NF_ROUND;
else
dwState &= ~NF_ROUND;
szSrc--;
}
szSrc--;
}
}
else
{
LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
if (dwDecimals <= lpFormat->NumDigits)
{
dwDecimals = lpFormat->NumDigits - dwDecimals;
while (dwDecimals--)
*szOut-- = '0'; /* Pad to correct number of dp */
}
else
{
dwDecimals -= lpFormat->NumDigits;
/* Skip excess decimals, and determine if we have to round the number */
while (dwDecimals--)
{
if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
dwState |= NF_ROUND;
else
dwState &= ~NF_ROUND;
szSrc--;
}
}
if (dwState & NF_ISREAL)
{
while (*szSrc != '.')
{
if (dwState & NF_ROUND)
{
if (*szSrc == '9')
*szOut-- = '0'; /* continue rounding */
else
{
dwState &= ~NF_ROUND;
*szOut-- = (*szSrc)+1;
}
szSrc--;
}
else
*szOut-- = *szSrc--; /* Write existing decimals */
}
szSrc--; /* Skip '.' */
}
while (lpszDec >= lpFormat->lpDecimalSep)
*szOut-- = *lpszDec--; /* Write decimal seperator */
}
dwGroupCount = lpFormat->Grouping == 32 ? 3 : lpFormat->Grouping;
/* Write the remaining whole number digits, including grouping chars */
while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
{
if (dwState & NF_ROUND)
{
if (*szSrc == '9')
*szOut-- = '0'; /* continue rounding */
else
{
dwState &= ~NF_ROUND;
*szOut-- = (*szSrc)+1;
}
szSrc--;
}
else
*szOut-- = *szSrc--;
dwState |= NF_DIGITS_OUT;
dwCurrentGroupCount++;
if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount)
{
LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
while (lpszGrp >= lpFormat->lpThousandSep)
*szOut-- = *lpszGrp--; /* Write grouping char */
dwCurrentGroupCount = 0;
if (lpFormat->Grouping == 32)
dwGroupCount = 2; /* Indic grouping: 3 then 2 */
}
}
if (dwState & NF_ROUND)
{
*szOut-- = '1'; /* e.g. .6 > 1.0 */
}
else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
*szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
/* Add any leading negative sign */
if (dwState & NF_ISNEGATIVE)
{
switch (lpFormat->NegativeOrder)
{
case NLS_NEG_PARENS:
*szOut-- = '(';
break;
case NLS_NEG_LEFT_SPACE:
*szOut-- = ' ';
/* Fall through */
case NLS_NEG_LEFT:
while (lpszNeg >= lpszNegStart)
*szOut-- = *lpszNeg--;
break;
}
}
szOut++;
iRet = strlenW(szOut) + 1;
if (cchOut)
{
if (iRet <= cchOut)
memcpy(lpNumberStr, szOut, iRet * sizeof(WCHAR));
else
{
memcpy(lpNumberStr, szOut, cchOut * sizeof(WCHAR));
lpNumberStr[cchOut - 1] = '\0';
SetLastError(ERROR_INSUFFICIENT_BUFFER);
iRet = 0;
}
}
return iRet;
}
/**************************************************************************
* GetCurrencyFormatA (KERNEL32.@)
*
* Format a currency string for a given locale.
*
* PARAMS
* lcid [I] Locale to format for
* dwFlags [I] LOCALE_ flags from "winnls.h"
* lpszValue [I] String to format
* lpFormat [I] Formatting overrides
* lpCurrencyStr [O] Destination for formatted string
* cchOut [I] Size of lpCurrencyStr, or 0 to calculate the resulting size
*
* NOTES
* - lpszValue can contain only '0' - '9', '-' and '.'.
* - If lpFormat is non-NULL, dwFlags must be 0. In this case lpszValue will
* be formatted according to the format details returned by GetLocaleInfoA().
* - This function rounds the currency if the number of decimals exceeds the
* locales number of currency decimal places.
* - If cchOut is 0, this function does not write to lpCurrencyStr.
* - The Ascii version of this function fails if lcid is Unicode only.
*
* RETURNS
* Success: The number of character written to lpNumberStr, or that would
* have been written, if cchOut is 0.
* Failure: 0. Use GetLastError() to determine the cause.
*/
INT WINAPI GetCurrencyFormatA(LCID lcid, DWORD dwFlags,
LPCSTR lpszValue, const CURRENCYFMTA *lpFormat,
LPSTR lpCurrencyStr, int cchOut)
{
DWORD cp = CP_ACP;
WCHAR szDec[8], szGrp[8], szCy[8], szIn[128], szOut[128];
CURRENCYFMTW fmt;
const CURRENCYFMTW *pfmt = NULL;
INT iRet;
TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_a(lpszValue),
lpFormat, lpCurrencyStr, cchOut);
if (NLS_IsUnicodeOnlyLcid(lcid))
{
GetCurrencyFormatA_InvalidParameter:
SetLastError(ERROR_INVALID_PARAMETER);
return 0;
}
if (!(dwFlags & LOCALE_USE_CP_ACP))
{
const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
if (!node)
goto GetCurrencyFormatA_InvalidParameter;
cp = node->dwCodePage;
}
if (lpFormat)
{
memcpy(&fmt, lpFormat, sizeof(fmt));
pfmt = &fmt;
if (lpFormat->lpDecimalSep)
{
MultiByteToWideChar(cp, 0, lpFormat->lpDecimalSep, -1, szDec, sizeof(szDec)/sizeof(WCHAR));
fmt.lpDecimalSep = szDec;
}
if (lpFormat->lpThousandSep)
{
MultiByteToWideChar(cp, 0, lpFormat->lpThousandSep, -1, szGrp, sizeof(szGrp)/sizeof(WCHAR));
fmt.lpThousandSep = szGrp;
}
if (lpFormat->lpCurrencySymbol)
{
MultiByteToWideChar(cp, 0, lpFormat->lpCurrencySymbol, -1, szCy, sizeof(szCy)/sizeof(WCHAR));
fmt.lpCurrencySymbol = szCy;
}
}
if (lpszValue)
MultiByteToWideChar(cp, 0, lpszValue, -1, szIn, sizeof(szIn)/sizeof(WCHAR));
if (cchOut > (int)(sizeof(szOut)/sizeof(WCHAR)))
cchOut = sizeof(szOut)/sizeof(WCHAR);
szOut[0] = '\0';
iRet = GetCurrencyFormatW(lcid, dwFlags, lpszValue ? szIn : NULL, pfmt,
lpCurrencyStr ? szOut : NULL, cchOut);
if (szOut[0] && lpCurrencyStr)
WideCharToMultiByte(cp, 0, szOut, -1, lpCurrencyStr, cchOut, 0, 0);
return iRet;
}
/* Formatting states for Currencies. We use flags to avoid code duplication. */
#define CF_PARENS 0x1 /* Parentheses */
#define CF_MINUS_LEFT 0x2 /* '-' to the left */
#define CF_MINUS_RIGHT 0x4 /* '-' to the right */
#define CF_MINUS_BEFORE 0x8 /* '-' before '$' */
#define CF_CY_LEFT 0x10 /* '$' to the left */
#define CF_CY_RIGHT 0x20 /* '$' to the right */
#define CF_CY_SPACE 0x40 /* ' ' by '$' */
/**************************************************************************
* GetCurrencyFormatW (KERNEL32.@)
*
* See GetCurrencyFormatA.
*/
INT WINAPI GetCurrencyFormatW(LCID lcid, DWORD dwFlags,
LPCWSTR lpszValue, const CURRENCYFMTW *lpFormat,
LPWSTR lpCurrencyStr, int cchOut)
{
static const BYTE NLS_NegCyFormats[16] =
{
CF_PARENS|CF_CY_LEFT, /* ($1.1) */
CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT, /* -$1.1 */
CF_MINUS_LEFT|CF_CY_LEFT, /* $-1.1 */
CF_MINUS_RIGHT|CF_CY_LEFT, /* $1.1- */
CF_PARENS|CF_CY_RIGHT, /* (1.1$) */
CF_MINUS_LEFT|CF_CY_RIGHT, /* -1.1$ */
CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT, /* 1.1-$ */
CF_MINUS_RIGHT|CF_CY_RIGHT, /* 1.1$- */
CF_MINUS_LEFT|CF_CY_RIGHT|CF_CY_SPACE, /* -1.1 $ */
CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT|CF_CY_SPACE, /* -$ 1.1 */
CF_MINUS_RIGHT|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $- */
CF_MINUS_RIGHT|CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1- */
CF_MINUS_LEFT|CF_CY_LEFT|CF_CY_SPACE, /* $ -1.1 */
CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT|CF_CY_SPACE, /* 1.1- $ */
CF_PARENS|CF_CY_LEFT|CF_CY_SPACE, /* ($ 1.1) */
CF_PARENS|CF_CY_RIGHT|CF_CY_SPACE, /* (1.1 $) */
};
static const BYTE NLS_PosCyFormats[4] =
{
CF_CY_LEFT, /* $1.1 */
CF_CY_RIGHT, /* 1.1$ */
CF_CY_LEFT|CF_CY_SPACE, /* $ 1.1 */
CF_CY_RIGHT|CF_CY_SPACE, /* 1.1 $ */
};
WCHAR szBuff[128], *szOut = szBuff + sizeof(szBuff) / sizeof(WCHAR) - 1;
WCHAR szNegBuff[8];
const WCHAR *lpszNeg = NULL, *lpszNegStart, *szSrc, *lpszCy, *lpszCyStart;
DWORD dwState = 0, dwDecimals = 0, dwGroupCount = 0, dwCurrentGroupCount = 0, dwFmt;
INT iRet;
TRACE("(0x%04lx,0x%08lx,%s,%p,%p,%d)\n", lcid, dwFlags, debugstr_w(lpszValue),
lpFormat, lpCurrencyStr, cchOut);
if (!lpszValue || cchOut < 0 || (cchOut > 0 && !lpCurrencyStr) ||
!IsValidLocale(lcid, 0) ||
(lpFormat && (dwFlags || !lpFormat->lpDecimalSep || !lpFormat->lpThousandSep ||
!lpFormat->lpCurrencySymbol || lpFormat->NegativeOrder > 15 ||
lpFormat->PositiveOrder > 3)))
{
GetCurrencyFormatW_Error:
SetLastError(lpFormat && dwFlags ? ERROR_INVALID_FLAGS : ERROR_INVALID_PARAMETER);
return 0;
}
if (!lpFormat)
{
const NLS_FORMAT_NODE *node = NLS_GetFormats(lcid, dwFlags);
if (!node)
goto GetCurrencyFormatW_Error;
lpFormat = &node->cyfmt;
lpszNegStart = lpszNeg = GetNegative(node);
}
else
{
GetLocaleInfoW(lcid, LOCALE_SNEGATIVESIGN|(dwFlags & LOCALE_NOUSEROVERRIDE),
szNegBuff, sizeof(szNegBuff)/sizeof(WCHAR));
lpszNegStart = lpszNeg = szNegBuff;
}
dwFlags &= (LOCALE_NOUSEROVERRIDE|LOCALE_USE_CP_ACP);
lpszNeg = lpszNeg + strlenW(lpszNeg) - 1;
lpszCyStart = lpFormat->lpCurrencySymbol;
lpszCy = lpszCyStart + strlenW(lpszCyStart) - 1;
/* Format the currency backwards into a temporary buffer */
szSrc = lpszValue;
*szOut-- = '\0';
/* Check the number for validity */
while (*szSrc)
{
if (*szSrc >= '0' && *szSrc <= '9')
{
dwState |= NF_DIGITS;
if (dwState & NF_ISREAL)
dwDecimals++;
}
else if (*szSrc == '-')
{
if (dwState)
goto GetCurrencyFormatW_Error; /* '-' not first character */
dwState |= NF_ISNEGATIVE;
}
else if (*szSrc == '.')
{
if (dwState & NF_ISREAL)
goto GetCurrencyFormatW_Error; /* More than one '.' */
dwState |= NF_ISREAL;
}
else
goto GetCurrencyFormatW_Error; /* Invalid char */
szSrc++;
}
szSrc--; /* Point to last character */
if (!(dwState & NF_DIGITS))
goto GetCurrencyFormatW_Error; /* No digits */
if (dwState & NF_ISNEGATIVE)
dwFmt = NLS_NegCyFormats[lpFormat->NegativeOrder];
else
dwFmt = NLS_PosCyFormats[lpFormat->PositiveOrder];
/* Add any trailing negative or currency signs */
if (dwFmt & CF_PARENS)
*szOut-- = ')';
while (dwFmt & (CF_MINUS_RIGHT|CF_CY_RIGHT))
{
switch (dwFmt & (CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT))
{
case CF_MINUS_RIGHT:
case CF_MINUS_RIGHT|CF_CY_RIGHT:
while (lpszNeg >= lpszNegStart)
*szOut-- = *lpszNeg--;
dwFmt &= ~CF_MINUS_RIGHT;
break;
case CF_CY_RIGHT:
case CF_MINUS_BEFORE|CF_CY_RIGHT:
case CF_MINUS_RIGHT|CF_MINUS_BEFORE|CF_CY_RIGHT:
while (lpszCy >= lpszCyStart)
*szOut-- = *lpszCy--;
if (dwFmt & CF_CY_SPACE)
*szOut-- = ' ';
dwFmt &= ~(CF_CY_RIGHT|CF_MINUS_BEFORE);
break;
}
}
/* Copy all digits up to the decimal point */
if (!lpFormat->NumDigits)
{
if (dwState & NF_ISREAL)
{
while (*szSrc != '.') /* Don't write any decimals or a seperator */
{
if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
dwState |= NF_ROUND;
else
dwState &= ~NF_ROUND;
szSrc--;
}
szSrc--;
}
}
else
{
LPWSTR lpszDec = lpFormat->lpDecimalSep + strlenW(lpFormat->lpDecimalSep) - 1;
if (dwDecimals <= lpFormat->NumDigits)
{
dwDecimals = lpFormat->NumDigits - dwDecimals;
while (dwDecimals--)
*szOut-- = '0'; /* Pad to correct number of dp */
}
else
{
dwDecimals -= lpFormat->NumDigits;
/* Skip excess decimals, and determine if we have to round the number */
while (dwDecimals--)
{
if (*szSrc >= '5' || (*szSrc == '4' && (dwState & NF_ROUND)))
dwState |= NF_ROUND;
else
dwState &= ~NF_ROUND;
szSrc--;
}
}
if (dwState & NF_ISREAL)
{
while (*szSrc != '.')
{
if (dwState & NF_ROUND)
{
if (*szSrc == '9')
*szOut-- = '0'; /* continue rounding */
else
{
dwState &= ~NF_ROUND;
*szOut-- = (*szSrc)+1;
}
szSrc--;
}
else
*szOut-- = *szSrc--; /* Write existing decimals */
}
szSrc--; /* Skip '.' */
}
while (lpszDec >= lpFormat->lpDecimalSep)
*szOut-- = *lpszDec--; /* Write decimal seperator */
}
dwGroupCount = lpFormat->Grouping;
/* Write the remaining whole number digits, including grouping chars */
while (szSrc >= lpszValue && *szSrc >= '0' && *szSrc <= '9')
{
if (dwState & NF_ROUND)
{
if (*szSrc == '9')
*szOut-- = '0'; /* continue rounding */
else
{
dwState &= ~NF_ROUND;
*szOut-- = (*szSrc)+1;
}
szSrc--;
}
else
*szOut-- = *szSrc--;
dwState |= NF_DIGITS_OUT;
dwCurrentGroupCount++;
if (szSrc >= lpszValue && dwCurrentGroupCount == dwGroupCount)
{
LPWSTR lpszGrp = lpFormat->lpThousandSep + strlenW(lpFormat->lpThousandSep) - 1;
while (lpszGrp >= lpFormat->lpThousandSep)
*szOut-- = *lpszGrp--; /* Write grouping char */
dwCurrentGroupCount = 0;
} }
}
if (dwState & NF_ROUND)
*szOut-- = '1'; /* e.g. .6 > 1.0 */
else if (!(dwState & NF_DIGITS_OUT) && lpFormat->LeadingZero)
*szOut-- = '0'; /* Add leading 0 if we have no digits before the decimal point */
/* Add any leading negative or currency sign */
while (dwFmt & (CF_MINUS_LEFT|CF_CY_LEFT))
{
switch (dwFmt & (CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT))
{
case CF_MINUS_LEFT:
case CF_MINUS_LEFT|CF_CY_LEFT:
while (lpszNeg >= lpszNegStart)
*szOut-- = *lpszNeg--;
dwFmt &= ~CF_MINUS_LEFT;
break;
ret = get_date_time_formatW(thislocale, flags, 0, &t, thisformat, date, datelen, 1); case CF_CY_LEFT:
case CF_CY_LEFT|CF_MINUS_BEFORE:
case CF_MINUS_LEFT|CF_MINUS_BEFORE|CF_CY_LEFT:
if (dwFmt & CF_CY_SPACE)
*szOut-- = ' ';
while (lpszCy >= lpszCyStart)
*szOut-- = *lpszCy--;
dwFmt &= ~(CF_CY_LEFT|CF_MINUS_BEFORE);
break;
}
}
if (dwFmt & CF_PARENS)
*szOut-- = '(';
szOut++;
TRACE("GetDateFormatW() returning %d, with data=%s\n", iRet = strlenW(szOut) + 1;
ret, debugstr_w(date)); if (cchOut)
return ret; {
if (iRet <= cchOut)
memcpy(lpCurrencyStr, szOut, iRet * sizeof(WCHAR));
else
{
memcpy(lpCurrencyStr, szOut, cchOut * sizeof(WCHAR));
lpCurrencyStr[cchOut - 1] = '\0';
SetLastError(ERROR_INSUFFICIENT_BUFFER);
iRet = 0;
}
}
return iRet;
} }
/* FIXME: Everything below here needs to move somewhere else along with the
* other EnumXXX functions, when a method for storing resources for
* alternate calendars is determined.
*/
/************************************************************************** /**************************************************************************
* EnumDateFormatsA (KERNEL32.@) * EnumDateFormatsA (KERNEL32.@)
...@@ -896,772 +1934,6 @@ BOOL WINAPI EnumTimeFormatsW( TIMEFMT_ENUMPROCW lpTimeFmtEnumProc, LCID Locale, ...@@ -896,772 +1934,6 @@ BOOL WINAPI EnumTimeFormatsW( TIMEFMT_ENUMPROCW lpTimeFmtEnumProc, LCID Locale,
return FALSE; return FALSE;
} }
/**************************************************************************
* This function is used just locally !
* Description: Inverts a string.
*/
static void invert_string(char* string)
{
int i, j;
for (i = 0, j = strlen(string) - 1; i < j; i++, j--)
{
char ch = string[i];
string[i] = string[j];
string[j] = ch;
}
}
/***************************************************************************************
* This function is used just locally !
* Description: Test if the given string (psNumber) is valid or not.
* The valid characters are the following:
* - Characters '0' through '9'.
* - One decimal point (dot) if the number is a floating-point value.
* - A minus sign in the first character position if the number is
* a negative value.
* If the function succeeds, psBefore/psAfter will point to the string
* on the right/left of the decimal symbol. pbNegative indicates if the
* number is negative.
*/
static INT get_number_components(char* pInput, char* psBefore, char* psAfter, BOOL* pbNegative)
{
char sNumberSet[] = "0123456789";
BOOL bInDecimal = FALSE;
/* Test if we do have a minus sign */
if ( *pInput == '-' )
{
*pbNegative = TRUE;
pInput++; /* Jump to the next character. */
}
while(*pInput != '\0')
{
/* Do we have a valid numeric character */
if ( strchr(sNumberSet, *pInput) != NULL )
{
if (bInDecimal == TRUE)
*psAfter++ = *pInput;
else
*psBefore++ = *pInput;
}
else
{
/* Is this a decimal point (dot) */
if ( *pInput == '.' )
{
/* Is it the first time we find it */
if ((bInDecimal == FALSE))
bInDecimal = TRUE;
else
return -1; /* ERROR: Invalid parameter */
}
else
{
/* It's neither a numeric character, nor a decimal point.
* Thus, return an error.
*/
return -1;
}
}
pInput++;
}
/* Add an End of Line character to the output buffers */
*psBefore = '\0';
*psAfter = '\0';
return 0;
}
/**************************************************************************
* This function is used just locally !
* Description: A number could be formatted using different numbers
* of "digits in group" (example: 4;3;2;0).
* The first parameter of this function is an array
* containing the rule to be used. Its format is the following:
* |NDG|DG1|DG2|...|0|
* where NDG is the number of used "digits in group" and DG1, DG2,
* are the corresponding "digits in group".
* Thus, this function returns the grouping value in the array
* pointed by the second parameter.
*/
static INT get_grouping(char* sRule, INT index)
{
char sData[2], sRuleSize[2];
INT nData, nRuleSize;
memcpy(sRuleSize, sRule, 1);
memcpy(sRuleSize+1, "\0", 1);
nRuleSize = atoi(sRuleSize);
if (index > 0 && index < nRuleSize)
{
memcpy(sData, sRule+index, 1);
memcpy(sData+1, "\0", 1);
nData = atoi(sData);
}
else
{
memcpy(sData, sRule+nRuleSize-1, 1);
memcpy(sData+1, "\0", 1);
nData = atoi(sData);
}
return nData;
}
/**************************************************************************
* GetNumberFormatA (KERNEL32.@)
*/
INT WINAPI GetNumberFormatA(LCID locale, DWORD dwflags,
LPCSTR lpvalue, const NUMBERFMTA * lpFormat,
LPSTR lpNumberStr, int cchNumber)
{
char sNumberDigits[3], sDecimalSymbol[5], sDigitsInGroup[11], sDigitGroupSymbol[5], sILZero[2];
INT nNumberDigits, nNumberDecimal, i, j, nCounter, nStep, nRuleIndex, nGrouping, nDigits, retVal, nLZ;
char sNumber[128], sDestination[128], sDigitsAfterDecimal[10], sDigitsBeforeDecimal[128];
char sRule[10], sSemiColumn[]=";", sBuffer[5], sNegNumber[2];
char *pStr = NULL, *pTmpStr = NULL;
LCID systemDefaultLCID;
BOOL bNegative = FALSE;
enum Operations
{
USE_PARAMETER,
USE_LOCALEINFO,
USE_SYSTEMDEFAULT,
RETURN_ERROR
} used_operation;
strncpy(sNumber, lpvalue, 128);
sNumber[127] = '\0';
/* Make sure we have a valid input string, get the number
* of digits before and after the decimal symbol, and check
* if this is a negative number.
*/
if (get_number_components(sNumber, sDigitsBeforeDecimal, sDigitsAfterDecimal, &bNegative) != -1)
{
nNumberDecimal = strlen(sDigitsBeforeDecimal);
nDigits = strlen(sDigitsAfterDecimal);
}
else
{
SetLastError(ERROR_INVALID_PARAMETER);
return 0;
}
/* Which source will we use to format the string */
used_operation = RETURN_ERROR;
if (lpFormat != NULL)
{
if (dwflags == 0)
used_operation = USE_PARAMETER;
}
else
{
if (dwflags & LOCALE_NOUSEROVERRIDE)
used_operation = USE_SYSTEMDEFAULT;
else
used_operation = USE_LOCALEINFO;
}
/* Load the fields we need */
switch(used_operation)
{
case USE_LOCALEINFO:
GetLocaleInfoA(locale, LOCALE_IDIGITS, sNumberDigits, sizeof(sNumberDigits));
GetLocaleInfoA(locale, LOCALE_SDECIMAL, sDecimalSymbol, sizeof(sDecimalSymbol));
GetLocaleInfoA(locale, LOCALE_SGROUPING, sDigitsInGroup, sizeof(sDigitsInGroup));
GetLocaleInfoA(locale, LOCALE_STHOUSAND, sDigitGroupSymbol, sizeof(sDigitGroupSymbol));
GetLocaleInfoA(locale, LOCALE_ILZERO, sILZero, sizeof(sILZero));
GetLocaleInfoA(locale, LOCALE_INEGNUMBER, sNegNumber, sizeof(sNegNumber));
break;
case USE_PARAMETER:
sprintf(sNumberDigits, "%d",lpFormat->NumDigits);
strcpy(sDecimalSymbol, lpFormat->lpDecimalSep);
sprintf(sDigitsInGroup, "%d;0",lpFormat->Grouping);
/* Win95-WinME only allow 0-9 for grouping, no matter what MSDN says. */
strcpy(sDigitGroupSymbol, lpFormat->lpThousandSep);
sprintf(sILZero, "%d",lpFormat->LeadingZero);
sprintf(sNegNumber, "%d",lpFormat->NegativeOrder);
break;
case USE_SYSTEMDEFAULT:
systemDefaultLCID = GetSystemDefaultLCID();
GetLocaleInfoA(systemDefaultLCID, LOCALE_IDIGITS, sNumberDigits, sizeof(sNumberDigits));
GetLocaleInfoA(systemDefaultLCID, LOCALE_SDECIMAL, sDecimalSymbol, sizeof(sDecimalSymbol));
GetLocaleInfoA(systemDefaultLCID, LOCALE_SGROUPING, sDigitsInGroup, sizeof(sDigitsInGroup));
GetLocaleInfoA(systemDefaultLCID, LOCALE_STHOUSAND, sDigitGroupSymbol, sizeof(sDigitGroupSymbol));
GetLocaleInfoA(systemDefaultLCID, LOCALE_ILZERO, sILZero, sizeof(sILZero));
GetLocaleInfoA(systemDefaultLCID, LOCALE_INEGNUMBER, sNegNumber, sizeof(sNegNumber));
break;
default:
SetLastError(ERROR_INVALID_PARAMETER);
return 0;
}
nNumberDigits = atoi(sNumberDigits);
/* Remove the ";" */
i=0;
j = 1;
for (nCounter=0; nCounter<strlen(sDigitsInGroup); nCounter++)
{
if ( memcmp(sDigitsInGroup + nCounter, sSemiColumn, 1) != 0 )
{
memcpy(sRule + j, sDigitsInGroup + nCounter, 1);
i++;
j++;
}
}
sprintf(sBuffer, "%d", i);
memcpy(sRule, sBuffer, 1); /* Number of digits in the groups ( used by get_grouping() ) */
memcpy(sRule + j, "\0", 1);
/* First, format the digits before the decimal. */
if ((nNumberDecimal>0) && (atoi(sDigitsBeforeDecimal) != 0))
{
/* Working on an inverted string is easier ! */
invert_string(sDigitsBeforeDecimal);
nStep = nCounter = i = j = 0;
nRuleIndex = 1;
nGrouping = get_grouping(sRule, nRuleIndex);
if (nGrouping == 0) /* If the first grouping is zero */
nGrouping = nNumberDecimal; /* Don't do grouping */
/* Here, we will loop until we reach the end of the string.
* An internal counter (j) is used in order to know when to
* insert the "digit group symbol".
*/
while (nNumberDecimal > 0)
{
i = nCounter + nStep;
memcpy(sDestination + i, sDigitsBeforeDecimal + nCounter, 1);
nCounter++;
j++;
if (j >= nGrouping)
{
j = 0;
if (nRuleIndex < sRule[0])
nRuleIndex++;
nGrouping = get_grouping(sRule, nRuleIndex);
memcpy(sDestination + i+1, sDigitGroupSymbol, strlen(sDigitGroupSymbol));
nStep+= strlen(sDigitGroupSymbol);
}
nNumberDecimal--;
}
memcpy(sDestination + i+1, "\0", 1);
/* Get the string in the right order ! */
invert_string(sDestination);
}
else
{
nLZ = atoi(sILZero);
if (nLZ != 0)
{
/* Use 0.xxx instead of .xxx */
memcpy(sDestination, "0", 1);
memcpy(sDestination+1, "\0", 1);
}
else
memcpy(sDestination, "\0", 1);
}
/* Second, format the digits after the decimal. */
j = 0;
nCounter = nNumberDigits;
if ( (nDigits>0) && (pStr = strstr (sNumber, ".")) )
{
i = strlen(sNumber) - strlen(pStr) + 1;
strncpy ( sDigitsAfterDecimal, sNumber + i, nNumberDigits);
j = strlen(sDigitsAfterDecimal);
if (j < nNumberDigits)
nCounter = nNumberDigits-j;
}
for (i=0;i<nCounter;i++)
memcpy(sDigitsAfterDecimal+i+j, "0", 1);
memcpy(sDigitsAfterDecimal + nNumberDigits, "\0", 1);
i = strlen(sDestination);
j = strlen(sDigitsAfterDecimal);
/* Finally, construct the resulting formatted string. */
for (nCounter=0; nCounter<i; nCounter++)
memcpy(sNumber + nCounter, sDestination + nCounter, 1);
memcpy(sNumber + nCounter, sDecimalSymbol, strlen(sDecimalSymbol));
for (i=0; i<j; i++)
memcpy(sNumber + nCounter+i+strlen(sDecimalSymbol), sDigitsAfterDecimal + i, 1);
memcpy(sNumber + nCounter+i+ (i ? strlen(sDecimalSymbol) : 0), "\0", 1);
/* Is it a negative number */
if (bNegative == TRUE)
{
i = atoi(sNegNumber);
pStr = sDestination;
pTmpStr = sNumber;
switch (i)
{
case 0:
*pStr++ = '(';
while (*sNumber != '\0')
*pStr++ = *pTmpStr++;
*pStr++ = ')';
break;
case 1:
*pStr++ = '-';
while (*pTmpStr != '\0')
*pStr++ = *pTmpStr++;
break;
case 2:
*pStr++ = '-';
*pStr++ = ' ';
while (*pTmpStr != '\0')
*pStr++ = *pTmpStr++;
break;
case 3:
while (*pTmpStr != '\0')
*pStr++ = *pTmpStr++;
*pStr++ = '-';
break;
case 4:
while (*pTmpStr != '\0')
*pStr++ = *pTmpStr++;
*pStr++ = ' ';
*pStr++ = '-';
break;
default:
while (*pTmpStr != '\0')
*pStr++ = *pTmpStr++;
break;
}
}
else
strcpy(sDestination, sNumber);
/* If cchNumber is zero, then returns the number of bytes or characters
* required to hold the formatted number string
*/
retVal = strlen(sDestination) + 1;
if (cchNumber!=0)
{
memcpy( lpNumberStr, sDestination, min(cchNumber, retVal) );
if (cchNumber < retVal) {
retVal = 0;
SetLastError(ERROR_INSUFFICIENT_BUFFER);
}
}
return retVal;
}
/**************************************************************************
* GetNumberFormatW (KERNEL32.@)
*/
INT WINAPI GetNumberFormatW(LCID locale, DWORD dwflags,
LPCWSTR lpvalue, const NUMBERFMTW * lpFormat,
LPWSTR lpNumberStr, int cchNumber)
{
FIXME("%s: stub, no reformatting done\n",debugstr_w(lpvalue));
lstrcpynW( lpNumberStr, lpvalue, cchNumber );
return cchNumber? strlenW( lpNumberStr ) : 0;
}
/**************************************************************************
* GetCurrencyFormatA (KERNEL32.@)
*/
INT WINAPI GetCurrencyFormatA(LCID locale, DWORD dwflags,
LPCSTR lpvalue, const CURRENCYFMTA * lpFormat,
LPSTR lpCurrencyStr, int cchCurrency)
{
UINT nPosOrder, nNegOrder;
INT retVal;
char sDestination[128], sNegOrder[8], sPosOrder[8], sCurrencySymbol[8];
char *pDestination = sDestination;
char sNumberFormated[128];
char *pNumberFormated = sNumberFormated;
LCID systemDefaultLCID;
BOOL bIsPositive = FALSE, bValidFormat = FALSE;
enum Operations
{
USE_PARAMETER,
USE_LOCALEINFO,
USE_SYSTEMDEFAULT,
RETURN_ERROR
} used_operation;
NUMBERFMTA numberFmt;
/* Which source will we use to format the string */
used_operation = RETURN_ERROR;
if (lpFormat != NULL)
{
if (dwflags == 0)
used_operation = USE_PARAMETER;
}
else
{
if (dwflags & LOCALE_NOUSEROVERRIDE)
used_operation = USE_SYSTEMDEFAULT;
else
used_operation = USE_LOCALEINFO;
}
/* Load the fields we need */
switch(used_operation)
{
case USE_LOCALEINFO:
/* Specific to CURRENCYFMTA */
GetLocaleInfoA(locale, LOCALE_INEGCURR, sNegOrder, sizeof(sNegOrder));
GetLocaleInfoA(locale, LOCALE_ICURRENCY, sPosOrder, sizeof(sPosOrder));
GetLocaleInfoA(locale, LOCALE_SCURRENCY, sCurrencySymbol, sizeof(sCurrencySymbol));
nPosOrder = atoi(sPosOrder);
nNegOrder = atoi(sNegOrder);
break;
case USE_PARAMETER:
/* Specific to CURRENCYFMTA */
nNegOrder = lpFormat->NegativeOrder;
nPosOrder = lpFormat->PositiveOrder;
strcpy(sCurrencySymbol, lpFormat->lpCurrencySymbol);
break;
case USE_SYSTEMDEFAULT:
systemDefaultLCID = GetSystemDefaultLCID();
/* Specific to CURRENCYFMTA */
GetLocaleInfoA(systemDefaultLCID, LOCALE_INEGCURR, sNegOrder, sizeof(sNegOrder));
GetLocaleInfoA(systemDefaultLCID, LOCALE_ICURRENCY, sPosOrder, sizeof(sPosOrder));
GetLocaleInfoA(systemDefaultLCID, LOCALE_SCURRENCY, sCurrencySymbol, sizeof(sCurrencySymbol));
nPosOrder = atoi(sPosOrder);
nNegOrder = atoi(sNegOrder);
break;
default:
SetLastError(ERROR_INVALID_PARAMETER);
return 0;
}
/* Construct a temporary number format structure */
if (lpFormat != NULL)
{
numberFmt.NumDigits = lpFormat->NumDigits;
numberFmt.LeadingZero = lpFormat->LeadingZero;
numberFmt.Grouping = lpFormat->Grouping;
numberFmt.NegativeOrder = 0;
numberFmt.lpDecimalSep = lpFormat->lpDecimalSep;
numberFmt.lpThousandSep = lpFormat->lpThousandSep;
bValidFormat = TRUE;
}
/* Make a call to GetNumberFormatA() */
if (*lpvalue == '-')
{
bIsPositive = FALSE;
retVal = GetNumberFormatA(locale,0,lpvalue+1,(bValidFormat)?&numberFmt:NULL,pNumberFormated,128);
}
else
{
bIsPositive = TRUE;
retVal = GetNumberFormatA(locale,0,lpvalue,(bValidFormat)?&numberFmt:NULL,pNumberFormated,128);
}
if (retVal == 0)
return 0;
/* construct the formatted string */
if (bIsPositive)
{
switch (nPosOrder)
{
case 0: /* Prefix, no separation */
strcpy (pDestination, sCurrencySymbol);
strcat (pDestination, pNumberFormated);
break;
case 1: /* Suffix, no separation */
strcpy (pDestination, pNumberFormated);
strcat (pDestination, sCurrencySymbol);
break;
case 2: /* Prefix, 1 char separation */
strcpy (pDestination, sCurrencySymbol);
strcat (pDestination, " ");
strcat (pDestination, pNumberFormated);
break;
case 3: /* Suffix, 1 char separation */
strcpy (pDestination, pNumberFormated);
strcat (pDestination, " ");
strcat (pDestination, sCurrencySymbol);
break;
default:
SetLastError(ERROR_INVALID_PARAMETER);
return 0;
}
}
else /* negative number */
{
switch (nNegOrder)
{
case 0: /* format: ($1.1) */
strcpy (pDestination, "(");
strcat (pDestination, sCurrencySymbol);
strcat (pDestination, pNumberFormated);
strcat (pDestination, ")");
break;
case 1: /* format: -$1.1 */
strcpy (pDestination, "-");
strcat (pDestination, sCurrencySymbol);
strcat (pDestination, pNumberFormated);
break;
case 2: /* format: $-1.1 */
strcpy (pDestination, sCurrencySymbol);
strcat (pDestination, "-");
strcat (pDestination, pNumberFormated);
break;
case 3: /* format: $1.1- */
strcpy (pDestination, sCurrencySymbol);
strcat (pDestination, pNumberFormated);
strcat (pDestination, "-");
break;
case 4: /* format: (1.1$) */
strcpy (pDestination, "(");
strcat (pDestination, pNumberFormated);
strcat (pDestination, sCurrencySymbol);
strcat (pDestination, ")");
break;
case 5: /* format: -1.1$ */
strcpy (pDestination, "-");
strcat (pDestination, pNumberFormated);
strcat (pDestination, sCurrencySymbol);
break;
case 6: /* format: 1.1-$ */
strcpy (pDestination, pNumberFormated);
strcat (pDestination, "-");
strcat (pDestination, sCurrencySymbol);
break;
case 7: /* format: 1.1$- */
strcpy (pDestination, pNumberFormated);
strcat (pDestination, sCurrencySymbol);
strcat (pDestination, "-");
break;
case 8: /* format: -1.1 $ */
strcpy (pDestination, "-");
strcat (pDestination, pNumberFormated);
strcat (pDestination, " ");
strcat (pDestination, sCurrencySymbol);
break;
case 9: /* format: -$ 1.1 */
strcpy (pDestination, "-");
strcat (pDestination, sCurrencySymbol);
strcat (pDestination, " ");
strcat (pDestination, pNumberFormated);
break;
case 10: /* format: 1.1 $- */
strcpy (pDestination, pNumberFormated);
strcat (pDestination, " ");
strcat (pDestination, sCurrencySymbol);
strcat (pDestination, "-");
break;
case 11: /* format: $ 1.1- */
strcpy (pDestination, sCurrencySymbol);
strcat (pDestination, " ");
strcat (pDestination, pNumberFormated);
strcat (pDestination, "-");
break;
case 12: /* format: $ -1.1 */
strcpy (pDestination, sCurrencySymbol);
strcat (pDestination, " ");
strcat (pDestination, "-");
strcat (pDestination, pNumberFormated);
break;
case 13: /* format: 1.1- $ */
strcpy (pDestination, pNumberFormated);
strcat (pDestination, "-");
strcat (pDestination, " ");
strcat (pDestination, sCurrencySymbol);
break;
case 14: /* format: ($ 1.1) */
strcpy (pDestination, "(");
strcat (pDestination, sCurrencySymbol);
strcat (pDestination, " ");
strcat (pDestination, pNumberFormated);
strcat (pDestination, ")");
break;
case 15: /* format: (1.1 $) */
strcpy (pDestination, "(");
strcat (pDestination, pNumberFormated);
strcat (pDestination, " ");
strcat (pDestination, sCurrencySymbol);
strcat (pDestination, ")");
break;
default:
SetLastError(ERROR_INVALID_PARAMETER);
return 0;
}
}
retVal = strlen(pDestination) + 1;
if (cchCurrency)
{
memcpy( lpCurrencyStr, pDestination, min(cchCurrency, retVal) );
if (cchCurrency < retVal) {
retVal = 0;
SetLastError(ERROR_INSUFFICIENT_BUFFER);
}
}
return retVal;
}
/**************************************************************************
* GetCurrencyFormatW (KERNEL32.@)
*/
INT WINAPI GetCurrencyFormatW(LCID locale, DWORD dwflags,
LPCWSTR lpvalue, const CURRENCYFMTW * lpFormat,
LPWSTR lpCurrencyStr, int cchCurrency)
{
FIXME("This API function is NOT implemented !\n");
return 0;
}
/******************************************************************************
* GetTimeFormatA [KERNEL32.@]
* Makes an ASCII string of the time
*
* Formats date according to format, or locale default if format is
* NULL. The format consists of literal characters and fields as follows:
*
* h hours with no leading zero (12-hour)
* hh hours with full two digits
* H hours with no leading zero (24-hour)
* HH hours with full two digits
* m minutes with no leading zero
* mm minutes with full two digits
* s seconds with no leading zero
* ss seconds with full two digits
* t time marker (A or P)
* tt time marker (AM, PM)
*
*/
INT WINAPI
GetTimeFormatA(LCID locale, /* [in] */
DWORD flags, /* [in] */
const SYSTEMTIME* xtime, /* [in] */
LPCSTR format, /* [in] */
LPSTR timestr, /* [out] */
INT timelen /* [in] */)
{
INT ret;
LPWSTR wformat = NULL;
LPWSTR wtime = NULL;
if (format)
{
wformat = HeapAlloc(GetProcessHeap(), 0,
(strlen(format) + 1) * sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, 0, format, -1, wformat, strlen(format) + 1);
}
if (timestr && timelen)
{
wtime = HeapAlloc(GetProcessHeap(), 0,
(timelen + 1) * sizeof(wchar_t));
}
ret = GetTimeFormatW(locale, flags, xtime, wformat, wtime, timelen);
if (wtime)
{
WideCharToMultiByte(CP_ACP, 0, wtime, ret, timestr, timelen, NULL, NULL);
HeapFree(GetProcessHeap(), 0, wtime);
}
if (wformat)
{
HeapFree(GetProcessHeap(), 0, wformat);
}
return ret;
}
/******************************************************************************
* GetTimeFormatW [KERNEL32.@]
* Makes a Unicode string of the time
*
* NOTE: See get_date_time_formatW() for further documentation
*/
INT WINAPI
GetTimeFormatW(LCID locale, /* [in] */
DWORD flags, /* [in] */
const SYSTEMTIME* xtime, /* [in] */
LPCWSTR format, /* [in] */
LPWSTR timestr, /* [out] */
INT timelen /* [in] */)
{ WCHAR format_buf[40];
LPCWSTR thisformat;
SYSTEMTIME t;
const SYSTEMTIME* thistime;
DWORD thisflags=LOCALE_STIMEFORMAT; /* standard timeformat */
INT ret;
TRACE("GetTimeFormat(0x%04lx,0x%08lx,%p,%s,%p,%d)\n",locale,flags,
xtime,debugstr_w(format),timestr,timelen);
if (!locale) locale = LOCALE_SYSTEM_DEFAULT;
locale = ConvertDefaultLocale( locale );
/* if the user didn't specify a format we use the default */
/* format for this locale */
if (format == NULL)
{
if (flags & LOCALE_NOUSEROVERRIDE) /* use system default */
{
locale = GetSystemDefaultLCID();
}
GetLocaleInfoW(locale, thisflags, format_buf, 40);
thisformat = format_buf;
}
else
{
/* if non-null format and LOCALE_NOUSEROVERRIDE then fail */
/* NOTE: this could be either invalid flags or invalid parameter */
/* windows sets it to invalid flags */
if (flags & LOCALE_NOUSEROVERRIDE)
{
SetLastError(ERROR_INVALID_FLAGS);
return 0;
}
thisformat = format;
}
if (xtime == NULL) /* NULL means use the current local time */
{ GetLocalTime(&t);
thistime = &t;
}
else
{
/* check time values */
if((xtime->wHour > 24) || (xtime->wMinute >= 60) || (xtime->wSecond >= 60))
{
SetLastError(ERROR_INVALID_PARAMETER);
return 0;
}
thistime = xtime;
}
ret = get_date_time_formatW(locale, thisflags, flags, thistime, thisformat,
timestr, timelen, 0);
return ret;
}
/****************************************************************************** /******************************************************************************
* EnumCalendarInfoA [KERNEL32.@] * EnumCalendarInfoA [KERNEL32.@]
*/ */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment