اگر چه بر اساس قانون مصوب 11 فروردین 1304 هجری خورشیدی مجلس شورای ملی و اصل هفدهم قانون اساسی جمهوری اسلامی ایران مصوب 1358 تقویم هجری خورشیدی تقویم رسمی ایران است اما در امور تجاری ، ارتباطات جهانی و تاریخ نگاری نیاز به استفاده از گاه شماری میلادی یا تقویم گریگوری (Gregorian Calendar) بسیار پررنگ می نمایاند. همچنین پشتیبانی نشدن تاریخ هجری خورشیدی از سوی طیف وسیعی از نرم افزارها ی سیستمی و برنامه های کاربردی مانند سامانه های مدیریت پایگاه داده ، زبانهای برنامه نویسی ، سیستم عامل ها و ... ضرورت طراحی و پیاده سازی الگوریتم های دقیق و کاربردی تبدیل گاهشماری میلادی و هجری خورشیدی توسط برنامه نویسان را دو چندان ساخته است .
در این مقاله با نگاهی کوتاه به جنبه های مختلف گاهشماری میلادی و هجری خورشیدی بر شیوه کبیسه گیری این دو تقویم که هسته اصلی الگوریتم تبدیل تاریخ را تشکیل می دهد متمرکز خواهیم شد و در پایان با استفاده از زبان برنامه نویسی جاوا کلاس ساده ای برای کار با تقویم هجری خورشیدی ارائه خواهیم کرد . لازم به گفتن است که منبع اصلی این مقاله نوشته های دکتر ایرج ملک پور اختر شناس ایرانی و از اساتید موسسه ژئوفیزیک دانشگاه تهران در کتاب ((تقویم هجری شمسی، هجری قمری و میلادی)) از انتشارات دانش نگار است.
هرچند که بیشتر افراد تقویم را جدولی از روزهای سال به همراه داده های دیگری چون روزهای هفته ، مناسبتها و مانند آن تصور می کنند اما لازم است بدانیم که اهمیت تقویم در تعریف و اندازه گیری واحد های زمان است که بیانگر جایگاه ارزنده آن در نظم بخشیدن به فعالیت های مختلف در زندگی انسانهاست و بدون وجود آن بسیاری از جنبه های زندگی آدمی دستخوش بی نظمی و بی انظباطی شدید گردیده و شیرازه امور از هم خواهد گسست خواهد شد.
انسانهای نخستین از پدیده های طبیعی که دارای ویژگی تکرار در بازه های زمانی منظم هستند و به صورت مستقیم قابل مشاهده اند برای تعیین و اندازه گیری واحد های زمان ( روز ، ماه ، سال ، ساعت ، دقیقه و ...) و تدوین تقویم استفاده می کردند. از مهمترین پدیده های طبیعی پذیرفته شده در تنظیم و تدوین گاه شماری های مختلف می توان به حرکت چرخشی زمین به دور محور خودش ، چرخش ماه به گرد زمین و حرکت ظاهری سالانه خورشید اشاره کرد. از این رو بهره گیری از نظم موجود در حرکت ماه و خورشید نسخه های مختلفی از گاه شماری را پدید آورده است که مهمترین آنها عبارتند از:
تقویم میلادی کنونی ، یا تقویم گریگوری (Gregorian Calendar) در بسیاری از کشورهای جهان به عنوان تقویم رسمی است . این تقویم حاصل اصلاح گاهشماری قدیمی ژولی (Julian Calndar) است و باهدف انطباق دقیق عید پاک مسیحیان در زمان واقعی تدوین گردیده است چرا که طول متوسط سال در تقویم ژولی برابر 365.25 شبانروز است و از مدت متوسط سال خورشیدی که برابر 365.2422 شبانروز است در حدود 0.0078 شبانروز بیشتر است،که این تفاوت اندک سبب می شود تا فصلهای تقویمی در هر 128 سال یک شبانروز ، در هر 400 سال در حدود سه شانروز و در هر 1000 سال حدود هشت شبانروز زودتر از زمان واقعی شان آغاز شوند برای مثال اگر آغاز بهار سالی در 21 مارس باشد.
پس از گذشت هزار سال ژولی در 13 مارس خواهد بود که این باعث می شود تا عید پاک و دیگر اعیاد گردان مسیحیان که بر مبنای تاریخ عید پاک محاسبه می شوند نیز دیرتر از زمان واقعی شان رخ دهند. این تقویم از آن رو گریگوری نامیده می شود که در 1576 میلادی ، پاپ گریگوری سیزدهم (Pope Gregore XIII) گروهی متشکل از ریاضی دانان ، ستاره شناسان ، تاریخ نگاران و اسقف ها را مامور بررسی و اصلاح تقویم ژولی نمود و حاصل کار این گروه طی فرمان 24 فوریه 1582 پاپ گریگوری منتشر گردید. تقویم جدید که همان تقویم میلادی کنونی است ، به عنوان تقویم رسمی ، در بیشتر کشورهای جهان پذیرفته شده است.
بر اساس قاعده کبیسه تقویم گریگوری بجز سالهای انتهای سده (سالهای با دو صفر در سمت راست برای مثال سال 2000 یا 1900) هر سال بخشپذیر بر عدد 4 کبیسه است و سالهای انتهای سده که بر عدد 400 بخشپذیر باشند نیز کبیسه خواهند بود. برای مثال سالهای 2004 ، 2008 ، 2020 ، 2048 کبیسه هستند چون بر چهار بخش پذیر بوده و سال انتهای سده نیز نیستند همچنین سالهای 1700 و 1800 و 1900 با وجود آنکه بر چهار قابل قسمت هستند ، کبیسه شمرده نمی شوند. چرا که سال انتهای سده بوده و بر 400 بخشپذیر نمی باشند اما سال 2000 کبیسه است زیرا سال انتهای سده بوده و بر 400 نیز بخشپذیر است.
public boolean GregorianIsLeap(int year) { if(((year%100)!=0 && (year%4==0))||((year%100)==0 && (year%400==0))) return true; return false; }
قطعه کد دریافت تعداد روزهای موجود در یک ماه میلادی :
public int GregorianDaysInMounth(int year,int Month ) { int days=0; switch(Month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: days=31; break; case 4: case 6: case 9: case 11: days=30; break; case 2 : days=(GregorianIsLeap(year))? 29 : 28; break; } return days; }
تقویم هجری خورشیدی بنا بر قانون مصوب سه شنبه 11 فروردین 1304 هجری خورشیدی مجلس شورای ملی و اصل هفدهم قانون اساسی جمهوری اسلامی ایران مصوب 1358 تقویم رسمی ایران شد. تقویم رسمی ایران برگرفته از تقویم با شکوه و بسیار دقیق جلالی است که خود نتیجه اصلاح صورت گرفته در تقویم یزدگردی توسط منجمان معروف ایرانی است . گاهشمار یزدگردی که در ایران پیش از هجوم تازیان استفاده می شد به خاطر اجرا نشدن کبیسه های آن در روزگار هرج و مرج حمله اعراب،دقت خود را از دست داد و به رغم اصلاح صورت گرفته در روزگار خلافت عباسیان ، اول فروردین تقویم یزدگردی بر آغاز بهار نجومی (لحظه عبور قرص خورشید از نقطه اعتدال بهاری) منطبق نگردید.
از این رو سلطان جلال الدین ملکشاه سلجوقی طی فرمانی که در 18 فروردین 444 یزدگردی صادر شد هیاتی از منجمان ایرانی را مامور اصلاح تقویم یزدگردی کرد . این هیات پس از چهار سال رصد و محاسبه دریافت که نوروز سال 448 یزدگردی 18 شبانروز زودتر از بهار نجومی آغاز شده است از این رو با هدف انطباق نوروز یزدگردی بر آغاز بهار نجومی ، با کبیسه کردن 18 شبانروز 19 فروردین سال 448 یزدگردی ، نوروز اعلام شد و آن را نوروز سلطانی نامیدند و سال 448 یزدگردی را که در برگیرنده 383 شبانروز بود سال کبیسه ملکشاهی نام گذاشتند ، تقویم اصلاح شده را به افتخار سلطان جلال الدین ملکشاه سلجوقی تقویم جلالی نامیدند.ایرانیان لحضه رسیدن مرکز قرص خورشید به نقطه اعتدال فروردین را لحظه تحویل سال نامیده اند و جشن های سال نو را از لحظه یاد شده آغاز می کنند اما در تقویم میلادی آغاز سال نو به طور قراردادی ، همواره از ساعت 24 روز 31 دسامبر آغاز می شود
برای تثبیت همیشگی نوروز در آغاز بهار طبیعی، مقرر شد که آغاز سال روزی باشد که در نصفالنهار مربوط به آن، خورشید به برج حمل رسیده باشد، مشروط بر آنکه تا نصفالنهار آن روز، خورشید در اولین درجه برج حمل و در نصفالنهار روز پیش در برج حوت بوده باشد. با این قاعده نجومی تعیین آغاز سال نو، کبیسههای تقویم جلالی هر ۴ یا ۵ سال اتفاق میافتند. تقویم جلالی شامل 12 ماه 30 شبانروزی و 5 یا 6 شبانروز اضافی بود ، روزهای اضافی به آخر برج حوت ( یا آخر ماه اسفند که در آن روزگار اسفندارمذ خوانده می شد ) اضافه می گردید.سر آغاز گاهشماری هجری خورشیدی روز جمعه یک فروردین سال یکم هجری است که برابر 23 مارس 622 میلادی (گرگورین) است به بیان دیگر مبدا تاریخ هجری خورشیدی آغاز بهار سال هجرت پیامبر گرامی اسلام حضرت محمد (ص) از مکه به مدینه است.
ماده اول - مجلس شورای ملی تصویب مینماید که از نوروز (1304) تاریخ رسمی سالیانه مملکت به ترتیب ذیل معمول گردد و دولت مکلفاست که در تمام دوایر دولتی اجرا نماید:
تبصره - در سنین کبیسه اسفند 30 روز خواهد بود.
ماده دوم - ترتیب سالشماری ختا وایغور که در تقویمهای سابق معمول بوده از تاریخ تصویب این قانون منسوخ خواهد بود.
این قانون که مشتمل بر دو ماده است در جلسه یازدهم ماه فروردین یک هزار و سیصد و چهار شمسی به تصویب مجلس شورای ملی رسید.
مبدأ تاریخ رسمی کشور هجرت پیامبر اسلام ( صلی الله علیه و آله و سلم ) است و تاریخ هجری شمسی و هجری قمری هر دو معتبر است، اما مبنای کار ادارات دولتی هجری شمسی است. تعطیل رسمی هفتگی روز جمعه است.قطعه کدی که تعداد روزهای موجود در هر ماه هجری خورشیدی را بر می گرداند :
public int DaysInMounth( int year,int month) { if(month<7) return 31; else if(!isleap(year)&& month==12) return 29; else return 30; }
بر خلاف گاهشماری گریگوری که تعیین کبیسه بودن یک سال تنها با یک محاسبه ساده ریاضی انجام می شود در گاهشماری هجری خورشیدی سال کبیسه به کمک قاعده تعیین نخستین روز سال تقویمی صورت می پذیرد. در تقویم هجری خورشیدی نخستین روز سال تقویمی نیز با قاعده تعیین نوروز تقویم جلالی پیدا می شود در این قاعده ، لحظه تحویل با لحظه ظهر روز تحویل سال مقایسه می گردد حال چنانچه لحظه تحویل پیش از لحظه ظهر واقع گردد در این حالت روز تحویل نخستین روز سال است و چنانچه لحظه تحویل منطبق بر لحضه ظهر و یا بعد از لحظه ظهر واقع گردد ، فردای روز تحویل ، نخستین روز سال است.
با تعیین نخستین روز سال تقویمی می توان نوع سال تقویم هجری خورشیدی از نظر عادی (365 شبانروزی) و یا کبیسه بودن (366 شبانروزی) را مشخص کرد. برای این کار کافی است که تاریخ نخستین روز سال مورد نظر با تاریخ 29 اسفند سال پیش مقایسه گردد حال چنانچه نخستین روز سال در فردای 29 اسفند سال پیش واقع شود ، این حالت سال عادی است و شامل 6 ماه 31 شبانروزی ، 5 ماه 30 شبانروزی و یک ماه 29 شبانروزی است یعنی سال دارای 365 شبانروز است. اما چنانچه بین نخستین روز سال و 29 اسفند سال پیش یک شبانروز فاصله وجود داشته باشد ناگزیر اسفند ماه سال پیش را 30 شبانروز در نظر می گیرند که در این حالت سال دارای 6 ماه 31 شبانروزی و 6 ماه 30 شبانروزی است یعنی سال دارای 366 شبانروز خواهد بود چنین سالی سال کبیسه نامیده می شود. لازم به گفتن است است که محاسبات یاد شده برای نصف النهار رسمی کشور تعیین میشود.
برای مثال لحظه تحویل سال 1388 هجری شمسی ساعت 5 و 13 دقیقه و 49 ثانیه روز جمعه ، روز اول بعد از 29 اسفند 1387 است و لحظه ظهر روز یاد شده ساعت 12 و 7 دقیقه و 33 ثانیه است بنابراین ، روز شنبه نخستین روز سال 1388 هجری خورشیدی است و از این رو سال 1387 یک سال کبیسه است.در باره کبیسه های گاهشماری هجری خورشیدی بر اساس بررسی های دکتر ایرج ملک پورلازم است گفته شود که :
ترتیب و توالی این دوره ها نیز منظم نیست و از قاعده مشخصی پیروی نمی کنند از سال 3525 هجری خورشیدی پیش از هجرت تا سال 1498 هجری خورشیدی یعنی در مدت 5032 سال تعداد 155 دوره وجود دارد که به تقریب 19 درصد آنها 29 سالی و 77 درصد 33 سالی و 4 درصد نیز 37 سالی اند.هرچند که در گاهشماری هجری خورشیدی ، یافتن سال کبیسه نیازمند رصد و انجام محاسبات نجومی است اما زمانی که پای طراحی یک نرم افزار تبدیل تاریخ هجری شمسی به میلادی و برعکس در میان باشد ، چاره ای جز پذیرش یک شیوه محاسباتی برای تعیین سال کبیسه باقی نمی ماند.
به نظر می رسد در بین شیوه های حسابی مختلف تشخیص سال کبیسه روشی که شرکت مایکروسافت در نسخه های قبل از .NET Framework 4.6 جهت تشخیص سال کبیسه در متد IsLeapYear کلاس PersianCalendar بکار برده است تطابق دقیقی با سالهای کبیسه گاهشمار رسمی ایران از 1304 تا 1395 دارد و با کبیسه های معرفی شده توسط دکتر ایرج ملک پور در کتاب تقویم هجری شمسی ، هجری قمری و میلادی ایشان برای سالهای 1380 تا 1450 نیز سازگار است.
بنابراین الگوریتم شرکت مایکروسافت جهت ساخت یک تقویم نرم افزاری کاربردی و دقیق در بازه زمانی 1304 تا 1450 بسیار مناسب است بر اساس روش یاد شده سال کبیسه در گاهشماری هجری خورشیدی سالی است که باقی مانده تقسیم آن بر 33 یک از اعداد 1, 5, 9, 13, 17, 22, 26 و یا 30 باشد به نظر می رسد مایکروسافت روش خود را وامدار شیوه شادروان استاد احمد بیرشک در تشخیص سال های کبیسه است.قطعه کد تشخیص سال کبیسه در تقویم هجری خورشیدی :
public boolean isleap(int pyear) { int[] mod= {1, 5, 9, 13, 17, 22, 26,30}; for(int i:mod) if (i==(pyear%33)) return true; return false; }
کد منبع یک کلاس ساده برا ی تقویم هجری خورشیدی :
توجه 1- این کلاس تنها با هدف آموزش مبانی تقویم هجری خورشیدی طراحی شده و بهینه سازی کد و یا مدیریت استثنا در آن انجام نشده است.
توجه 2-در این کلاس برای افزایش دقت و کاربردی بودن هر چه بیشتر ، مبدا تاریخ میلادی ، تاریخ گریگوری مطابق با یکم فروردین ماه سال یک هزارو سیصدو چهار یعنی سال رسمی شدن گاهنامه هجری خورشیدی در ایران انتخاب شده است که برابر است با بیست و یکم مارس سال یک هزاررو نهصدو بیست و پنج گریگوری همچنین مبدا تاریخ هجری شمسی نیز یکم فروردین ماه سال یک هزارو سیصدو چهار در نظر گرفته شده است.
package PersianCalendar; public class PersianDate { private int JalaliYear; private int JalaliMonth; private int JalaliDay; /******************************************/ public PersianDate(int Pyear,int Pmonth,int Pday){ JalaliYear=Pyear; JalaliMonth=Pmonth; JalaliDay=Pday; } /*************************************************/ public int getyear(){ return JalaliYear; } /*************************************************/ public void setyear(int year){ JalaliYear=year; } /*************************************************/ public int getmonth(){ return JalaliMonth; } /*************************************************/ public void setmonth(int month){ JalaliMonth=month; } /*************************************************/ public int getday(){ return JalaliDay; } /*************************************************/ public void setday(int day){ JalaliDay=day; } /*************************************************/ public String To_String(){ return JalaliYear + "/"+ JalaliMonth + "/"+JalaliDay; } //این متد کبیسه بودن سال هجری خورشیدی را تشخیص می دهد public boolean isleap() { int[] mod= {1, 5, 9, 13, 17, 22, 26,30}; for(int i:mod) if (i==(JalaliYear%33)) return true; return false; } /*************************************/ public boolean isleap(int pyear) { int[] mod= {1, 5, 9, 13, 17, 22, 26,30}; for(int i:mod) if (i==(pyear%33)) return true; return false; } //تعداد روزهای گذشته از ابتدای سال تا یک تاریخ مشخص در آن سال را بر می گرداند به بیان دیگر این متد می گوید که در یک تاریخ مشخص در چندمین روز سال قرار داریم public long DaysFromBeginingOfJalaliYear() { int day=((JalaliMonth-1)<=6)? (((JalaliMonth-1)*31)+JalaliDay):((((JalaliMonth-1)-6)*30)+(6*31)+JalaliDay); return day; } /* تعداد روزهای گذشته از مبدا تاریخ تا یک تاریخ مشخص را بر می گردانددر این کلاس برای افزایش دقت و کاربردی بودن هر چه بیشتر تاریخ مبدا یکم فروردین ماه سال یک هزارو سیصدو چهار یعنی سال رسمی شدن گاهنامه هجری خورشیدی در ایران انتخاب شده است */ public long DaysFromBeginingOfEra( ) { long days=0; int jyear=0; for(int i=1304;i<JalaliYear;i++) { if (isleap(i)) jyear=366; else jyear=365; days+=jyear; } return (days-1)+DaysFromBeginingOfJalaliYear(); } /*************************************************/ //این متد تعداد روزهای موجود در یک ماه معین را بر می گرداند public int DaysInMounth( ) { if(JalaliMonth<7) return 31; else if(!isleap(JalaliYear)&& JalaliMonth==12) return 29; else return 30; } /*************************************************/ public int DaysInMounth( int year,int month) { if(month<7) return 31; else if(!isleap(year)&& month==12) return 29; else return 30; } //این متد روزهای مشخصی را به یک تاریخ معین هجری خورشیدی اضافه می کند public void AddDay(long days ) { for(int d=1;d<days+1;d++) { JalaliDay+=1; if (JalaliDay>DaysInMounth()) { JalaliDay=1; JalaliMonth+=1; if(JalaliMonth>12) { JalaliMonth=1; JalaliYear+=1; } } } } /*************************************************/ public String AddDay(int JYear,int JMonth, int JDay,long days ) { for(int d=1;d<days+1;d++) { JDay+=1; if (JDay>DaysInMounth(JYear,JMonth)) { JDay=1; JMonth+=1; if(JMonth>12) { JMonth=1; JYear+=1; } } } return JYear + "/" +JMonth + "/" +JDay; } /**************************************************************/ //این متد مشخص می کند که آیا یک سال مشخص میلادی کبیسه است یا خیر private boolean GregorianIsLeap(int year) { if(((year%100)!=0 && (year%4==0))||((year%100)==0 && (year%400==0))) return true; return false; } /*************************************************/ //این متد تعداد روزهای موجود در یک ماه معین میلادی را بر می گرداند private int GregorianDaysInMounth(int year,int Month ) { int days=0; switch(Month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: days=31; break; case 4: case 6: case 9: case 11: days=30; break; case 2 : days=(GregorianIsLeap(year))? 29 : 28; break; } return days; } /*************************************************/ //این متد به ما می گوید که در یک تاریخ خاص میلادی در چندمین روز سال قرار داریم یعنی تعداد روزهای گذشته از ابتدای سال در یک تاریخ مشخص را بر می گرداند private long GregorianDaysFromBeginingOfGregorianDate(int year,int Month, int Day) { int sum=0; for(int i=1 ; i<Month;i++) sum+=GregorianDaysInMounth(year,i); return sum+Day; } /*************************************************/ /* تعداد روزهای گذشته از مبدا تاریخ تا یک تاریخ مشخص را بر می گردانددر این کلاس برای افزایش دقت و کاربردی بودن هر چه بیشتر تاریخ مبدا میلادی تاریخ گرگورین مصادف با یکم فروردین ماه سال یک هزارو سیصدو چهار یعنی سال رسمی شدن گاهنامه هجری خورشیدی در ایران یعنی بیست و یکم مارس سال یک هزاررو نهصدو بیست و پنج گریگوریبه عنوان مبدا تاریخ گریگوری انتخاب شده است 1304/1/1 == 1925/3/21 */ private long GregorianDaysFromEra(int year,int Month, int Day ) { long days=0; int Gyear=0; for(int i=1925;i<year;i++) { if (GregorianIsLeap(i)) Gyear=366; else Gyear=365; days+=Gyear; } return (days-GregorianDaysFromBeginingOfGregorianDate(1925,3,21))+GregorianDaysFromBeginingOfGregorianDate(year,Month,Day); } /***********************************************************/ private String GregorianAddDay(int Year,int Month, int Day,long Days ) { for(int d=1;d<Days+1;d++) { Day+=1; if (Day>GregorianDaysInMounth(Year, Month)) { Day=1; Month+=1; if(Month>12) { Month=1; Year+=1; } } } return Year + "-" +Month+ "-"+ Day; } /**************************************************** * **************************************************/ public String Gregorian_To_Jalali(int year,int Month, int Day){ return AddDay(1304,1,1,GregorianDaysFromEra(year, Month, Day)) ; } /*******************************************************/ public String Jalali_To_Gregorian(){ return GregorianAddDay(1925,3,21,DaysFromBeginingOfEra( )) ; } }//انتهای کلاس
یک مثال کاربردی از کلاس PersianDate :
PersianDate pd=new PersianDate(1397,12,29); System.out.println(pd.Jalali_To_Gregorian()); System.out.println(pd.Gregorian_To_Jalali(2018,8, 2));
که خروجی آن به صورت زیر است :
2019-3-20
1397-5-11
به امید رضایت شما
محمد ایزانلو تابستان 1397
زمان پاسخ گویی روز های شنبه الی چهارشنبه ساعت 9 الی 18
فقط به موضوعات مربوط به محصولات آموزشی و فروش پاسخ داده می شود