Click here to Skip to main content
15,880,392 members
Articles / Programming Languages / C#

Fixing Optional Calendars for Persian Culture in .NET

Rate me:
Please Sign up or sign in to vote.
4.60/5 (6 votes)
30 Mar 2013CPOL2 min read 46.4K   2.8K   21   19
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:

C#
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.

C#
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:

C#
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.

C#
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:

C#
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:

C#
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)


Written By
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.

Comments and Discussions

 
QuestionBy using this we get Gregorian Year value in DateTime.Year property Pin
AmirAlilou30-Jun-17 23:09
AmirAlilou30-Jun-17 23:09 
Questiondevexpress فارسی کردن تاریخ کامپوننتهای تقویم Pin
alireza najafi26-May-15 9:49
alireza najafi26-May-15 9:49 
Questionhelp Pin
Member 1021206419-Jan-15 8:21
professionalMember 1021206419-Jan-15 8:21 
Questionchane monthcalendar Pin
sh-a3-Apr-13 10:23
sh-a3-Apr-13 10:23 
AnswerRe: chane monthcalendar Pin
Babak Mahmoudi3-Apr-13 18:08
Babak Mahmoudi3-Apr-13 18:08 
Questionsource code not available Pin
saeedgholamrezaee11-Dec-12 22:52
saeedgholamrezaee11-Dec-12 22:52 
AnswerRe: source code not available Pin
Mohammad Jamshid Shafiee4-Mar-13 19:11
Mohammad Jamshid Shafiee4-Mar-13 19:11 
AnswerRe: source code not available Pin
Babak Mahmoudi30-Mar-13 19:28
Babak Mahmoudi30-Mar-13 19:28 
QuestionProblem with zip file Pin
Shadi Rahmani6-Nov-12 1:25
Shadi 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 Pin
LoveIcon29-Jul-12 20:32
LoveIcon29-Jul-12 20:32 
AnswerRe: Source Code Pin
Mohammad Jamshid Shafiee4-Mar-13 19:13
Mohammad Jamshid Shafiee4-Mar-13 19:13 
GeneralRe: Source Code Pin
Babak Mahmoudi3-Apr-13 2:02
Babak Mahmoudi3-Apr-13 2:02 
BugwaCalendarsField Is Null in .NET4 Pin
Mahdi Yousefi9-Apr-12 9:11
Mahdi Yousefi9-Apr-12 9:11 
GeneralRe: waCalendarsField Is Null in .NET4 Pin
Babak Mahmoudi11-Apr-12 19:12
Babak Mahmoudi11-Apr-12 19:12 
Questionseems cannot use in WPF Pin
ajaywazir31-Mar-12 2:04
ajaywazir31-Mar-12 2:04 
AnswerRe: seems cannot use in WPF Pin
Member 14826538-Apr-12 23:03
Member 14826538-Apr-12 23:03 
GeneralRe: seems cannot use in WPF Pin
ajaywazir9-Apr-12 0:28
ajaywazir9-Apr-12 0:28 
AnswerRe: seems cannot use in WPF Pin
Babak Mahmoudi11-Apr-12 19:20
Babak Mahmoudi11-Apr-12 19:20 
Questionwhy PersianCultureHelper is not a public class in source code? Pin
Mohammad Mahdi Saffari1-Mar-12 7:16
Mohammad Mahdi Saffari1-Mar-12 7:16 

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

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