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