|
| 1 | +#! /usr/bin/env python3 |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | +""" |
| 4 | +Hijri Islamic Calendar converting functions, |
| 5 | +Copyright (c) 2006-2008 Muayyad Saleh Alsadi<[email protected]> |
| 6 | +Based on an enhanced algorithm designed by me |
| 7 | +the algorithm is discussed in a book titled "حتى لا ندخل جحور الضباب" |
| 8 | +(not yet published) |
| 9 | +
|
| 10 | +This file can be used to implement apps, gdesklets or karamba ..etc |
| 11 | +
|
| 12 | +This algorithm is based on integer operations |
| 13 | +which that there is no round errors (given accurate coefficients) |
| 14 | +the accuracy of this algorithm is based on 3 constants (p,q and a) |
| 15 | +where p/q is the full months percentage [ gcd(p,q) must be 1 ] |
| 16 | +currently it's set to 191/360 which mean that there is 191 months |
| 17 | +having 30 days in a span of 360 years, other months are 29 days. |
| 18 | +and a is just a shift. |
| 19 | +
|
| 20 | +
|
| 21 | + Released under terms on Waqf Public License. |
| 22 | + This program is free software; you can redistribute it and/or modify |
| 23 | + it under the terms of the latest version Waqf Public License as |
| 24 | + published by Ojuba.org. |
| 25 | +
|
| 26 | + This program is distributed in the hope that it will be useful, |
| 27 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 28 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| 29 | +
|
| 30 | + The Latest version of the license can be found on |
| 31 | + "http://www.ojuba.org/wiki/doku.php/waqf/license" |
| 32 | +
|
| 33 | +
|
| 34 | +Portions of this algorithm is based on that found on GNU EMACS |
| 35 | +the difference is that this algorithm does not set |
| 36 | +all months to a fixed number of days (in the original algorithm |
| 37 | +first month always have 30 days) |
| 38 | +
|
| 39 | +
|
| 40 | +The original GNU Emacs LISP algorithm |
| 41 | +Copyright (C) 1995, 1997, 2001 Free Software Foundation, Inc. |
| 42 | + Edward M. Reingold <[email protected]> |
| 43 | + Technical details of all the calendrical calculations can be found in |
| 44 | + ``Calendrical Calculations'' by Nachum Dershowitz and Edward M. Reingold, |
| 45 | + Cambridge University Press (1997). |
| 46 | + Comments, corrections, and improvements should be sent to |
| 47 | + Edward M. Reingold Department of Computer Science |
| 48 | + (217) 333-6733 University of Illinois at Urbana-Champaign |
| 49 | + [email protected] 1304 West Springfield Avenue |
| 50 | +""" |
| 51 | +__p_const=191 |
| 52 | +__q_const=360 |
| 53 | +__a_const=48 |
| 54 | +__hijri_epoch=227015; # = Julian 0622-7-16 = gregorian 0759-6-11 (I think it should be 622, 7, 19) |
| 55 | +# TODO: Why does the the hijri_epoch 227015 does not give the expected value when converted to gregorian |
| 56 | + |
| 57 | +def get_consts(): |
| 58 | + """Return a tuple of the 3 constants (p,q,a) used by this algothim, for debuging""" |
| 59 | + return (__p_const, __q_const, __a_const) |
| 60 | +def get_epoch(): |
| 61 | + """Return Hijri epoch, number of days since gregorian epoch, (should be Julian 0622-7-16 (ie. 227015 days)""" |
| 62 | + return __hijri_epoch |
| 63 | + |
| 64 | +def hijri_month_days(Y,M): |
| 65 | + """Return the number of days in a given hijri month M in a given Y""" |
| 66 | + Mc = ( Y -1) *12 + M |
| 67 | + if (((Mc+ __a_const) * __p_const) % __q_const) < __p_const : return 30 |
| 68 | + else: return 29 |
| 69 | + |
| 70 | +# NOTE: trivial implementation |
| 71 | +#def hijri_days_before_month_(Y,M): # simple mothod, optimization is possible by reusing Mc ..etc. |
| 72 | +# """Return the number of days before a given moth M in a given year Y""" |
| 73 | +# sum=0 |
| 74 | +# for i in range(1,M): sum+=hijri_month_days(Y,i); |
| 75 | +# return sum |
| 76 | + |
| 77 | +def hijri_days_before_month(Y,M): |
| 78 | + """Return the number of days before a given moth M in a given year Y (0 for M=1)""" |
| 79 | + Mc = (Y -1) *12 + 1 + __a_const |
| 80 | + McM=Mc * __p_const |
| 81 | + sum=0 |
| 82 | + for i in range(1,M): |
| 83 | + if (McM % __q_const) < __p_const : sum+=30 |
| 84 | + else: sum+=29 |
| 85 | + McM+=__p_const |
| 86 | + return sum |
| 87 | + |
| 88 | +#TEST: PASSED |
| 89 | +# test that the faster hijri_days_before_month is ok |
| 90 | +#def test_hijri_days_before_month(): |
| 91 | +# l=[(y,m) for y in range(1400,1499) for m in range(1,13)] |
| 92 | +# for y,m in l: |
| 93 | +# d1=hijri_days_before_month(y,m) |
| 94 | +# d2=hijri_days_before_month_(y,m) |
| 95 | +# if d1!=d2: print y,m,d1,d2 |
| 96 | + |
| 97 | + |
| 98 | +def hijri_year_days(Y): |
| 99 | + """Return the number of days in a given year Y""" |
| 100 | + return hijri_days_before_month(Y,13) |
| 101 | + |
| 102 | +def hijri_day_number (Y, M, D): |
| 103 | + """Return the day number within the year of the Islamic date (Y, M, D), 1 for 1/1 in any year""" |
| 104 | + return hijri_days_before_month(Y,M)+D |
| 105 | + |
| 106 | + |
| 107 | +# BAD fast implementation |
| 108 | +#def hijri_to_absolute_ (Y, M, D): |
| 109 | +# """Return absolute date of Hijri (Y,M,D), eg. ramadan (9),1,1427 -> 732578 """ |
| 110 | +# Mc=(Y-1)*12 |
| 111 | +# # days before Hijra + days in the years before + days from the begining of that year |
| 112 | +# return __hijri_epoch + \ |
| 113 | +# Mc*29 + Mc*__p_const/__q_const + \ # this line should involve __a_const |
| 114 | +# hijri_day_number (Y, M, D) - 1 |
| 115 | + |
| 116 | +# correct implementation # TODO: optimize it more and test that after optimization |
| 117 | +def hijri_to_absolute (Y, M, D): |
| 118 | + """Return absolute date of Hijri (Y,M,D), eg. ramadan (9),1,1427 -> 732578 """ |
| 119 | + Mc=(Y-1)*12 |
| 120 | + # day count=days before Hijra plus (...) |
| 121 | + dc=__hijri_epoch |
| 122 | + # plus days in the years before till first multiples of q plus (...) |
| 123 | + Mc-=Mc % __q_const |
| 124 | + y=Y-Mc//12 |
| 125 | + dc+=Mc*29 + Mc*__p_const//__q_const |
| 126 | + # plus those after the multiples plus (...) |
| 127 | + for i in range(1,y): dc += hijri_year_days(i) |
| 128 | + # plus days from the begining of that year |
| 129 | + dc+=hijri_day_number (Y, M, D) - 1 |
| 130 | + return dc |
| 131 | + |
| 132 | +def hijri_month_days_(y,m): |
| 133 | + """Return the number of days in a given hijri month M in a given Y""" |
| 134 | + return hijri_to_absolute(y+m//12,m%12+1,1)-hijri_to_absolute(y,m,1) |
| 135 | + |
| 136 | +# TEST: PASSED |
| 137 | +#def test_hijri_to_absolute_v_month_days(): |
| 138 | +# #l=[(y,m) for y in range(1,31) for m in range(1,13)] |
| 139 | +# l=[(y,m) for y in range(1400,1499) for m in range(1,13)] |
| 140 | +# for y,m in l: |
| 141 | +# d1=hijri_month_days(y,m) |
| 142 | +# d2=hijri_to_absolute(y+m/12,m%12+1,1)-hijri_to_absolute(y,m,1) |
| 143 | +# if d1!=d2: print y,m,y+m/12,m%12+1,'d1=',d1,", d2=",d2 |
| 144 | + |
| 145 | +# round then move to exact, very slow perfect implementation |
| 146 | +#def absolute_to_hijri_ (date): # TODO: check if it's always compatible with absolute_from_hijri |
| 147 | +# """Return Hijri date (Y,M,D) corresponding to the given absolute number of days.""" |
| 148 | +# if date < __hijri_epoch: return None; # pre-Islamic date |
| 149 | +# dd=date-__hijri_epoch |
| 150 | +# Mc=dd/(29*(__q_const-__p_const)+ 30*__p_const)*__q_const # mounth count till multibles of q |
| 151 | +# Y=y=Mc/12+1; M=m=(Mc%12)+1 |
| 152 | +# while(date > hijri_to_absolute(Y,M,1)): |
| 153 | +# y,m=Y,M |
| 154 | +# M+=1 |
| 155 | +# if M>12: M=1; Y+=1 |
| 156 | +# Y=y; M=m |
| 157 | +# D=1 + date - hijri_to_absolute(Y,M,1) |
| 158 | +# if D>hijri_month_days(Y,M): |
| 159 | +# M+=1 |
| 160 | +# if M>12: M=1; Y+=1 |
| 161 | +# D=1 + date - hijri_to_absolute(Y,M,1) |
| 162 | +# return (Y,M,D) |
| 163 | + |
| 164 | + |
| 165 | +# direct way, test PASSED |
| 166 | +def absolute_to_hijri (date): |
| 167 | + """Return Hijri date (Y,M,D) corresponding to the given absolute number of days.""" |
| 168 | + if date < __hijri_epoch: return None; # pre-Islamic date |
| 169 | + Mc=(date-__hijri_epoch+1)*__q_const//(29*__q_const+__p_const) |
| 170 | + Y=Mc//12+1; M=(Mc%12)+1 |
| 171 | + # consistency check |
| 172 | + d=hijri_to_absolute(Y,M,1) # TODO: this is an expensive call |
| 173 | + if (date < d): # go one month back if needed |
| 174 | + M-=1 |
| 175 | + if M==0: Y-=1; M=12 |
| 176 | + d-=hijri_month_days(Y,M) # this call is fast |
| 177 | + # |
| 178 | + D=1 + date - d |
| 179 | + return (Y,M,D) |
| 180 | + |
| 181 | +# TEST: PASSED |
| 182 | +#def test_c(): |
| 183 | +# l=[(y,m) for y in range(1400,1499) for m in range(1,13)] |
| 184 | +# for y,m in l: |
| 185 | +# d=hijri_month_days(y,m) |
| 186 | +# if absolute_to_hijri(hijri_to_absolute(y,m,1))!=(y,m,1): print y,m,1, absolute_to_hijri(hijri_to_absolute(y,m,1)) |
| 187 | +# if absolute_to_hijri(hijri_to_absolute(y,m,d))!=(y,m,d): print y,m,d, absolute_to_hijri(hijri_to_absolute(y,m,d)) |
| 188 | + |
| 189 | +def hijri_day_of_week (Y, M, D): |
| 190 | + """Return the day-of-the-week index of hijri (Y,M,D) Date, 0 for Sunday, 1 for Monday, etc.""" |
| 191 | + return hijri_to_absolute (Y,M, D) % 7 |
| 192 | +# /////////////////////////////// |
| 193 | +# high level converting functions |
| 194 | + |
| 195 | +def hijri_to_gregorian (year, month, day): |
| 196 | + """Return gregorian (year, month, day) converted from Islamic Hijri calender""" |
| 197 | + return absolute_to_gregorian( hijri_to_absolute (year, month, day)) |
| 198 | + |
| 199 | +def gregorian_to_hijri (year, month, day): |
| 200 | + """Return Hijri (year, month, day) converted from gregorian calender""" |
| 201 | + return absolute_to_hijri( gregorian_to_absolute (year, month, day)) |
| 202 | + |
| 203 | +#/////////////////////////////////// |
| 204 | +# Gregorian functions |
| 205 | +#/////////////////////////////////// |
| 206 | +# This Portions of is based on that found on GNU EMACS |
| 207 | + |
| 208 | +#The original GNU Emacs LISP algorithm |
| 209 | +#Copyright (C) 1995, 1997, 2001 Free Software Foundation, Inc. |
| 210 | +# Edward M. Reingold <[email protected]> |
| 211 | +# Technical details of all the calendrical calculations can be found in |
| 212 | +# ``Calendrical Calculations'' by Nachum Dershowitz and Edward M. Reingold, |
| 213 | +# Cambridge University Press (1997). |
| 214 | +# Comments, corrections, and improvements should be sent to |
| 215 | +# Edward M. Reingold Department of Computer Science |
| 216 | +# (217) 333-6733 University of Illinois at Urbana-Champaign |
| 217 | +# [email protected] 1304 West Springfield Avenue |
| 218 | + |
| 219 | +days_in_month=( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); |
| 220 | +def gregorian_leap_year_p (year): |
| 221 | + """Return 1 (True) if YEAR is a Gregorian leap year.""" |
| 222 | + if ((year % 4) == 0 and ((year % 100) or (year % 400) == 0)): return 1; |
| 223 | + return 0; |
| 224 | + |
| 225 | +def gregorian_month_days (year, month): |
| 226 | + """The last day in MONTH during YEAR.""" |
| 227 | + if (month == 2 and gregorian_leap_year_p (year)): return 29; |
| 228 | + return days_in_month[month-1]; |
| 229 | + |
| 230 | +def gregorian_day_number (year, month, day): |
| 231 | + """Return the day number within the year of the date (year,month, day)""" |
| 232 | + if month<3: return day + (31 * (month - 1)) |
| 233 | + return day + (31 * (month - 1)) - \ |
| 234 | + ((month << 2) + 23) // 10 + (gregorian_leap_year_p (year) & 1); |
| 235 | + |
| 236 | +def gregorian_to_absolute (year, month, day): |
| 237 | + prior_years = year - 1 |
| 238 | + return gregorian_day_number (year, month, day) + \ |
| 239 | + (365 * prior_years + (prior_years >> 2)) - \ |
| 240 | + (prior_years // 100) + (prior_years // 400) |
| 241 | + |
| 242 | +def absolute_to_gregorian(date): |
| 243 | + """return (year month day) corresponding to the absolute DATE. |
| 244 | +The absolute date is the number of days elapsed since the (imaginary) |
| 245 | +Gregorian date Sunday, December 31, 1 BC.""" |
| 246 | + |
| 247 | +# See the footnote on page 384 of ``Calendrical Calculations, Part II: |
| 248 | +# Three Historical Calendars'' by E. M. Reingold, N. Dershowitz, and S. M. |
| 249 | +# Clamen, Software--Practice and Experience, Volume 23, Number 4 |
| 250 | +# (April, 1993), pages 383-404 for an explanation. |
| 251 | + d0 = date - 1; |
| 252 | + n400 = d0 // 146097; |
| 253 | + d1 = d0 % 146097; |
| 254 | + n100 = d1 // 36524; |
| 255 | + d2 = d1 % 36524; |
| 256 | + n4 = d2 // 1461; |
| 257 | + d3 = d2 % 1461; |
| 258 | + n1 = d3 // 365; |
| 259 | + dd = (d3 % 365) + 1; |
| 260 | + yy = ((400 * n400) + (100 * n100) + (n4 * 4) + n1); |
| 261 | + if (n100 == 4) or (n1 == 4): return (yy, 12, 31); |
| 262 | + yy=yy+1; |
| 263 | + mm = 1; |
| 264 | + while(date >= gregorian_to_absolute (yy,mm, 1)): mm+=1; |
| 265 | + d=gregorian_to_absolute (yy, mm-1, 1); |
| 266 | + return (yy, mm-1,date-d+1); |
| 267 | +# d = calendar_absolute_from_gregorian (1, 1, yy); |
| 268 | +# mm=1; |
| 269 | +# while(mm <= 12): |
| 270 | +# dd = date - d + 1; |
| 271 | +# dm = calendar_last_day_of_month (mm, yy); |
| 272 | +# if dd <= dm: return (mm,dd+1,yy); |
| 273 | +# d += dm; |
| 274 | +# mm=mm+1; |
| 275 | +# return 0; # should not happened |
| 276 | +def gregorian_day_of_week (yy, mm, dd): |
| 277 | + """Return the day-of-the-week index of gregorian (yy, mm, dd) DATE, 0 for Sunday, 1 for Monday, etc.""" |
| 278 | + return gregorian_to_absolute (yy,mm, dd) % 7; |
| 279 | +# /////////////////////////////// |
| 280 | +# some tests for debuging to be removed |
| 281 | + |
| 282 | +def test1(): |
| 283 | + global __a_const; |
| 284 | + __a_const=48 |
| 285 | + for __a_const in range(0,100): unmatched=0; from_y=1; to_y=4001 |
| 286 | + for y in range(from_y,to_y): |
| 287 | + if hijri_days(y)!=emacs_hijri_days(y): unmatched+=1 |
| 288 | + print ("%d years (%g %%) unmatched when a=%d", (unmatched, float(float(unmatched)/(to_y-from_y)), __a_const)) |
| 289 | + __a_const=48 |
| 290 | + sum=0.0 |
| 291 | + for y in range(1,4001): sum+=hijri_days(y) |
| 292 | + print ("year len=%f ", float(float(sum)/4000.0*100.0)) |
| 293 | + __a_const=47 |
| 294 | + sum=0.0 |
| 295 | + for y in range(1,4001): sum+=hijri_days(y) |
| 296 | + print ("year len=%f ", float(float(sum)/4000.0*100.0)) |
| 297 | +########################## |
| 298 | +if __name__ == "__main__": |
| 299 | + # conclusion |
| 300 | + # 0% for a=16 48 65 |
| 301 | + # 7% for a=1 31 33 50 80 82 97 99 |
| 302 | + # 13% for a=14 18 46 63 67 95 |
| 303 | + # 20% for a=12 29 3 35 52 78 84 |
| 304 | + # ... |
| 305 | + # 73% for a=45 47 49 ..etc. |
| 306 | + ########################## |
| 307 | + __a_const=16 |
| 308 | + print ("for a=%d", __a_const) |
| 309 | + sum=0.0 |
| 310 | + for y in range(1,4001): sum+= float(hijri_month_days(y,12)==30) |
| 311 | + print ("perfect thu-hijja months is %f %% ", float(float(sum)/4000.0*100.0)) |
| 312 | + sum=0.0 |
| 313 | + for y in range(1,4001): sum+= float(hijri_month_days(y,9)==30) |
| 314 | + print ("perfect Ramadan months is %f %% ", float(float(sum)/4000.0*100.0)) |
| 315 | + |
| 316 | + __a_const=48 |
| 317 | + print ("for a=%d", __a_const) |
| 318 | + sum=0.0 |
| 319 | + for y in range(1,4001): sum+= float(hijri_month_days(y,12)==30) |
| 320 | + print ("perfect thu-hijja months is %f %% ", float(float(sum)/4000.0*100.0)) |
| 321 | + sum=0.0 |
| 322 | + for y in range(1,4001): sum+= float(hijri_month_days(y,9)==30) |
| 323 | + print ("perfect Ramadan months is %f %% ", float(float(sum)/4000.0*100.0)) |
| 324 | + |
| 325 | + __a_const=65 |
| 326 | + print ("for a=%d", __a_const) |
| 327 | + sum=0.0 |
| 328 | + for y in range(1,4001): sum+= float(hijri_month_days(y,12)==30) |
| 329 | + print ("perfect thu-hijja months is %f %% ", float(float(sum)/4000.0*100.0)) |
| 330 | + sum=0.0 |
| 331 | + for y in range(1,4001): sum+= float(hijri_month_days(y,9)==30) |
| 332 | + print ("perfect Ramadan months is %f %% ", float(float(sum)/4000.0*100.0)) |
| 333 | + |
| 334 | + __a_const=48 |
| 335 | + print ("for a=%d", __a_const) |
| 336 | + for m in range(1,13): |
| 337 | + sum=0.0 |
| 338 | + for y in range(1,4001): sum+= float(hijri_month_days(y,m)==30) |
| 339 | + print ("perfect %d months is %f %% ", (m,float(float(sum)/4000.0*100.0))) |
0 commit comments