001/*
002 * Zmanim Java API
003 * Copyright (C) 2017 - 2025 Eliyahu Hershfeld
004 *
005 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General
006 * Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option)
007 * any later version.
008 *
009 * This library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied
010 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
011 * details.
012 * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to
013 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA,
014 * or connect to: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
015 */
016package com.kosherjava.zmanim.hebrewcalendar;
017
018import java.util.Calendar;
019import java.util.GregorianCalendar;
020
021
022/**
023 * This class calculates the <a href="https://en.wikipedia.org/wiki/Jerusalem_Talmud">Talmud Yerusalmi</a> <a href=
024 * "https://en.wikipedia.org/wiki/Daf_Yomi">Daf Yomi</a> page ({@link Daf}) for the a given date.
025 * 
026 * @author &copy; elihaidv
027 * @author &copy; Eliyahu Hershfeld 2017 - 2025
028 */
029public class YerushalmiYomiCalculator {
030        
031        /**
032         * The start date of the first Daf Yomi Yerushalmi cycle of February 2, 1980 / 15 Shevat, 5740.
033         */
034        private final static Calendar DAF_YOMI_START_DAY = new GregorianCalendar(1980, Calendar.FEBRUARY, 2);
035        /** The number of milliseconds in a day. */
036        private final static int DAY_MILIS = 1000 * 60 * 60 * 24;
037        /** The number of pages in the Talmud Yerushalmi.*/
038        private final static int WHOLE_SHAS_DAFS = 1554;
039        /** The number of pages per <em>masechta</em> (tractate).*/
040        private final static int[] BLATT_PER_MASECHTA = { 
041                        68, 37, 34, 44, 31, 59, 26, 33, 28, 20, 13, 92, 65, 71, 22, 22, 42, 26, 26, 33, 34, 22,
042                        19, 85, 72, 47, 40, 47, 54, 48, 44, 37, 34, 44, 9, 57, 37, 19, 13};
043
044        /**
045         * Default constructor.
046         */
047        public YerushalmiYomiCalculator() {
048                // nothing here
049        }
050        
051        /**
052         * Returns the <a href="https://en.wikipedia.org/wiki/Daf_Yomi">Daf Yomi</a>
053         * <a href="https://en.wikipedia.org/wiki/Jerusalem_Talmud">Yerusalmi</a> page ({@link Daf}) for a given date.
054         * The first Daf Yomi cycle started on 15 Shevat (Tu Bishvat), 5740 (February, 2, 1980) and calculations
055         * prior to this date will result in an IllegalArgumentException thrown. A null will be returned on Tisha B'Av or
056         * Yom Kippur.
057         *
058         * @param calendar
059         *            the calendar date for calculation
060         * @return the {@link Daf} or null if the date is on Tisha B'Av or Yom Kippur.
061         *
062         * @throws IllegalArgumentException
063         *             if the date is prior to the February 2, 1980, the start of the first Daf Yomi Yerushalmi cycle
064         */
065        public static Daf getDafYomiYerushalmi(JewishCalendar calendar) {
066                
067                Calendar nextCycle = new GregorianCalendar();
068                Calendar prevCycle = new GregorianCalendar();
069                Calendar requested = calendar.getGregorianCalendar();
070                int masechta = 0;
071                Daf dafYomi = null;
072
073                // There isn't Daf Yomi on Yom Kippur or Tisha B'Av.
074                if ( calendar.getYomTovIndex() == JewishCalendar.YOM_KIPPUR ||
075                                calendar.getYomTovIndex() == JewishCalendar.TISHA_BEAV ) {
076                        return null;
077                }
078                
079                
080                if (requested.before(DAF_YOMI_START_DAY)) {
081                        throw new IllegalArgumentException(requested + " is prior to organized Daf Yomi Yerushalmi cycles that started on "
082                                        + DAF_YOMI_START_DAY);
083                }
084                
085                // Start to calculate current cycle. init the start day
086                nextCycle.setTime(DAF_YOMI_START_DAY.getTime());
087                
088                // Go cycle by cycle, until we get the next cycle
089                while (requested.after(nextCycle)) {
090                        prevCycle.setTime(nextCycle.getTime());
091                        
092                        // Adds the number of whole shas dafs. and the number of days that not have daf.
093                        nextCycle.add(Calendar.DAY_OF_MONTH, WHOLE_SHAS_DAFS);
094                        nextCycle.add(Calendar.DAY_OF_MONTH, getNumOfSpecialDays(prevCycle, nextCycle));                
095                }
096                
097                // Get the number of days from cycle start until request.
098                int dafNo = (int)(getDiffBetweenDays(prevCycle, requested));
099                
100                // Get the number of special day to subtract
101                int specialDays = getNumOfSpecialDays(prevCycle, requested);
102                int total = dafNo - specialDays;
103                                
104                // Finally find the daf.
105                for (int i : BLATT_PER_MASECHTA) {
106                        if (total < i) {
107                                dafYomi = new Daf(masechta, total + 1);
108                                break;
109                        }
110                        total -= i;
111                        masechta++;
112                }
113
114                return dafYomi;
115        }
116        
117        /**
118         * Return the number of special days (Yom Kippur and Tisha Beav, where there are no dafim), between the start date
119         * (as a <code>Calendar</code>) and end date (also as a <code>Calendar</code>).
120         * 
121         * @param start date to start calculating from
122         * @param end date to finish calculating at
123         * @return the number of special days between the start and end dates
124         */
125        private static int getNumOfSpecialDays(Calendar start, Calendar end) {
126                
127                // Find the start and end Jewish years
128                int startYear = new JewishCalendar(start).getJewishYear();
129                int endYear = new JewishCalendar(end).getJewishYear();
130                
131                // Value to return
132                int specialDays = 0;
133                
134                //Instant of special Dates
135                JewishCalendar yom_kippur = new JewishCalendar(5770, 7, 10);
136                JewishCalendar tisha_beav = new JewishCalendar(5770, 5, 9);
137
138                // Go over the years and find special dates
139                for (int i = startYear; i <= endYear; i++) {
140                        yom_kippur.setJewishYear(i);
141                        tisha_beav.setJewishYear(i);
142                        
143                        if (isBetween(start, yom_kippur.getGregorianCalendar(), end)) {
144                                specialDays++;
145                        }
146                        if (isBetween(start, tisha_beav.getGregorianCalendar(), end)) {
147                                specialDays++;
148                        }
149                }
150                
151                return specialDays;
152        }
153
154        /**
155         * Return if the date is between two dates
156         * 
157         * @param start the start date
158         * @param date the date being compared
159         * @param end the end date
160         * @return if the date is between the start and end dates
161         */
162        private static boolean isBetween(Calendar start, Calendar date, Calendar end ) {
163                return start.before(date) && end.after(date);
164        }
165        
166        /**
167         * Return the number of days between the dates passed in
168         * @param start the start date
169         * @param end the end date
170         * @return the number of days between the start and end dates
171         */
172        private static long getDiffBetweenDays(Calendar start, Calendar end) {
173                return  (end.getTimeInMillis() - start.getTimeInMillis()) / DAY_MILIS;
174        }
175}