Recipe4.9.Working with the Hebrew Calendar


Recipe 4.9. Working with the Hebrew Calendar

Problem

You need to work with dates in the Hebrew system.

Solution

You need to build up some basic utilities to work effectively with the Hebrew calendar. Hebrew years have 12 months in a regular year and 13 in a leap year. Leap years occur on the 3rd, 6th, 8th, 11th, 14th, 17th, and 19th years of the Metonic cycle (see the "Discussion" section). A concise means of making this determination is given by the relation 7y + 1 mod 19 < 7. From this, you can easily devise a function to determine the last month of any Hebrew year:

<xsl:template name="ckbk:last-month-of-hebrew-year">      <xsl:param name="year"/>      <xsl:choose>           <xsl:when test="(7 * $year + 1) mod 19 &lt; 7">                <xsl:value-of select="13"/>           </xsl:when>           <xsl:otherwise>                <xsl:value-of select="12"/>           </xsl:otherwise>      </xsl:choose> </xsl:template>

As a prerequisite to determining the number of days in any given month or year, you need to encapsulate the complex rules that determine when the Hebrew new year starts. See the paper by Dershowitz and Reingold for detailed explanation:

 <!-- Number of days elapsed from the Sunday prior to the start of the  Hebrew calender to the mean conjunction of Tishri of Hebrew year. -->     <xsl:template name="ckbk:hebrew-calendar-elapsed-days">      <xsl:param name="year"/>          <xsl:variable name="hebrew-leap-year"            select="(7 * $year + 1) mod 19 &lt; 7"/>      <xsl:variable name="hebrew-leap-year-last-year"            select="(7 * ($year - 1) + 1) mod 19 &lt; 7"/>            <xsl:variable name="months-elapsed"            select="235 * floor(($year -1) div 19) +                12 * (($year -1) mod 19) +                floor((7 * (($year - 1) mod 19) + 1) div 19)"/>          <xsl:variable name="parts-elapsed"            select="13753 * $months-elapsed + 5604"/>          <xsl:variable name="day" select="1 + 29 * $months-elapsed +                      floor($parts-elapsed div 25920)"/>          <xsl:variable name="parts" select="$parts-elapsed mod 25920"/>            <xsl:variable name="alternative-day">        <xsl:choose>          <xsl:when test="$parts >= 19440">           <xsl:value-of select="$day + 1"/>          </xsl:when>          <xsl:when test="$day mod 7 = 2 and $parts >= 9924 and                 not($hebrew-leap-year)">           <xsl:value-of select="$day + 1"/>          </xsl:when>          <xsl:when test="$day mod 7 = 1 and $parts >= 16789 and                 $hebrew-leap-year-last-year">           <xsl:value-of select="$day + 1"/>          </xsl:when>          <xsl:otherwise>           <xsl:value-of select="$day"/>          </xsl:otherwise>        </xsl:choose>      </xsl:variable>          <xsl:choose>        <xsl:when test="$alternative-day mod 7 = 0">          <xsl:value-of select="$alternative-day + 1"/>        </xsl:when>        <xsl:when test="$alternative-day mod 7 =3">          <xsl:value-of select="$alternative-day + 1"/>        </xsl:when>        <xsl:when test="$alternative-day mod 7 = 5">          <xsl:value-of select="$alternative-day + 1"/>        </xsl:when>        <xsl:otherwise>          <xsl:value-of select="$alternative-day"/>        </xsl:otherwise>      </xsl:choose>        </xsl:template>

The number of days in a Hebrew year is calculated as the difference between the elapsed days in successive years:

<xsl:template name="ckbk:days-in-hebrew-year">      <xsl:param name="year"/>            <xsl:variable name="e1">        <xsl:call-template name="ckbk:hebrew-calendar-elapsed-days">          <xsl:with-param name="year" select="$year + 1"/>        </xsl:call-template>      </xsl:variable>          <xsl:variable name="e2">        <xsl:call-template name="ckbk:hebrew-calendar-elapsed-days">          <xsl:with-param name="year" select="$year"/>        </xsl:call-template>      </xsl:variable>            <xsl:value-of select="$e1 - $e2"/> </xsl:template>

