kernel32/tests: A spelling fix in a comment.
[wine.git] / dlls / oleaut32 / varformat.c
blob696763e7ed7c997acadc1b15e29947bbf22c5895
1 /*
2 * Variant formatting functions
4 * Copyright 2008 Damjan Jovanovic
5 * Copyright 2003 Jon Griffiths
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 * NOTES
22 * Since the formatting functions aren't properly documented, I used the
23 * Visual Basic documentation as a guide to implementing these functions. This
24 * means that some named or user-defined formats may work slightly differently.
25 * Please submit a test case if you find a difference.
28 #include "config.h"
30 #include <string.h>
31 #include <stdlib.h>
32 #include <stdarg.h>
33 #include <stdio.h>
35 #include "windef.h"
36 #include "winbase.h"
37 #include "wine/unicode.h"
38 #include "winerror.h"
39 #include "variant.h"
40 #include "wine/debug.h"
42 WINE_DEFAULT_DEBUG_CHANNEL(variant);
44 /* Make sure internal conversions to strings use the '.','+'/'-' and ','
45 * format chars from the US locale. This enables us to parse the created
46 * strings to determine the number of decimal places, exponent, etc.
48 #define LCID_US MAKELCID(MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),SORT_DEFAULT)
50 static const WCHAR szPercent_d[] = { '%','d','\0' };
51 static const WCHAR szPercentZeroTwo_d[] = { '%','0','2','d','\0' };
52 static const WCHAR szPercentZeroStar_d[] = { '%','0','*','d','\0' };
54 /******************************************************************************
55 * Variant-Formats {OLEAUT32}
57 * NOTES
58 * When formatting a variant a variety of format strings may be used to generate
59 * different kinds of formatted output. A format string consists of either a named
60 * format, or a user-defined format.
62 * The following named formats are defined:
63 *| Name Description
64 *| ---- -----------
65 *| General Date Display Date, and time for non-integer values
66 *| Short Date Short date format as defined by locale settings
67 *| Medium Date Medium date format as defined by locale settings
68 *| Long Date Long date format as defined by locale settings
69 *| Short Time Short Time format as defined by locale settings
70 *| Medium Time Medium time format as defined by locale settings
71 *| Long Time Long time format as defined by locale settings
72 *| True/False Localised text of "True" or "False"
73 *| Yes/No Localised text of "Yes" or "No"
74 *| On/Off Localised text of "On" or "Off"
75 *| General Number No thousands separator. No decimal points for integers
76 *| Currency General currency format using localised characters
77 *| Fixed At least one whole and two fractional digits
78 *| Standard Same as 'Fixed', but including decimal separators
79 *| Percent Multiply by 100 and display a trailing '%' character
80 *| Scientific Display with exponent
82 * User-defined formats consist of a combination of tokens and literal
83 * characters. Literal characters are copied unmodified to the formatted
84 * output at the position they occupy in the format string. Any character
85 * that is not recognised as a token is treated as a literal. A literal can
86 * also be specified by preceding it with a backslash character
87 * (e.g. "\L\i\t\e\r\a\l") or enclosing it in double quotes.
89 * A user-defined format can have up to 4 sections, depending on the type of
90 * format. The following table lists sections and their meaning:
91 *| Format Type Sections Meaning
92 *| ----------- -------- -------
93 *| Number 1 Use the same format for all numbers
94 *| Number 2 Use format 1 for positive and 2 for negative numbers
95 *| Number 3 Use format 1 for positive, 2 for zero, and 3
96 *| for negative numbers.
97 *| Number 4 Use format 1 for positive, 2 for zero, 3 for
98 *| negative, and 4 for null numbers.
99 *| String 1 Use the same format for all strings
100 *| String 2 Use format 2 for null and empty strings, otherwise
101 *| use format 1.
102 *| Date 1 Use the same format for all dates
104 * The formatting tokens fall into several categories depending on the type
105 * of formatted output. For more information on each type, see
106 * VarFormat-Dates(), VarFormat-Strings() and VarFormat-Numbers().
108 * SEE ALSO
109 * VarTokenizeFormatString(), VarFormatFromTokens(), VarFormat(),
110 * VarFormatDateTime(), VarFormatNumber(), VarFormatCurrency().
113 /******************************************************************************
114 * VarFormat-Strings {OLEAUT32}
116 * NOTES
117 * When formatting a variant as a string, it is first converted to a VT_BSTR.
118 * The user-format string defines which characters are copied into which
119 * positions in the output string. Literals may be inserted in the format
120 * string. When creating the formatted string, excess characters in the string
121 * (those not consumed by a token) are appended to the end of the output. If
122 * there are more tokens than characters in the string to format, spaces will
123 * be inserted at the start of the string if the '@' token was used.
125 * By default strings are converted to lowercase, or uppercase if the '>' token
126 * is encountered. This applies to the whole string: it is not possible to
127 * generate a mixed-case output string.
129 * In user-defined string formats, the following tokens are recognised:
130 *| Token Description
131 *| ----- -----------
132 *| '@' Copy a char from the source, or a space if no chars are left.
133 *| '&' Copy a char from the source, or write nothing if no chars are left.
134 *| '<' Output the whole string as lower-case (the default).
135 *| '>' Output the whole string as upper-case.
136 *| '!' MSDN indicates that this character should cause right-to-left
137 *| copying, however tests show that it is tokenised but not processed.
141 * Common format definitions
144 /* Format types */
145 #define FMT_TYPE_UNKNOWN 0x0
146 #define FMT_TYPE_GENERAL 0x1
147 #define FMT_TYPE_NUMBER 0x2
148 #define FMT_TYPE_DATE 0x3
149 #define FMT_TYPE_STRING 0x4
151 #define FMT_TO_STRING 0x0 /* If header->size == this, act like VB's Str() fn */
153 typedef struct tagFMT_SHORT_HEADER
155 BYTE size; /* Size of tokenised block (including header), or FMT_TO_STRING */
156 BYTE type; /* Allowable types (FMT_TYPE_*) */
157 BYTE offset[1]; /* Offset of the first (and only) format section */
158 } FMT_SHORT_HEADER;
160 typedef struct tagFMT_HEADER
162 BYTE size; /* Total size of the whole tokenised block (including header) */
163 BYTE type; /* Allowable types (FMT_TYPE_*) */
164 BYTE starts[4]; /* Offset of each of the 4 format sections, or 0 if none */
165 } FMT_HEADER;
167 #define FmtGetPositive(x) (x->starts[0])
168 #define FmtGetNegative(x) (x->starts[1] ? x->starts[1] : x->starts[0])
169 #define FmtGetZero(x) (x->starts[2] ? x->starts[2] : x->starts[0])
170 #define FmtGetNull(x) (x->starts[3] ? x->starts[3] : x->starts[0])
173 * String formats
176 #define FMT_FLAG_LT 0x1 /* Has '<' (lower case) */
177 #define FMT_FLAG_GT 0x2 /* Has '>' (upper case) */
178 #define FMT_FLAG_RTL 0x4 /* Has '!' (Copy right to left) */
180 typedef struct tagFMT_STRING_HEADER
182 BYTE flags; /* LT, GT, RTL */
183 BYTE unknown1;
184 BYTE unknown2;
185 BYTE copy_chars; /* Number of chars to be copied */
186 BYTE unknown3;
187 } FMT_STRING_HEADER;
190 * Number formats
193 #define FMT_FLAG_PERCENT 0x1 /* Has '%' (Percentage) */
194 #define FMT_FLAG_EXPONENT 0x2 /* Has 'e' (Exponent/Scientific notation) */
195 #define FMT_FLAG_THOUSANDS 0x4 /* Has ',' (Standard use of the thousands separator) */
196 #define FMT_FLAG_BOOL 0x20 /* Boolean format */
198 typedef struct tagFMT_NUMBER_HEADER
200 BYTE flags; /* PERCENT, EXPONENT, THOUSANDS, BOOL */
201 BYTE multiplier; /* Multiplier, 100 for percentages */
202 BYTE divisor; /* Divisor, 1000 if '%%' was used */
203 BYTE whole; /* Number of digits before the decimal point */
204 BYTE fractional; /* Number of digits after the decimal point */
205 } FMT_NUMBER_HEADER;
208 * Date Formats
210 typedef struct tagFMT_DATE_HEADER
212 BYTE flags;
213 BYTE unknown1;
214 BYTE unknown2;
215 BYTE unknown3;
216 BYTE unknown4;
217 } FMT_DATE_HEADER;
220 * Format token values
222 #define FMT_GEN_COPY 0x00 /* \n, "lit" => 0,pos,len: Copy len chars from input+pos */
223 #define FMT_GEN_INLINE 0x01 /* => 1,len,[chars]: Copy len chars from token stream */
224 #define FMT_GEN_END 0x02 /* \0,; => 2: End of the tokenised format */
225 #define FMT_DATE_TIME_SEP 0x03 /* Time separator char */
226 #define FMT_DATE_DATE_SEP 0x04 /* Date separator char */
227 #define FMT_DATE_GENERAL 0x05 /* General format date */
228 #define FMT_DATE_QUARTER 0x06 /* Quarter of the year from 1-4 */
229 #define FMT_DATE_TIME_SYS 0x07 /* System long time format */
230 #define FMT_DATE_DAY 0x08 /* Day with no leading 0 */
231 #define FMT_DATE_DAY_0 0x09 /* Day with leading 0 */
232 #define FMT_DATE_DAY_SHORT 0x0A /* Short day name */
233 #define FMT_DATE_DAY_LONG 0x0B /* Long day name */
234 #define FMT_DATE_SHORT 0x0C /* Short date format */
235 #define FMT_DATE_LONG 0x0D /* Long date format */
236 #define FMT_DATE_MEDIUM 0x0E /* Medium date format */
237 #define FMT_DATE_DAY_WEEK 0x0F /* First day of the week */
238 #define FMT_DATE_WEEK_YEAR 0x10 /* First week of the year */
239 #define FMT_DATE_MON 0x11 /* Month with no leading 0 */
240 #define FMT_DATE_MON_0 0x12 /* Month with leading 0 */
241 #define FMT_DATE_MON_SHORT 0x13 /* Short month name */
242 #define FMT_DATE_MON_LONG 0x14 /* Long month name */
243 #define FMT_DATE_YEAR_DOY 0x15 /* Day of the year with no leading 0 */
244 #define FMT_DATE_YEAR_0 0x16 /* 2 digit year with leading 0 */
245 /* NOTE: token 0x17 is not defined, 'yyy' is not valid */
246 #define FMT_DATE_YEAR_LONG 0x18 /* 4 digit year */
247 #define FMT_DATE_MIN 0x1A /* Minutes with no leading 0 */
248 #define FMT_DATE_MIN_0 0x1B /* Minutes with leading 0 */
249 #define FMT_DATE_SEC 0x1C /* Seconds with no leading 0 */
250 #define FMT_DATE_SEC_0 0x1D /* Seconds with leading 0 */
251 #define FMT_DATE_HOUR 0x1E /* Hours with no leading 0 */
252 #define FMT_DATE_HOUR_0 0x1F /* Hours with leading 0 */
253 #define FMT_DATE_HOUR_12 0x20 /* Hours with no leading 0, 12 hour clock */
254 #define FMT_DATE_HOUR_12_0 0x21 /* Hours with leading 0, 12 hour clock */
255 #define FMT_DATE_TIME_UNK2 0x23 /* same as FMT_DATE_HOUR_0, for "short time" format */
256 /* FIXME: probably missing some here */
257 #define FMT_DATE_AMPM_SYS1 0x2E /* AM/PM as defined by system settings */
258 #define FMT_DATE_AMPM_UPPER 0x2F /* Upper-case AM or PM */
259 #define FMT_DATE_A_UPPER 0x30 /* Upper-case A or P */
260 #define FMT_DATE_AMPM_SYS2 0x31 /* AM/PM as defined by system settings */
261 #define FMT_DATE_AMPM_LOWER 0x32 /* Lower-case AM or PM */
262 #define FMT_DATE_A_LOWER 0x33 /* Lower-case A or P */
263 #define FMT_NUM_COPY_ZERO 0x34 /* Copy 1 digit or 0 if no digit */
264 #define FMT_NUM_COPY_SKIP 0x35 /* Copy 1 digit or skip if no digit */
265 #define FMT_NUM_DECIMAL 0x36 /* Decimal separator */
266 #define FMT_NUM_EXP_POS_U 0x37 /* Scientific notation, uppercase, + sign */
267 #define FMT_NUM_EXP_NEG_U 0x38 /* Scientific notation, uppercase, - sign */
268 #define FMT_NUM_EXP_POS_L 0x39 /* Scientific notation, lowercase, + sign */
269 #define FMT_NUM_EXP_NEG_L 0x3A /* Scientific notation, lowercase, - sign */
270 #define FMT_NUM_CURRENCY 0x3B /* Currency symbol */
271 #define FMT_NUM_TRUE_FALSE 0x3D /* Convert to "True" or "False" */
272 #define FMT_NUM_YES_NO 0x3E /* Convert to "Yes" or "No" */
273 #define FMT_NUM_ON_OFF 0x3F /* Convert to "On" or "Off" */
274 #define FMT_STR_COPY_SPACE 0x40 /* Copy len chars with space if no char */
275 #define FMT_STR_COPY_SKIP 0x41 /* Copy len chars or skip if no char */
277 /* Named Formats and their tokenised values */
278 static const WCHAR szGeneralDate[] = { 'G','e','n','e','r','a','l',' ','D','a','t','e','\0' };
279 static const BYTE fmtGeneralDate[0x0a] =
281 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
282 0x0,0x0,0x0,0x0,0x0,
283 FMT_DATE_GENERAL,FMT_GEN_END
286 static const WCHAR szShortDate[] = { 'S','h','o','r','t',' ','D','a','t','e','\0' };
287 static const BYTE fmtShortDate[0x0a] =
289 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
290 0x0,0x0,0x0,0x0,0x0,
291 FMT_DATE_SHORT,FMT_GEN_END
294 static const WCHAR szMediumDate[] = { 'M','e','d','i','u','m',' ','D','a','t','e','\0' };
295 static const BYTE fmtMediumDate[0x0a] =
297 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
298 0x0,0x0,0x0,0x0,0x0,
299 FMT_DATE_MEDIUM,FMT_GEN_END
302 static const WCHAR szLongDate[] = { 'L','o','n','g',' ','D','a','t','e','\0' };
303 static const BYTE fmtLongDate[0x0a] =
305 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
306 0x0,0x0,0x0,0x0,0x0,
307 FMT_DATE_LONG,FMT_GEN_END
310 static const WCHAR szShortTime[] = { 'S','h','o','r','t',' ','T','i','m','e','\0' };
311 static const BYTE fmtShortTime[0x0c] =
313 0x0c,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
314 0x0,0x0,0x0,0x0,0x0,
315 FMT_DATE_TIME_UNK2,FMT_DATE_TIME_SEP,FMT_DATE_MIN_0,FMT_GEN_END
318 static const WCHAR szMediumTime[] = { 'M','e','d','i','u','m',' ','T','i','m','e','\0' };
319 static const BYTE fmtMediumTime[0x11] =
321 0x11,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
322 0x0,0x0,0x0,0x0,0x0,
323 FMT_DATE_HOUR_12_0,FMT_DATE_TIME_SEP,FMT_DATE_MIN_0,
324 FMT_GEN_INLINE,0x01,' ','\0',FMT_DATE_AMPM_SYS1,FMT_GEN_END
327 static const WCHAR szLongTime[] = { 'L','o','n','g',' ','T','i','m','e','\0' };
328 static const BYTE fmtLongTime[0x0d] =
330 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
331 0x0,0x0,0x0,0x0,0x0,
332 FMT_DATE_TIME_SYS,FMT_GEN_END
335 static const WCHAR szTrueFalse[] = { 'T','r','u','e','/','F','a','l','s','e','\0' };
336 static const BYTE fmtTrueFalse[0x0d] =
338 0x0d,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
339 FMT_FLAG_BOOL,0x0,0x0,0x0,0x0,
340 FMT_NUM_TRUE_FALSE,FMT_GEN_END
343 static const WCHAR szYesNo[] = { 'Y','e','s','/','N','o','\0' };
344 static const BYTE fmtYesNo[0x0d] =
346 0x0d,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
347 FMT_FLAG_BOOL,0x0,0x0,0x0,0x0,
348 FMT_NUM_YES_NO,FMT_GEN_END
351 static const WCHAR szOnOff[] = { 'O','n','/','O','f','f','\0' };
352 static const BYTE fmtOnOff[0x0d] =
354 0x0d,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
355 FMT_FLAG_BOOL,0x0,0x0,0x0,0x0,
356 FMT_NUM_ON_OFF,FMT_GEN_END
359 static const WCHAR szGeneralNumber[] = { 'G','e','n','e','r','a','l',' ','N','u','m','b','e','r','\0' };
360 static const BYTE fmtGeneralNumber[sizeof(FMT_HEADER)] =
362 sizeof(FMT_HEADER),FMT_TYPE_GENERAL,sizeof(FMT_HEADER),0x0,0x0,0x0
365 static const WCHAR szCurrency[] = { 'C','u','r','r','e','n','c','y','\0' };
366 static const BYTE fmtCurrency[0x26] =
368 0x26,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x12,0x0,0x0,
369 /* Positive numbers */
370 FMT_FLAG_THOUSANDS,0xcc,0x0,0x1,0x2,
371 FMT_NUM_CURRENCY,FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,
372 FMT_GEN_END,
373 /* Negative numbers */
374 FMT_FLAG_THOUSANDS,0xcc,0x0,0x1,0x2,
375 FMT_GEN_INLINE,0x1,'(','\0',FMT_NUM_CURRENCY,FMT_NUM_COPY_ZERO,0x1,
376 FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_GEN_INLINE,0x1,')','\0',
377 FMT_GEN_END
380 static const WCHAR szFixed[] = { 'F','i','x','e','d','\0' };
381 static const BYTE fmtFixed[0x11] =
383 0x11,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
384 0x0,0x0,0x0,0x1,0x2,
385 FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_GEN_END
388 static const WCHAR szStandard[] = { 'S','t','a','n','d','a','r','d','\0' };
389 static const BYTE fmtStandard[0x11] =
391 0x11,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
392 FMT_FLAG_THOUSANDS,0x0,0x0,0x1,0x2,
393 FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_GEN_END
396 static const WCHAR szPercent[] = { 'P','e','r','c','e','n','t','\0' };
397 static const BYTE fmtPercent[0x15] =
399 0x15,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
400 FMT_FLAG_PERCENT,0x1,0x0,0x1,0x2,
401 FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,
402 FMT_GEN_INLINE,0x1,'%','\0',FMT_GEN_END
405 static const WCHAR szScientific[] = { 'S','c','i','e','n','t','i','f','i','c','\0' };
406 static const BYTE fmtScientific[0x13] =
408 0x13,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
409 FMT_FLAG_EXPONENT,0x0,0x0,0x1,0x2,
410 FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_NUM_EXP_POS_U,0x2,FMT_GEN_END
413 typedef struct tagNAMED_FORMAT
415 LPCWSTR name;
416 const BYTE* format;
417 } NAMED_FORMAT;
419 /* Format name to tokenised format. Must be kept sorted by name */
420 static const NAMED_FORMAT VARIANT_NamedFormats[] =
422 { szCurrency, fmtCurrency },
423 { szFixed, fmtFixed },
424 { szGeneralDate, fmtGeneralDate },
425 { szGeneralNumber, fmtGeneralNumber },
426 { szLongDate, fmtLongDate },
427 { szLongTime, fmtLongTime },
428 { szMediumDate, fmtMediumDate },
429 { szMediumTime, fmtMediumTime },
430 { szOnOff, fmtOnOff },
431 { szPercent, fmtPercent },
432 { szScientific, fmtScientific },
433 { szShortDate, fmtShortDate },
434 { szShortTime, fmtShortTime },
435 { szStandard, fmtStandard },
436 { szTrueFalse, fmtTrueFalse },
437 { szYesNo, fmtYesNo }
439 typedef const NAMED_FORMAT *LPCNAMED_FORMAT;
441 static int FormatCompareFn(const void *l, const void *r)
443 return strcmpiW(((LPCNAMED_FORMAT)l)->name, ((LPCNAMED_FORMAT)r)->name);
446 static inline const BYTE *VARIANT_GetNamedFormat(LPCWSTR lpszFormat)
448 NAMED_FORMAT key;
449 LPCNAMED_FORMAT fmt;
451 key.name = lpszFormat;
452 fmt = bsearch(&key, VARIANT_NamedFormats,
453 sizeof(VARIANT_NamedFormats)/sizeof(NAMED_FORMAT),
454 sizeof(NAMED_FORMAT), FormatCompareFn);
455 return fmt ? fmt->format : NULL;
458 /* Return an error if the token for the value will not fit in the destination */
459 #define NEED_SPACE(x) if (cbTok < (int)(x)) return TYPE_E_BUFFERTOOSMALL; cbTok -= (x)
461 /* Non-zero if the format is unknown or a given type */
462 #define COULD_BE(typ) ((!fmt_number && header->type==FMT_TYPE_UNKNOWN)||header->type==typ)
464 /* State during tokenising */
465 #define FMT_STATE_OPEN_COPY 0x1 /* Last token written was a copy */
466 #define FMT_STATE_WROTE_DECIMAL 0x2 /* Already wrote a decimal separator */
467 #define FMT_STATE_SEEN_HOURS 0x4 /* See the hh specifier */
468 #define FMT_STATE_WROTE_MINUTES 0x8 /* Wrote minutes */
470 /**********************************************************************
471 * VarTokenizeFormatString [OLEAUT32.140]
473 * Convert a format string into tokenised form.
475 * PARAMS
476 * lpszFormat [I] Format string to tokenise
477 * rgbTok [O] Destination for tokenised format
478 * cbTok [I] Size of rgbTok in bytes
479 * nFirstDay [I] First day of the week (1-7, or 0 for current system default)
480 * nFirstWeek [I] How to treat the first week (see notes)
481 * lcid [I] Locale Id of the format string
482 * pcbActual [O] If non-NULL, filled with the first token generated
484 * RETURNS
485 * Success: S_OK. rgbTok contains the tokenised format.
486 * Failure: E_INVALIDARG, if any argument is invalid.
487 * TYPE_E_BUFFERTOOSMALL, if rgbTok is not large enough.
489 * NOTES
490 * Valid values for the nFirstWeek parameter are:
491 *| Value Meaning
492 *| ----- -------
493 *| 0 Use the current system default
494 *| 1 The first week is that containing Jan 1
495 *| 2 Four or more days of the first week are in the current year
496 *| 3 The first week is 7 days long
497 * See Variant-Formats(), VarFormatFromTokens().
499 HRESULT WINAPI VarTokenizeFormatString(LPOLESTR lpszFormat, LPBYTE rgbTok,
500 int cbTok, int nFirstDay, int nFirstWeek,
501 LCID lcid, int *pcbActual)
503 /* Note: none of these strings should be NUL terminated */
504 static const WCHAR szTTTTT[] = { 't','t','t','t','t' };
505 static const WCHAR szAMPM[] = { 'A','M','P','M' };
506 static const WCHAR szampm[] = { 'a','m','p','m' };
507 static const WCHAR szAMSlashPM[] = { 'A','M','/','P','M' };
508 static const WCHAR szamSlashpm[] = { 'a','m','/','p','m' };
509 const BYTE *namedFmt;
510 FMT_HEADER *header = (FMT_HEADER*)rgbTok;
511 FMT_STRING_HEADER *str_header = (FMT_STRING_HEADER*)(rgbTok + sizeof(FMT_HEADER));
512 FMT_NUMBER_HEADER *num_header = (FMT_NUMBER_HEADER*)str_header;
513 BYTE* pOut = rgbTok + sizeof(FMT_HEADER) + sizeof(FMT_STRING_HEADER);
514 BYTE* pLastHours = NULL;
515 BYTE fmt_number = 0;
516 DWORD fmt_state = 0;
517 LPCWSTR pFormat = lpszFormat;
519 TRACE("(%s,%p,%d,%d,%d,0x%08x,%p)\n", debugstr_w(lpszFormat), rgbTok, cbTok,
520 nFirstDay, nFirstWeek, lcid, pcbActual);
522 if (!rgbTok ||
523 nFirstDay < 0 || nFirstDay > 7 || nFirstWeek < 0 || nFirstWeek > 3)
524 return E_INVALIDARG;
526 if (!lpszFormat || !*lpszFormat)
528 /* An empty string means 'general format' */
529 NEED_SPACE(sizeof(BYTE));
530 *rgbTok = FMT_TO_STRING;
531 if (pcbActual)
532 *pcbActual = FMT_TO_STRING;
533 return S_OK;
536 if (cbTok > 255)
537 cbTok = 255; /* Ensure we error instead of wrapping */
539 /* Named formats */
540 namedFmt = VARIANT_GetNamedFormat(lpszFormat);
541 if (namedFmt)
543 NEED_SPACE(namedFmt[0]);
544 memcpy(rgbTok, namedFmt, namedFmt[0]);
545 TRACE("Using pre-tokenised named format %s\n", debugstr_w(lpszFormat));
546 /* FIXME: pcbActual */
547 return S_OK;
550 /* Insert header */
551 NEED_SPACE(sizeof(FMT_HEADER) + sizeof(FMT_STRING_HEADER));
552 memset(header, 0, sizeof(FMT_HEADER));
553 memset(str_header, 0, sizeof(FMT_STRING_HEADER));
555 header->starts[fmt_number] = sizeof(FMT_HEADER);
557 while (*pFormat)
559 /* --------------
560 * General tokens
561 * --------------
563 if (*pFormat == ';')
565 while (*pFormat == ';')
567 TRACE(";\n");
568 if (++fmt_number > 3)
569 return E_INVALIDARG; /* too many formats */
570 pFormat++;
572 if (*pFormat)
574 TRACE("New header\n");
575 NEED_SPACE(sizeof(BYTE) + sizeof(FMT_STRING_HEADER));
576 *pOut++ = FMT_GEN_END;
578 header->starts[fmt_number] = pOut - rgbTok;
579 str_header = (FMT_STRING_HEADER*)pOut;
580 num_header = (FMT_NUMBER_HEADER*)pOut;
581 memset(str_header, 0, sizeof(FMT_STRING_HEADER));
582 pOut += sizeof(FMT_STRING_HEADER);
583 fmt_state = 0;
584 pLastHours = NULL;
587 else if (*pFormat == '\\')
589 /* Escaped character */
590 if (pFormat[1])
592 NEED_SPACE(3 * sizeof(BYTE));
593 pFormat++;
594 *pOut++ = FMT_GEN_COPY;
595 *pOut++ = pFormat - lpszFormat;
596 *pOut++ = 0x1;
597 fmt_state |= FMT_STATE_OPEN_COPY;
598 TRACE("'\\'\n");
600 else
601 fmt_state &= ~FMT_STATE_OPEN_COPY;
602 pFormat++;
604 else if (*pFormat == '"')
606 /* Escaped string
607 * Note: Native encodes "" as a copy of length zero. That's just dumb, so
608 * here we avoid encoding anything in this case.
610 if (!pFormat[1])
611 pFormat++;
612 else if (pFormat[1] == '"')
614 pFormat += 2;
616 else
618 LPCWSTR start = ++pFormat;
619 while (*pFormat && *pFormat != '"')
620 pFormat++;
621 NEED_SPACE(3 * sizeof(BYTE));
622 *pOut++ = FMT_GEN_COPY;
623 *pOut++ = start - lpszFormat;
624 *pOut++ = pFormat - start;
625 if (*pFormat == '"')
626 pFormat++;
627 TRACE("Quoted string pos %d, len %d\n", pOut[-2], pOut[-1]);
629 fmt_state &= ~FMT_STATE_OPEN_COPY;
631 /* -------------
632 * Number tokens
633 * -------------
635 else if (*pFormat == '0' && COULD_BE(FMT_TYPE_NUMBER))
637 /* Number formats: Digit from number or '0' if no digits
638 * Other formats: Literal
639 * Types the format if found
641 header->type = FMT_TYPE_NUMBER;
642 NEED_SPACE(2 * sizeof(BYTE));
643 *pOut++ = FMT_NUM_COPY_ZERO;
644 *pOut = 0x0;
645 while (*pFormat == '0')
647 *pOut = *pOut + 1;
648 pFormat++;
650 if (fmt_state & FMT_STATE_WROTE_DECIMAL)
651 num_header->fractional += *pOut;
652 else
653 num_header->whole += *pOut;
654 TRACE("%d 0's\n", *pOut);
655 pOut++;
656 fmt_state &= ~FMT_STATE_OPEN_COPY;
658 else if (*pFormat == '#' && COULD_BE(FMT_TYPE_NUMBER))
660 /* Number formats: Digit from number or blank if no digits
661 * Other formats: Literal
662 * Types the format if found
664 header->type = FMT_TYPE_NUMBER;
665 NEED_SPACE(2 * sizeof(BYTE));
666 *pOut++ = FMT_NUM_COPY_SKIP;
667 *pOut = 0x0;
668 while (*pFormat == '#')
670 *pOut = *pOut + 1;
671 pFormat++;
673 if (fmt_state & FMT_STATE_WROTE_DECIMAL)
674 num_header->fractional += *pOut;
675 else
676 num_header->whole += *pOut;
677 TRACE("%d #'s\n", *pOut);
678 pOut++;
679 fmt_state &= ~FMT_STATE_OPEN_COPY;
681 else if (*pFormat == '.' && COULD_BE(FMT_TYPE_NUMBER) &&
682 !(fmt_state & FMT_STATE_WROTE_DECIMAL))
684 /* Number formats: Decimal separator when 1st seen, literal thereafter
685 * Other formats: Literal
686 * Types the format if found
688 header->type = FMT_TYPE_NUMBER;
689 NEED_SPACE(sizeof(BYTE));
690 *pOut++ = FMT_NUM_DECIMAL;
691 fmt_state |= FMT_STATE_WROTE_DECIMAL;
692 fmt_state &= ~FMT_STATE_OPEN_COPY;
693 pFormat++;
694 TRACE("decimal sep\n");
696 else if ((*pFormat == 'e' || *pFormat == 'E') && (pFormat[1] == '-' ||
697 pFormat[1] == '+') && header->type == FMT_TYPE_NUMBER)
699 /* Number formats: Exponent specifier
700 * Other formats: Literal
702 num_header->flags |= FMT_FLAG_EXPONENT;
703 NEED_SPACE(2 * sizeof(BYTE));
704 if (*pFormat == 'e') {
705 if (pFormat[1] == '+')
706 *pOut = FMT_NUM_EXP_POS_L;
707 else
708 *pOut = FMT_NUM_EXP_NEG_L;
709 } else {
710 if (pFormat[1] == '+')
711 *pOut = FMT_NUM_EXP_POS_U;
712 else
713 *pOut = FMT_NUM_EXP_NEG_U;
715 pFormat += 2;
716 *++pOut = 0x0;
717 while (*pFormat == '0')
719 *pOut = *pOut + 1;
720 pFormat++;
722 pOut++;
723 TRACE("exponent\n");
725 /* FIXME: %% => Divide by 1000 */
726 else if (*pFormat == ',' && header->type == FMT_TYPE_NUMBER)
728 /* Number formats: Use the thousands separator
729 * Other formats: Literal
731 num_header->flags |= FMT_FLAG_THOUSANDS;
732 pFormat++;
733 fmt_state &= ~FMT_STATE_OPEN_COPY;
734 TRACE("thousands sep\n");
736 /* -----------
737 * Date tokens
738 * -----------
740 else if (*pFormat == '/' && COULD_BE(FMT_TYPE_DATE))
742 /* Date formats: Date separator
743 * Other formats: Literal
744 * Types the format if found
746 header->type = FMT_TYPE_DATE;
747 NEED_SPACE(sizeof(BYTE));
748 *pOut++ = FMT_DATE_DATE_SEP;
749 pFormat++;
750 fmt_state &= ~FMT_STATE_OPEN_COPY;
751 TRACE("date sep\n");
753 else if (*pFormat == ':' && COULD_BE(FMT_TYPE_DATE))
755 /* Date formats: Time separator
756 * Other formats: Literal
757 * Types the format if found
759 header->type = FMT_TYPE_DATE;
760 NEED_SPACE(sizeof(BYTE));
761 *pOut++ = FMT_DATE_TIME_SEP;
762 pFormat++;
763 fmt_state &= ~FMT_STATE_OPEN_COPY;
764 TRACE("time sep\n");
766 else if ((*pFormat == 'a' || *pFormat == 'A') &&
767 !strncmpiW(pFormat, szAMPM, sizeof(szAMPM)/sizeof(WCHAR)))
769 /* Date formats: System AM/PM designation
770 * Other formats: Literal
771 * Types the format if found
773 header->type = FMT_TYPE_DATE;
774 NEED_SPACE(sizeof(BYTE));
775 pFormat += sizeof(szAMPM)/sizeof(WCHAR);
776 if (!strncmpW(pFormat, szampm, sizeof(szampm)/sizeof(WCHAR)))
777 *pOut++ = FMT_DATE_AMPM_SYS2;
778 else
779 *pOut++ = FMT_DATE_AMPM_SYS1;
780 if (pLastHours)
781 *pLastHours = *pLastHours + 2;
782 TRACE("ampm\n");
784 else if (*pFormat == 'a' && pFormat[1] == '/' &&
785 (pFormat[2] == 'p' || pFormat[2] == 'P'))
787 /* Date formats: lowercase a or p designation
788 * Other formats: Literal
789 * Types the format if found
791 header->type = FMT_TYPE_DATE;
792 NEED_SPACE(sizeof(BYTE));
793 pFormat += 3;
794 *pOut++ = FMT_DATE_A_LOWER;
795 if (pLastHours)
796 *pLastHours = *pLastHours + 2;
797 TRACE("a/p\n");
799 else if (*pFormat == 'A' && pFormat[1] == '/' &&
800 (pFormat[2] == 'p' || pFormat[2] == 'P'))
802 /* Date formats: Uppercase a or p designation
803 * Other formats: Literal
804 * Types the format if found
806 header->type = FMT_TYPE_DATE;
807 NEED_SPACE(sizeof(BYTE));
808 pFormat += 3;
809 *pOut++ = FMT_DATE_A_UPPER;
810 if (pLastHours)
811 *pLastHours = *pLastHours + 2;
812 TRACE("A/P\n");
814 else if (*pFormat == 'a' &&
815 !strncmpW(pFormat, szamSlashpm, sizeof(szamSlashpm)/sizeof(WCHAR)))
817 /* Date formats: lowercase AM or PM designation
818 * Other formats: Literal
819 * Types the format if found
821 header->type = FMT_TYPE_DATE;
822 NEED_SPACE(sizeof(BYTE));
823 pFormat += sizeof(szamSlashpm)/sizeof(WCHAR);
824 *pOut++ = FMT_DATE_AMPM_LOWER;
825 if (pLastHours)
826 *pLastHours = *pLastHours + 2;
827 TRACE("AM/PM\n");
829 else if (*pFormat == 'A' &&
830 !strncmpW(pFormat, szAMSlashPM, sizeof(szAMSlashPM)/sizeof(WCHAR)))
832 /* Date formats: Uppercase AM or PM designation
833 * Other formats: Literal
834 * Types the format if found
836 header->type = FMT_TYPE_DATE;
837 NEED_SPACE(sizeof(BYTE));
838 pFormat += sizeof(szAMSlashPM)/sizeof(WCHAR);
839 *pOut++ = FMT_DATE_AMPM_UPPER;
840 TRACE("AM/PM\n");
842 else if ((*pFormat == 'c' || *pFormat == 'C') && COULD_BE(FMT_TYPE_DATE))
844 /* Date formats: General date format
845 * Other formats: Literal
846 * Types the format if found
848 header->type = FMT_TYPE_DATE;
849 NEED_SPACE(sizeof(BYTE));
850 pFormat += sizeof(szAMSlashPM)/sizeof(WCHAR);
851 *pOut++ = FMT_DATE_GENERAL;
852 TRACE("gen date\n");
854 else if ((*pFormat == 'd' || *pFormat == 'D') && COULD_BE(FMT_TYPE_DATE))
856 /* Date formats: Day specifier
857 * Other formats: Literal
858 * Types the format if found
860 int count = -1;
861 header->type = FMT_TYPE_DATE;
862 while ((*pFormat == 'd' || *pFormat == 'D') && count < 6)
864 pFormat++;
865 count++;
867 NEED_SPACE(sizeof(BYTE));
868 *pOut++ = FMT_DATE_DAY + count;
869 fmt_state &= ~FMT_STATE_OPEN_COPY;
870 /* When we find the days token, reset the seen hours state so that
871 * 'mm' is again written as month when encountered.
873 fmt_state &= ~FMT_STATE_SEEN_HOURS;
874 TRACE("%d d's\n", count + 1);
876 else if ((*pFormat == 'h' || *pFormat == 'H') && COULD_BE(FMT_TYPE_DATE))
878 /* Date formats: Hour specifier
879 * Other formats: Literal
880 * Types the format if found
882 header->type = FMT_TYPE_DATE;
883 NEED_SPACE(sizeof(BYTE));
884 pFormat++;
885 /* Record the position of the hours specifier - if we encounter
886 * an am/pm specifier we will change the hours from 24 to 12.
888 pLastHours = pOut;
889 if (*pFormat == 'h' || *pFormat == 'H')
891 pFormat++;
892 *pOut++ = FMT_DATE_HOUR_0;
893 TRACE("hh\n");
895 else
897 *pOut++ = FMT_DATE_HOUR;
898 TRACE("h\n");
900 fmt_state &= ~FMT_STATE_OPEN_COPY;
901 /* Note that now we have seen an hours token, the next occurrence of
902 * 'mm' indicates minutes, not months.
904 fmt_state |= FMT_STATE_SEEN_HOURS;
906 else if ((*pFormat == 'm' || *pFormat == 'M') && COULD_BE(FMT_TYPE_DATE))
908 /* Date formats: Month specifier (or Minute specifier, after hour specifier)
909 * Other formats: Literal
910 * Types the format if found
912 int count = -1;
913 header->type = FMT_TYPE_DATE;
914 while ((*pFormat == 'm' || *pFormat == 'M') && count < 4)
916 pFormat++;
917 count++;
919 NEED_SPACE(sizeof(BYTE));
920 if (count <= 1 && fmt_state & FMT_STATE_SEEN_HOURS &&
921 !(fmt_state & FMT_STATE_WROTE_MINUTES))
923 /* We have seen an hours specifier and not yet written a minutes
924 * specifier. Write this as minutes and thereafter as months.
926 *pOut++ = count == 1 ? FMT_DATE_MIN_0 : FMT_DATE_MIN;
927 fmt_state |= FMT_STATE_WROTE_MINUTES; /* Hereafter write months */
929 else
930 *pOut++ = FMT_DATE_MON + count; /* Months */
931 fmt_state &= ~FMT_STATE_OPEN_COPY;
932 TRACE("%d m's\n", count + 1);
934 else if ((*pFormat == 'n' || *pFormat == 'N') && COULD_BE(FMT_TYPE_DATE))
936 /* Date formats: Minute specifier
937 * Other formats: Literal
938 * Types the format if found
940 header->type = FMT_TYPE_DATE;
941 NEED_SPACE(sizeof(BYTE));
942 pFormat++;
943 if (*pFormat == 'n' || *pFormat == 'N')
945 pFormat++;
946 *pOut++ = FMT_DATE_MIN_0;
947 TRACE("nn\n");
949 else
951 *pOut++ = FMT_DATE_MIN;
952 TRACE("n\n");
954 fmt_state &= ~FMT_STATE_OPEN_COPY;
956 else if ((*pFormat == 'q' || *pFormat == 'Q') && COULD_BE(FMT_TYPE_DATE))
958 /* Date formats: Quarter specifier
959 * Other formats: Literal
960 * Types the format if found
962 header->type = FMT_TYPE_DATE;
963 NEED_SPACE(sizeof(BYTE));
964 *pOut++ = FMT_DATE_QUARTER;
965 pFormat++;
966 fmt_state &= ~FMT_STATE_OPEN_COPY;
967 TRACE("quarter\n");
969 else if ((*pFormat == 's' || *pFormat == 'S') && COULD_BE(FMT_TYPE_DATE))
971 /* Date formats: Second specifier
972 * Other formats: Literal
973 * Types the format if found
975 header->type = FMT_TYPE_DATE;
976 NEED_SPACE(sizeof(BYTE));
977 pFormat++;
978 if (*pFormat == 's' || *pFormat == 'S')
980 pFormat++;
981 *pOut++ = FMT_DATE_SEC_0;
982 TRACE("ss\n");
984 else
986 *pOut++ = FMT_DATE_SEC;
987 TRACE("s\n");
989 fmt_state &= ~FMT_STATE_OPEN_COPY;
991 else if ((*pFormat == 't' || *pFormat == 'T') &&
992 !strncmpiW(pFormat, szTTTTT, sizeof(szTTTTT)/sizeof(WCHAR)))
994 /* Date formats: System time specifier
995 * Other formats: Literal
996 * Types the format if found
998 header->type = FMT_TYPE_DATE;
999 pFormat += sizeof(szTTTTT)/sizeof(WCHAR);
1000 NEED_SPACE(sizeof(BYTE));
1001 *pOut++ = FMT_DATE_TIME_SYS;
1002 fmt_state &= ~FMT_STATE_OPEN_COPY;
1004 else if ((*pFormat == 'w' || *pFormat == 'W') && COULD_BE(FMT_TYPE_DATE))
1006 /* Date formats: Week of the year/Day of the week
1007 * Other formats: Literal
1008 * Types the format if found
1010 header->type = FMT_TYPE_DATE;
1011 pFormat++;
1012 if (*pFormat == 'w' || *pFormat == 'W')
1014 NEED_SPACE(3 * sizeof(BYTE));
1015 pFormat++;
1016 *pOut++ = FMT_DATE_WEEK_YEAR;
1017 *pOut++ = nFirstDay;
1018 *pOut++ = nFirstWeek;
1019 TRACE("ww\n");
1021 else
1023 NEED_SPACE(2 * sizeof(BYTE));
1024 *pOut++ = FMT_DATE_DAY_WEEK;
1025 *pOut++ = nFirstDay;
1026 TRACE("w\n");
1029 fmt_state &= ~FMT_STATE_OPEN_COPY;
1031 else if ((*pFormat == 'y' || *pFormat == 'Y') && COULD_BE(FMT_TYPE_DATE))
1033 /* Date formats: Day of year/Year specifier
1034 * Other formats: Literal
1035 * Types the format if found
1037 int count = -1;
1038 header->type = FMT_TYPE_DATE;
1039 while ((*pFormat == 'y' || *pFormat == 'Y') && count < 4)
1041 pFormat++;
1042 count++;
1044 if (count == 2)
1046 count--; /* 'yyy' has no meaning, despite what MSDN says */
1047 pFormat--;
1049 NEED_SPACE(sizeof(BYTE));
1050 *pOut++ = FMT_DATE_YEAR_DOY + count;
1051 fmt_state &= ~FMT_STATE_OPEN_COPY;
1052 TRACE("%d y's\n", count + 1);
1054 /* -------------
1055 * String tokens
1056 * -------------
1058 else if (*pFormat == '@' && COULD_BE(FMT_TYPE_STRING))
1060 /* String formats: Character from string or space if no char
1061 * Other formats: Literal
1062 * Types the format if found
1064 header->type = FMT_TYPE_STRING;
1065 NEED_SPACE(2 * sizeof(BYTE));
1066 *pOut++ = FMT_STR_COPY_SPACE;
1067 *pOut = 0x0;
1068 while (*pFormat == '@')
1070 *pOut = *pOut + 1;
1071 str_header->copy_chars++;
1072 pFormat++;
1074 TRACE("%d @'s\n", *pOut);
1075 pOut++;
1076 fmt_state &= ~FMT_STATE_OPEN_COPY;
1078 else if (*pFormat == '&' && COULD_BE(FMT_TYPE_STRING))
1080 /* String formats: Character from string or skip if no char
1081 * Other formats: Literal
1082 * Types the format if found
1084 header->type = FMT_TYPE_STRING;
1085 NEED_SPACE(2 * sizeof(BYTE));
1086 *pOut++ = FMT_STR_COPY_SKIP;
1087 *pOut = 0x0;
1088 while (*pFormat == '&')
1090 *pOut = *pOut + 1;
1091 str_header->copy_chars++;
1092 pFormat++;
1094 TRACE("%d &'s\n", *pOut);
1095 pOut++;
1096 fmt_state &= ~FMT_STATE_OPEN_COPY;
1098 else if ((*pFormat == '<' || *pFormat == '>') && COULD_BE(FMT_TYPE_STRING))
1100 /* String formats: Use upper/lower case
1101 * Other formats: Literal
1102 * Types the format if found
1104 header->type = FMT_TYPE_STRING;
1105 if (*pFormat == '<')
1106 str_header->flags |= FMT_FLAG_LT;
1107 else
1108 str_header->flags |= FMT_FLAG_GT;
1109 TRACE("to %s case\n", *pFormat == '<' ? "lower" : "upper");
1110 pFormat++;
1111 fmt_state &= ~FMT_STATE_OPEN_COPY;
1113 else if (*pFormat == '!' && COULD_BE(FMT_TYPE_STRING))
1115 /* String formats: Copy right to left
1116 * Other formats: Literal
1117 * Types the format if found
1119 header->type = FMT_TYPE_STRING;
1120 str_header->flags |= FMT_FLAG_RTL;
1121 pFormat++;
1122 fmt_state &= ~FMT_STATE_OPEN_COPY;
1123 TRACE("copy right-to-left\n");
1125 /* --------
1126 * Literals
1127 * --------
1129 /* FIXME: [ seems to be ignored */
1130 else
1132 if (*pFormat == '%' && header->type == FMT_TYPE_NUMBER)
1134 /* Number formats: Percentage indicator, also a literal
1135 * Other formats: Literal
1136 * Doesn't type the format
1138 num_header->flags |= FMT_FLAG_PERCENT;
1141 if (fmt_state & FMT_STATE_OPEN_COPY)
1143 pOut[-1] = pOut[-1] + 1; /* Increase the length of the open copy */
1144 TRACE("extend copy (char '%c'), length now %d\n", *pFormat, pOut[-1]);
1146 else
1148 /* Create a new open copy */
1149 TRACE("New copy (char '%c')\n", *pFormat);
1150 NEED_SPACE(3 * sizeof(BYTE));
1151 *pOut++ = FMT_GEN_COPY;
1152 *pOut++ = pFormat - lpszFormat;
1153 *pOut++ = 0x1;
1154 fmt_state |= FMT_STATE_OPEN_COPY;
1156 pFormat++;
1160 *pOut++ = FMT_GEN_END;
1162 header->size = pOut - rgbTok;
1163 if (pcbActual)
1164 *pcbActual = header->size;
1166 return S_OK;
1169 /* Number formatting state flags */
1170 #define NUM_WROTE_DEC 0x01 /* Written the decimal separator */
1171 #define NUM_WRITE_ON 0x02 /* Started to write the number */
1172 #define NUM_WROTE_SIGN 0x04 /* Written the negative sign */
1174 /* Format a variant using a number format */
1175 static HRESULT VARIANT_FormatNumber(LPVARIANT pVarIn, LPOLESTR lpszFormat,
1176 LPBYTE rgbTok, ULONG dwFlags,
1177 BSTR *pbstrOut, LCID lcid)
1179 BYTE rgbDig[256], *prgbDig;
1180 NUMPARSE np;
1181 int have_int, need_int = 0, have_frac, need_frac, exponent = 0, pad = 0;
1182 WCHAR buff[256], *pBuff = buff;
1183 WCHAR thousandSeparator[32];
1184 VARIANT vString, vBool;
1185 DWORD dwState = 0;
1186 FMT_HEADER *header = (FMT_HEADER*)rgbTok;
1187 FMT_NUMBER_HEADER *numHeader;
1188 const BYTE* pToken = NULL;
1189 HRESULT hRes = S_OK;
1191 TRACE("(%s,%s,%p,0x%08x,%p,0x%08x)\n", debugstr_variant(pVarIn), debugstr_w(lpszFormat),
1192 rgbTok, dwFlags, pbstrOut, lcid);
1194 V_VT(&vString) = VT_EMPTY;
1195 V_VT(&vBool) = VT_BOOL;
1197 if (V_TYPE(pVarIn) == VT_EMPTY || V_TYPE(pVarIn) == VT_NULL)
1199 have_int = have_frac = 0;
1200 numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetNull(header));
1201 V_BOOL(&vBool) = VARIANT_FALSE;
1203 else
1205 /* Get a number string from pVarIn, and parse it */
1206 hRes = VariantChangeTypeEx(&vString, pVarIn, lcid, VARIANT_NOUSEROVERRIDE, VT_BSTR);
1207 if (FAILED(hRes))
1208 return hRes;
1210 np.cDig = sizeof(rgbDig);
1211 np.dwInFlags = NUMPRS_STD;
1212 hRes = VarParseNumFromStr(V_BSTR(&vString), lcid, 0, &np, rgbDig);
1213 if (FAILED(hRes))
1214 return hRes;
1216 have_int = np.cDig;
1217 have_frac = 0;
1218 exponent = np.nPwr10;
1220 /* Figure out which format to use */
1221 if (np.dwOutFlags & NUMPRS_NEG)
1223 numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetNegative(header));
1224 V_BOOL(&vBool) = VARIANT_TRUE;
1226 else if (have_int == 1 && !exponent && rgbDig[0] == 0)
1228 numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetZero(header));
1229 V_BOOL(&vBool) = VARIANT_FALSE;
1231 else
1233 numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetPositive(header));
1234 V_BOOL(&vBool) = VARIANT_TRUE;
1237 TRACE("num header: flags = 0x%x, mult=%d, div=%d, whole=%d, fract=%d\n",
1238 numHeader->flags, numHeader->multiplier, numHeader->divisor,
1239 numHeader->whole, numHeader->fractional);
1241 need_int = numHeader->whole;
1242 need_frac = numHeader->fractional;
1244 if (numHeader->flags & FMT_FLAG_PERCENT &&
1245 !(have_int == 1 && !exponent && rgbDig[0] == 0))
1246 exponent += 2;
1248 if (numHeader->flags & FMT_FLAG_EXPONENT)
1250 /* Exponent format: length of the integral number part is fixed and
1251 specified by the format. */
1252 pad = need_int - have_int;
1253 exponent -= pad;
1254 if (pad < 0)
1256 have_int = need_int;
1257 have_frac -= pad;
1258 pad = 0;
1261 else
1263 /* Convert the exponent */
1264 pad = max(exponent, -have_int);
1265 exponent -= pad;
1266 if (pad < 0)
1268 have_int += pad;
1269 have_frac = -pad;
1270 pad = 0;
1272 if(exponent < 0 && exponent > (-256 + have_int + have_frac))
1274 /* Remove exponent notation */
1275 memmove(rgbDig - exponent, rgbDig, have_int + have_frac);
1276 ZeroMemory(rgbDig, -exponent);
1277 have_frac -= exponent;
1278 exponent = 0;
1282 /* Rounding the number */
1283 if (have_frac > need_frac)
1285 prgbDig = &rgbDig[have_int + need_frac];
1286 have_frac = need_frac;
1287 if (*prgbDig >= 5)
1289 while (prgbDig-- > rgbDig && *prgbDig == 9)
1290 *prgbDig = 0;
1291 if (prgbDig < rgbDig)
1293 /* We reached the first digit and that was also a 9 */
1294 rgbDig[0] = 1;
1295 if (numHeader->flags & FMT_FLAG_EXPONENT)
1296 exponent++;
1297 else
1299 rgbDig[have_int + need_frac] = 0;
1300 if (exponent < 0)
1301 exponent++;
1302 else
1303 have_int++;
1306 else
1307 (*prgbDig)++;
1309 /* We converted trailing digits to zeroes => have_frac has changed */
1310 while (have_frac > 0 && rgbDig[have_int + have_frac - 1] == 0)
1311 have_frac--;
1313 TRACE("have_int=%d,need_int=%d,have_frac=%d,need_frac=%d,pad=%d,exp=%d\n",
1314 have_int, need_int, have_frac, need_frac, pad, exponent);
1317 if (numHeader->flags & FMT_FLAG_THOUSANDS)
1319 if (!GetLocaleInfoW(lcid, LOCALE_STHOUSAND, thousandSeparator,
1320 sizeof(thousandSeparator)/sizeof(WCHAR)))
1322 thousandSeparator[0] = ',';
1323 thousandSeparator[1] = 0;
1327 pToken = (const BYTE*)numHeader + sizeof(FMT_NUMBER_HEADER);
1328 prgbDig = rgbDig;
1330 while (SUCCEEDED(hRes) && *pToken != FMT_GEN_END)
1332 WCHAR defaultChar = '?';
1333 DWORD boolFlag, localeValue = 0;
1334 BOOL shouldAdvance = TRUE;
1336 if (pToken - rgbTok > header->size)
1338 ERR("Ran off the end of the format!\n");
1339 hRes = E_INVALIDARG;
1340 goto VARIANT_FormatNumber_Exit;
1343 switch (*pToken)
1345 case FMT_GEN_COPY:
1346 TRACE("copy %s\n", debugstr_wn(lpszFormat + pToken[1], pToken[2]));
1347 memcpy(pBuff, lpszFormat + pToken[1], pToken[2] * sizeof(WCHAR));
1348 pBuff += pToken[2];
1349 pToken += 2;
1350 break;
1352 case FMT_GEN_INLINE:
1353 pToken += 2;
1354 TRACE("copy %s\n", debugstr_a((LPCSTR)pToken));
1355 while (*pToken)
1356 *pBuff++ = *pToken++;
1357 break;
1359 case FMT_NUM_YES_NO:
1360 boolFlag = VAR_BOOLYESNO;
1361 goto VARIANT_FormatNumber_Bool;
1363 case FMT_NUM_ON_OFF:
1364 boolFlag = VAR_BOOLONOFF;
1365 goto VARIANT_FormatNumber_Bool;
1367 case FMT_NUM_TRUE_FALSE:
1368 boolFlag = VAR_LOCALBOOL;
1370 VARIANT_FormatNumber_Bool:
1372 BSTR boolStr = NULL;
1374 if (pToken[1] != FMT_GEN_END)
1376 ERR("Boolean token not at end of format!\n");
1377 hRes = E_INVALIDARG;
1378 goto VARIANT_FormatNumber_Exit;
1380 hRes = VarBstrFromBool(V_BOOL(&vBool), lcid, boolFlag, &boolStr);
1381 if (SUCCEEDED(hRes))
1383 strcpyW(pBuff, boolStr);
1384 SysFreeString(boolStr);
1385 while (*pBuff)
1386 pBuff++;
1389 break;
1391 case FMT_NUM_DECIMAL:
1392 if ((np.dwOutFlags & NUMPRS_NEG) && !(dwState & NUM_WROTE_SIGN) && !header->starts[1])
1394 /* last chance for a negative sign in the .# case */
1395 TRACE("write negative sign\n");
1396 localeValue = LOCALE_SNEGATIVESIGN;
1397 defaultChar = '-';
1398 dwState |= NUM_WROTE_SIGN;
1399 shouldAdvance = FALSE;
1400 break;
1402 TRACE("write decimal separator\n");
1403 localeValue = LOCALE_SDECIMAL;
1404 defaultChar = '.';
1405 dwState |= NUM_WROTE_DEC;
1406 break;
1408 case FMT_NUM_CURRENCY:
1409 TRACE("write currency symbol\n");
1410 localeValue = LOCALE_SCURRENCY;
1411 defaultChar = '$';
1412 break;
1414 case FMT_NUM_EXP_POS_U:
1415 case FMT_NUM_EXP_POS_L:
1416 case FMT_NUM_EXP_NEG_U:
1417 case FMT_NUM_EXP_NEG_L:
1418 if (*pToken == FMT_NUM_EXP_POS_L || *pToken == FMT_NUM_EXP_NEG_L)
1419 *pBuff++ = 'e';
1420 else
1421 *pBuff++ = 'E';
1422 if (exponent < 0)
1424 *pBuff++ = '-';
1425 sprintfW(pBuff, szPercentZeroStar_d, pToken[1], -exponent);
1427 else
1429 if (*pToken == FMT_NUM_EXP_POS_L || *pToken == FMT_NUM_EXP_POS_U)
1430 *pBuff++ = '+';
1431 sprintfW(pBuff, szPercentZeroStar_d, pToken[1], exponent);
1433 while (*pBuff)
1434 pBuff++;
1435 pToken++;
1436 break;
1438 case FMT_NUM_COPY_ZERO:
1439 dwState |= NUM_WRITE_ON;
1440 /* Fall through */
1442 case FMT_NUM_COPY_SKIP:
1443 TRACE("write %d %sdigits or %s\n", pToken[1],
1444 dwState & NUM_WROTE_DEC ? "fractional " : "",
1445 *pToken == FMT_NUM_COPY_ZERO ? "0" : "skip");
1447 if (dwState & NUM_WROTE_DEC)
1449 int count, i;
1451 if (!(numHeader->flags & FMT_FLAG_EXPONENT) && exponent < 0)
1453 /* Pad with 0 before writing the fractional digits */
1454 pad = max(exponent, -pToken[1]);
1455 exponent -= pad;
1456 count = min(have_frac, pToken[1] + pad);
1457 for (i = 0; i > pad; i--)
1458 *pBuff++ = '0';
1460 else
1461 count = min(have_frac, pToken[1]);
1463 pad += pToken[1] - count;
1464 have_frac -= count;
1465 while (count--)
1466 *pBuff++ = '0' + *prgbDig++;
1467 if (*pToken == FMT_NUM_COPY_ZERO)
1469 for (; pad > 0; pad--)
1470 *pBuff++ = '0'; /* Write zeros for missing trailing digits */
1473 else
1475 int count, count_max, position;
1477 if ((np.dwOutFlags & NUMPRS_NEG) && !(dwState & NUM_WROTE_SIGN) && !header->starts[1])
1479 TRACE("write negative sign\n");
1480 localeValue = LOCALE_SNEGATIVESIGN;
1481 defaultChar = '-';
1482 dwState |= NUM_WROTE_SIGN;
1483 shouldAdvance = FALSE;
1484 break;
1487 position = have_int + pad;
1488 if (dwState & NUM_WRITE_ON)
1489 position = max(position, need_int);
1490 need_int -= pToken[1];
1491 count_max = have_int + pad - need_int;
1492 if (count_max < 0)
1493 count_max = 0;
1494 if (dwState & NUM_WRITE_ON)
1496 count = pToken[1] - count_max;
1497 TRACE("write %d leading zeros\n", count);
1498 while (count-- > 0)
1500 *pBuff++ = '0';
1501 if ((numHeader->flags & FMT_FLAG_THOUSANDS) &&
1502 position > 1 && (--position % 3) == 0)
1504 int k;
1505 TRACE("write thousand separator\n");
1506 for (k = 0; thousandSeparator[k]; k++)
1507 *pBuff++ = thousandSeparator[k];
1511 if (*pToken == FMT_NUM_COPY_ZERO || have_int > 1 ||
1512 (have_int > 0 && *prgbDig > 0))
1514 count = min(count_max, have_int);
1515 count_max -= count;
1516 have_int -= count;
1517 TRACE("write %d whole number digits\n", count);
1518 while (count--)
1520 dwState |= NUM_WRITE_ON;
1521 *pBuff++ = '0' + *prgbDig++;
1522 if ((numHeader->flags & FMT_FLAG_THOUSANDS) &&
1523 position > 1 && (--position % 3) == 0)
1525 int k;
1526 TRACE("write thousand separator\n");
1527 for (k = 0; thousandSeparator[k]; k++)
1528 *pBuff++ = thousandSeparator[k];
1532 count = min(count_max, pad);
1533 pad -= count;
1534 TRACE("write %d whole trailing 0's\n", count);
1535 while (count--)
1537 *pBuff++ = '0';
1538 if ((numHeader->flags & FMT_FLAG_THOUSANDS) &&
1539 position > 1 && (--position % 3) == 0)
1541 int k;
1542 TRACE("write thousand separator\n");
1543 for (k = 0; thousandSeparator[k]; k++)
1544 *pBuff++ = thousandSeparator[k];
1548 pToken++;
1549 break;
1551 default:
1552 ERR("Unknown token 0x%02x!\n", *pToken);
1553 hRes = E_INVALIDARG;
1554 goto VARIANT_FormatNumber_Exit;
1556 if (localeValue)
1558 if (GetLocaleInfoW(lcid, localeValue, pBuff,
1559 sizeof(buff)/sizeof(WCHAR)-(pBuff-buff)))
1561 TRACE("added %s\n", debugstr_w(pBuff));
1562 while (*pBuff)
1563 pBuff++;
1565 else
1567 TRACE("added %d '%c'\n", defaultChar, defaultChar);
1568 *pBuff++ = defaultChar;
1571 if (shouldAdvance)
1572 pToken++;
1575 VARIANT_FormatNumber_Exit:
1576 VariantClear(&vString);
1577 *pBuff = '\0';
1578 TRACE("buff is %s\n", debugstr_w(buff));
1579 if (SUCCEEDED(hRes))
1581 *pbstrOut = SysAllocString(buff);
1582 if (!*pbstrOut)
1583 hRes = E_OUTOFMEMORY;
1585 return hRes;
1588 /* Format a variant using a date format */
1589 static HRESULT VARIANT_FormatDate(LPVARIANT pVarIn, LPOLESTR lpszFormat,
1590 LPBYTE rgbTok, ULONG dwFlags,
1591 BSTR *pbstrOut, LCID lcid)
1593 WCHAR buff[256], *pBuff = buff;
1594 VARIANT vDate;
1595 UDATE udate;
1596 FMT_HEADER *header = (FMT_HEADER*)rgbTok;
1597 FMT_DATE_HEADER *dateHeader;
1598 const BYTE* pToken = NULL;
1599 HRESULT hRes;
1601 TRACE("(%s,%s,%p,0x%08x,%p,0x%08x)\n", debugstr_variant(pVarIn),
1602 debugstr_w(lpszFormat), rgbTok, dwFlags, pbstrOut, lcid);
1604 V_VT(&vDate) = VT_EMPTY;
1606 if (V_TYPE(pVarIn) == VT_EMPTY || V_TYPE(pVarIn) == VT_NULL)
1608 dateHeader = (FMT_DATE_HEADER*)(rgbTok + FmtGetNegative(header));
1609 V_DATE(&vDate) = 0;
1611 else
1613 USHORT usFlags = dwFlags & VARIANT_CALENDAR_HIJRI ? VAR_CALENDAR_HIJRI : 0;
1615 hRes = VariantChangeTypeEx(&vDate, pVarIn, lcid, usFlags, VT_DATE);
1616 if (FAILED(hRes))
1617 return hRes;
1618 dateHeader = (FMT_DATE_HEADER*)(rgbTok + FmtGetPositive(header));
1621 hRes = VarUdateFromDate(V_DATE(&vDate), 0 /* FIXME: flags? */, &udate);
1622 if (FAILED(hRes))
1623 return hRes;
1624 pToken = (const BYTE*)dateHeader + sizeof(FMT_DATE_HEADER);
1626 while (*pToken != FMT_GEN_END)
1628 DWORD dwVal = 0, localeValue = 0, dwFmt = 0;
1629 LPCWSTR szPrintFmt = NULL;
1630 WCHAR defaultChar = '?';
1632 if (pToken - rgbTok > header->size)
1634 ERR("Ran off the end of the format!\n");
1635 hRes = E_INVALIDARG;
1636 goto VARIANT_FormatDate_Exit;
1639 switch (*pToken)
1641 case FMT_GEN_COPY:
1642 TRACE("copy %s\n", debugstr_wn(lpszFormat + pToken[1], pToken[2]));
1643 memcpy(pBuff, lpszFormat + pToken[1], pToken[2] * sizeof(WCHAR));
1644 pBuff += pToken[2];
1645 pToken += 2;
1646 break;
1648 case FMT_GEN_INLINE:
1649 pToken += 2;
1650 TRACE("copy %s\n", debugstr_a((LPCSTR)pToken));
1651 while (*pToken)
1652 *pBuff++ = *pToken++;
1653 break;
1655 case FMT_DATE_TIME_SEP:
1656 TRACE("time separator\n");
1657 localeValue = LOCALE_STIME;
1658 defaultChar = ':';
1659 break;
1661 case FMT_DATE_DATE_SEP:
1662 TRACE("date separator\n");
1663 localeValue = LOCALE_SDATE;
1664 defaultChar = '/';
1665 break;
1667 case FMT_DATE_GENERAL:
1669 BSTR date = NULL;
1670 WCHAR *pDate;
1671 hRes = VarBstrFromDate(V_DATE(&vDate), lcid, 0, &date);
1672 if (FAILED(hRes))
1673 goto VARIANT_FormatDate_Exit;
1674 pDate = date;
1675 while (*pDate)
1676 *pBuff++ = *pDate++;
1677 SysFreeString(date);
1679 break;
1681 case FMT_DATE_QUARTER:
1682 if (udate.st.wMonth <= 3)
1683 *pBuff++ = '1';
1684 else if (udate.st.wMonth <= 6)
1685 *pBuff++ = '2';
1686 else if (udate.st.wMonth <= 9)
1687 *pBuff++ = '3';
1688 else
1689 *pBuff++ = '4';
1690 break;
1692 case FMT_DATE_TIME_SYS:
1694 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1695 BSTR date = NULL;
1696 WCHAR *pDate;
1697 hRes = VarBstrFromDate(V_DATE(&vDate), lcid, VAR_TIMEVALUEONLY, &date);
1698 if (FAILED(hRes))
1699 goto VARIANT_FormatDate_Exit;
1700 pDate = date;
1701 while (*pDate)
1702 *pBuff++ = *pDate++;
1703 SysFreeString(date);
1705 break;
1707 case FMT_DATE_DAY:
1708 szPrintFmt = szPercent_d;
1709 dwVal = udate.st.wDay;
1710 break;
1712 case FMT_DATE_DAY_0:
1713 szPrintFmt = szPercentZeroTwo_d;
1714 dwVal = udate.st.wDay;
1715 break;
1717 case FMT_DATE_DAY_SHORT:
1718 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1719 TRACE("short day\n");
1720 localeValue = LOCALE_SABBREVDAYNAME1 + (udate.st.wDayOfWeek + 6)%7;
1721 defaultChar = '?';
1722 break;
1724 case FMT_DATE_DAY_LONG:
1725 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1726 TRACE("long day\n");
1727 localeValue = LOCALE_SDAYNAME1 + (udate.st.wDayOfWeek + 6)%7;
1728 defaultChar = '?';
1729 break;
1731 case FMT_DATE_SHORT:
1732 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1733 dwFmt = LOCALE_SSHORTDATE;
1734 break;
1736 case FMT_DATE_LONG:
1737 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1738 dwFmt = LOCALE_SLONGDATE;
1739 break;
1741 case FMT_DATE_MEDIUM:
1742 FIXME("Medium date treated as long date\n");
1743 dwFmt = LOCALE_SLONGDATE;
1744 break;
1746 case FMT_DATE_DAY_WEEK:
1747 szPrintFmt = szPercent_d;
1748 if (pToken[1])
1749 dwVal = udate.st.wDayOfWeek + 2 - pToken[1];
1750 else
1752 GetLocaleInfoW(lcid,LOCALE_RETURN_NUMBER|LOCALE_IFIRSTDAYOFWEEK,
1753 (LPWSTR)&dwVal, sizeof(dwVal)/sizeof(WCHAR));
1754 dwVal = udate.st.wDayOfWeek + 1 - dwVal;
1756 pToken++;
1757 break;
1759 case FMT_DATE_WEEK_YEAR:
1760 szPrintFmt = szPercent_d;
1761 dwVal = udate.wDayOfYear / 7 + 1;
1762 pToken += 2;
1763 FIXME("Ignoring nFirstDay of %d, nFirstWeek of %d\n", pToken[0], pToken[1]);
1764 break;
1766 case FMT_DATE_MON:
1767 szPrintFmt = szPercent_d;
1768 dwVal = udate.st.wMonth;
1769 break;
1771 case FMT_DATE_MON_0:
1772 szPrintFmt = szPercentZeroTwo_d;
1773 dwVal = udate.st.wMonth;
1774 break;
1776 case FMT_DATE_MON_SHORT:
1777 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1778 TRACE("short month\n");
1779 localeValue = LOCALE_SABBREVMONTHNAME1 + udate.st.wMonth - 1;
1780 defaultChar = '?';
1781 break;
1783 case FMT_DATE_MON_LONG:
1784 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1785 TRACE("long month\n");
1786 localeValue = LOCALE_SMONTHNAME1 + udate.st.wMonth - 1;
1787 defaultChar = '?';
1788 break;
1790 case FMT_DATE_YEAR_DOY:
1791 szPrintFmt = szPercent_d;
1792 dwVal = udate.wDayOfYear;
1793 break;
1795 case FMT_DATE_YEAR_0:
1796 szPrintFmt = szPercentZeroTwo_d;
1797 dwVal = udate.st.wYear % 100;
1798 break;
1800 case FMT_DATE_YEAR_LONG:
1801 szPrintFmt = szPercent_d;
1802 dwVal = udate.st.wYear;
1803 break;
1805 case FMT_DATE_MIN:
1806 szPrintFmt = szPercent_d;
1807 dwVal = udate.st.wMinute;
1808 break;
1810 case FMT_DATE_MIN_0:
1811 szPrintFmt = szPercentZeroTwo_d;
1812 dwVal = udate.st.wMinute;
1813 break;
1815 case FMT_DATE_SEC:
1816 szPrintFmt = szPercent_d;
1817 dwVal = udate.st.wSecond;
1818 break;
1820 case FMT_DATE_SEC_0:
1821 szPrintFmt = szPercentZeroTwo_d;
1822 dwVal = udate.st.wSecond;
1823 break;
1825 case FMT_DATE_HOUR:
1826 szPrintFmt = szPercent_d;
1827 dwVal = udate.st.wHour;
1828 break;
1830 case FMT_DATE_HOUR_0:
1831 case FMT_DATE_TIME_UNK2:
1832 szPrintFmt = szPercentZeroTwo_d;
1833 dwVal = udate.st.wHour;
1834 break;
1836 case FMT_DATE_HOUR_12:
1837 szPrintFmt = szPercent_d;
1838 dwVal = udate.st.wHour ? udate.st.wHour > 12 ? udate.st.wHour - 12 : udate.st.wHour : 12;
1839 break;
1841 case FMT_DATE_HOUR_12_0:
1842 szPrintFmt = szPercentZeroTwo_d;
1843 dwVal = udate.st.wHour ? udate.st.wHour > 12 ? udate.st.wHour - 12 : udate.st.wHour : 12;
1844 break;
1846 case FMT_DATE_AMPM_SYS1:
1847 case FMT_DATE_AMPM_SYS2:
1848 localeValue = udate.st.wHour < 12 ? LOCALE_S1159 : LOCALE_S2359;
1849 defaultChar = '?';
1850 break;
1852 case FMT_DATE_AMPM_UPPER:
1853 *pBuff++ = udate.st.wHour < 12 ? 'A' : 'P';
1854 *pBuff++ = 'M';
1855 break;
1857 case FMT_DATE_A_UPPER:
1858 *pBuff++ = udate.st.wHour < 12 ? 'A' : 'P';
1859 break;
1861 case FMT_DATE_AMPM_LOWER:
1862 *pBuff++ = udate.st.wHour < 12 ? 'a' : 'p';
1863 *pBuff++ = 'm';
1864 break;
1866 case FMT_DATE_A_LOWER:
1867 *pBuff++ = udate.st.wHour < 12 ? 'a' : 'p';
1868 break;
1870 default:
1871 ERR("Unknown token 0x%02x!\n", *pToken);
1872 hRes = E_INVALIDARG;
1873 goto VARIANT_FormatDate_Exit;
1875 if (localeValue)
1877 *pBuff = '\0';
1878 if (GetLocaleInfoW(lcid, localeValue, pBuff,
1879 sizeof(buff)/sizeof(WCHAR)-(pBuff-buff)))
1881 TRACE("added %s\n", debugstr_w(pBuff));
1882 while (*pBuff)
1883 pBuff++;
1885 else
1887 TRACE("added %d %c\n", defaultChar, defaultChar);
1888 *pBuff++ = defaultChar;
1891 else if (dwFmt)
1893 WCHAR fmt_buff[80];
1895 if (!GetLocaleInfoW(lcid, dwFmt, fmt_buff, sizeof(fmt_buff)/sizeof(WCHAR)) ||
1896 !get_date_format(lcid, 0, &udate.st, fmt_buff, pBuff,
1897 sizeof(buff)/sizeof(WCHAR)-(pBuff-buff)))
1899 hRes = E_INVALIDARG;
1900 goto VARIANT_FormatDate_Exit;
1902 while (*pBuff)
1903 pBuff++;
1905 else if (szPrintFmt)
1907 sprintfW(pBuff, szPrintFmt, dwVal);
1908 while (*pBuff)
1909 pBuff++;
1911 pToken++;
1914 VARIANT_FormatDate_Exit:
1915 *pBuff = '\0';
1916 TRACE("buff is %s\n", debugstr_w(buff));
1917 if (SUCCEEDED(hRes))
1919 *pbstrOut = SysAllocString(buff);
1920 if (!*pbstrOut)
1921 hRes = E_OUTOFMEMORY;
1923 return hRes;
1926 /* Format a variant using a string format */
1927 static HRESULT VARIANT_FormatString(LPVARIANT pVarIn, LPOLESTR lpszFormat,
1928 LPBYTE rgbTok, ULONG dwFlags,
1929 BSTR *pbstrOut, LCID lcid)
1931 static WCHAR szEmpty[] = { '\0' };
1932 WCHAR buff[256], *pBuff = buff;
1933 WCHAR *pSrc;
1934 FMT_HEADER *header = (FMT_HEADER*)rgbTok;
1935 FMT_STRING_HEADER *strHeader;
1936 const BYTE* pToken = NULL;
1937 VARIANT vStr;
1938 int blanks_first;
1939 BOOL bUpper = FALSE;
1940 HRESULT hRes = S_OK;
1942 TRACE("%s,%s,%p,0x%08x,%p,0x%08x)\n", debugstr_variant(pVarIn), debugstr_w(lpszFormat),
1943 rgbTok, dwFlags, pbstrOut, lcid);
1945 V_VT(&vStr) = VT_EMPTY;
1947 if (V_TYPE(pVarIn) == VT_EMPTY || V_TYPE(pVarIn) == VT_NULL)
1949 strHeader = (FMT_STRING_HEADER*)(rgbTok + FmtGetNegative(header));
1950 V_BSTR(&vStr) = szEmpty;
1952 else
1954 hRes = VariantChangeTypeEx(&vStr, pVarIn, lcid, VARIANT_NOUSEROVERRIDE, VT_BSTR);
1955 if (FAILED(hRes))
1956 return hRes;
1958 if (V_BSTR(&vStr)[0] == '\0')
1959 strHeader = (FMT_STRING_HEADER*)(rgbTok + FmtGetNegative(header));
1960 else
1961 strHeader = (FMT_STRING_HEADER*)(rgbTok + FmtGetPositive(header));
1963 pSrc = V_BSTR(&vStr);
1964 if ((strHeader->flags & (FMT_FLAG_LT|FMT_FLAG_GT)) == FMT_FLAG_GT)
1965 bUpper = TRUE;
1966 blanks_first = strHeader->copy_chars - strlenW(pSrc);
1967 pToken = (const BYTE*)strHeader + sizeof(FMT_DATE_HEADER);
1969 while (*pToken != FMT_GEN_END)
1971 int dwCount = 0;
1973 if (pToken - rgbTok > header->size)
1975 ERR("Ran off the end of the format!\n");
1976 hRes = E_INVALIDARG;
1977 goto VARIANT_FormatString_Exit;
1980 switch (*pToken)
1982 case FMT_GEN_COPY:
1983 TRACE("copy %s\n", debugstr_wn(lpszFormat + pToken[1], pToken[2]));
1984 memcpy(pBuff, lpszFormat + pToken[1], pToken[2] * sizeof(WCHAR));
1985 pBuff += pToken[2];
1986 pToken += 2;
1987 break;
1989 case FMT_STR_COPY_SPACE:
1990 case FMT_STR_COPY_SKIP:
1991 dwCount = pToken[1];
1992 if (*pToken == FMT_STR_COPY_SPACE && blanks_first > 0)
1994 TRACE("insert %d initial spaces\n", blanks_first);
1995 while (dwCount > 0 && blanks_first > 0)
1997 *pBuff++ = ' ';
1998 dwCount--;
1999 blanks_first--;
2002 TRACE("copy %d chars%s\n", dwCount,
2003 *pToken == FMT_STR_COPY_SPACE ? " with space" :"");
2004 while (dwCount > 0 && *pSrc)
2006 if (bUpper)
2007 *pBuff++ = toupperW(*pSrc);
2008 else
2009 *pBuff++ = tolowerW(*pSrc);
2010 dwCount--;
2011 pSrc++;
2013 if (*pToken == FMT_STR_COPY_SPACE && dwCount > 0)
2015 TRACE("insert %d spaces\n", dwCount);
2016 while (dwCount-- > 0)
2017 *pBuff++ = ' ';
2019 pToken++;
2020 break;
2022 default:
2023 ERR("Unknown token 0x%02x!\n", *pToken);
2024 hRes = E_INVALIDARG;
2025 goto VARIANT_FormatString_Exit;
2027 pToken++;
2030 VARIANT_FormatString_Exit:
2031 /* Copy out any remaining chars */
2032 while (*pSrc)
2034 if (bUpper)
2035 *pBuff++ = toupperW(*pSrc);
2036 else
2037 *pBuff++ = tolowerW(*pSrc);
2038 pSrc++;
2040 VariantClear(&vStr);
2041 *pBuff = '\0';
2042 TRACE("buff is %s\n", debugstr_w(buff));
2043 if (SUCCEEDED(hRes))
2045 *pbstrOut = SysAllocString(buff);
2046 if (!*pbstrOut)
2047 hRes = E_OUTOFMEMORY;
2049 return hRes;
2052 #define NUMBER_VTBITS (VTBIT_I1|VTBIT_UI1|VTBIT_I2|VTBIT_UI2| \
2053 VTBIT_I4|VTBIT_UI4|VTBIT_I8|VTBIT_UI8| \
2054 VTBIT_R4|VTBIT_R8|VTBIT_CY|VTBIT_DECIMAL| \
2055 VTBIT_BOOL|VTBIT_INT|VTBIT_UINT)
2057 /**********************************************************************
2058 * VarFormatFromTokens [OLEAUT32.139]
2060 HRESULT WINAPI VarFormatFromTokens(LPVARIANT pVarIn, LPOLESTR lpszFormat,
2061 LPBYTE rgbTok, ULONG dwFlags,
2062 BSTR *pbstrOut, LCID lcid)
2064 FMT_SHORT_HEADER *header = (FMT_SHORT_HEADER *)rgbTok;
2065 VARIANT vTmp;
2066 HRESULT hres;
2068 TRACE("(%p,%s,%p,%x,%p,0x%08x)\n", pVarIn, debugstr_w(lpszFormat),
2069 rgbTok, dwFlags, pbstrOut, lcid);
2071 if (!pbstrOut)
2072 return E_INVALIDARG;
2074 *pbstrOut = NULL;
2076 if (!pVarIn || !rgbTok)
2077 return E_INVALIDARG;
2079 if (V_VT(pVarIn) == VT_NULL)
2080 return S_OK;
2082 if (*rgbTok == FMT_TO_STRING || header->type == FMT_TYPE_GENERAL)
2084 /* According to MSDN, general format acts somewhat like the 'Str'
2085 * function in Visual Basic.
2087 VarFormatFromTokens_AsStr:
2088 V_VT(&vTmp) = VT_EMPTY;
2089 hres = VariantChangeTypeEx(&vTmp, pVarIn, lcid, dwFlags, VT_BSTR);
2090 *pbstrOut = V_BSTR(&vTmp);
2092 else
2094 if (header->type == FMT_TYPE_NUMBER ||
2095 (header->type == FMT_TYPE_UNKNOWN && ((1 << V_TYPE(pVarIn)) & NUMBER_VTBITS)))
2097 hres = VARIANT_FormatNumber(pVarIn, lpszFormat, rgbTok, dwFlags, pbstrOut, lcid);
2099 else if (header->type == FMT_TYPE_DATE ||
2100 (header->type == FMT_TYPE_UNKNOWN && V_TYPE(pVarIn) == VT_DATE))
2102 hres = VARIANT_FormatDate(pVarIn, lpszFormat, rgbTok, dwFlags, pbstrOut, lcid);
2104 else if (header->type == FMT_TYPE_STRING || V_TYPE(pVarIn) == VT_BSTR)
2106 hres = VARIANT_FormatString(pVarIn, lpszFormat, rgbTok, dwFlags, pbstrOut, lcid);
2108 else
2110 ERR("unrecognised format type 0x%02x\n", header->type);
2111 return E_INVALIDARG;
2113 /* If the coercion failed, still try to create output, unless the
2114 * VAR_FORMAT_NOSUBSTITUTE flag is set.
2116 if ((hres == DISP_E_OVERFLOW || hres == DISP_E_TYPEMISMATCH) &&
2117 !(dwFlags & VAR_FORMAT_NOSUBSTITUTE))
2118 goto VarFormatFromTokens_AsStr;
2121 return hres;
2124 /**********************************************************************
2125 * VarFormat [OLEAUT32.87]
2127 * Format a variant from a format string.
2129 * PARAMS
2130 * pVarIn [I] Variant to format
2131 * lpszFormat [I] Format string (see notes)
2132 * nFirstDay [I] First day of the week, (See VarTokenizeFormatString() for details)
2133 * nFirstWeek [I] First week of the year (See VarTokenizeFormatString() for details)
2134 * dwFlags [I] Flags for the format (VAR_ flags from "oleauto.h")
2135 * pbstrOut [O] Destination for formatted string.
2137 * RETURNS
2138 * Success: S_OK. pbstrOut contains the formatted value.
2139 * Failure: E_INVALIDARG, if any parameter is invalid.
2140 * E_OUTOFMEMORY, if enough memory cannot be allocated.
2141 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
2143 * NOTES
2144 * - See Variant-Formats for details concerning creating format strings.
2145 * - This function uses LOCALE_USER_DEFAULT when calling VarTokenizeFormatString()
2146 * and VarFormatFromTokens().
2148 HRESULT WINAPI VarFormat(LPVARIANT pVarIn, LPOLESTR lpszFormat,
2149 int nFirstDay, int nFirstWeek, ULONG dwFlags,
2150 BSTR *pbstrOut)
2152 BYTE buff[256];
2153 HRESULT hres;
2155 TRACE("(%s,%s,%d,%d,0x%08x,%p)\n", debugstr_variant(pVarIn), debugstr_w(lpszFormat),
2156 nFirstDay, nFirstWeek, dwFlags, pbstrOut);
2158 if (!pbstrOut)
2159 return E_INVALIDARG;
2160 *pbstrOut = NULL;
2162 hres = VarTokenizeFormatString(lpszFormat, buff, sizeof(buff), nFirstDay,
2163 nFirstWeek, LOCALE_USER_DEFAULT, NULL);
2164 if (SUCCEEDED(hres))
2165 hres = VarFormatFromTokens(pVarIn, lpszFormat, buff, dwFlags,
2166 pbstrOut, LOCALE_USER_DEFAULT);
2167 TRACE("returning 0x%08x, %s\n", hres, debugstr_w(*pbstrOut));
2168 return hres;
2171 /**********************************************************************
2172 * VarFormatDateTime [OLEAUT32.97]
2174 * Format a variant value as a date and/or time.
2176 * PARAMS
2177 * pVarIn [I] Variant to format
2178 * nFormat [I] Format type (see notes)
2179 * dwFlags [I] Flags for the format (VAR_ flags from "oleauto.h")
2180 * pbstrOut [O] Destination for formatted string.
2182 * RETURNS
2183 * Success: S_OK. pbstrOut contains the formatted value.
2184 * Failure: E_INVALIDARG, if any parameter is invalid.
2185 * E_OUTOFMEMORY, if enough memory cannot be allocated.
2186 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
2188 * NOTES
2189 * This function uses LOCALE_USER_DEFAULT when determining the date format
2190 * characters to use.
2191 * Possible values for the nFormat parameter are:
2192 *| Value Meaning
2193 *| ----- -------
2194 *| 0 General date format
2195 *| 1 Long date format
2196 *| 2 Short date format
2197 *| 3 Long time format
2198 *| 4 Short time format
2200 HRESULT WINAPI VarFormatDateTime(LPVARIANT pVarIn, INT nFormat, ULONG dwFlags, BSTR *pbstrOut)
2202 static WCHAR szEmpty[] = { '\0' };
2203 const BYTE* lpFmt = NULL;
2205 TRACE("%s,%d,0x%08x,%p)\n", debugstr_variant(pVarIn), nFormat, dwFlags, pbstrOut);
2207 if (!pVarIn || !pbstrOut || nFormat < 0 || nFormat > 4)
2208 return E_INVALIDARG;
2210 switch (nFormat)
2212 case 0: lpFmt = fmtGeneralDate; break;
2213 case 1: lpFmt = fmtLongDate; break;
2214 case 2: lpFmt = fmtShortDate; break;
2215 case 3: lpFmt = fmtLongTime; break;
2216 case 4: lpFmt = fmtShortTime; break;
2218 return VarFormatFromTokens(pVarIn, szEmpty, (BYTE*)lpFmt, dwFlags,
2219 pbstrOut, LOCALE_USER_DEFAULT);
2222 #define GETLOCALENUMBER(type,field) GetLocaleInfoW(LOCALE_USER_DEFAULT, \
2223 type|LOCALE_RETURN_NUMBER, \
2224 (LPWSTR)&numfmt.field, \
2225 sizeof(numfmt.field)/sizeof(WCHAR))
2227 /**********************************************************************
2228 * VarFormatNumber [OLEAUT32.107]
2230 * Format a variant value as a number.
2232 * PARAMS
2233 * pVarIn [I] Variant to format
2234 * nDigits [I] Number of digits following the decimal point (-1 = user default)
2235 * nLeading [I] Use a leading zero (-2 = user default, -1 = yes, 0 = no)
2236 * nParens [I] Use brackets for values < 0 (-2 = user default, -1 = yes, 0 = no)
2237 * nGrouping [I] Use grouping characters (-2 = user default, -1 = yes, 0 = no)
2238 * dwFlags [I] Currently unused, set to zero
2239 * pbstrOut [O] Destination for formatted string.
2241 * RETURNS
2242 * Success: S_OK. pbstrOut contains the formatted value.
2243 * Failure: E_INVALIDARG, if any parameter is invalid.
2244 * E_OUTOFMEMORY, if enough memory cannot be allocated.
2245 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
2247 * NOTES
2248 * This function uses LOCALE_USER_DEFAULT when determining the number format
2249 * characters to use.
2251 HRESULT WINAPI VarFormatNumber(LPVARIANT pVarIn, INT nDigits, INT nLeading, INT nParens,
2252 INT nGrouping, ULONG dwFlags, BSTR *pbstrOut)
2254 HRESULT hRet;
2255 VARIANT vStr;
2257 TRACE("(%s,%d,%d,%d,%d,0x%08x,%p)\n", debugstr_variant(pVarIn), nDigits, nLeading,
2258 nParens, nGrouping, dwFlags, pbstrOut);
2260 if (!pVarIn || !pbstrOut || nDigits > 9)
2261 return E_INVALIDARG;
2263 *pbstrOut = NULL;
2265 V_VT(&vStr) = VT_EMPTY;
2266 hRet = VariantCopyInd(&vStr, pVarIn);
2268 if (SUCCEEDED(hRet))
2269 hRet = VariantChangeTypeEx(&vStr, &vStr, LCID_US, 0, VT_BSTR);
2271 if (SUCCEEDED(hRet))
2273 WCHAR buff[256], decimal[8], thousands[8];
2274 NUMBERFMTW numfmt;
2276 /* Although MSDN makes it clear that the native versions of these functions
2277 * are implemented using VarTokenizeFormatString()/VarFormatFromTokens(),
2278 * using NLS gives us the same result.
2280 if (nDigits < 0)
2281 GETLOCALENUMBER(LOCALE_IDIGITS, NumDigits);
2282 else
2283 numfmt.NumDigits = nDigits;
2285 if (nLeading == -2)
2286 GETLOCALENUMBER(LOCALE_ILZERO, LeadingZero);
2287 else if (nLeading == -1)
2288 numfmt.LeadingZero = 1;
2289 else
2290 numfmt.LeadingZero = 0;
2292 if (nGrouping == -2)
2294 WCHAR grouping[16];
2295 grouping[2] = '\0';
2296 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, grouping,
2297 sizeof(grouping)/sizeof(WCHAR));
2298 numfmt.Grouping = grouping[2] == '2' ? 32 : grouping[0] - '0';
2300 else if (nGrouping == -1)
2301 numfmt.Grouping = 3; /* 3 = "n,nnn.nn" */
2302 else
2303 numfmt.Grouping = 0; /* 0 = No grouping */
2305 if (nParens == -2)
2306 GETLOCALENUMBER(LOCALE_INEGNUMBER, NegativeOrder);
2307 else if (nParens == -1)
2308 numfmt.NegativeOrder = 0; /* 0 = "(xxx)" */
2309 else
2310 numfmt.NegativeOrder = 1; /* 1 = "-xxx" */
2312 numfmt.lpDecimalSep = decimal;
2313 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, decimal,
2314 sizeof(decimal)/sizeof(WCHAR));
2315 numfmt.lpThousandSep = thousands;
2316 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, thousands,
2317 sizeof(thousands)/sizeof(WCHAR));
2319 if (GetNumberFormatW(LOCALE_USER_DEFAULT, 0, V_BSTR(&vStr), &numfmt,
2320 buff, sizeof(buff)/sizeof(WCHAR)))
2322 *pbstrOut = SysAllocString(buff);
2323 if (!*pbstrOut)
2324 hRet = E_OUTOFMEMORY;
2326 else
2327 hRet = DISP_E_TYPEMISMATCH;
2329 SysFreeString(V_BSTR(&vStr));
2331 return hRet;
2334 /**********************************************************************
2335 * VarFormatPercent [OLEAUT32.117]
2337 * Format a variant value as a percentage.
2339 * PARAMS
2340 * pVarIn [I] Variant to format
2341 * nDigits [I] Number of digits following the decimal point (-1 = user default)
2342 * nLeading [I] Use a leading zero (-2 = user default, -1 = yes, 0 = no)
2343 * nParens [I] Use brackets for values < 0 (-2 = user default, -1 = yes, 0 = no)
2344 * nGrouping [I] Use grouping characters (-2 = user default, -1 = yes, 0 = no)
2345 * dwFlags [I] Currently unused, set to zero
2346 * pbstrOut [O] Destination for formatted string.
2348 * RETURNS
2349 * Success: S_OK. pbstrOut contains the formatted value.
2350 * Failure: E_INVALIDARG, if any parameter is invalid.
2351 * E_OUTOFMEMORY, if enough memory cannot be allocated.
2352 * DISP_E_OVERFLOW, if overflow occurs during the conversion.
2353 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
2355 * NOTES
2356 * This function uses LOCALE_USER_DEFAULT when determining the number format
2357 * characters to use.
2359 HRESULT WINAPI VarFormatPercent(LPVARIANT pVarIn, INT nDigits, INT nLeading, INT nParens,
2360 INT nGrouping, ULONG dwFlags, BSTR *pbstrOut)
2362 static const WCHAR szPercent[] = { '%','\0' };
2363 static const WCHAR szPercentBracket[] = { '%',')','\0' };
2364 WCHAR buff[256];
2365 HRESULT hRet;
2366 VARIANT vDbl;
2368 TRACE("(%s,%d,%d,%d,%d,0x%08x,%p)\n", debugstr_variant(pVarIn), nDigits, nLeading,
2369 nParens, nGrouping, dwFlags, pbstrOut);
2371 if (!pVarIn || !pbstrOut || nDigits > 9)
2372 return E_INVALIDARG;
2374 *pbstrOut = NULL;
2376 V_VT(&vDbl) = VT_EMPTY;
2377 hRet = VariantCopyInd(&vDbl, pVarIn);
2379 if (SUCCEEDED(hRet))
2381 hRet = VariantChangeTypeEx(&vDbl, &vDbl, LOCALE_USER_DEFAULT, 0, VT_R8);
2383 if (SUCCEEDED(hRet))
2385 if (V_R8(&vDbl) > (R8_MAX / 100.0))
2386 return DISP_E_OVERFLOW;
2388 V_R8(&vDbl) *= 100.0;
2389 hRet = VarFormatNumber(&vDbl, nDigits, nLeading, nParens,
2390 nGrouping, dwFlags, pbstrOut);
2392 if (SUCCEEDED(hRet))
2394 DWORD dwLen = strlenW(*pbstrOut);
2395 BOOL bBracket = (*pbstrOut)[dwLen] == ')';
2397 dwLen -= bBracket;
2398 memcpy(buff, *pbstrOut, dwLen * sizeof(WCHAR));
2399 strcpyW(buff + dwLen, bBracket ? szPercentBracket : szPercent);
2400 SysFreeString(*pbstrOut);
2401 *pbstrOut = SysAllocString(buff);
2402 if (!*pbstrOut)
2403 hRet = E_OUTOFMEMORY;
2407 return hRet;
2410 /**********************************************************************
2411 * VarFormatCurrency [OLEAUT32.127]
2413 * Format a variant value as a currency.
2415 * PARAMS
2416 * pVarIn [I] Variant to format
2417 * nDigits [I] Number of digits following the decimal point (-1 = user default)
2418 * nLeading [I] Use a leading zero (-2 = user default, -1 = yes, 0 = no)
2419 * nParens [I] Use brackets for values < 0 (-2 = user default, -1 = yes, 0 = no)
2420 * nGrouping [I] Use grouping characters (-2 = user default, -1 = yes, 0 = no)
2421 * dwFlags [I] Currently unused, set to zero
2422 * pbstrOut [O] Destination for formatted string.
2424 * RETURNS
2425 * Success: S_OK. pbstrOut contains the formatted value.
2426 * Failure: E_INVALIDARG, if any parameter is invalid.
2427 * E_OUTOFMEMORY, if enough memory cannot be allocated.
2428 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
2430 * NOTES
2431 * This function uses LOCALE_USER_DEFAULT when determining the currency format
2432 * characters to use.
2434 HRESULT WINAPI VarFormatCurrency(LPVARIANT pVarIn, INT nDigits, INT nLeading,
2435 INT nParens, INT nGrouping, ULONG dwFlags,
2436 BSTR *pbstrOut)
2438 HRESULT hRet;
2439 VARIANT vStr;
2441 TRACE("(%s,%d,%d,%d,%d,0x%08x,%p)\n", debugstr_variant(pVarIn), nDigits, nLeading,
2442 nParens, nGrouping, dwFlags, pbstrOut);
2444 if (!pVarIn || !pbstrOut || nDigits > 9)
2445 return E_INVALIDARG;
2447 *pbstrOut = NULL;
2449 V_VT(&vStr) = VT_EMPTY;
2450 hRet = VariantCopyInd(&vStr, pVarIn);
2452 if (SUCCEEDED(hRet))
2453 hRet = VariantChangeTypeEx(&vStr, &vStr, LOCALE_USER_DEFAULT, 0, VT_BSTR);
2455 if (SUCCEEDED(hRet))
2457 WCHAR buff[256], decimal[8], thousands[8], currency[8];
2458 CURRENCYFMTW numfmt;
2460 if (nDigits < 0)
2461 GETLOCALENUMBER(LOCALE_IDIGITS, NumDigits);
2462 else
2463 numfmt.NumDigits = nDigits;
2465 if (nLeading == -2)
2466 GETLOCALENUMBER(LOCALE_ILZERO, LeadingZero);
2467 else if (nLeading == -1)
2468 numfmt.LeadingZero = 1;
2469 else
2470 numfmt.LeadingZero = 0;
2472 if (nGrouping == -2)
2474 WCHAR grouping[16];
2475 grouping[2] = '\0';
2476 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, grouping,
2477 sizeof(grouping)/sizeof(WCHAR));
2478 numfmt.Grouping = grouping[2] == '2' ? 32 : grouping[0] - '0';
2480 else if (nGrouping == -1)
2481 numfmt.Grouping = 3; /* 3 = "n,nnn.nn" */
2482 else
2483 numfmt.Grouping = 0; /* 0 = No grouping */
2485 if (nParens == -2)
2486 GETLOCALENUMBER(LOCALE_INEGCURR, NegativeOrder);
2487 else if (nParens == -1)
2488 numfmt.NegativeOrder = 0; /* 0 = "(xxx)" */
2489 else
2490 numfmt.NegativeOrder = 1; /* 1 = "-xxx" */
2492 GETLOCALENUMBER(LOCALE_ICURRENCY, PositiveOrder);
2494 numfmt.lpDecimalSep = decimal;
2495 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, decimal,
2496 sizeof(decimal)/sizeof(WCHAR));
2497 numfmt.lpThousandSep = thousands;
2498 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, thousands,
2499 sizeof(thousands)/sizeof(WCHAR));
2500 numfmt.lpCurrencySymbol = currency;
2501 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, currency,
2502 sizeof(currency)/sizeof(WCHAR));
2504 /* use NLS as per VarFormatNumber() */
2505 if (GetCurrencyFormatW(LOCALE_USER_DEFAULT, 0, V_BSTR(&vStr), &numfmt,
2506 buff, sizeof(buff)/sizeof(WCHAR)))
2508 *pbstrOut = SysAllocString(buff);
2509 if (!*pbstrOut)
2510 hRet = E_OUTOFMEMORY;
2512 else
2513 hRet = DISP_E_TYPEMISMATCH;
2515 SysFreeString(V_BSTR(&vStr));
2517 return hRet;
2520 /**********************************************************************
2521 * VarMonthName [OLEAUT32.129]
2523 * Print the specified month as localized name.
2525 * PARAMS
2526 * iMonth [I] month number 1..12
2527 * fAbbrev [I] 0 - full name, !0 - abbreviated name
2528 * dwFlags [I] flag stuff. only VAR_CALENDAR_HIJRI possible.
2529 * pbstrOut [O] Destination for month name
2531 * RETURNS
2532 * Success: S_OK. pbstrOut contains the name.
2533 * Failure: E_INVALIDARG, if any parameter is invalid.
2534 * E_OUTOFMEMORY, if enough memory cannot be allocated.
2536 HRESULT WINAPI VarMonthName(INT iMonth, INT fAbbrev, ULONG dwFlags, BSTR *pbstrOut)
2538 DWORD localeValue;
2539 INT size;
2541 if ((iMonth < 1) || (iMonth > 12))
2542 return E_INVALIDARG;
2544 if (dwFlags)
2545 FIXME("Does not support dwFlags 0x%x, ignoring.\n", dwFlags);
2547 if (fAbbrev)
2548 localeValue = LOCALE_SABBREVMONTHNAME1 + iMonth - 1;
2549 else
2550 localeValue = LOCALE_SMONTHNAME1 + iMonth - 1;
2552 size = GetLocaleInfoW(LOCALE_USER_DEFAULT,localeValue, NULL, 0);
2553 if (!size) {
2554 ERR("GetLocaleInfo 0x%x failed.\n", localeValue);
2555 return HRESULT_FROM_WIN32(GetLastError());
2557 *pbstrOut = SysAllocStringLen(NULL,size - 1);
2558 if (!*pbstrOut)
2559 return E_OUTOFMEMORY;
2560 size = GetLocaleInfoW(LOCALE_USER_DEFAULT,localeValue, *pbstrOut, size);
2561 if (!size) {
2562 ERR("GetLocaleInfo of 0x%x failed in 2nd stage?!\n", localeValue);
2563 SysFreeString(*pbstrOut);
2564 return HRESULT_FROM_WIN32(GetLastError());
2566 return S_OK;
2569 /**********************************************************************
2570 * VarWeekdayName [OLEAUT32.129]
2572 * Print the specified weekday as localized name.
2574 * PARAMS
2575 * iWeekday [I] day of week, 1..7, 1="the first day of the week"
2576 * fAbbrev [I] 0 - full name, !0 - abbreviated name
2577 * iFirstDay [I] first day of week,
2578 * 0=system default, 1=Sunday, 2=Monday, .. (contrary to MSDN)
2579 * dwFlags [I] flag stuff. only VAR_CALENDAR_HIJRI possible.
2580 * pbstrOut [O] Destination for weekday name.
2582 * RETURNS
2583 * Success: S_OK, pbstrOut contains the name.
2584 * Failure: E_INVALIDARG, if any parameter is invalid.
2585 * E_OUTOFMEMORY, if enough memory cannot be allocated.
2587 HRESULT WINAPI VarWeekdayName(INT iWeekday, INT fAbbrev, INT iFirstDay,
2588 ULONG dwFlags, BSTR *pbstrOut)
2590 DWORD localeValue;
2591 INT size;
2593 /* Windows XP oleaut32.dll doesn't allow iWekday==0, contrary to MSDN */
2594 if (iWeekday < 1 || iWeekday > 7)
2595 return E_INVALIDARG;
2596 if (iFirstDay < 0 || iFirstDay > 7)
2597 return E_INVALIDARG;
2598 if (!pbstrOut)
2599 return E_INVALIDARG;
2601 if (dwFlags)
2602 FIXME("Does not support dwFlags 0x%x, ignoring.\n", dwFlags);
2604 /* If we have to use the default firstDay, find which one it is */
2605 if (iFirstDay == 0) {
2606 DWORD firstDay;
2607 localeValue = LOCALE_RETURN_NUMBER | LOCALE_IFIRSTDAYOFWEEK;
2608 size = GetLocaleInfoW(LOCALE_USER_DEFAULT, localeValue,
2609 (LPWSTR)&firstDay, sizeof(firstDay) / sizeof(WCHAR));
2610 if (!size) {
2611 ERR("GetLocaleInfo 0x%x failed.\n", localeValue);
2612 return HRESULT_FROM_WIN32(GetLastError());
2614 iFirstDay = firstDay + 2;
2617 /* Determine what we need to return */
2618 localeValue = fAbbrev ? LOCALE_SABBREVDAYNAME1 : LOCALE_SDAYNAME1;
2619 localeValue += (7 + iWeekday - 1 + iFirstDay - 2) % 7;
2621 /* Determine the size of the data, allocate memory and retrieve the data */
2622 size = GetLocaleInfoW(LOCALE_USER_DEFAULT, localeValue, NULL, 0);
2623 if (!size) {
2624 ERR("GetLocaleInfo 0x%x failed.\n", localeValue);
2625 return HRESULT_FROM_WIN32(GetLastError());
2627 *pbstrOut = SysAllocStringLen(NULL, size - 1);
2628 if (!*pbstrOut)
2629 return E_OUTOFMEMORY;
2630 size = GetLocaleInfoW(LOCALE_USER_DEFAULT, localeValue, *pbstrOut, size);
2631 if (!size) {
2632 ERR("GetLocaleInfo 0x%x failed in 2nd stage?!\n", localeValue);
2633 SysFreeString(*pbstrOut);
2634 return HRESULT_FROM_WIN32(GetLastError());
2636 return S_OK;