1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2000-2006 Donald N. Allingham 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program; if not, write to the Free Software 18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19# 20 21""" 22Provide calendar to sdn (serial date number) conversion. 23""" 24 25#------------------------------------------------------------------------- 26# 27# Python modules 28# 29#------------------------------------------------------------------------- 30import math 31 32#------------------------------------------------------------------------- 33# 34# Constants 35# 36#------------------------------------------------------------------------- 37_GRG_SDN_OFFSET = 32045 38_GRG_DAYS_PER_5_MONTHS = 153 39_GRG_DAYS_PER_4_YEARS = 1461 40_GRG_DAYS_PER_400_YEARS = 146097 41 42_JLN_SDN_OFFSET = 32083 43_JLN_DAYS_PER_5_MONTHS = 153 44_JLN_DAYS_PER_4_YEARS = 1461 45 46_HBR_HALAKIM_PER_HOUR = 1080 47_HBR_HALAKIM_PER_DAY = 25920 48_HBR_HALAKIM_PER_LUNAR_CYCLE = 29 * _HBR_HALAKIM_PER_DAY + 13753 49_HBR_HALAKIM_PER_METONIC_CYCLE = _HBR_HALAKIM_PER_LUNAR_CYCLE * (12 * 19 + 7) 50_HBR_SDN_OFFSET = 347997 51_HBR_NEW_MOON_OF_CREATION = 31524 52_HBR_NOON = 18 * _HBR_HALAKIM_PER_HOUR 53_HBR_AM3_11_20 = (9 * _HBR_HALAKIM_PER_HOUR) + 204 54_HBR_AM9_32_43 = (15 * _HBR_HALAKIM_PER_HOUR) + 589 55 56_HBR_SUNDAY = 0 57_HBR_MONDAY = 1 58_HBR_TUESDAY = 2 59_HBR_WEDNESDAY = 3 60_HBR_FRIDAY = 5 61 62_HBR_MONTHS_PER_YEAR = [ 63 12, 12, 13, 12, 12, 13, 12, 13, 12, 12, 64 13, 12, 12, 13, 12, 12, 13, 12, 13 65 ] 66 67_HBR_YEAR_OFFSET = [ 68 0, 12, 24, 37, 49, 61, 74, 86, 99, 111, 123, 69 136, 148, 160, 173, 185, 197, 210, 222 70 ] 71 72_FR_SDN_OFFSET = 2375474 73_FR_DAYS_PER_4_YEARS = 1461 74_FR_DAYS_PER_MONTH = 30 75_PRS_EPOCH = 1948320.5 76_ISM_EPOCH = 1948439.5 77 78def _tishri1(metonic_year, molad_day, molad_halakim): 79 80 tishri1 = molad_day 81 dow = tishri1 % 7 82 leap_year = metonic_year in [2, 5, 7, 10, 13, 16, 18] 83 last_was_leap_year = metonic_year in [3, 6, 8, 11, 14, 17, 0] 84 85 # Apply rules 2, 3 and 4. 86 if ((molad_halakim >= _HBR_NOON) or 87 ((not leap_year) and dow == _HBR_TUESDAY and 88 molad_halakim >= _HBR_AM3_11_20) or 89 (last_was_leap_year and dow == _HBR_MONDAY 90 and molad_halakim >= _HBR_AM9_32_43)): 91 tishri1 += 1 92 dow += 1 93 if dow == 7: 94 dow = 0 95 96 # Apply rule 1 after the others because it can cause an additional 97 # delay of one day 98 if dow == _HBR_WEDNESDAY or dow == _HBR_FRIDAY or dow == _HBR_SUNDAY: 99 tishri1 += 1 100 101 return tishri1 102 103def _tishri_molad(input_day): 104 """ 105 Estimate the metonic cycle number. 106 107 Note that this may be an under estimate because there are 6939.6896 days 108 in a metonic cycle not 6940, but it will never be an over estimate. The 109 loop below will correct for any error in this estimate. 110 """ 111 112 metonic_cycle = (input_day + 310) // 6940 113 114 # Calculate the time of the starting molad for this metonic cycle. 115 116 (molad_day, molad_halakim) = _molad_of_metonic_cycle(metonic_cycle) 117 118 # If the above was an under estimate, increment the cycle number until 119 # the correct one is found. For modern dates this loop is about 98.6% 120 # likely to not execute, even once, because the above estimate is 121 # really quite close. 122 123 while molad_day < (input_day - 6940 + 310): 124 metonic_cycle += 1 125 molad_halakim += _HBR_HALAKIM_PER_METONIC_CYCLE 126 molad_day += molad_halakim // _HBR_HALAKIM_PER_DAY 127 molad_halakim = molad_halakim % _HBR_HALAKIM_PER_DAY 128 129 # Find the molad of Tishri closest to this date. 130 131 for metonic_year in range(0, 20): 132 if molad_day > input_day - 74: 133 break 134 135 molad_halakim += (_HBR_HALAKIM_PER_LUNAR_CYCLE 136 * _HBR_MONTHS_PER_YEAR[metonic_year]) 137 molad_day += molad_halakim // _HBR_HALAKIM_PER_DAY 138 molad_halakim = molad_halakim % _HBR_HALAKIM_PER_DAY 139 140 return (metonic_cycle, metonic_year, molad_day, molad_halakim) 141 142def _molad_of_metonic_cycle(metonic_cycle): 143 """ 144 Start with the time of the first molad after creation. 145 """ 146 147 r1 = _HBR_NEW_MOON_OF_CREATION 148 149 # Calculate metonic_cycle * HALAKIM_PER_METONIC_CYCLE. The upper 32 150 # bits of the result will be in r2 and the lower 16 bits will be 151 # in r1. 152 153 r1 = r1 + (metonic_cycle * (_HBR_HALAKIM_PER_METONIC_CYCLE & 0xFFFF)) 154 r2 = r1 >> 16 155 r2 = r2 + (metonic_cycle * ((_HBR_HALAKIM_PER_METONIC_CYCLE >> 16)&0xFFFF)) 156 157 # Calculate r2r1 / HALAKIM_PER_DAY. The remainder will be in r1, the 158 # upper 16 bits of the quotient will be in d2 and the lower 16 bits 159 # will be in d1. 160 161 d2 = r2 // _HBR_HALAKIM_PER_DAY 162 r2 -= d2 * _HBR_HALAKIM_PER_DAY 163 r1 = (r2 << 16) | (r1 & 0xFFFF) 164 d1 = r1 // _HBR_HALAKIM_PER_DAY 165 r1 -= d1 * _HBR_HALAKIM_PER_DAY 166 167 molad_day = (d2 << 16) | d1 168 molad_halakim = r1 169 170 return (molad_day, molad_halakim) 171 172def _start_of_year(year): 173 """ 174 Calculate the start of the year. 175 """ 176 metonic_cycle = (year - 1) // 19 177 metonic_year = (year - 1) % 19 178 (molad_day, molad_halakim) = _molad_of_metonic_cycle(metonic_cycle) 179 180 molad_halakim = molad_halakim + (_HBR_HALAKIM_PER_LUNAR_CYCLE 181 * _HBR_YEAR_OFFSET[metonic_year]) 182 molad_day = molad_day + (molad_halakim // _HBR_HALAKIM_PER_DAY) 183 molad_halakim = molad_halakim % _HBR_HALAKIM_PER_DAY 184 185 ptishri1 = _tishri1(metonic_year, molad_day, molad_halakim) 186 187 return (metonic_cycle, metonic_year, molad_day, molad_halakim, ptishri1) 188 189def hebrew_sdn(year, month, day): 190 """Convert a Jewish calendar date to an SDN number.""" 191 192 if month == 1 or month == 2: 193 # It is Tishri or Heshvan - don't need the year length. 194 (metonic_cycle, metonic_year, 195 molad_day, molad_halakim, tishri1) = _start_of_year(year) 196 if month == 1: 197 sdn = tishri1 + day - 1 198 else: 199 sdn = tishri1 + day + 29 200 elif month == 3: 201 # It is Kislev - must find the year length. 202 203 # Find the start of the year. 204 (metonic_cycle, metonic_year, 205 molad_day, molad_halakim, tishri1) = _start_of_year(year) 206 207 # Find the end of the year. 208 molad_halakim = molad_halakim + (_HBR_HALAKIM_PER_LUNAR_CYCLE 209 *_HBR_MONTHS_PER_YEAR[metonic_year]) 210 molad_day = molad_day + (molad_halakim // _HBR_HALAKIM_PER_DAY) 211 molad_halakim = molad_halakim % _HBR_HALAKIM_PER_DAY 212 tishri1_after = _tishri1((metonic_year + 1) 213 % 19, molad_day, molad_halakim) 214 215 year_length = tishri1_after - tishri1 216 217 if year_length == 355 or year_length == 385: 218 sdn = tishri1 + day + 59 219 else: 220 sdn = tishri1 + day + 58 221 elif month == 4 or month == 5 or month == 6: 222 # It is Tevet, Shevat or Adar I - don't need the year length 223 224 (metonic_cycle, metonic_year, 225 molad_day, molad_halakim, tishri1_after) = _start_of_year(year+1) 226 227 if _HBR_MONTHS_PER_YEAR[(year - 1) % 19] == 12: 228 length_of_adar_1and2 = 29 229 else: 230 length_of_adar_1and2 = 59 231 232 if month == 4: 233 sdn = tishri1_after + day - length_of_adar_1and2 - 237 234 elif month == 5: 235 sdn = tishri1_after + day - length_of_adar_1and2 - 208 236 else: 237 sdn = tishri1_after + day - length_of_adar_1and2 - 178 238 else: 239 # It is Adar II or later - don't need the year length. 240 (metonic_cycle, metonic_year, 241 molad_day, molad_halakim, tishri1_after) = _start_of_year(year+1) 242 243 if month == 7: 244 sdn = tishri1_after + day - 207 245 elif month == 8: 246 sdn = tishri1_after + day - 178 247 elif month == 9: 248 sdn = tishri1_after + day - 148 249 elif month == 10: 250 sdn = tishri1_after + day - 119 251 elif month == 11: 252 sdn = tishri1_after + day - 89 253 elif month == 12: 254 sdn = tishri1_after + day - 60 255 elif month == 13: 256 sdn = tishri1_after + day - 30 257 else: 258 return 0 259 return sdn + _HBR_SDN_OFFSET 260 261def hebrew_ymd(sdn): 262 """Convert an SDN number to a Hebrew calendar date.""" 263 264 input_day = sdn - _HBR_SDN_OFFSET 265 # TODO if input_day <= 0, the result is a date invalid in Hebrew calendar! 266 267 (metonic_cycle, metonic_year, day1, halakim) = _tishri_molad(input_day) 268 tishri1 = _tishri1(metonic_year, day1, halakim) 269 270 if input_day >= tishri1: 271 # It found Tishri 1 at the start of the year 272 273 year = (metonic_cycle * 19) + metonic_year + 1 274 if input_day < tishri1 + 59: 275 if input_day < tishri1 + 30: 276 month = 1 277 day = input_day - tishri1 + 1 278 else: 279 month = 2 280 day = input_day - tishri1 - 29 281 return (year, month, day) 282 283 # We need the length of the year to figure this out, so find 284 # Tishri 1 of the next year. 285 286 halakim += (_HBR_HALAKIM_PER_LUNAR_CYCLE 287 * _HBR_MONTHS_PER_YEAR[metonic_year]) 288 day1 += halakim // _HBR_HALAKIM_PER_DAY 289 halakim = halakim % _HBR_HALAKIM_PER_DAY 290 tishri1_after = _tishri1((metonic_year + 1) % 19, day1, halakim) 291 else: 292 # It found Tishri 1 at the end of the year. 293 294 year = metonic_cycle * 19 + metonic_year 295 if input_day >= tishri1 - 177: 296 # It is one of the last 6 months of the year. 297 if input_day > tishri1 - 30: 298 month = 13 299 day = input_day - tishri1 + 30 300 elif input_day > tishri1 - 60: 301 month = 12 302 day = input_day - tishri1 + 60 303 elif input_day > tishri1 - 89: 304 month = 11 305 day = input_day - tishri1 + 89 306 elif input_day > tishri1 - 119: 307 month = 10 308 day = input_day - tishri1 + 119 309 elif input_day > tishri1 - 148: 310 month = 9 311 day = input_day - tishri1 + 148 312 else: 313 month = 8 314 day = input_day - tishri1 + 178 315 return (year, month, day) 316 else: 317 if _HBR_MONTHS_PER_YEAR[(year - 1) % 19] == 13: 318 month = 7 319 day = input_day - tishri1 + 207 320 if day > 0: 321 return (year, month, day) 322 month -= 1 323 day += 30 324 if day > 0: 325 return (year, month, day) 326 month -= 1 327 day += 30 328 else: 329 month = 6 330 day = input_day - tishri1 + 207 331 if day > 0: 332 return (year, month, day) 333 month -= 1 334 day += 30 335 336 if day > 0: 337 return (year, month, day) 338 month -= 1 339 day += 29 340 if day > 0: 341 return (year, month, day) 342 343 # We need the length of the year to figure this out, so find 344 # Tishri 1 of this year 345 tishri1_after = tishri1 346 (metonic_cycle, metonic_year, day1, halakim) = _tishri_molad(day1-365) 347 tishri1 = _tishri1(metonic_year, day1, halakim) 348 349 year_length = tishri1_after - tishri1 350 day = input_day - tishri1 - 29 351 if year_length == 355 or year_length == 385: 352 # Heshvan has 30 days 353 if day <= 30: 354 month = 2 355 return (year, month, day) 356 day -= 30 357 else: 358 # Heshvan has 29 days 359 if day <= 29: 360 month = 2 361 return (year, month, day) 362 363 day -= 29 364 365 # It has to be Kislev 366 return (year, 3, day) 367 368def julian_sdn(year, month, day): 369 """Convert a Julian calendar date to an SDN number.""" 370 371 if year < 0: 372 year += 4801 373 else: 374 year += 4800 375 376 # Adjust the start of the year 377 if month > 2: 378 month -= 3 379 else: 380 month += 9 381 year -= 1 382 383 return (year * _JLN_DAYS_PER_4_YEARS) // 4 \ 384 + (month * _JLN_DAYS_PER_5_MONTHS + 2) // 5 \ 385 + day - _JLN_SDN_OFFSET 386 387def julian_ymd(sdn): 388 """Convert an SDN number to a Julian date.""" 389 temp = (sdn + _JLN_SDN_OFFSET) * 4 - 1 390 391 # Calculate the year and day of year (1 <= day_of_year <= 366) 392 year = temp // _JLN_DAYS_PER_4_YEARS 393 day_of_year = (temp % _JLN_DAYS_PER_4_YEARS) // 4 + 1 394 395 # Calculate the month and day of month 396 temp = day_of_year * 5 - 3 397 month = temp // _JLN_DAYS_PER_5_MONTHS 398 day = (temp % _JLN_DAYS_PER_5_MONTHS) // 5 + 1 399 400 # Convert to the normal beginning of the year 401 if month < 10: 402 month += 3 403 else: 404 year += 1 405 month -= 9 406 407 # Adjust to the B.C./A.D. type numbering 408 year -= 4800 409 if year <= 0: 410 year -= 1 411 412 return (year, month, day) 413 414def gregorian_sdn(year, month, day): 415 """Convert a gregorian date to an SDN number.""" 416 if year < 0: 417 year += 4801 418 else: 419 year += 4800 420 421 # Adjust the start of the year 422 if month > 2: 423 month -= 3 424 else: 425 month += 9 426 year -= 1 427 428 return(((year // 100) * _GRG_DAYS_PER_400_YEARS) // 4 429 + ((year % 100) * _GRG_DAYS_PER_4_YEARS) // 4 430 + (month * _GRG_DAYS_PER_5_MONTHS + 2) // 5 431 + day 432 - _GRG_SDN_OFFSET) 433 434def gregorian_ymd(sdn): 435 """Convert an SDN number to a gregorian date.""" 436 temp = (_GRG_SDN_OFFSET + sdn) * 4 - 1 437 438 # Calculate the century (year/100) 439 century = temp // _GRG_DAYS_PER_400_YEARS 440 441 # Calculate the year and day of year (1 <= day_of_year <= 366) 442 temp = ((temp % _GRG_DAYS_PER_400_YEARS) // 4) * 4 + 3 443 year = (century * 100) + (temp // _GRG_DAYS_PER_4_YEARS) 444 day_of_year = (temp % _GRG_DAYS_PER_4_YEARS) // 4 + 1 445 446 # Calculate the month and day of month 447 temp = day_of_year * 5 - 3 448 month = temp // _GRG_DAYS_PER_5_MONTHS 449 day = (temp % _GRG_DAYS_PER_5_MONTHS) // 5 + 1 450 451 # Convert to the normal beginning of the year 452 if month < 10: 453 month = month + 3 454 else: 455 year = year + 1 456 month = month - 9 457 458 # Adjust to the B.C./A.D. type numbering 459 year = year - 4800 460 if year <= 0: 461 year = year - 1 462 return (year, month, day) 463 464def _check_republican_period(sdn, restrict_period): 465 # French Republican calendar wasn't in use before 22.9.1792 or after 1.1.1806 466 if restrict_period and (sdn < 2375840 or sdn > 2380688): 467 raise ValueError("Outside of the French Republican period") 468 469def french_sdn(year, month, day, restrict_period=False): 470 """Convert a French Republican Calendar date to an SDN number.""" 471 sdn = (year*_FR_DAYS_PER_4_YEARS) // 4 + \ 472 (month-1)*_FR_DAYS_PER_MONTH + \ 473 day + _FR_SDN_OFFSET 474 _check_republican_period(sdn, restrict_period) 475 return sdn 476 477def french_ymd(sdn, restrict_period=False): 478 """Convert an SDN number to a French Republican Calendar date.""" 479 _check_republican_period(sdn, restrict_period) 480 temp = (sdn-_FR_SDN_OFFSET)*4 - 1 481 year = temp // _FR_DAYS_PER_4_YEARS 482 day_of_year = (temp % _FR_DAYS_PER_4_YEARS) // 4 483 month = (day_of_year // _FR_DAYS_PER_MONTH) + 1 484 day = (day_of_year % _FR_DAYS_PER_MONTH) + 1 485 return (year, month, day) 486 487def persian_sdn(year, month, day): 488 """Convert a Persian date to an SDN number.""" 489 if year >= 0: 490 epbase = year - 474 491 else: 492 epbase = year - 473 493 494 epyear = 474 + epbase % 2820 495 496 if month <= 7: 497 v1 = (month - 1) * 31 498 else: 499 v1 = ((month - 1) * 30) + 6 500 v2 = ((epyear * 682) - 110) // 2816 501 v3 = (epyear - 1) * 365 + day 502 v4 = (epbase // 2820) * 1029983 503 504 return int(math.ceil(v1 + v2 + v3 + v4 + _PRS_EPOCH - 1)) 505 506def persian_ymd(sdn): 507 """Convert an SDN number to a Persian calendar date.""" 508 sdn = math.floor(sdn) + 0.5 #float 509 510 depoch = sdn - 2121446 #float 511 cycle = math.floor(depoch / 1029983) #int 512 cyear = depoch % 1029983 #int 513 if cyear == 1029982: 514 ycycle = 2820 515 else: 516 aux1 = cyear // 366 #int 517 aux2 = cyear % 366 #int 518 ycycle = (((2134*aux1)+(2816*aux2)+2815) // 1028522) + aux1 + 1 519 520 year = ycycle + (2820 * cycle) + 474 521 if year <= 0: 522 year = year - 1 523 524 yday = sdn - persian_sdn(year, 1, 1) + 1 #float ! 525 if yday < 186: 526 month = math.ceil(yday / 31) 527 else: 528 month = math.ceil((yday - 6) / 30) 529 day = (sdn - persian_sdn(year, month, 1)) + 1 530 return (int(year), int(month), int(day)) 531 532def islamic_sdn(year, month, day): 533 """Convert an Islamic date to an SDN number.""" 534 v1 = math.ceil(29.5 * (month - 1)) 535 v2 = (year - 1) * 354 536 v3 = math.floor((3 + (11 *year)) // 30) 537 538 return int(math.ceil((day + v1 + v2 + v3 + _ISM_EPOCH) - 1)) 539 540def islamic_ymd(sdn): 541 """Convert an SDN number to an Islamic calendar date.""" 542 sdn = math.floor(sdn) + 0.5 543 year = int(math.floor(((30*(sdn-_ISM_EPOCH))+10646) / 10631)) 544 month = int(min(12, math.ceil((sdn-(29+islamic_sdn(year, 1, 1))) / 29.5) + 1)) 545 day = int((sdn - islamic_sdn(year, month, 1)) + 1) 546 return (year, month, day) 547 548def swedish_sdn(year, month, day): 549 """Convert a Swedish date to an SDN number.""" 550 datum = (year, month, day) 551 # Swedish Calendar 552 if (1700, 3, 1) <= datum <= (1712, 2, 30): 553 return julian_sdn(year, month, day) -1 554 # Gregorian Calendar (1753-03-01) 555 elif (1753, 3, 1) <= datum: 556 return gregorian_sdn(year, month, day) 557 else: 558 return julian_sdn(year, month, day) 559 560def swedish_ymd(sdn): 561 """Convert an SDN number to a Swedish calendar date.""" 562 if sdn == 2346425: 563 return (1712, 2, 30) 564 # Swedish Calendar 565 elif 2342042 <= sdn < 2346425: 566 return julian_ymd(sdn+1) 567 # Gregorian Calendar (1753-03-01) 568 elif sdn >= 2361390: 569 return gregorian_ymd(sdn) 570 else: 571 return julian_ymd(sdn) 572