1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 ////////////////////////////////////////////////////////////////////////////
10 // Purpose: This module implements the methods of the CalendarData
11 // class. These methods are the helper functions for the
16 ////////////////////////////////////////////////////////////////////////////
22 #include "interoputil.h"
27 #include "calendardata.h"
31 ////////////////////////////////////////////////////////////////////////
33 // Call the Win32 GetCalendarInfoEx() using the specified calendar and LCTYPE.
34 // The return value can be INT32 or an allocated managed string object, depending on
35 // which version's called.
38 // OUT pOutputInt32 The output int32 value.
39 // OUT pOutputRef The output string value.
41 ////////////////////////////////////////////////////////////////////////
42 BOOL CalendarData::CallGetCalendarInfoEx(LPCWSTR localeName, int calendar, int calType, INT32* pOutputInt32)
53 BEGIN_SO_INTOLERANT_CODE_NOTHROW(GetThread(), return result; )
55 // Just stick it right into the output int
56 _ASSERT((calType & CAL_RETURN_NUMBER) != 0);
57 result = NewApis::GetCalendarInfoEx(localeName, calendar, NULL, calType, NULL, 0, (LPDWORD)pOutputInt32);
59 END_SO_INTOLERANT_CODE
64 BOOL CalendarData::CallGetCalendarInfoEx(LPCWSTR localeName, int calendar, int calType, STRINGREF* pOutputStrRef)
68 THROWS; // We can throw since we are allocating managed string.
69 DISABLED(GC_TRIGGERS); // Disabled 'cause it don't work right now
74 // The maximum size for values returned from GetLocaleInfo is 80 characters.
78 BEGIN_SO_INTOLERANT_CODE_NOTHROW(GetThread(), return result; )
80 _ASSERT((calType & CAL_RETURN_NUMBER) == 0);
81 result = NewApis::GetCalendarInfoEx(localeName, calendar, NULL, calType, buffer, 80, NULL);
85 _ASSERTE(pOutputStrRef != NULL);
86 *pOutputStrRef = StringObject::NewString(buffer, result - 1);
89 END_SO_INTOLERANT_CODE
94 ////////////////////////////////////////////////////////////////////////
96 // Get the native day names
98 // NOTE: There's a disparity between .Net & windows day orders, the input day should
102 // OUT pOutputStrings The output string[] value.
104 ////////////////////////////////////////////////////////////////////////
105 BOOL CalendarData::GetCalendarDayInfo(LPCWSTR localeName, int calendar, int calType, PTRARRAYREF* pOutputStrings)
109 THROWS; // We can throw since we are allocating managed string.
110 INJECT_FAULT(COMPlusThrowOM());
116 // The maximum size for values returned from GetLocaleInfo is 80 characters.
120 _ASSERT((calType & CAL_RETURN_NUMBER) == 0);
122 BEGIN_SO_INTOLERANT_CODE_NOTHROW(GetThread(), return result;)
125 // We'll need a new array of 7 items
127 // Allocate the array of STRINGREFs. We don't need to check for null because the GC will throw
128 // an OutOfMemoryException if there's not enough memory.
130 PTRARRAYREF ResultArray = (PTRARRAYREF)AllocateObjectArray(7, g_pStringClass);
132 GCPROTECT_BEGIN(ResultArray);
134 // Get each one of them
135 for (int i = 0; i < 7; i++, calType++)
137 result = NewApis::GetCalendarInfoEx(localeName, calendar, NULL, calType, buffer, 80, NULL);
139 // Note that the returned string is null terminated, so even an empty string will be 1
142 // Make a string for this entry
143 STRINGREF stringResult = StringObject::NewString(buffer, result - 1);
144 ResultArray->SetAt(i, (OBJECTREF)stringResult);
147 // On the first iteration we need to go from CAL_SDAYNAME7 to CAL_SDAYNAME1, so subtract 7 before the ++ happens
148 // This is because the framework starts on sunday and windows starts on monday when counting days
149 if (i == 0) calType -= 7;
153 _ASSERTE(pOutputStrings != NULL);
154 *pOutputStrings = ResultArray;
156 END_SO_INTOLERANT_CODE
158 return (result != 0);
163 ////////////////////////////////////////////////////////////////////////
165 // Get the native month names
168 // OUT pOutputStrings The output string[] value.
170 ////////////////////////////////////////////////////////////////////////
171 BOOL CalendarData::GetCalendarMonthInfo(LPCWSTR localeName, int calendar, int calType, PTRARRAYREF* pOutputStrings)
175 THROWS; // We can throw since we are allocating managed string.
176 INJECT_FAULT(COMPlusThrowOM());
182 // The maximum size for values returned from GetLocaleInfo is 80 characters.
186 _ASSERT((calType & CAL_RETURN_NUMBER) == 0);
188 BEGIN_SO_INTOLERANT_CODE_NOTHROW(GetThread(), return result;)
191 // We'll need a new array of 13 items
193 // Allocate the array of STRINGREFs. We don't need to check for null because the GC will throw
194 // an OutOfMemoryException if there's not enough memory.
196 PTRARRAYREF ResultArray = (PTRARRAYREF)AllocateObjectArray(13, g_pStringClass);
198 GCPROTECT_BEGIN(ResultArray);
200 // Get each one of them
201 for (int i = 0; i < 13; i++, calType++)
203 result = NewApis::GetCalendarInfoEx(localeName, calendar, NULL, calType, buffer, 80, NULL);
205 // If we still have failure, then mark as empty string
214 // Note that the returned string is null terminated, so even an empty string will be 1
215 // Make a string for this entry
216 STRINGREF stringResult = StringObject::NewString(buffer, result - 1);
217 ResultArray->SetAt(i, (OBJECTREF)stringResult);
221 _ASSERTE(pOutputStrings != NULL);
222 *pOutputStrings = ResultArray;
224 END_SO_INTOLERANT_CODE
226 return (result != 0);
230 // struct to help our calendar data enumaration callback
234 int count; // # of strings found so far
235 LPWSTR userOverride; // pointer to user override string if used
236 LPWSTR stringsBuffer; // pointer to a buffer to use for the strings.
237 LPWSTR endOfBuffer; // pointer to the end of the stringsBuffer ( must be < this to write)
243 BOOL CALLBACK EnumCalendarInfoCallback(__in_z LPWSTR lpCalendarInfoString, __in CALID Calendar, __in_opt LPWSTR pReserved, __in LPARAM lParam)
250 PRECONDITION(CheckPointer(lpCalendarInfoString));
251 PRECONDITION(CheckPointer((LPVOID)lParam));
254 // Cast our data to the right type
255 enumData* pData = (enumData*)lParam;
257 // If we had a user override, check to make sure this differs
258 if (pData->userOverride == NULL ||
259 wcscmp(pData->userOverride, lpCalendarInfoString) != 0)
261 // They're different, add it to our buffer
262 LPWSTR pStart = pData->stringsBuffer;
263 LPCWSTR pEnd = pData->endOfBuffer;
264 while (pStart < pEnd && *lpCalendarInfoString != 0)
266 *(pStart++) = *(lpCalendarInfoString++);
277 // It finished, use it
279 pData->stringsBuffer = pStart;
288 // CallEnumCalendarInfo
290 // Get the list of whichever calendar property from the OS. If user override is passed in, then check GetLocaleInfo as well
291 // to see if a user override is set.
293 // We build a list of strings, first calling getlocaleinfo if necessary, and then the enums. The strings are null terminated,
294 // with a double null ending the list. Once we have the list we can allocate our COMStrings and arrays from the count.
296 // We need a helper structure to pass as an lParam
298 BOOL CalendarData::CallEnumCalendarInfo(__in_z LPCWSTR localeName, __in int calendar, __in int calType,
299 __in int lcType, __inout PTRARRAYREF* pOutputStrings)
303 THROWS; // We can throw since we are allocating managed string.
304 INJECT_FAULT(COMPlusThrowOM());
305 DISABLED(GC_TRIGGERS); // Disabled 'cause it don't work right now
312 // Our longest string in culture.xml is shorter than this and it has lots of \x type characters, so this should be long enough by far.
313 WCHAR stringBuffer[512];
315 struct enumData data;
317 data.userOverride = NULL;
318 data.stringsBuffer = stringBuffer;
319 data.endOfBuffer = stringBuffer + 512; // We're adding WCHAR sizes
321 // First call GetLocaleInfo if necessary
322 if ((lcType && ((lcType & LOCALE_NOUSEROVERRIDE) == 0)) &&
323 // Get user locale, see if it matches localeName.
324 // Note that they should match exactly, including letter case
325 NewApis::GetUserDefaultLocaleName(stringBuffer, 512) && wcscmp(localeName, stringBuffer) == 0)
327 // They want user overrides, see if the user calendar matches the input calendar
328 CALID userCalendar = 0;
329 NewApis::GetLocaleInfoEx(localeName, LOCALE_ICALENDARTYPE | LOCALE_RETURN_NUMBER, (LPWSTR)&userCalendar,
330 sizeof(userCalendar) / sizeof(WCHAR) );
332 // If the calendars were the same, see if the locales were the same
333 if ((int)userCalendar == calendar) // todo: cast to compile on MAC
335 // They matched, get the user override since locale & calendar match
336 int i = NewApis::GetLocaleInfoEx(localeName, lcType, stringBuffer, 512);
338 // if it succeeded, advance the pointer and remember the override for the later callers
341 // Remember this was the override (so we can look for duplicates later in the enum function)
342 data.userOverride = data.stringsBuffer;
344 // Advance to the next free spot (i includes counting the \0)
345 data.stringsBuffer += i;
353 // Now call the enumeration API. Work is done by our callback function
354 NewApis::EnumCalendarInfoExEx(EnumCalendarInfoCallback, localeName, calendar, calType, (LPARAM)&data);
356 // Now we have a list of data, fail if we didn't find anything.
357 if (data.count == 0) return FALSE;
359 // Now we need to allocate our stringarray and populate it
360 STATIC_CONTRACT_SO_TOLERANT;
362 BEGIN_SO_INTOLERANT_CODE_NOTHROW(GetThread(), return (result); )
364 // Get our array object (will throw, don't have to check it)
365 PTRARRAYREF dataArray = (PTRARRAYREF) AllocateObjectArray(data.count, g_pStringClass);
367 GCPROTECT_BEGIN(dataArray);
368 LPCWSTR buffer = stringBuffer; // Restart @ buffer beginning
369 for(DWORD i = 0; i < (DWORD)data.count; i++)
371 OBJECTREF o = (OBJECTREF) StringObject::NewString(buffer);
373 if (calType == CAL_SABBREVERASTRING || calType == CAL_SERASTRING)
375 // Eras are enumerated backwards. (oldest era name first, but
376 // Japanese calendar has newest era first in array, and is only
377 // calendar with multiple eras)
378 dataArray->SetAt((DWORD)data.count - i - 1, o);
382 dataArray->SetAt(i, o);
385 buffer += (lstrlenW(buffer) + 1);
389 _ASSERTE(pOutputStrings != NULL);
390 *pOutputStrings = dataArray;
392 END_SO_INTOLERANT_CODE
397 ////////////////////////////////////////////////////////////////////////
399 // For calendars like Gregorain US/Taiwan/UmAlQura, they are not available
400 // in all OS or all localized versions of OS.
401 // If OS does not support these calendars, we will fallback by using the
402 // appropriate fallback calendar and locale combination to retrieve data from OS.
405 // __deref_inout pCalendarInt:
406 // Pointer to the calendar ID. This will be updated to new fallback calendar ID if needed.
407 // __in_out pLocaleNameStackBuffer
408 // Pointer to the StackSString object which holds the locale name to be checked.
409 // This will be updated to new fallback locale name if needed.
411 ////////////////////////////////////////////////////////////////////////
413 void CalendarData::CheckSpecialCalendar(INT32* pCalendarInt, StackSString* pLocaleNameStackBuffer)
415 // Gregorian-US isn't always available in the OS, however it is the same for all locales
416 switch (*pCalendarInt)
418 case CAL_GREGORIAN_US:
420 if (0 == NewApis::GetCalendarInfoEx(*pLocaleNameStackBuffer, *pCalendarInt, NULL, CAL_SCALNAME, NULL, 0, NULL))
422 // Failed, set it to a locale (fa-IR) that's alway has Gregorian US available in the OS
423 pLocaleNameStackBuffer->Set(W("fa-IR"), 5);
426 if (0 == NewApis::GetCalendarInfoEx(*pLocaleNameStackBuffer, *pCalendarInt, NULL, CAL_SCALNAME, NULL, 0, NULL))
428 // Failed again, just use en-US with the gregorian calendar
429 pLocaleNameStackBuffer->Set(W("en-US"), 5);
430 *pCalendarInt = CAL_GREGORIAN;
434 // Taiwan calendar data is not always in all language version of OS due to Geopolical reasons.
435 // It is only available in zh-TW localized versions of Windows.
436 // Let's check if OS supports it. If not, fallback to Greogrian localized for Taiwan calendar.
437 if (0 == NewApis::GetCalendarInfoEx(*pLocaleNameStackBuffer, *pCalendarInt, NULL, CAL_SCALNAME, NULL, 0, NULL))
439 *pCalendarInt = CAL_GREGORIAN;
443 // UmAlQura is only available in Vista and above, so we will need to fallback to Hijri if it is not available in the OS.
444 if (0 == NewApis::GetCalendarInfoEx(*pLocaleNameStackBuffer, *pCalendarInt, NULL, CAL_SCALNAME, NULL, 0, NULL))
446 // There are no differences in DATA between UmAlQura and Hijri, and
447 // UmAlQura isn't available before Vista, so just use Hijri..
448 *pCalendarInt = CAL_HIJRI;
454 ////////////////////////////////////////////////////////////////////////
456 // Implementation for CalendarInfo.nativeGetCalendarData
458 // Retrieve calendar properties from the native side
461 // pCalendarData: This is passed from a managed structure CalendarData.cs
462 // pLocaleNameUNSAFE: Locale name associated with the locale for this calendar
463 // calendar: Calendar ID
465 // NOTE: Calendars depend on the locale name that creates it. Only a few
466 // properties are available without locales using CalendarData.GetCalendar(int)
468 ////////////////////////////////////////////////////////////////////////
470 FCIMPL3(FC_BOOL_RET, CalendarData::nativeGetCalendarData, CalendarData* calendarDataUNSAFE, StringObject* pLocaleNameUNSAFE, INT32 calendar)
475 PRECONDITION(CheckPointer(pLocaleNameUNSAFE));
479 // The maximum allowed string length in GetLocaleInfo is 80 WCHARs.
484 STRINGREF localeName;
485 CALENDARDATAREF calendarData;
488 // Dereference our gc objects
489 gc.localeName = (STRINGREF)pLocaleNameUNSAFE;
490 gc.calendarData = (CALENDARDATAREF)calendarDataUNSAFE;
492 // Need to set up the frame since we will be allocating managed strings.
493 HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc);
495 // create a local copy of the string in order to pass it to helper methods that trigger GCs like GetCalendarDayInfo and GetCalendarMonthInfo
496 StackSString localeNameStackBuffer( gc.localeName->GetBuffer() );
498 // Conveniently this is the same as LOCALE_NOUSEROVERRIDE, so we can use this for both
499 int useOverrides = (gc.calendarData->bUseUserOverrides) ? 0 : CAL_NOUSEROVERRIDE;
502 STRINGREF stringResult = NULL;
505 // Windows doesn't support some calendars right now, so remap those.
507 if (calendar >= 13 && calendar < 23)
511 case RESERVED_CAL_PERSIAN: // don't change if we have Persian calendar
514 case RESERVED_CAL_JAPANESELUNISOLAR: // Data looks like Japanese
517 case RESERVED_CAL_JULIAN: // Data looks like gregorian US
518 case RESERVED_CAL_CHINESELUNISOLAR: // Algorithmic, so actual data is irrelevent
519 case RESERVED_CAL_SAKA: // reserved to match Office but not implemented in our code, so data is irrelevent
520 case RESERVED_CAL_LUNAR_ETO_CHN: // reserved to match Office but not implemented in our code, so data is irrelevent
521 case RESERVED_CAL_LUNAR_ETO_KOR: // reserved to match Office but not implemented in our code, so data is irrelevent
522 case RESERVED_CAL_LUNAR_ETO_ROKUYOU: // reserved to match Office but not implemented in our code, so data is irrelevent
523 case RESERVED_CAL_KOREANLUNISOLAR: // Algorithmic, so actual data is irrelevent
524 case RESERVED_CAL_TAIWANLUNISOLAR: // Algorithmic, so actual data is irrelevent
526 calendar = CAL_GREGORIAN_US;
532 // Speical handling for some special calendar due to OS limitation.
533 // This includes calendar like Taiwan calendar, UmAlQura calendar, etc.
535 CheckSpecialCalendar(&calendar, &localeNameStackBuffer);
539 ret &= CallGetCalendarInfoEx(localeNameStackBuffer, calendar,
540 CAL_ITWODIGITYEARMAX | CAL_RETURN_NUMBER | useOverrides,
541 &(gc.calendarData->iTwoDigitYearMax));
543 if (ret == FALSE) // failed call
545 _ASSERTE(!"nativeGetCalendarData could not read CAL_ITWODIGITYEARMAX");
548 _ASSERTE(ret == TRUE);
551 if (CallGetCalendarInfoEx(localeNameStackBuffer, calendar, CAL_SCALNAME , &stringResult))
552 SetObjectReference((OBJECTREF*)&(gc.calendarData->sNativeName), stringResult, NULL);
555 _ASSERTE(!"nativeGetCalendarData could not read CAL_SCALNAME");
558 if (CallGetCalendarInfoEx(localeNameStackBuffer, calendar, CAL_SMONTHDAY | useOverrides, &stringResult))
559 SetObjectReference((OBJECTREF*)&(gc.calendarData->sMonthDay), stringResult, NULL);
562 _ASSERTE(!"nativeGetCalendarData could not read RESERVED_CAL_SMONTHDAY");
568 PTRARRAYREF array = NULL;
569 if (CallEnumCalendarInfo(localeNameStackBuffer, calendar, CAL_SSHORTDATE, LOCALE_SSHORTDATE | useOverrides, &array))
570 SetObjectReference((OBJECTREF*)&(gc.calendarData->saShortDates), array, NULL);
573 _ASSERTE(!"nativeGetCalendarData could not read CAL_SSHORTDATE");
576 if (CallEnumCalendarInfo(localeNameStackBuffer, calendar, CAL_SLONGDATE, LOCALE_SLONGDATE | useOverrides, &array))
577 SetObjectReference((OBJECTREF*)&(gc.calendarData->saLongDates), array, NULL);
580 _ASSERTE(!"nativeGetCalendarData could not read CAL_SLONGDATE");
584 // Get the YearMonth pattern.
585 // Before Windows Vista, NLS would not write the Year/Month pattern into the reg key.
586 // This causes GetLocaleInfo() to retrieve the Gregorian localized calendar pattern even when the calendar is not Gregorian localized.
587 // So we will call GetLocaleInfo() only when the reg key for sYearMonth is there.
589 // If the key does not exist, leave yearMonthPattern to be null, so that we will pick up the default table value.
591 int useOverridesForYearMonthPattern = useOverrides;
592 if (useOverridesForYearMonthPattern == 0)
595 useOverridesForYearMonthPattern = CAL_NOUSEROVERRIDE;
596 if (WszRegOpenKeyEx(HKEY_CURRENT_USER, W("Control Panel\\International"), 0, KEY_READ, &hkey) == ERROR_SUCCESS)
598 if (WszRegQueryValueEx(hkey, W("sYearMonth"), 0, NULL, NULL, NULL) == ERROR_SUCCESS)
600 // The sYearMonth key exists. Call GetLocaleInfo() to read it.
601 useOverridesForYearMonthPattern = 0; // now we can use the overrides
607 if (CallEnumCalendarInfo(localeNameStackBuffer, calendar, CAL_SYEARMONTH, LOCALE_SYEARMONTH | useOverridesForYearMonthPattern, &array))
608 SetObjectReference((OBJECTREF*)&(gc.calendarData->saYearMonths), array, NULL);
611 _ASSERTE(!"nativeGetCalendarData could not read CAL_SYEARMONTH");
616 // These are all single calType entries, 1 per day, so we have to make 7 or 13 calls to collect all the names
619 // Note that we're off-by-one since managed starts on sunday and windows starts on monday
620 if (GetCalendarDayInfo(localeNameStackBuffer, calendar, CAL_SDAYNAME7, &array))
621 SetObjectReference((OBJECTREF*)&(gc.calendarData->saDayNames), array, NULL);
624 _ASSERTE(!"nativeGetCalendarData could not read CAL_SDAYNAME7");
627 if (GetCalendarDayInfo(localeNameStackBuffer, calendar, CAL_SABBREVDAYNAME7, &array))
628 SetObjectReference((OBJECTREF*)&(gc.calendarData->saAbbrevDayNames), array, NULL);
631 _ASSERTE(!"nativeGetCalendarData could not read CAL_SABBREVDAYNAME7");
636 if (GetCalendarMonthInfo(localeNameStackBuffer, calendar, CAL_SMONTHNAME1, &array))
637 SetObjectReference((OBJECTREF*)&(gc.calendarData->saMonthNames), array, NULL);
640 _ASSERTE(!"nativeGetCalendarData could not read CAL_SMONTHNAME1");
644 if (GetCalendarMonthInfo(localeNameStackBuffer, calendar, CAL_SABBREVMONTHNAME1, &array))
645 SetObjectReference((OBJECTREF*)&(gc.calendarData->saAbbrevMonthNames), array, NULL);
648 _ASSERTE(!"nativeGetCalendarData could not read CAL_SABBREVMONTHNAME1");
653 // The following LCTYPE are not supported in some platforms. If the call fails,
654 // don't return a failure.
656 if (GetCalendarDayInfo(localeNameStackBuffer, calendar, CAL_SSHORTESTDAYNAME7, &array))
657 SetObjectReference((OBJECTREF*)&(gc.calendarData->saSuperShortDayNames), array, NULL);
660 // Gregorian may have genitive month names
661 if (calendar == CAL_GREGORIAN)
663 if (GetCalendarMonthInfo(localeNameStackBuffer, calendar, CAL_SMONTHNAME1 | CAL_RETURN_GENITIVE_NAMES, &array))
664 SetObjectReference((OBJECTREF*)&(gc.calendarData->saMonthGenitiveNames), array, NULL);
665 // else we ignore the error and let managed side copy the normal month names
666 if (GetCalendarMonthInfo(localeNameStackBuffer, calendar, CAL_SABBREVMONTHNAME1 | CAL_RETURN_GENITIVE_NAMES, &array))
667 SetObjectReference((OBJECTREF*)&(gc.calendarData->saAbbrevMonthGenitiveNames), array, NULL);
668 // else we ignore the error and let managed side copy the normal month names
671 // leap year names are only different for month 6 in Hebrew calendar
672 // PTRARRAYREF saLeapYearMonthNames ; // Multiple strings for the month names in a leap year. (Hebrew's the only one that has these)
674 // Calendar Parts Names
675 if (CallEnumCalendarInfo(localeNameStackBuffer, calendar, CAL_SERASTRING, NULL, &array))
676 SetObjectReference((OBJECTREF*)&(gc.calendarData->saEraNames), array, NULL);
677 // else we set the era in managed code
678 if (CallEnumCalendarInfo(localeNameStackBuffer, calendar, CAL_SABBREVERASTRING, NULL, &array))
679 SetObjectReference((OBJECTREF*)&(gc.calendarData->saAbbrevEraNames), array, NULL);
680 // else we set the era in managed code
682 // PTRARRAYREF saAbbrevEnglishEraNames ; // Abbreviated Era Names in English
686 // Note that calendar era data (offsets, etc) is hard coded for each calendar since this
687 // data is implementation specific and not dynamic (except perhaps Japanese)
690 HELPER_METHOD_FRAME_END();
697 // Get the system two digit year max value for the specified calendar
699 FCIMPL1(INT32, CalendarData::nativeGetTwoDigitYearMax, INT32 calendar)
703 DWORD dwTwoDigitYearMax = (DWORD) -1;
705 HELPER_METHOD_FRAME_BEGIN_RET_0();
707 WCHAR strName[LOCALE_NAME_MAX_LENGTH];
709 // Really we should just be able to pass NULL for the locale name since that
710 // causes the OS to look up the user default locale name. The downlevel APIS could
711 // emulate this as necessary
712 if (NewApis::GetUserDefaultLocaleName(strName,NumItems(strName)) == 0 ||
713 NewApis::GetCalendarInfoEx(strName, calendar, NULL, CAL_ITWODIGITYEARMAX | CAL_RETURN_NUMBER, NULL, 0,&dwTwoDigitYearMax) == 0)
715 dwTwoDigitYearMax = (DWORD) -1;
720 HELPER_METHOD_FRAME_END();
722 return (dwTwoDigitYearMax);
727 // nativeGetCalendars
729 // Get the list of acceptable calendars for this user/locale
731 // Might be a better way to marshal the int[] for calendars
732 // We expect the input array to be 23 ints long. We then fill up the first "count" ints and return the count.
733 // The caller should then make it a smaller array.
736 // Perhaps we could do something more like this...
737 //U1ARRAYREF rgbOut = (U1ARRAYREF) AllocatePrimitiveArray(ELEMENT_TYPE_U1, cb);
738 //memcpyNoGCRefs(rgbOut->GetDirectPointerToNonObjectElements(), rgbKey, cb * sizeof(BYTE));
740 //refRetVal = rgbOut;
742 //HELPER_METHOD_FRAME_END();
743 //return (U1Array*) OBJECTREFToObject(refRetVal);
746 // struct to help our calendar data enumaration callback
748 struct enumCalendarsData
750 int count; // # of strings found so far
751 CALID userOverride; // user override value (if found)
752 INT32* calendarList; // list of calendars found so far
758 BOOL CALLBACK EnumCalendarsCallback(__in_z LPWSTR lpCalendarInfoString, __in CALID Calendar, __in_opt LPWSTR pReserved, __in LPARAM lParam)
765 PRECONDITION(CheckPointer(lpCalendarInfoString));
766 PRECONDITION(CheckPointer((LPVOID)lParam));
769 // Cast our data to the right type
770 enumCalendarsData* pData = (enumCalendarsData*)lParam;
772 // If we had a user override, check to make sure this differs
773 if (pData->userOverride == Calendar)
775 // Its the same, just return
779 // They're different, add it to our buffer, check we have room
780 if (pData->count < 23)
782 pData->calendarList[pData->count++] = Calendar;
788 FCIMPL3(INT32, CalendarData::nativeGetCalendars, StringObject* pLocaleNameUNSAFE, CLR_BOOL useOverrides, I4Array* calendarsUNSAFE)
793 PRECONDITION(CheckPointer(pLocaleNameUNSAFE));
800 STRINGREF localeName;
801 I4ARRAYREF calendarsRef;
804 // Dereference our string
805 gc.localeName = (STRINGREF)pLocaleNameUNSAFE;
806 gc.calendarsRef = (I4ARRAYREF)calendarsUNSAFE;
807 HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc);
809 int calendarBuffer[23];
810 struct enumCalendarsData data;
812 data.userOverride = 0;
813 data.calendarList = calendarBuffer;
815 // First call GetLocaleInfo if necessary
818 // They want user overrides, see if the user calendar matches the input calendar
820 CALID userCalendar = 0;
821 NewApis::GetLocaleInfoEx( gc.localeName->GetBuffer(), LOCALE_ICALENDARTYPE | LOCALE_RETURN_NUMBER,
822 (LPWSTR)&userCalendar, sizeof(userCalendar) / sizeof(WCHAR) );
824 // If we got a default, then use it as the first calendar
825 if (userCalendar != 0)
827 data.userOverride = userCalendar;
828 data.calendarList[data.count++] = userCalendar;
832 // Now call the enumeration API. Work is done by our callback function
833 NewApis::EnumCalendarInfoExEx(EnumCalendarsCallback, gc.localeName->GetBuffer(), ENUM_ALL_CALENDARS, CAL_ICALINTVALUE, (LPARAM)&(data));
835 // Copy to the output array
836 for (int i = 0; i < data.count; i++)
838 (gc.calendarsRef->GetDirectPointerToNonObjectElements())[i] = calendarBuffer[i];
842 HELPER_METHOD_FRAME_END();
844 // Now we have a list of data, return the count
850 // nativeEnumTimeFormats
852 // Enumerate all of the time formats (long times) on the system.
853 // Windows only has 1 time format so there's nothing like an LCTYPE here.
855 // Note that if the locale is the user default locale windows ALWAYS returns the user override value first.
856 // (ie: there's no no-user-override option for this API)
858 // We reuse the enumData structure since it works for us.
864 BOOL CALLBACK EnumTimeFormatsCallback(__in_z LPCWSTR lpTimeFormatString, __in LPARAM lParam)
871 PRECONDITION(CheckPointer(lpTimeFormatString));
872 PRECONDITION(CheckPointer((LPVOID)lParam));
875 // Cast our data to the right type
876 enumData* pData = (enumData*)lParam;
878 // Don't have to worry about user overrides (the enum adds them)
879 // add it to our buffer
880 LPWSTR pStart = pData->stringsBuffer;
881 LPCWSTR pEnd = pData->endOfBuffer;
882 while (pStart < pEnd && *lpTimeFormatString != 0)
884 *(pStart++) = *(lpTimeFormatString++);
895 // It finished, use it
897 pData->stringsBuffer = pStart;
905 // nativeEnumTimeFormats that calls the callback above
907 FCIMPL3(Object*, CalendarData::nativeEnumTimeFormats,
908 StringObject* pLocaleNameUNSAFE, INT32 dwFlags, CLR_BOOL useUserOverride)
913 PRECONDITION(CheckPointer(pLocaleNameUNSAFE));
919 STRINGREF localeName;
920 PTRARRAYREF timeFormatsArray;
923 // Dereference our gc objects
924 gc.localeName = (STRINGREF)pLocaleNameUNSAFE;
925 gc.timeFormatsArray = NULL;
927 HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc);
929 // Our longest string in culture.xml is shorter than this and it has lots of \x type characters, so this should be long enough by far.
930 WCHAR stringBuffer[512];
931 struct enumData data;
933 data.userOverride = NULL;
934 data.stringsBuffer = stringBuffer;
935 data.endOfBuffer = stringBuffer + 512; // We're adding WCHAR sizes
937 // Now call the enumeration API. Work is done by our callback function
938 NewApis::EnumTimeFormatsEx((TIMEFMT_ENUMPROCEX)EnumTimeFormatsCallback, gc.localeName->GetBuffer(), dwFlags, (LPARAM)&data);
942 // Now we need to allocate our stringarray and populate it
943 // Get our array object (will throw, don't have to check it)
944 gc.timeFormatsArray = (PTRARRAYREF) AllocateObjectArray(data.count, g_pStringClass);
946 LPCWSTR buffer = stringBuffer; // Restart @ buffer beginning
947 for(DWORD i = 0; i < (DWORD)data.count; i++) // todo: cast to compile on Mac
949 OBJECTREF o = (OBJECTREF) StringObject::NewString(buffer);
950 gc.timeFormatsArray->SetAt(i, o);
952 buffer += (lstrlenW(buffer) + 1);
955 if(!useUserOverride && data.count > 1)
957 // Since there is no "NoUserOverride" aware EnumTimeFormatsEx, we always get an override
958 // The override is the first entry if it is overriden.
959 // We can check if we have overrides by checking the GetLocaleInfo with no override
960 // If we do have an override, we don't know if it is a user defined override or if the
961 // user has just selected one of the predefined formats so we can't just remove it
962 // but we can move it down.
963 WCHAR timeFormatNoUserOverride[LOCALE_NAME_MAX_LENGTH];
964 DWORD lcType = (dwFlags == TIME_NOSECONDS) ? LOCALE_SSHORTTIME : LOCALE_STIMEFORMAT;
965 lcType |= LOCALE_NOUSEROVERRIDE;
966 int result = NewApis::GetLocaleInfoEx(gc.localeName->GetBuffer(), lcType, timeFormatNoUserOverride, LOCALE_NAME_MAX_LENGTH);
969 STRINGREF firstTimeFormat = (STRINGREF)gc.timeFormatsArray->GetAt(0);
970 if(wcscmp(timeFormatNoUserOverride, firstTimeFormat->GetBuffer())!=0)
972 gc.timeFormatsArray->SetAt(0, gc.timeFormatsArray->GetAt(1));
973 gc.timeFormatsArray->SetAt(1, firstTimeFormat);
980 HELPER_METHOD_FRAME_END();
981 return OBJECTREFToObject(gc.timeFormatsArray);