dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.16k stars 4.72k forks source link

Use different calender and format info for DateTime.ToString() and DateTime.Parse() #63939

Closed rezathecoder closed 2 years ago

rezathecoder commented 2 years ago

I use globalization and localization in my Asp.Net Core app and i want to use PersianCalendar when requested culture is set to 'fa-IR'.

This needs to happen whenever a DateTime.ToString() happens. I want it to be converted to persian date automatically and i don't want to change the code that is calling ToString() method (because other cultures still needs to see gregorian calendar).

The request localization works fine andCultureInfo.CurrentCulture is correclty set to 'fa-IR' but the default calendar for 'fa-IR' culture is GregorianCalendar.

So i created a subclass of CultureInfo like this:

public class PersianCulture : CultureInfo
{
    private readonly Calendar _calendar;
    private readonly Calendar[] _optionalCalendars;
    private DateTimeFormatInfo _dateTimeFormatInfo;

    public PersianCulture() : base("fa-IR") {
        _calendar = new PersianCalendar();

        _optionalCalendars = new List<Calendar>
        {
            new PersianCalendar(),
            new GregorianCalendar()
        }.ToArray();

        var dateTimeFormatInfo = CultureInfo.CreateSpecificCulture("fa-IR").DateTimeFormat;
        dateTimeFormatInfo.Calendar = _calendar;
        _dateTimeFormatInfo = dateTimeFormatInfo;
    }

    public override Calendar Calendar => _calendar;

    public override Calendar[] OptionalCalendars => _optionalCalendars;

    public override DateTimeFormatInfo DateTimeFormat {
        get => _dateTimeFormatInfo;
        set => _dateTimeFormatInfo = value;
    }
}

Then i added a middleware that checks if requested culture is 'fa-IR' then sets the CurrentCulture to PersianCulture as follows:

if ( CultureInfo.CurrentCulture.Name == "fa-IR" ) {
        var culture = new PersianCulture();
        CultureInfo.CurrentCulture = culture;
        CultureInfo.CurrentUICulture = culture;
   }

And i get the expected result. Whenever a someDate.ToString() is called the output is converted to persian date. For exapmle DateTime.Now.ToString() will output 16/10/1400 02:53:40 ب.ظ.

The problem is that the same Calendar and DateTimeFormatInfo is used whenever a DateTime.Parse() happens and the parsed date will be wrong because the source is in gregorian format but treated as persian.

How can i use my custom PersianCulture class for converting to string and use C# default's 'fa-IR' calendar for parsing dates?

If there is no way right now implementing it seems to require following changes: 1.Add two different Calendar and DateFormatInfo to CultureInfo class. One used for parsing and one used for converting to string 2.Change Parse and ToString methods to use appropriate properties

mkArtakMSFT commented 2 years ago

Thanks for contacting us. Is this a Blazor app?

ghost commented 2 years ago

Hi @rezathecoder. We have added the "Needs: Author Feedback" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

ghost commented 2 years ago

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.

See our Issue Management Policies for more information.

rezathecoder commented 2 years ago

Thanks for contacting us. Is this a Blazor app?

No it's ASP.NET Core 5 app. But i think it's a C# related issue. This feature could be used in any type of apps.

ghost commented 2 years ago

Tagging subscribers to this area: @dotnet/area-system-globalization See info in area-owners.md if you want to be subscribed.