Heshvan and Kislev are the eighth and ninth months of the Hebrew year and their number of days can vary. You need to know when Heshvan is long and Kislev is short, so you create two predicates:

<xsl:template name="ckbk:long-heshvan">      <xsl:param name="year"/>            <xsl:variable name="days">           <xsl:call-template name="ckbk:days-in-hebrew-year">                <xsl:with-param name="year" select="$year"/>           </xsl:call-template>      </xsl:variable>          <xsl:if select="$days mod 10 = 5">           <xsl:value-of select="true( )"/>      </xsl:if> </xsl:template>     <xsl:template name="ckbk:short-kislev">      <xsl:param name="year"/>          <xsl:variable name="days">           <xsl:call-template name="ckbk:days-in-hebrew-year">                <xsl:with-param name="year" select="$year"/>           </xsl:call-template>      </xsl:variable>          <xsl:if select="$days mod 10 = 3">           <xsl:value-of select="true( )"/>      </xsl:if> </xsl:template>

If you write predicate templates in XSLT 1.0, you should code them to return true( ) (or alternatively, 'true') for true but '' (null string) for false. The problem here is that templates return trees, and any tree, even one whose only node contains false( ) or '', will evaluate to true. The advantage of a tree containing '' is that it can be effectively evaluated as a Boolean by using the string() conversion. This is one of the many awkward facts of XSLT. In XSLT 2.0 you can use functions instead as they can return Booleanvalues.


Most of the machinery is now in place to tackle standard date-time functions provided in other recipes. The first standard function gives the last day in a month for a specified Hebrew month and year:

<xsl:template name="ckbk:last-day-of-hebrew-month">      <xsl:param name="month"/>      <xsl:param name="year"/>            <xsl:variable name="hebrew-leap-year"            select="(7 * $year + 1) mod 19 &lt; 7"/>            <xsl:variable name="long-heshvan">        <xsl:call-template name="ckbk:long-heshvan">          <xsl:with-param name="year" select="$year"/>        </xsl:call-template>      </xsl:variable>          <xsl:variable name="short-kislev">        <xsl:call-template name="ckbk:short-kislev">          <xsl:with-param name="year" select="$year"/>        </xsl:call-template>      </xsl:variable>            <xsl:choose>        <xsl:when test="$month=12 and $hebrew-leap-year">          <xsl:value-of select="30"/>        </xsl:when>        <xsl:when test="$month=8 and string($long-heshvan)">          <xsl:value-of select="30"/>        </xsl:when>        <xsl:when test="$month=9 and string($short-kislev)">          <xsl:value-of select="29"/>        </xsl:when>        <xsl:when test="$month=13">          <xsl:value-of select="29"/>        </xsl:when>        <xsl:when test="$month mod 2 = 0">          <xsl:value-of select="29"/>        </xsl:when>        <xsl:otherwise>          <xsl:value-of select="30"/>        </xsl:otherwise>      </xsl:choose> </xsl:template>

This recursive utility lets you sum the last days in a range of Hebrew months for a given year. It is used when converting a Hebrew date to an absolute year:

