0

I have a problem converting Gregorian calendar dates to Persian(Hijri calendar) using default system "en-GB" culture in my ASP.NET MVC Application. I used the globalization tag in my web.config so the default culture would be "en-GB":

    <system.web>
        ...
        <globalization uiCulture="en-GB" culture="en-GB" />
        ...
    </system.web>

The conversion will happen for any date except for these exact dates that I listed them below and will throw "String was not recognized as a valid DateTime.” error.

The exact dates that will throw exception are:

  1. 22/07/(any year)
  2. 22/09/(any year)
  3. 19/05/(any year)
  4. 20/05/(any year)
  5. 21/05/(any year)

A part of my sample code:

using System.Globalization;
    ...
    ...
    public static PersianCalendar PC = new PersianCalendar();
    ...

    //I'm using this method for conversion
    public static DateTime GregorianToPersian(DateTime date)
    {
                ...

                string stringDate = string.Format("{0}/{1}/{2} {3}:{4}:{5}"
                    , PC.GetDayOfMonth(date), PC.GetMonth(date), PC.GetYear(date)
                    , PC.GetHour(date), PC.GetMinute(date), PC.GetSecond(date));
                return DateTime.Parse(stringDate); //error!
    }

Hope you guys can help me out with. Thanks.

aRasH
  • 144
  • 1
  • 3
  • 12
  • 2
    Please could you provide a [mcve] rather than snippets, and give more detail of what you're trying to achieve? Bear in mind that a `DateTime` is *always* in the Gregorian calendar - your "conversion" code doesn't look like it's going to achieve a *useful* result to me. (You may well find my Noda Time project more useful here, as its `LocalDate` etc types *do* retain calendar information.) – Jon Skeet May 26 '17 at 06:18
  • As an example, 22/07/2017 (Gregorian) is 31/04/1396 (Persian). How do you expect that to parse as a Gregorian date? – Jon Skeet May 26 '17 at 06:58
  • Why are you building that string in the first place? If you want to print a dateetime using the persian calendar culture, why not just specify the culture in the format string, eg `String.Format(CultureInfo.GetCultureInfo("fa-IR"),"{0}",DateTime.Now)`. The *DateTime* value doesn't change – Panagiotis Kanavos May 26 '17 at 08:50
  • @JonSkeet I'm trying to convert "22/07/2017" which is a Gregorian date to "31/04/1396" which is a Persian date, using my method that I described, and it returns error as I mentioned. – aRasH May 26 '17 at 08:52
  • @arash_dev why? The date value is the same. Just change the culture when formatting the value `String.Format(CultureInfo.GetCultureInfo("fa-IR"),"{0:d}",new DateTime(2017,7,22))` returns `31/04/1396`. `String.Format(CultureInfo.GetCultureInfo("fa-IR"),"{0:d}",DateTime.Now)` returns `05/03/1396` – Panagiotis Kanavos May 26 '17 at 08:52
  • Anyway, if you wanted to convert values from one calendar to the other, use the appropriate calendar classes. Don't try to go through strings – Panagiotis Kanavos May 26 '17 at 08:54
  • @arash_dev: Right, and did you read my comment on *why* you'll get that error? There are only 30 days in April. **There's no such thing as a Persian DateTime value. Every DateTime is in the Gregorian calendar.** It's vital you understand that. – Jon Skeet May 26 '17 at 09:01
  • @PanagiotisKanavos the way that I'm using the format is not optimized i guess, but the real problem is in the DateTime.Parse line, and I'm trying to get return value as DateTime not String. – aRasH May 26 '17 at 09:02
  • @PanagiotisKanavos I see that the Parse thing is in Gregorian format only, as you said it with the April example but what is the solution here – aRasH May 26 '17 at 09:04
  • @arash_dev: It's not `Parse` that's particularly Gregorian-oriented - it's `DateTime` itself. You're trying to do something that's just impossible using `DateTime`. – Jon Skeet May 26 '17 at 09:06
  • Extended answer by Jon Skeet [here](https://stackoverflow.com/questions/13329631/how-to-convert-a-persian-date-into-a-gregorian-date) – Panagiotis Kanavos May 26 '17 at 09:09

2 Answers2

8

Leaving the parsing error aside, this isn't going to achieve what you want to achieve. A DateTime value is always in the Gregorian calendar - trying to parse 31/04/1396 as a DateTime is never going to work because it's not a valid date in the Gregorian calendar.

I would advise using my Noda Time library for this instead, where appropriate types "know" which calendar system they're part of, and you can explicitly convert between them. For example:

// The Gregorian calendar is used implicitly
LocalDate gregorian = new LocalDate(2017, 7, 22);
LocalDate persian = gregorian.WithCalendar(CalendarSystem.PersianArithmetic);
Console.WriteLine($"{persian:yyyy-MM-dd}");

Output:

1396-04-31

You can then do sensible arithmetic with persian such as adding a month etc, which would give the wrong answer if you tried doing it with DateTime naively. (You could use Calendar.AddMonths etc, but you'd need to remember to do so everywhere.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
1

DateTime object is in Gregorian calendar (from MSDN DateTime specification ) :

The DateTime value type represents dates and times with values ranging from 00:00:00 (midnight), January 1, 0001 Anno Domini (Common Era) through 11:59:59 P.M., December 31, 9999 A.D. (C.E.) in the Gregorian calendar.

Your solution is valid until target days are in range of target month in Gregorian Calendar. (Un)fortunely DateTime struct is sealed which mean, you can't override it.

Because of this limits, I suggest make some kind of PersianDateTimeUtility class, where would be logic of parsing object as DateTime to specified type (for example String), comparing dates etc. There are some frameworks for this purpose, but i haven't tested them so I will not describe them.

MarBog
  • 21
  • 5
  • thanks, but can you give me a piece of valid code please – aRasH May 26 '17 at 09:05
  • To be clear - the `DateTime` isn't "in Gregorian Calendar". In itself, all it does is count the number of 100 nanosecond intervals since some fixed point in the past. I believe that the documentation, at that point, is saying that it supports a range of dates and that range of dates corresponds to 01/01/0001 - 31/12/9999 in the Gregorian Calendar. That is, the "in the Gregorian Calendar" attaches to the range, not to the `DateTime` class. – Damien_The_Unbeliever May 26 '17 at 09:06
  • I didn't found solution how to make representation of `31.04.YYYY` date as `DateTime`, but propably it is impossible in that way. Valid piece of code depends of purpose. Are you need only show this date? Or compare? Are you Serialize it? There are too much unknowns in this topic. – MarBog May 26 '17 at 09:34