Issue Details
I use globalization and localization in my Asp.Net Core app and i want to use `PersianCalendar` when requested culture is set to 'fa-IR'. This needs to happen whenever a `DateTime.ToString()` happens. I want it to be converted to persian date automatically and i don't want to change the code that is calling `ToString()` method (because other cultures still needs to see gregorian calendar). The request localization works fine and` CultureInfo.CurrentCulture` is correclty set to 'fa-IR' but the default calendar for 'fa-IR' culture is `GregorianCalendar`. So i created a subclass of CultureInfo like this: ```C# public class PersianCulture : CultureInfo { private readonly Calendar _calendar; private readonly Calendar[] _optionalCalendars; private DateTimeFormatInfo _dateTimeFormatInfo; public PersianCulture() : base("fa-IR") { _calendar = new PersianCalendar(); _optionalCalendars = new List { new PersianCalendar(), new GregorianCalendar() }.ToArray(); var dateTimeFormatInfo = CultureInfo.CreateSpecificCulture("fa-IR").DateTimeFormat; dateTimeFormatInfo.Calendar = _calendar; _dateTimeFormatInfo = dateTimeFormatInfo; } public override Calendar Calendar => _calendar; public override Calendar[] OptionalCalendars => _optionalCalendars; public override DateTimeFormatInfo DateTimeFormat { get => _dateTimeFormatInfo; set => _dateTimeFormatInfo = value; } } ``` Then i added a middleware that checks if requested culture is 'fa-IR' then sets the `CurrentCulture` to `PersianCulture` as follows: ```C# if ( CultureInfo.CurrentCulture.Name == "fa-IR" ) { var culture = new PersianCulture(); CultureInfo.CurrentCulture = culture; CultureInfo.CurrentUICulture = culture; } ``` And i get the expected result. Whenever a `someDate.ToString()` is called the output is converted to persian date. For exapmle `DateTime.Now.ToString()` will output `16/10/1400 02:53:40 ب.ظ`. The problem is that the same `Calendar` and `DateTimeFormatInfo` is used whenever a `DateTime.Parse()` happens and the parsed date will be wrong because the source is in gregorian format but treated as persian. How can i use my custom `PersianCulture` class for converting to string and use C# default's 'fa-IR' calendar for parsing dates? If there is no way right now implementing it seems to require following changes: 1.Add two different `Calendar` and `DateFormatInfo` to `CultureInfo` class. One used for parsing and one used for converting to string 2.Change `Parse` and `ToString` methods to use appropriate properties
Author: rezathecoder
Assignees: -
Labels: `area-System.Globalization`, `untriaged`
Milestone: -
Clockwork-Muse commented 2 years ago

How can i use my custom PersianCulture class for converting to string and use C# default's 'fa-IR' calendar for parsing dates?

Be explicit about the culture used for parsing/formatting. That is, don't use bare ToString(), use DateTime.ToString(IFormatProvider). I personally recommend doing this for all values that you want localized; don't depend on the implicit culture context. For one thing, making it explicit makes it easier to test. For another, it makes it so you can output both "localized" values (Persian and Gregorian).

For exapmle DateTime.Now.ToString()

Calling DateTime.Now in a server context is (almost) always a bug - you should be getting UTC time (ie, DateTime.UtcNow) and being explicit about the destination timezone.

If there is no way right now implementing it seems to require following changes:

Your underlying problem is that you're dealing with two different cultures, with two different calendars, and this probably isn't an appropriate way to handle it.

tarekgh commented 2 years ago

The request localization works fine and CultureInfo.CurrentCulture is correclty set to 'fa-IR' but the default calendar for 'fa-IR' culture is GregorianCalendar.

Which version of .NET you are using? in .NET 5.0 and 6.0 you should have the PersianCalendar as the default calendar for fa-IR.

So i created a subclass of CultureInfo like this:

You don't need to do that. you can have a code like:

            if (CultureInfo.CurrentCulture.Name == "fa-IR")
            {
                CultureInfo ci = CultureInfo.CurrentCulture;

                if (ci.IsReadOnly && ci.DateTimeFormat.Calendar is not PersianCalendar)
                {
                    ci = (CultureInfo) ci.Clone();
                    CultureInfo.CurrentCulture = ci;
                }
                if (ci.DateTimeFormat.Calendar is not PersianCalendar)
                {
                    ci.DateTimeFormat.Calendar = new PersianCalendar();
                }
            }

How can i use my custom PersianCulture class for converting to string and use C# default's 'fa-IR' calendar for parsing dates?

@Clockwork-Muse already answered that. It is not right to have one current culture that can format a date with a specific calendar and parse the date with another calendar. Imagine some other code not owned by you using the current culture, you can break many different other scenarios.

If there is no way right now implementing it seems to require following changes:

@Clockwork-Muse replied that too.

You need just pass the right culture to the DateTime.ToString and DateTime.Parse and not try to just handle everything through the current culture. As I mentioned this can break other code depend on the current culture. The other idea would be, provide a wrapper method for DateTime.ToString and DateTime.Parse and apply whatever logic you want there. Then have your code just call such wrapper method.

By that, I am closing this issue. Feel free to reply back if you have any more questions.