<xsl:template name="ckbk:sum-last-day-in-hebrew-months">      <xsl:param name="year"/>      <xsl:param name="from-month"/>      <xsl:param name="to-month"/>      <xsl:param name="accum" select="0"/>          <xsl:choose>        <xsl:when test="$from-month &lt;= $to-month">          <xsl:call-template name="ckbk:sum-last-day-in-hebrew-months">           <xsl:with-param name="year" select="$year"/>           <xsl:with-param name="from-month" select="$from-month+1"/>           <xsl:with-param name="to-month" select="$to-month"/>           <xsl:with-param name="accum">             <xsl:variable name="temp">               <xsl:call-template name="ckbk:last-day-of-hebrew-month">                <xsl:with-param name="year" select="$year"/>                <xsl:with-param name="month" select="$from-month"/>               </xsl:call-template>             </xsl:variable>             <xsl:value-of select="$temp + $accum"/>           </xsl:with-param>          </xsl:call-template>        </xsl:when>        <xsl:otherwise>          <xsl:value-of select="$accum"/>        </xsl:otherwise>      </xsl:choose>      </xsl:template>     <xsl:template name="ckbk:hebrew-date-to-absolute-day">     <xsl:param name="year"/>     <xsl:param name="month"/>     <xsl:param name="day"/>          <xsl:variable name="prior-months-days">        <xsl:choose>          <xsl:when test="7 > $month"> <!-- before Tishri -->           <xsl:variable name="last-month-of-year">             <xsl:call-template name="ckbk:last-month-of-hebrew-year">               <xsl:with-param name="year" select="$year"/>             </xsl:call-template>           </xsl:variable>           <!-- Add days before and after Nisan -->           <xsl:variable name="days-before-nisan">             <xsl:call-template name="ckbk:sum-last-day-in-hebrew-months">               <xsl:with-param name="year" select="$year"/>               <xsl:with-param name="from-month" select="7"/>               <xsl:with-param name="to-month"                 select="$last-month-of-year"/>             </xsl:call-template>           </xsl:variable>           <xsl:call-template name="ckbk:sum-last-day-in-hebrew-months">             <xsl:with-param name="year" select="$year"/>             <xsl:with-param name="from-month" select="1"/>             <xsl:with-param name="to-month" select="$month - 1"/>             <xsl:with-param name="accum" select="$days-before-nisan"/>           </xsl:call-template>          </xsl:when>          <xsl:otherwise>           <!-- days in prior months this year-->           <xsl:call-template name="ckbk:sum-last-day-in-hebrew-months">             <xsl:with-param name="year" select="$year"/>             <xsl:with-param name="from-month" select="7"/>             <xsl:with-param name="to-month" select="$month - 1"/>           </xsl:call-template>          </xsl:otherwise>        </xsl:choose>          </xsl:variable>            <xsl:variable name="days-in-prior-years">        <xsl:call-template name="ckbk:hebrew-calendar-elapsed-days">          <xsl:with-param name="year" select="$year"/>        </xsl:call-template>      </xsl:variable>          <!--      1373429 days before absolute day 1 -->      <xsl:value-of select="$day + $prior-months-days +            $days-in-prior-years - 1373429"/> </xsl:template>

Before implementing absolute-day-to-hebrew-date, you need two more recursive summation utilities that will help search for the actual year and month corresponding to an absolute day from approximations of the same year and month:

