Click here to Skip to main content
15,885,164 members
Articles / Desktop Programming / WPF

PersianDate and Some WPF Controls For It

Rate me:
Please Sign up or sign in to vote.
4.83/5 (44 votes)
25 Apr 2012Ms-PL7 min read 110.5K   12.7K   29   43
PersianDate type, and two WPF controls (PersianCalendar, and PersianDatePicker) for working with Persian dates

Introduction

The source code consists of three main projects: PersianDate, which is a type (structure actually) for holding values of the Persian calendar, PersianDateControls, which has two WPF controls for Persian calendar: PersianCalendar and PersianDatePicker (these controls are pretty much like the ones found in the WPF control library: Calendar and DatePicker; these two controls use the type PersianDate to work with values of the Persian calendar), and the third project is a simple demo that demonstrates using these controls.

About the Persian Calendar

The Persian calendar is a sonar calendar, like Gregorian calendar, but there are some differences. One is that the origins are different, and the Persian calendar's origin is about 621 years after Gregorian calendar's; another one is that Persian calendar's first day of year is March 21; and probably the most important one is that the average length of a Persian calendar year is different from that of a Gregorian calendar: the Persian calendar has 8 leap years (that is a year that has an extra day than normal years) in each 33 years, whereas the Gregorian calendar has 8 leap years in each 32 years. This little difference means that Persian dates cannot be calculated directly from Gregorian dates.

The PersianDate Structure

The PersianDate structure stores dates of the Persian calendar. It is somehow similar to the DateTime structure in the .NET Framework Class Library, except that PersianDate only stores the date, and not the time. This structure has only one field, which stores the number of days passed since the first day of the first year of the Persian calendar(1/1/1):

C#
uint n; //the only field, stores the number of days passed 1/1/1 

The calculations for the year, month, and day are based on this single value. The private yearMonthDay() method takes this number and returns the day, month, and year represented by this number. In order to do so, dates are divided into some groups:

C#
const int period33y = 365 * 33 + 8;

const int p33p1 = 366;
const int p33p2 = 365 * 20 + 4;
const int p33p3 = 366;
const int p33p4 = 365 * 11 + 2;

The first constant (period33y) is the number of days in each 33 years; this is divided into four groups: in each one, the number of days in a year are the same. For example, if n % period33y is 400, these categories mean that the date is in the p33p2 group since it is > p33p1 and <= p33p1+p33p2, and that means that the date is not in a leap year, and by doing some relatively simple calculations, the year of the date can be extracted. After that, the day and month parts of the date are extracted in this method.

These constants are also used when calculating n from the year, month, and day. The days() method does this. This method is used in one of the constructors of the structure which takes the year, month, and day as arguments.

A few Noteworthy Points About PersianDate

The PersianDate is a structure, not a class, because of performance reasons. As mentioned before, this structure has only one 4 byte field, so it is much more rational to make it a structure than a class. Since access to objects of classes are indirect, and a reference to an object is at least 4 bytes long, which is the size of the data itself, it doesn't seem reasonable to make this type a reference type.

The type is immutable, and this is the recommended way of making structures. The reason is that mutable structures have weird behaviours in certain scenarios. For example, suppose that the type was mutable, and there was an AddDays() method which would add the number of days given as the parameter to the instance. Now suppose that there is a Foo class which has a field of type PersianDate named DateField:

C#
Foo foo=new Foo();
foo.DateField=new PersianDate(1376,2,22);
foo.DateField.AddDays(12);
System.Console.WriteLine(foo.dateField.ToString());

The output would be 1376/3/2, which is what you might have expected. Now, suppose that this Foo class also had a property of type PersianDate, named dateProperty, and let's say it was auto implemented:

C#
class Foo{
    public PersianDate DateProperty{get; set;}
    ...
}

If you wrote a similar code to the one you had written for DateField...

C#
Foo foo=new Foo();
foo.DateProperty=new PersianDate(1376,2,22);
foo.DateProperty.AddDays(12);
System.Console.WriteLine(foo.DateProperty.ToString());

...the output would be 1376/2/22! The reason is that since PersianDate is a value type, the getter of dateProperty would return a copy of the value stored, and the AddDays method would mutate this copy, and not the actual backing field; and that is why the value of the property is not changed.

So that is why PersianDate is immutable.

The PersianCalendar Class

PersianCalendar is a WPF user control. In WPF, user controls derive from the System.Windows.Controls.UserControl base class. This WPF control represents the Persian calendar, very much like the Calendar control (System.Windows.Controls.Calendar). Just like WPF's Calendar, it has a property called DisplayMode which can be used to choose how the calendar is displayed: whether it displays years in a decade, months in a year, or days in a month.

This control uses the PersianDate type to work with the Persian calendar, so properties like DisplayDate or SelectedDate are of this type.

 I have used WPF's UniformGrid control as the container control to arrange the date buttons in PersianCalendar:

