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