Click here to Skip to main content
Click here to Skip to main content

Fixing Optional Calendars for Persian Culture in .NET

, 30 Mar 2013
Rate this:
Please Sign up or sign in to vote.
This article describes how to fix CultureInfo for Persian culture so that .NET developers can use PersianCalendar.

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"))
    {
        // This is .Net 4. 
        return _FixOptionalCalendars4(culture, CalenadrIndex);
    }

    InvokeHelper ivTableRecord = 
      new InvokeHelper(ivCultureInfo.GetField("m_cultureTableRecord"));
    // Get the m_pData pointer as *void
    System.Reflection.Pointer m_pData = 
      (System.Reflection.Pointer)ivTableRecord.GetField("m_pData");
    ConstructorInfo _intPtrCtor = typeof(IntPtr).GetConstructor(
                    new Type[] { Type.GetType("System.Void*") });
    // Construct a new IntPtr
    IntPtr DataIntPtr = (IntPtr)_intPtrCtor.Invoke(new object[1] { m_pData });
        
    Type TCultureTableData = Type.GetType("System.Globalization.CultureTableData");
    // Convert the Pointer class to object if type CultureTableData to work with
    // reflection API.
    Object oCultureTableData = 
      System.Runtime.InteropServices.Marshal.PtrToStructure(DataIntPtr, TCultureTableData);
    InvokeHelper ivCultureTableData = new InvokeHelper(oCultureTableData);
    // Get waCalendars pointer
    uint waCalendars = (uint)ivCultureTableData.GetField("waCalendars");
    object IOPTIONALCALENDARS = ivTableRecord.GetProperty("IOPTIONALCALENDARS");

    // Get m_Pool pointer
    System.Reflection.Pointer m_pool = (
      System.Reflection.Pointer)ivTableRecord.GetField("m_pPool");

    IntPtr PoolInPtr = (IntPtr)_intPtrCtor.Invoke(new object[1] { m_pool });
    // Add the waCalendars offset to pool pointer
    IntPtr shortArrayPtr = new IntPtr((PoolInPtr.ToInt64() + waCalendars*sizeof(ushort)));
    short[] shortArray = new short[1];
    // Now shortArray points to an arry of short integers.
    // Go to read the first value which is the number of elements.
    // Marshal array to read elements.
    System.Runtime.InteropServices.Marshal.Copy(shortArrayPtr, shortArray, 0, 1);
    // shortArray[0] is the number of optional calendars.
    short[] calArray = new short[shortArray[0]];
    // Add one element of short type to point to array of calendars
    IntPtr calArrayPtr = new IntPtr(shortArrayPtr.ToInt64() + sizeof(short));
    // Finally read the array
    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*") });
// Construct a new IntPtr
IntPtr DataIntPtr = (IntPtr)_intPtrCtor.Invoke(new object[1] { m_pData });
        
Type TCultureTableData = Type.GetType("System.Globalization.CultureTableData");
// Convert the Pointer class to object if type CultureTableData to work with
// reflection API.
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Babak Mahmoudi
Software Developer (Senior) Gostareh Negar
Iran (Islamic Republic Of) Iran (Islamic Republic Of)
I've been programming since 1990, C, Basic, Pascal, Assembly, C++ and...
Since I restablished Gostareh Negar in 1999, I've been quite busy localizing internationally accepted software for Persian natives. So I feel quite aware of various aspects of localization, specially when Persian language is the case.
Follow on   Twitter

Comments and Discussions

 
Questionchane monthcalendar Pinmembersh-a3-Apr-13 10:23 
AnswerRe: chane monthcalendar PinmemberBabak Mahmoudi3-Apr-13 18:08 
Questionsource code not available Pinmembersaeedgholamrezaee11-Dec-12 22:52 
AnswerRe: source code not available PinmemberMohammad Jamshid Shafiee4-Mar-13 19:11 
AnswerRe: source code not available PinmemberBabak Mahmoudi30-Mar-13 19:28 
QuestionProblem with zip file PinmemberShadi Rahmani6-Nov-12 1:25 
Hi, I have a problem, i can't unzip these folders.
How could i access to unzipped files?
shadirahmani

QuestionSource Code PinmemberLoveIcon29-Jul-12 20:32 
AnswerRe: Source Code PinmemberMohammad Jamshid Shafiee4-Mar-13 19:13 
GeneralRe: Source Code PinmemberBabak Mahmoudi3-Apr-13 2:02 
BugwaCalendarsField Is Null in .NET4 PinmemberMahdi Yousefi9-Apr-12 9:11 
GeneralRe: waCalendarsField Is Null in .NET4 PinmemberBabak Mahmoudi11-Apr-12 19:12 
Questionseems cannot use in WPF Pinmemberajaywazir31-Mar-12 2:04 
AnswerRe: seems cannot use in WPF PinmemberMember 14826538-Apr-12 23:03 
GeneralRe: seems cannot use in WPF Pinmemberajaywazir9-Apr-12 0:28 
AnswerRe: seems cannot use in WPF PinmemberBabak Mahmoudi11-Apr-12 19:20 
Questionwhy PersianCultureHelper is not a public class in source code? Pinmembermmsaffari1-Mar-12 7:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 31 Mar 2013
Article Copyright 2011 by Babak Mahmoudi
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid