تاریخ شمسی با Extension Method برای DateTime
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: دو دقیقه

برای تبدیل تاریخ میلادی به شمسی راه‌های زیادی وجود داره که خوب این هم یک نوع از اونها هست.
برای این کار با استفاده از Extension Method برای کلاس DateTime تاریخ رو تبدیل می‌کنیم.
برای فرمت‌های‌های نمایشی تاریخ هم می‌بایست به صورت متن به Method بدهید تا تاریخ رو به اون صورتی که می‌خواهید نمایش بدهد، برای نوشتن فرمت‌ها هم می‌توانید از این لینک استفاده کنید.
خوب برای این کار ابتدا یک کلاس با نام PersianDateExtensionMethods  بسازید و تمام کد زیر رو جایگزین کد‌های کلاس کنید:
using System;
using System.Globalization;
using System.Reflection;

namespace System 
{
    public static class PersianDateExtensionMethods
    {
        private static CultureInfo _Culture;
        public static CultureInfo GetPersianCulture()
        {
            if (_Culture == null)
            {
                _Culture = new CultureInfo("fa-IR");
                DateTimeFormatInfo formatInfo = _Culture.DateTimeFormat;
                formatInfo.AbbreviatedDayNames = new[] { "ی", "د", "س", "چ", "پ", "ج", "ش" };
                formatInfo.DayNames = new[] { "یکشنبه", "دوشنبه", "سه شنبه", "چهار شنبه", "پنجشنبه", "جمعه", "شنبه" };
                var monthNames = new[]
                {
                    "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن",
                    "اسفند",
                    ""
                };
                formatInfo.AbbreviatedMonthNames =
                    formatInfo.MonthNames =
                    formatInfo.MonthGenitiveNames = formatInfo.AbbreviatedMonthGenitiveNames = monthNames;
                formatInfo.AMDesignator = "ق.ظ";
                formatInfo.PMDesignator = "ب.ظ";
                formatInfo.ShortDatePattern = "yyyy/MM/dd";
                formatInfo.LongDatePattern = "dddd, dd MMMM,yyyy";
                formatInfo.FirstDayOfWeek = DayOfWeek.Saturday;
                System.Globalization.Calendar cal = new PersianCalendar();

                FieldInfo fieldInfo = _Culture.GetType().GetField("calendar", BindingFlags.NonPublic | BindingFlags.Instance);
                if (fieldInfo != null)
                    fieldInfo.SetValue(_Culture, cal);

                FieldInfo info = formatInfo.GetType().GetField("calendar", BindingFlags.NonPublic | BindingFlags.Instance);
                if (info != null)
                    info.SetValue(formatInfo, cal);

                _Culture.NumberFormat.NumberDecimalSeparator = "/";
                _Culture.NumberFormat.DigitSubstitution = DigitShapes.NativeNational;
                _Culture.NumberFormat.NumberNegativePattern = 0;
            }
            return _Culture;
        }

        public static string ToPeString(this DateTime date,string format = "yyyy/MM/dd")
        {
            return date.ToString(format,GetPersianCulture());
        }
    }
}

در کد بالا ما بااستفاده از متد GetPersianCulture یک کالچر برای تاریخ شمسی و فرمت‌های اون می‌سازیم و در متد ToPeString از اون استفاده و به متد ToString از کلاس DateTime پاس می‌دهیم.
چند مثال هم برای استفاده از متد ToPeString :
var date1 = DateTime.Now.ToPeString("yyyy/MM/dd");
var date2 = DateTime.Now.ToPeString("dddd, dd MMMM,yyyy");

//Output:
//1391/12/13
//یکشنبه, 13 اسفند,1391

نکته : با استفاده از Culture ای که در کلاس بالا در متد GetPersianCulture ساخته می‌شود امکانش هست که خود کلاس DateTime رو به شمسی تبدیل کرد، برای این کار باید به صورت زیر عمل کرد:
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture =  PersianDateExtensionMethods.GetPersianCulture();

var d1 = DateTime.Now.ToString();
//Output : 1391/12/13 11:25:44 ب.ظ

