Introduction
This article describes how Persian culture can be fixed so that Persian calendar can be used with this culture.
It's been annoying for programmers in Persian language that the favorite simple code line:
CultureInfo.CurrentCulture.DateTimeFormat.Calendar = new PersianCalendar()
doesn't work and raises an ArgumentOutOfRange
exception. This article shows how to get rid of this.
Background
Although .NET provides both PersianCalendar
and Persian CultureInfo
, there are some issues in Persian culture that prevents the smooth use
of Persian culture. You can see the user complaints under the PersianCalendar
page on MSDN. The questions are:
- How to set
PersianCalendar
on a DateTimeFormatInfo
?
- Why calendar resources such as month names are not correct and how to fix them?
- Why Persian calendar is not included in the set of Optional Calendars in Persian culture?
These problems have already been noted by Reza Taroosheh in his great article
How to set PersianCalendar to CultureInfo. He also provides a PersianCalendarHelper
class
to fix these issues based on Reflection. This class reasonably addresses the first two issues. It adds Persian month names and also sets the Persian calendar
on the DateTimeFormat
. This normally suffices to have a working Persian culture for a .NET application.
But it fails to add the Persian calendar to the set of optional calendars. Troosheh suggests to install a new custom locale to solve this issue. In this article, a new approach
is introduced to fix the optional calendars issue.
Fixing Optional Calendars
Inspecting internal codes of CultureInfo
by Reflection reveals that the array of optional calendars is actually read from a TableRecord
structure
from the Windows stored locale data. The actual location of this array is back calculated, so that one can fix this array. Actually, the memory is in a protected area,
so that we should use the VirtualProtect
API before trying to fix the array.
public static CultureInfo FixOptionalCalendars(CultureInfo culture, int CalenadrIndex)
{
InvokeHelper ivCultureInfo = new InvokeHelper(culture);
if (!ivCultureInfo.HasField("m_cultureTableRecord"))
{
return _FixOptionalCalendars4(culture, CalenadrIndex);
}
InvokeHelper ivTableRecord =
new InvokeHelper(ivCultureInfo.GetField("m_cultureTableRecord"));
System.Reflection.Pointer m_pData =
(System.Reflection.Pointer)ivTableRecord.GetField("m_pData");
ConstructorInfo _intPtrCtor = typeof(IntPtr).GetConstructor(
new Type[] { Type.GetType("System.Void*") });
IntPtr DataIntPtr = (IntPtr)_intPtrCtor.Invoke(new object[1] { m_pData });
Type TCultureTableData = Type.GetType("System.Globalization.CultureTableData");
Object oCultureTableData =
System.Runtime.InteropServices.Marshal.PtrToStructure(DataIntPtr, TCultureTableData);
InvokeHelper ivCultureTableData = new InvokeHelper(oCultureTableData);
uint waCalendars = (uint)ivCultureTableData.GetField("waCalendars");
object IOPTIONALCALENDARS = ivTableRecord.GetProperty("IOPTIONALCALENDARS");
System.Reflection.Pointer m_pool = (
System.Reflection.Pointer)ivTableRecord.GetField("m_pPool");
IntPtr PoolInPtr = (IntPtr)_intPtrCtor.Invoke(new object[1] { m_pool });
IntPtr shortArrayPtr = new IntPtr((PoolInPtr.ToInt64() + waCalendars*sizeof(ushort)));
short[] shortArray = new short[1];
System.Runtime.InteropServices.Marshal.Copy(shortArrayPtr, shortArray, 0, 1);
short[] calArray = new short[shortArray[0]];
IntPtr calArrayPtr = new IntPtr(shortArrayPtr.ToInt64() + sizeof(short));
System.Runtime.InteropServices.Marshal.Copy(calArrayPtr, calArray, 0, shortArray[0]);
uint old;
VirtualProtect(calArrayPtr, 100, 0x4, out old);
calArray[CalenadrIndex] = 0x16;
System.Runtime.InteropServices.Marshal.Copy(calArray, 0, calArrayPtr, calArray.Length);
VirtualProtect(calArrayPtr, 100, old, out old);
return culture;
}
The InvokeHelper
class in the above code is just a helper class to get the private fields by Reflection.
Optional Calendars in .NET 4
The TableRecord
approach has been depreciated in .NET 4. Now OptionalCalendars
is a managed array that can be easily set through Reflection:
private static CultureInfo _FixOptionalCalendars4(CultureInfo culture, int CalenadrIndex)
{
FieldInfo cultureDataField = typeof(CultureInfo).GetField("m_cultureData",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
Object cultureData = cultureDataField.GetValue(culture);
PropertyInfo calendarIDProp = cultureData.GetType().GetProperty("CalendarIds",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
int[] calendars = (int[]) calendarIDProp.GetValue(cultureData,null);
if (CalenadrIndex >= 0 && CalenadrIndex < calendars.Length)
calendars[CalenadrIndex] = 0x16;
return culture;
}
How to Use the Code
One may use the static methods of PersianCalendarHelper
to fix the Persian culture. The simplest way is calling the FixAndSetCurrentCulture
method
somewhere in application startup. This will set the current thread culture to a new fixed culture.
PersianCultureHelper.FixAndSetCurrentCulture();
Another approach will be using the FixPersianCulture
method with the appropriate options. This way the programmer can control things to be fixed. For instance:
CultureInfo culture = PersianCultureHelper.FixPersianCulture(null,foptOptionalCalendars)
only fixes the optional calendars array.
Points of Interest
The m_pData
field of the TableRecord
class is actually an unsafe pointer. It was a challenge to cast it to an object so that Reflection could be used:
System.Reflection.Pointer m_pData =
(System.Reflection.Pointer)ivTableRecord.GetField("m_pData");
ConstructorInfo _intPtrCtor = typeof(IntPtr).GetConstructor(
new Type[] { Type.GetType("System.Void*") });
IntPtr DataIntPtr = (IntPtr)_intPtrCtor.Invoke(new object[1] { m_pData });
Type TCultureTableData = Type.GetType("System.Globalization.CultureTableData");
Object oCultureTableData =
System.Runtime.InteropServices.Marshal.PtrToStructure(DataIntPtr, TCultureTableData);
InvokeHelper ivCultureTableData = new InvokeHelper(oCultureTableData);
Acknowledgments
Again I should acknowledge the work of Taroosheh, this work is only a contribution to his effort.