XML
<UniformGrid Margin="3,26,3,2" Name="monthUniformGrid" 
  Rows="7" Columns="7"  FlowDirection="RightToLeft"/>
<UniformGrid Margin="3,26,3,2" Name="yearUniformGrid" 
  Columns="3" Rows="4" FlowDirection="RightToLeft"/>
<UniformGrid Margin="3,26,3,2" Name="decadeUniformGrid" 
  Columns="3" Rows="4" FlowDirection="RightToLeft"/>

Each one is used for one of DisplayMode's three values (Month, Year, and Decade). For example, when the DisplayMode is set to Month, monthUniformGrid is displayed and the other two are collapsed (that is, they are hidden and they don't reserve space):

private void setMonthMode()
{
    this.decadeUniformGrid.Visibility = 
      this.yearUniformGrid.Visibility = Visibility.Collapsed;
    this.monthUniformGrid.Visibility = Visibility.Visible;
    ...
}

Customizing the Appearance of Controls Using Styles and Templates

In order for the PersianCalendar to work, these UniformGrids must be filled with controls that display dates (or months or decades). Label is not a good choice because it doesn't have a Click event, so I have used Button; but Button's appearance didn't seem very pleasing for this purpose, so I have used Styles and Templates to change it:

XML
<Style x:Key="InsideButtonsStyle" TargetType="Button">
    ...
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border x:Name="Border" CornerRadius="2" 
                         BorderThickness="0" 
                         Background="{TemplateBinding Background}" 
                         BorderBrush="{StaticResource NormalBorderBrush}">
                    <ContentPresenter Margin="2" 
                       HorizontalAlignment="Center" 
                       VerticalAlignment="Center" 
                       RecognizesAccessKey="True"/>
                </Border>
                <ControlTemplate.Triggers>
                    ...
                    <Trigger Property="IsMouseOver" Value="true">
                        <Setter TargetName="Border" 
                            Property="Background" 
                            Value="{StaticResource HoverBackgroundBrush}" />
                        <Setter  Property="Foreground" 
                            Value="{StaticResource HoverForegroundBrush}" />
                    </Trigger>
                    <Trigger Property="IsMouseOver" Value="false">
                        <Setter  Property="Foreground" 
                           Value="{StaticResource HoverForegroundBrush}" />
                    </Trigger>
                    ...
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Inside the ControlTemplate, Triggers are used to alter the appearance of the buttons when, for example, they are hovered by the mouse, etc.

I used Template Binding to bind the value of the Button's Background property to the Background property of the template's visual tree element, Border.

This style, along with the template defined in it, is applied to buttons when they are created:

C#
Button newControl()
{
    var element = new Button
    {
        ...
        Style = (Style)this.FindResource("InsideButtonsStyle"),
        ...
    };
    return element;
}

Dependency Properties and Routed Events

All the properties defined in the PersianCalendar class are Dependency Properties, and the events are RoutedEvents; this is WPF's recommended style for making user controls. Making the properties dependency properties requires that no extra code be put in their getter and setter accessors, so PropertyMetaData is used whenever any validation or mutation of the PersianCalendar object is required. For example, consider the SelectedDate property:

C#
public PersianDate SelectedDate
{
    get { return (PersianDate)GetValue(SelectedDateProperty); }
    set { SetValue(SelectedDateProperty, value); }
}               
public static readonly DependencyProperty SelectedDateProperty;

As you can see, all the setter does is just set the value for the backing field, which is of type DependencyProperty, of course. The extra logic is put into the property's metadata (by using lambda expressions in my code) in the static constructor of the class:

C#
static PersianCalendar()
{
    ...
    PropertyMetadata selectedDateMetaData = new PropertyMetadata(
    (DependencyObject d, DependencyPropertyChangedEventArgs e) =>
    {
        PersianCalendar pc = d as PersianCalendar;
        pc.selectedDateCheck((PersianDate)e.OldValue);
    }
    );
    SelectedDateProperty=
        DependencyProperty.Register("SelectedDate", 
          typeof(PersianDate), typeof(PersianCalendar), selectedDateMetaData);
    ...
}

The PersianDatePicker Class

It doesn't do any magic really. This control just uses a TextBox and a PersianCalendar, and holds the PersianCalendar in a Popup control to display it in a different window whenever the corresponding button is clicked. There is one thing notable about this class though, and that is using Data Binding for connecting the properties of this class to those of the PersianCalendar. Here is the code that demonstrates using this feature for binding the SelectedDate property:

C#
Binding selectedDateBinding = new Binding
{
    Source = this,
    Path = new PropertyPath("SelectedDate"),
    Mode = BindingMode.TwoWay,
};
this.persianCalendar.SetBinding(PersianCalendar.SelectedDateProperty, selectedDateBinding);