یک نکته‌ی تکمیلی: کتابخانه‌ی «DNTPersianUtils.Core» به همراه نکات این مطلب است؛ مانند:
Thread.CurrentThread.CurrentCulture = PersianCulture.Instance;
  • #
    ‫۱۱ سال و ۷ ماه قبل، دوشنبه ۱۴ اسفند ۱۳۹۱، ساعت ۰۳:۴۷
    مطلب خوبی بود، ممنونم
    اگر احتیاجی به استفاده دوم نباشه، بهتر GetPersianCulture()  به صورت Private تعریف بشه.
    • #
      ‫۱۱ سال و ۷ ماه قبل، دوشنبه ۱۴ اسفند ۱۳۹۱، ساعت ۰۳:۵۳
      مرسی،
      درسته ، ولی برای نکته آخر مطلب به صورت public تعریف کردم 
  • #
    ‫۱۱ سال و ۷ ماه قبل، سه‌شنبه ۱۵ اسفند ۱۳۹۱، ساعت ۱۷:۲۶
    ممکنه درباره این خط کد توضیح بدین:
    FieldInfo fieldInfo = _Culture.GetType().GetField("calendar", BindingFlags.NonPublic | BindingFlags.Instance);
    سپاس فراوان
    • #
      ‫۱۱ سال و ۷ ماه قبل، سه‌شنبه ۱۵ اسفند ۱۳۹۱، ساعت ۱۸:۰۸
      علت دسترسی به فیلد غیرعمومی (شده توسط مایکروسافت) و مقدار دهی آن است.
    • #
      ‫۱۱ سال و ۷ ماه قبل، سه‌شنبه ۱۵ اسفند ۱۳۹۱، ساعت ۱۸:۴۹
      با استفاده از این خط کد فیلد calendar در آبجکت _Culture رو میگیره و در دو خط پایین مقدار دهی می‌کنه،
      علت هم این هست که چون اون فیلد public نیست با استفاده از reflection اون رو مقدار دهی می‌کنیم.
      در مورد متد GetField هم در پارامتر دوم هم اون یک حالت فیلتر کردن هست که میگه فیلدی که public نیست و شامل مدیفایر static نباشد، برای دیدن تمام BindingFlags‌ها و توضیحشون این لینک  رو مشاهده کنید.
      • #
        ‫۱۱ سال و ۷ ماه قبل، سه‌شنبه ۱۵ اسفند ۱۳۹۱، ساعت ۲۲:۴۳
        ممنون از توضیحات شما.
  • #
    ‫۱۱ سال و ۷ ماه قبل، چهارشنبه ۱۶ اسفند ۱۳۹۱، ساعت ۰۹:۲۵
    سلام 
    آیا با تغییر culture به فارسی, timeZone هم تغییر می‌کنه یا بایستی به صورت جدا timezone رو هم تغییر بدیم ؟
    • #
      ‫۱۱ سال و ۷ ماه قبل، چهارشنبه ۱۶ اسفند ۱۳۹۱، ساعت ۱۶:۱۶
      سلام
      نه Culture با Time Zone فرق داره، برای تغییر Time Zone باید جداگانه تغییرات رو انجام بدید.
      یکی از دلیل هایی که این دو رو با هم ترکیب نکردن این هست که زبان یک برنامه یا تقویم اون برنامه به منطقه ای که اون نرم افزار استفاده میشه مرتبط نیست و ممکنه تو هر منطقه زمانی باشه، که باید به اون منطقه زمانی ست بشه.
      • #
        ‫۱۱ سال و ۶ ماه قبل، پنجشنبه ۱۵ فروردین ۱۳۹۲، ساعت ۰۶:۰۰

        با سلام

        آقای جباری با این روش راهی برای فارسی دیده شدن کنترهای datetimepicker  یا monthcalendar  ویژوال استدیو وجود داره ؟

        ممنون میشم توصیح بدین

        • #
          ‫۱۱ سال و ۶ ماه قبل، پنجشنبه ۱۵ فروردین ۱۳۹۲، ساعت ۱۷:۰۰
          با سلام
          راستش برای کنترلر‌های مربوط به تاریخ نه این امکان وجود نداره، البته اگر منظورتون از فارسی کردن شمسی کردن تاریخ کنترلر هست ولی اگه منظورتون ترجمه اسم ماه‌ها و غیره هست آره میشه از همین روش استفاده کرد فقط باید تو قسمت آخر رفلکشن رو حذف کنید.
          قبلا در Winform و WPF تست کردم و جواب نداد، که البته مشکل از اونجایی هست که موقع نوشتن این کنترل‌ها این امکان که بشه فرمت تاریخ رو تغییر داد ( شمسی کردن )  قرار ندادن.
          البته قبلا در WPF یکی از دوستان کنترلر مربوط به تاریخ رو دوباره بازنویسی کرده بود که می‌تونستید با تغییر Culture فرمت تاریخ رو تغییر بدید، اگر پیدا کردم لینک رو اینجا قرار میدم.
  • #
    ‫۱۱ سال و ۶ ماه قبل، پنجشنبه ۱۵ فروردین ۱۳۹۲، ساعت ۱۶:۲۷
    با سلام و تشکر از مطلب خوبتون
    میخواستم بدونم آیا این روش با سال کبیسه مشکلی نداره؟ یعنی مورد سال کبیسه هم محاسبه شده تو این روش؟
    ممنون
    • #
      ‫۱۱ سال و ۶ ماه قبل، پنجشنبه ۱۵ فروردین ۱۳۹۲، ساعت ۱۷:۲۱
      با سلام
      اگر PersianCalendar با کبیسه مشکلی نداشته باشه نه این هم مشکلی نداره، البته من خودم تست کردم که مشکلی ندیدم.
      • #
        ‫۱۱ سال و ۶ ماه قبل، پنجشنبه ۱۵ فروردین ۱۳۹۲، ساعت ۲۲:۲۴

        ممنون از جواب دوستان . تا اینجا فکر کنم برای  کنترلهای نمایش تاریخ فارسی ، از کارهای آقای  هادی اسکندری استفاده کنم

        باتشکر

  • #
    ‫۱۱ سال و ۴ ماه قبل، چهارشنبه ۱۵ خرداد ۱۳۹۲، ساعت ۱۳:۳۳
    سلام
    در قسمتی که آرایه مربوط به مقدار دهی اولیه ماهها را تعریف کردید تعدا آنها 13 می‌باشد که آیتم اخری برابر با رشته متنی خالی می‌باشد . دلیل خاصی دارد ؟

    ممنون
    • #
      ‫۱۱ سال و ۴ ماه قبل، چهارشنبه ۱۵ خرداد ۱۳۹۲، ساعت ۱۹:۲۹
      سلام
      خود Culture اصلی هم برای ماه‌ها 13 تا تعریف کرده که البته من علتش رو نمی‌دونم ، داخل این لینک می تونید ببینید 
  • #
    ‫۱۱ سال و ۴ ماه قبل، چهارشنبه ۱۵ خرداد ۱۳۹۲، ساعت ۱۵:۴۰
    به نظر شما این روش بهتره (تغییر culture) یا استفاده مستقیم از یک کلاس برای تبدیل تاریخ (با استفاده از PersianCalendar) ؟ کدوم یکی از نظر بهینه بودن برنامه و منطق بهتره ؟
    • #
      ‫۱۱ سال و ۴ ماه قبل، چهارشنبه ۱۵ خرداد ۱۳۹۲، ساعت ۱۹:۳۳
      سلام
      من اول اشتباه منظورتون رو فهمیدم، بستگی به خودتون داره که از کدوم روش می‌خواهید استفاده کنید، خوب اگر Culture در Thread برنامه رو تغییر بدید باید از کلاس واسطه به تاریخ میلادی دسترسی داشته باشید،
      در هر صورت تفاوتی نداره چون در هر دو مورد یک بار روند ساخت Culture انجام میشه
  • #
    ‫۱۱ سال و ۱ ماه قبل، دوشنبه ۴ شهریور ۱۳۹۲، ساعت ۱۷:۴۱
    سلام. ممنون به خاطر این اطلاعات عالی.
    یه مشکل . من از این کلاس استفاده کردم تویه پروژه وب که با MVC نوشتم. برای تغییر کلی این تاریخ هم اون رو توی App-Start داخل فایل Global.asax گذاشتم. همه جا عالی کار میکنه و به صورت خودکار تاریخ فارسی میده. فقط یه جا نمیده. من برای بروزرسانی دیتابیسم از CodeFirst Migration استفاده میکنم. برای مقدار دهی اولیه به مدلم تویه تابع Seed یه فیلدی دارم که تاریخه. تو اونجا تاریخو داره همون میلادی میگیره بازم؟ چرا؟
    breakPoint هم که گذاشتم. با اینکه این تابع App_Start از همه زود‌تر اجرا میشه و تنظیمات رو اوکی میکنه ولی بازم تو اون تابع Seed همون میلادیه!
    نمیدونم کار خاصی باید کرد یا مثلا کدو تو یه جایه دیگه غیره App_Start بزارم و یا....
    ممنون میشم جواب بدین .
    • #
      ‫۱۱ سال و ۱ ماه قبل، دوشنبه ۴ شهریور ۱۳۹۲، ساعت ۱۸:۳۰
      جایی که Thread.CurrentThread.CurrentCulture رو مقدار دهی کردی، بررسی کن Thread.CurrentThread.ManagedThreadId چه مقداری داره و آیا با مقدار ManagedThreadId موجود در متد Seed یکی هست یا خیر.
    • #
      ‫۱۱ سال و ۱ ماه قبل، دوشنبه ۴ شهریور ۱۳۹۲، ساعت ۱۹:۴۹
      سلام
      آقای محسن خان درست میگن امکانش هست thread فرق کنه
      البته این رو هم در نظر بگیر که داخل دیتابیس به میلادی ذخیره میشه و موقع نمایش تبدیل میکنه و به شمسی به شما نشون میده
  • #
    ‫۱۱ سال و ۱ ماه قبل، دوشنبه ۴ شهریور ۱۳۹۲، ساعت ۲۱:۲۵
    ممنون بابت جواب. اون یکی رو با راهنمایی شما حل کردم. ممنون. یه مشکل دیگه . من در این پروژه از Telerik Extension استفاده کردم . خودش این امکانو داره که بتونیش Globalization کنی . من این CultureInfo رو که شما درست کردینو بهش میدم اما نصفه نیمه شده. مثلا نوشته 27 مرداد 2013؟
    قسمت بالاشم که اسم ماه و سال هست، نوشته مثلا مرداد 2013. در کل سال رو به همون صورت میلادی نشون میده و بقیه رو به شمسی. این CultureInfo شما چیزی دیگه‌ای کم نداره که اونم رو هم مقدار دهی کنیم، تکمیل بشه؟ من احساس میکنم همه توابع و خصوصیت‌هاش مقدار دهی نشده. در ضمن با اینکه تقویمشو Persian Calender دادیم پس چرا هنوز داره با همون میلادی روز‌ها و بقیه رو حساب میکنه. تازه خلاصه اسم روز‌ها رو هم نشون نمیده. جالب اینجاست که وقتی من تاریخ سیستم خودمو فارسی میکنم مثلا این چیزاشو نمایش میده. چیزایی مثله خلاصه اسم روز. اگه بشه اینو یه جورایی فارسی کرد خیلی خوب میشه. هم کلی تمپلیت داره. هم کلی افکت خوب داره. هم خود تلریک یه نوع Project template برای MVC4 درست کرده که وقتی باهاش پروژتونو ایجاد میکنید دیگه خودش اتوماتیک هرجا فیلدتونو تاریخ تعریف کنید خیلی باحال همه چیز رو میاره. ببینین میتونین یه کاریش بکنین. باز ممنون.

    • #
      ‫۱۱ سال و ۱ ماه قبل، دوشنبه ۴ شهریور ۱۳۹۲، ساعت ۲۲:۱۳
      سلام
      من خودم با اکستنشن telerik تو mvc کار نکردم ولی از صحبتی که شما کردید میشه جوابتون رو داد
      دقت کنید شما سمت سرور که زبان #C هست تاریخ رو به شمسی تغییر دادید، ولی سمت کلاینت که زبانش از جاوا اسکریپت هست تاریخ به میلادی هندل میشه که اگر بخواهید از خود telerik استفاده کنید باید فایل‌های جاوا اسکریپت قسمت datetimepicker (اگه اشتباه نگم اسمش رو) باید تغییر بدید ولی اگر هم بخواید از کامپوننت‌های آماده استفاده کنید که زیاد هست تو اینترنت سرچ کنید حتما پیدا می‌کنید
  • #
    ‫۱۰ سال و ۱۰ ماه قبل، دوشنبه ۱۸ آذر ۱۳۹۲، ساعت ۱۸:۵۴
    سلام خیلی خوبه منم از کلاس زیر استفاده می‌کنم
    اما تو سیلورلایت چطور استفاده کنم؟

    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Reflection;
    using System.Text;
    
    
    public class PersianCulture : CultureInfo
    {
        private readonly Calendar cal;
        private readonly Calendar[] optionals;
    
    
        public PersianCulture(): this("FA-IR", true)
        {
    
        }
    
        public PersianCulture(string cultureName, bool useUserOverride): base(cultureName, useUserOverride)
        {
            //Temporary Value for cal.
            cal = base.OptionalCalendars[0];
    
            //populating new list of optional calendars.
            var optionalCalendars = new List<Calendar>();
            optionalCalendars.AddRange(base.OptionalCalendars);
            optionalCalendars.Insert(0, new PersianCalendar());
    
    
            Type formatType = typeof(DateTimeFormatInfo);
            Type calendarType = typeof(Calendar);
    
    
            PropertyInfo idProperty = calendarType.GetProperty("ID", BindingFlags.Instance | BindingFlags.NonPublic);
            FieldInfo optionalCalendarfield = formatType.GetField("optionalCalendars",
                                                                    BindingFlags.Instance | BindingFlags.NonPublic);
    
            //populating new list of optional calendar ids
            var newOptionalCalendarIDs = new Int32[optionalCalendars.Count];
            for (int i = 0; i < newOptionalCalendarIDs.Length; i++)
                newOptionalCalendarIDs[i] = (Int32)idProperty.GetValue(optionalCalendars[i], null);
    
            optionalCalendarfield.SetValue(DateTimeFormat, newOptionalCalendarIDs);
    
            optionals = optionalCalendars.ToArray();
            cal = optionals[0];
            DateTimeFormat.Calendar = optionals[0];
    
            DateTimeFormat.MonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty };
            DateTimeFormat.MonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty };
            DateTimeFormat.AbbreviatedMonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty };
            DateTimeFormat.AbbreviatedMonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", string.Empty };
    
            DateTimeFormat.AbbreviatedDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" };
            DateTimeFormat.ShortestDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" };
            DateTimeFormat.DayNames = new string[] { "یکشنبه", "دوشنبه", "ﺳﻪشنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه" };
    
            DateTimeFormat.AMDesignator = "ق.ظ";
            DateTimeFormat.PMDesignator = "ب.ظ";
    
            
            DateTimeFormat.ShortDatePattern = "yyyy/MM/dd";
            DateTimeFormat.LongDatePattern = "yyyy/MM/dd";
                 
            DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy/MM/dd"}, 'd');
            //DateTimeFormat.SetAllDateTimePatterns(new[] {"dddd, dd MMMM yyyy"}, 'D');
            //DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'y');
            //DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'Y');
            
    
        }
    
        public override Calendar Calendar
        {
            get { return cal; }
        }
    
        public override Calendar[] OptionalCalendars
        {
            get { return optionals; }
        }
    }


  • #
    ‫۱۰ سال و ۸ ماه قبل، جمعه ۱۸ بهمن ۱۳۹۲، ساعت ۱۸:۴۳
    سلام . من وقتی تاریخ رو به فرمت خورشیدی با همین روش ودر global اعمال میکنم ، در kendoui در grid وقتی حالت ajax() رو فعال میکنم این Error رو میگیرم .
    Specified time is not supported in this calendar. It should be between 03/21/0622 00:00:00 (Gregorian date) and 12/31/9999 23:59:59 (Gregorian date), inclusive.
    Parameter name: time
    اما  در حالت server()  مشکلی نداره . اشکال از کجاست و چه جوری میشه حل کرد ؟
    • #
      ‫۸ سال و ۱۱ ماه قبل، پنجشنبه ۷ آبان ۱۳۹۴، ساعت ۰۳:۴۳
      با سلام،
      من هم با این مشکل رو به رو شده ام.وقتی مدلی که یک فیلد تاریخ Required دارد رو به ویو میفرستم این خطا رو میده . میخواستم بدونم راه حلی برای این مشکل پیدا کردید؟ممنون
      • #
        ‫۸ سال و ۱۱ ماه قبل، پنجشنبه ۷ آبان ۱۳۹۴، ساعت ۰۴:۲۸
        DateTime در دات نت یک value type هست و نال قبول نمی‌کنه؛ مگر اینکه ?DateTime تعریف بشه.
  • #
    ‫۱۰ سال و ۶ ماه قبل، شنبه ۲ فروردین ۱۳۹۳، ساعت ۱۹:۵۰
    سلام
    من از ASP.NET MVC5 استفاده میکنم.
    می‌خواستم بدونم دقیقاً این کلاس رو باید در کجا قرار داد و چه تنظیماتی در global.asax انجام داد تا همه چی به صورت خودکار اجرا بشه.
    ممنون میشم راهنمایی کنید.
    • #
      ‫۸ سال و ۱۱ ماه قبل، پنجشنبه ۷ آبان ۱۳۹۴، ساعت ۰۴:۴۸
      سلام فرقی نمی‌کنه کلاس را کجا قرار بدید. این کلاس ایستا حاوی یک extension method هست که با افزودنش به پروژه به طور خودکار نوع داده datetime آن را خواهد شناخت. توصیه میکنم مقالات مربوطه به اکستنشن متد را بخوانید تا با نحوه کار آن آشنا شوید.
      فایل global.asax مهم نیست  از هر جا به بعد که فرهنگ ترد جاری تغییر کند نوع خروجی datetime متفاوت خواهد شد ولی میگوییم global.asax چون توانایی اجرا در اولین فرصت را دارد یعنی زمانی که برنامه شروع به کار میکند. الزامی هم به پروژه وب نیست بلکه میتواند در Main یک اپلیکیشن ویندوزی هم اینکار را انجام داد.