

دو برنامه دقیق و قدرتمند
برای تبدیل تقویمها و از جمله گاهشماری ایرانی
آدینه،
24 اسفندماه 1386
الگوریتم گاهشماری ایرانی
برای نرمافزارهای مبدل تقویمها
رضا مرادی
غیاث آبادی
در روزهای اخیر چند پرسش در زمینه ساختار محاسباتی بکارگرفته شده
در الگوریتمهای متداول نرمافزارهای تبدیلکننده
تقویمها
(Calendar
converter)
برای گاهشماری ایرانی
و نیز دقیقترین و کارآمدترین نرمافزارهای موجود برای
تبدیل تقویم ایرانی به دیگر تقویمها به نگارنده رسیده
است که اکنون به پاسخ کوتاهی بسنده میشود تا در آینده
با تفصیل بیشتری به این نکته مهم پرداخته شود.
اکنون نرمافزارهای گوناگونی برای تبدیل متقابل تقویم
ایرانی (هجری خورشیدی/ هجری شمسی) به دیگر تقویمها
وجود دارد که هر یک از آنها محاسن و معایبی در قیاس با یکدیگر
دارند. مهمترین کمبود بیشتر نرمافزارها، بهرهگیری
از الگوریتمهایی با دامنه خطای بالا و کمکارآمد،
دامنه کوتاه مدت سالهای تطبیقپذیر و بسندهکردن به
تبدیل تقویم ایرانی با تقویم میلادی گریگوری است.
مناسبترین و کاراترین الگوریتم برای نظام کبیسهگیری
و نیز تبدیل گاهشماری ایرانی،
الگوریتمی است که متکی به 683 روز افزوده برای 683 سال
کبیسه با قواعد خاص توزیعی خود در یک دوره بزرگ 2820 ساله
باشد. در این صورت، طول متوسط سال تعریفشده یا طول
سال تقویمی ایرانی برابر است با 242199/365 روز یا 365
روز و 5 ساعت و 48 دقیقه و 46 ثانیه. دامنه خطای عملی
در مقایسه با تعریفهای بنیادین این نظام برابر است با
00000041844/0 روز یا 036/0 ثانیه در سال و یا
یک ثانیه در هر 28 سال و 102 ثانیه در هر دوره 2820
ساله. (این دقت در گاهشماری میلادی گریگوری 43/4 ثانیه
و در گاهشماری میلادی ژولی 675 ثانیه
در سال است.)
بکارگیری قواعد محاسباتی دوره 2820 ساله و دستگاه
توزیع روزهای افزوده در 683 سال کبیسه مفروض آن، منجر به
ایجاد کارآمدترین نظام ممکن برای تبدیل تقویمها و به
ویژه در قیاس با تقویم طبیعی خواهد شد.
الگوریتم زیر یکی از بهترین نمونهها بر اساس دوره
2820 ساله است که در بسیاری از برنامههای تبدیلی
تقویمهای جهان بکار میرود. این الگوریتم را به تازگی آقای
مارک جیسون دومینوس
(Mark Jason Dominus)
به زبان برنامهنویسی «پرل»
(Perl)
نیز نوشته که در پایان همین صفحه آمده است. آقای جان
واکر
John Walker
نیز در سال 2006 گونه توسعه
یافته این الگوریتم را در برنامه قدرتمند
Formilab
بکار گرفته و نیز در
Calendar Home
از آن اقتباس شده است.
نگارنده این دو برنامه را که از
الگوریتم یکسانی بهره گرفتهاند، برای دویست سال
گوناگون در یک دامنه پنجهزار ساله که از ترکیبهای
کبیسه متنوعی برخوردار بودهاند، آزمایش کرده و به
نتیجه نادرستی برخورد نکرده است. این آزمایش برای
تبدیل تقویم ایرانی و بالعکس به میلادی ژولی، میلادی
گریگوری، هجری قمری، عبرانی (بازمانده گاهشماری رسمی
شاهنشاهی هخامنشی) و روز ژولی انجام شد.
ضمن توصیه این نرمافزارها به علاقهمندان، یادآور
میشود که برای دادههای تاریخی که نتیجه تبدیل آنها
به تقویم میلادی یا برعکس، سالهای پیش از 1582 میلادی
باشد، میباید به دستگاه میلادی ژولی (و نه میلادی
گریگوری) رجوع کرد. همچنین برای برخی دادههای تاریخی
پس از سال 1582 نیز میباید به تقویم میلادی ژولی
مراجعه کرد. چرا که تقویم گریگوری در زمانی یکسان در
همه کشورها پذیرفته نشده و رویدادهای تاریخی در آن
کشورها گاه تا چند سده بعد همچنان به تقویم میلادی
ژولی ثبت شدهاند. این دو نکته، از خطاهای متداول در
تبدیل تقویمها است. خطای متداول دیگر، بیتوجهی به
وجود سال صفر در گاهشماری میلادی گریگوری است. چنانچه
کاربر به هر دلیلی قصد داشته باشد رویداد خاصی را با
تعمیم تقویم میلادی گریگوری به گذشته به دست آورد،
میباید این نکته را در نظر داشت که این گاهشماری
دارای سال صفر تعریفشده (برابر با سال 1- ژولی) است.
به عبارت دیگر برای سالهای پیش از مبدأ میلادی،
تفاوتی یک ساله در سالشماری میلادی گریگوری و ژولی
وجود دارد.
1. Let a = (y +
2345) % 2820
2.
If a is 2819, y is a leap year.
Otherwise,
3.
Let b = a % 128.
4.
If b < 29, let c = b.
Otherwise, let c = (b - 29) % 33.
5.
If c = 0, y is not a leap year.
Otherwise,
6.
If c is a multiple of 4, y is a leap
year. Otherwise,
7.
y is not leap year.
------------------------------
#!/usr/bin/perl
# is Persian year y a leap year in the Persian calendar?
sub is_leap_year {
my $y = shift;
my $a = ($y + 2345) % 2820;
return 1 if $a == 2819;
my $b = $a % 128;
my $c;
if ($b < 29) { $c = $b }
else { $c = ($b - 29) % 33 }
return 0 if $c == 0;
return 1 if $c %4 == 0;
return 0;
}
# Is Gregorian year g a leap year ** in the Persian calendar **?
sub is_gregorian_leap_year {
my $g = shift;
return is_leap_year($g - 2008 + 1386);
}
my $prev;
for (1979 .. 2028) {
my $is = is_g_leap_year($_);
printf "%4d %s\n", $_, $is ? "*" : " ";
die if $is && $prev;
$prev = $is;
# $ct++ if $is;
}
#print ">> $ct\n";
----------------------