Using this technique has relieved me from having to write all the messy code to keep these properties in sync.

How to Use the Code

If you just want to use PersianDate, you can either add its project to your solution, or build it and reference the assembly, or download the demo and reference PersianDate.dll.

If you want to use the WPF controls, you should either add both projects (PersianDate and PersianDateControls) to your solution, or build them both and reference them both, or download the demo and reference PersianDate.dll and PersianDateControls.dll.

History

  • Ver. 1.1
    • Removed PersianDateControl's dependency on the WPFToolkit assembly (actually, it only used the CalendarMode enum from that assembly, which is now in the CalendarMode.cs code file in the project), plus some other minor changes (note that you still need WPFToolkit if you want to build and run the demo project).
  • Ver. 1.2
    • Made a few minor tweaks, including solving the problem with tab orders.
      Note that this version is not compatible with the previous version, because TodayBackGround is changed to TodayBackground, and SelectedDateBackGround is changed to SelectedDateBackground.
  • Ver. 2.0
    • Now targets .NET 4.
    • Fixed the data binding bug in PersianDatePicker.
    • Added projects for testing PersianDate, and PersianDateControls.
    • Changed the demo project to use data binding to bind values between Calendar and PersianCalendar, and DatePicker and PersianDatePicker.
    • Some other minor changes.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Iran (Islamic Republic of) Iran (Islamic Republic of)
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generalرنگ کنترل Pin
mamad116618-Aug-19 19:16
mamad116618-Aug-19 19:16 
QuestionUsing in DataGrid Pin
Masterhame18-Aug-18 0:01
Masterhame18-Aug-18 0:01 
QuestionVery good, but... Pin
M.H. Shojaei14-Mar-18 20:29
M.H. Shojaei14-Mar-18 20:29 
QuestionHow to convert persianDate to/from normal system dateTime? Pin
AmirMohammad Naderi23-Jun-16 1:39
AmirMohammad Naderi23-Jun-16 1:39 
Questionتشکر Pin
Mohsen Panahi20-Mar-15 23:37
Mohsen Panahi20-Mar-15 23:37 
BugUsage inside DataGrid Pin
mammadalius11-May-14 12:35
mammadalius11-May-14 12:35 
Questionhow to add this component in toolbox? Pin
baharebaradari3-Sep-13 19:23
baharebaradari3-Sep-13 19:23 
AnswerRe: how to add this component in toolbox? Pin
Arash Sahebolamri6-Sep-13 3:00
Arash Sahebolamri6-Sep-13 3:00 
QuestionNice job Pin
Member 246906711-Aug-13 3:52
Member 246906711-Aug-13 3:52 
GeneralMy vote of 2 Pin
temperory20-Jul-13 21:26
temperory20-Jul-13 21:26 
QuestionHow to set DateTime to Control? Pin
temperory8-Jul-13 3:24
temperory8-Jul-13 3:24 
Questionmeeeeeeer30 Pin
fairy_I17-Feb-13 2:00
fairy_I17-Feb-13 2:00 
QuestionThank Pin
karimi55553-Feb-13 8:41
karimi55553-Feb-13 8:41 
GeneralMy vote of 5 Pin
Im4n19-Jun-12 3:25
Im4n19-Jun-12 3:25 
GeneralRe: My vote of 5 Pin
Shahin Khorshidnia9-Feb-14 0:54
professionalShahin Khorshidnia9-Feb-14 0:54 
GeneralMy vote of 5 Pin
Im4n19-Jun-12 3:23
Im4n19-Jun-12 3:23 
QuestionGood one. Pin
Kamran Behzad30-Apr-12 12:08
Kamran Behzad30-Apr-12 12:08 
QuestionMVVM and Binding Pin
hamid_ar8425-Apr-12 9:27
hamid_ar8425-Apr-12 9:27 
AnswerRe: MVVM and Binding Pin
Arash Sahebolamri25-Apr-12 18:57
Arash Sahebolamri25-Apr-12 18:57 
BugDataBinding not work Pin
Jalal Khordadi8-Apr-12 8:56
Jalal Khordadi8-Apr-12 8:56 
AnswerRe: DataBinding not work Pin
Arash Sahebolamri9-Apr-12 10:19
Arash Sahebolamri9-Apr-12 10:19 
Question.net 4 Pin
iranpcl30-Jan-12 11:14
iranpcl30-Jan-12 11:14 
AnswerRe: .net 4 Pin
Arash Sahebolamri9-Apr-12 10:20
Arash Sahebolamri9-Apr-12 10:20 
GeneralMy vote of 5 Pin
Shahin Khorshidnia26-Jan-12 4:47
professionalShahin Khorshidnia26-Jan-12 4:47 
GeneralRe: My vote of 5 Pin
Arash Sahebolamri9-Apr-12 10:21
Arash Sahebolamri9-Apr-12 10:21 

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.