<xsl:template name="ckbk:fixup-hebrew-year">      <xsl:param name="start-year"/>      <xsl:param name="abs-day"/>          <xsl:param name="accum" select="0"/>          <xsl:variable name="next">        <xsl:call-template name="ckbk:hebrew-date-to-absolute-day">          <xsl:with-param name="month" select="7"/>          <xsl:with-param name="day" select="1"/>          <xsl:with-param name="year" select="$start-year + 1"/>        </xsl:call-template>      </xsl:variable>            <xsl:choose>        <xsl:when test="$abs-day >= $next">          <xsl:call-template name="ckbk:fixup-hebrew-year">           <xsl:with-param name="start-year" select="$start-year+1"/>           <xsl:with-param name="abs-day" select="$abs-day"/>           <xsl:with-param name="accum" select="$accum + 1"/>          </xsl:call-template>        </xsl:when>        <xsl:otherwise>          <xsl:value-of select="$accum"/>        </xsl:otherwise>      </xsl:choose>      </xsl:template>     <xsl:template name="ckbk:fixup-hebrew-month">      <xsl:param name="year"/>      <xsl:param name="start-month"/>      <xsl:param name="abs-day"/>          <xsl:param name="accum" select="0"/>          <xsl:variable name="next">        <xsl:call-template name="ckbk:hebrew-date-to-absolute-day">          <xsl:with-param name="month" select="$start-month"/>          <xsl:with-param name="day">            <xsl:call-template name="ckbk:last-day-of-hebrew-month">             <xsl:with-param name="month" select="$start-month"/>             <xsl:with-param name="year" select="$year"/>            </xsl:call-template>          </xsl:with-param>          <xsl:with-param name="year" select="$year"/>        </xsl:call-template>      </xsl:variable>          <xsl:choose>        <xsl:when test="$abs-day > $next">          <xsl:call-template name="ckbk:fixup-hebrew-month">           <xsl:with-param name="year" select="$year"/>           <xsl:with-param name="start-month" select="$start-month + 1"/>           <xsl:with-param name="abs-day" select="$abs-day"/>           <xsl:with-param name="accum" select="$accum + 1"/>          </xsl:call-template>        </xsl:when>        <xsl:otherwise>          <xsl:value-of select="$accum"/>        </xsl:otherwise>      </xsl:choose>      </xsl:template>     <xsl:template name="ckbk:absolute-day-to-hebrew-date">     <xsl:param name="abs-day"/>          <xsl:variable name="year">        <xsl:variable name="approx"            select="floor(($abs-day + 1373429) div 366)"/>        <xsl:variable name="fixup">          <xsl:call-template name="ckbk:fixup-hebrew-year">            <xsl:with-param name="start-year" select="$approx"/>            <xsl:with-param name="abs-day" select="$abs-day"/>          </xsl:call-template>        </xsl:variable>        <xsl:value-of select="$approx + $fixup"/>      </xsl:variable>          <xsl:variable name="month">        <xsl:variable name="first-day-of-year">        <xsl:call-template name="ckbk:hebrew-date-to-absolute-day">          <xsl:with-param name="month" select="1"/>           <xsl:with-param name="day" select="1"/>           <xsl:with-param name="year" select="$year"/>          </xsl:call-template>        </xsl:variable>                   <xsl:variable name="approx">          <xsl:choose>           <xsl:when test="$abs-day &lt; $first-day-of-year">             <xsl:value-of select="7"/>           </xsl:when>           <xsl:otherwise>             <xsl:value-of select="1"/>           </xsl:otherwise>          </xsl:choose>        </xsl:variable>            <xsl:variable name="fixup">          <xsl:call-template name="ckbk:fixup-hebrew-month">           <xsl:with-param name="year" select="$year"/>           <xsl:with-param name="start-month" select="$approx"/>           <xsl:with-param name="abs-day" select="$abs-day"/>          </xsl:call-template>        </xsl:variable>            <xsl:value-of select="$approx + $fixup"/>      </xsl:variable>          <xsl:variable name="day">        <xsl:variable name="days-to-first-of-month">          <xsl:call-template name="ckbk:hebrew-date-to-absolute-day">            <xsl:with-param name="month" select="$month"/>            <xsl:with-param name="day" select="1"/>            <xsl:with-param name="year" select="$year"/>          </xsl:call-template>        </xsl:variable>              <xsl:value-of select="$abs-day - ($days-to-first-of-month - 1)"/>      </xsl:variable>          <xsl:value-of select="concat($year,'-',$month,'-',$day)"/>            </xsl:template>

In the previous example, you create three unique yet similar recursive templates that compute the sum of a function. Creating a generic utility that can sum an arbitrary function is highly desirable. Readers who are familiar with Lisp could probably guess that the original Lisp code from which this XSLT was derived used a Lisp macro to accomplish just that. Chapter 13 demonstrates how generic programming can be achieved in XSLT and how this recipe can be greatly simplified as a result.


Discussion

The Hebrew calendar is the most complex calendar covered in this chapter. Hence, the Hebrew date-time code is correspondingly complicated. The Hebrew calendar is intricate because its months are strictly lunar, yet it mandates that Passover must always occur in the spring. While most other calendars have a fixed number of months, the Hebrew calendar has 12 during a conventional year and 13 during a leap year.




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net