When working with dates, we must consider the fact that there are many different calendar systems worldwide. The default calendar used in C# DateTime operations is the Gregorian calendar, since it’s the most popular system. However, there are situations where we may need to work with other calendars, such as the Hijri, Hebrew, or Chinese calendar.

Thankfully, C# provides a native solution to work with these different calendars. In this article, we’ll dive into the details of calendars in C# and learn how to work with them.

To download the source code for this article, you can visit our GitHub repository.

Let’s get started.

What Are Calendars in C#?

The Calendar class in C# is an abstract class that serves as the base for all other calendar implementations. It is a member of the System.Globalization namespace, which also contains types that define culture-related information.

Calendar classes provide methods for operating on different date systems, such as determining the number of days in a month with GetDaysInMonth(), checking if a year is a leap year with IsLeapYear(), among others.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

To use a Calendar, we need to create an instance of one of its derived classes. For example, to get the number of days in January in the Gregorian calendar:

var calendar = new GregorianCalendar();
var daysInJanuary = calendar.GetDaysInMonth(2023, 1);

Assert.Equal(31, daysInJanuary);

The derived Calendar classes that we can use to instantiate different calendars are enumerated in the official documentation.

Apart from the methods inherited from the base class, System.Globalization.Calendar; there are methods specific to certain calendars. For example, the JapaneseLunisolarCalendar has GetSexagenaryYear(), which allows us to calculate the sexagenary year for a specific date. 

Working With Calendars and DateTime Objects in C#

The DateTime type can receive a Calendar instance in its constructor. This will allow us to instantiate DateTime objects by using any date in any of the available calendars, without needing a prior conversion.

For example, we can instantiate a DateTime instance that represents the day 02/04/1444 in Hijri calendar:

var calendar = new HijriCalendar();
var dateTime = new DateTime(1444, 4, 2, calendar);

In this example, we instantiate a DateTime with a HijriCalendar instance as the calendar parameter. By doing that, we ensure the constructor knows that the date parameters – the year, the month and the day, are relative to the Hijri calendar.

Now, we can check the Day, Year, Month properties of the DateTime instance: 

Assert.Equal(27, dateTime.Day);
Assert.Equal(10, dateTime.Month);
Assert.Equal(2022, dateTime.Year);

Although we have created the DateTime object using the HijriCalendar, the internal representation remains in the GregorianCalendar which is 27/10/2022. This happens because the DateTime struct merely represents a moment in time, by comparing the date information received in the constructor with the midnight of January 1, 0001 C.E. in the Gregorian calendar and checking how much time passed between both.

Calendars and Cultures

When working with applications that have a global scope, it’s not unusual for us to need to implement localization strategies. We can have an easier time doing so by using the C# CultureInfo class, which provides information about specific cultures.

This information includes, among other things, the calendars they use, represented by a Calendar property. This is important because different cultures may have different conventions for representing dates, with different divisions of years, months, and days.

By using the Calendar property of the CultureInfo object, we can get the main calendar of a specific country without having to directly instantiate it:

var usCulture = new CultureInfo("en-US");
var usCalendar = usCulture.Calendar;

var daysInMonth = usCalendar.GetDaysInMonth(2023, 3);
Assert.Equal(31, daysInMonth);

In this example, we get an instance of the United States calendar without having to use the GregorianCalendar class. This can be useful when working dynamically with cultures, since we may not know what class to use beforehand.

Also, it’s possible for some cultures to support more than one calendar. In these cases, we can get all optional calendars by using the CultureInfo.OptionalCalendars property:

var cultureInfo = new CultureInfo("ar-SA");
var optionalCalendars = cultureInfo.OptionalCalendars;

Assert.Equal(3, optionalCalendars.Length);                             
Assert.Equal("UmAlQuraCalendar", optionalCalendars[0].GetType().Name);
Assert.Equal("GregorianCalendar", optionalCalendars[1].GetType().Name);
Assert.Equal("HijriCalendar", optionalCalendars[2].GetType().Name);

For the Saudi culture, three calendars are supported, UmAlQuraCalendar, GregorianCalendar, and HijriCalendar.

Date Formats

Calendars also affect the way dates are formatted when we convert them to and parse them from strings. The ToString() method will use the Calendar property of CultureInfo.CurrentCulture.DateTimeFormat to determine how to format the date:

CultureInfo.CurrentCulture = new CultureInfo("fr-FR");
var date = new DateTime(2023, 06, 18, 14, 00, 00);
                                                           
var formattedDate = date.ToString("f");
                                                           
Assert.Equal("dimanche 18 juin 2023 14:00", formattedDate);

First, we set the current culture to be France’s French. After that, we call the ToString() method of  date with the f parameter to get the full date time. As a result, we get a string formatted in the default France calendar which is a French-localized version of the Gregorian Calendar. 

It’s important to note that we can change the CultureInfo.CurrentCulture.DateTimeFormat to any calendar from the OptionalCalendars of the current culture. If we try and set a calendar that is not in the OptionalCalendars list we get an exception: 

System.ArgumentOutOfRangeException: Not a valid calendar for the given culture

The CultureInfo.CurrentCulture.Calendar will always contain the default calendar of the current culture. In fact, the property is read-only and cannot be changed.

We can also change the format for the date string without having to change the current culture: 

CultureInfo.CurrentCulture = new CultureInfo("ar-SA");
var date = new DateTime(2023, 06, 18, 14, 00, 00);
                                                                 
var formattedDate = date.ToString(new CultureInfo("fr-FR"));
                                                                 
Assert.Equal("dimanche 18 juin 2023 14:00", formattedDate);

We first change the current culture to be Saudi Arabic and instantiate a date, when we call ToString(), we pass the CultureInfo that we want to use to format the date string. Consequently, we have a string formatted in France’s French instead of Saudi Arabic.  

Comparing Dates With the Help of Calendars

By using CultureInfo and Calendar instances, we can even compare dates in different countries with relative ease:

var usCulture = new CultureInfo("en-US");
var saudiCulture = new CultureInfo("ar-SA");

var usDate = new DateTime(2023, 3, 21, usCulture.Calendar);
var saudiDate = new DateTime(1444, 8, 10, saudiCulture.Calendar);

Assert.True(usDate > saudiDate);

As we have seen, by passing the different cultural calendars as arguments when instantiating the DateTime objects, we made it possible to compare dates given in both the US and Saudi Arabia, which use the Gregorian and Um Al-Qura calendars, respectively, without any issues.

Conclusion

In this article, we’ve learned what are DateTime Calendars in C# and how to use them with DateTime and CultureInfo objects. We went over the importance of having different calendar types and gave examples of how to use them to operate with dates in different cultural settings.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!