
/*

  Description:
  -----------
  Javascript to produce a Baha'i Calendar, by Burgh House Software   
  http://software.burghhouse.com
  Versiondate: 2009 08 09
  -- Improvements: now shows the first two (customisable) months of the following year

  Complimentary Software Licence
  ==============================
  This software is free: you do not have to pay us to download and/or use it.
  Please feel free to copy it and use it for any purpose.
  Intellectual property rights remain with Burgh House Limited.
  Please don't sell it or pass it off as your own.
  Please don't attempt to re-engineer it or break any security protections.
  We do not offer any support for this software and you use it entirely at your own risk.
  If you have any problems with it, or need it adapted and don't know how, please feel free to contact us - we'll help if we can.
  Please see http://software.burghhouse.com for our contact details.

  Customisation:
  -------------
  You can easily change the colours or other display parameters by amending the Stylexxxx variables defined below.
  Your style-sheet will define how the fundamental HTML elements are displayed.
  It would probably be fairly easy to convert to many other languages.

  Bugs:
  ----
  If you spot a bug please let us know.  We may even fix it and send you back a new version . . .

*/

/*  ------------  */
/*  -- SETUP  --  */
/*  ------------  */

// Style definitions: you may change these in any way to suit your page
var StyleMainHolyDay  = 'color: #ffffff; background: #cc0000; ';
var StyleRidvan       = 'color: #ffffff; background: #0000ff; ';
var StyleMonthFeast   = 'color: #000000; background: #00ff00; ';
var StyleOtherHolyDay = 'color: #000000; background: #00ffff; ';
var StyleFast         = 'color: #000000; background: #ffffcc; ';
var StyleToday        = 'color: #ffffff; background: #880088; ';
var StyleNormal       = 'color: #000000; background: #ffffff; ';
var StyleBlank        = 'color: #666666; background: #eeeeee; ';
var StyleIntercalary  = 'color: #000000; background: #ff9900; ';

// Extra Months (of next year) to display at the end of the current year (0..20)
var NumExtraMonths = 3;

// Weekday names
var Weekday = new Array("Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday");

// Function: Returns the date-part of a date serial as a string with day-of-week
function DatePartStr(UsrDateSerial) 
  {
  var UsrDateStr = UsrDateSerial.toLocaleString();
  var TempDatePos = UsrDateStr.indexOf(':',0)-3;
  return Weekday[UsrDateSerial.getDay()]+' '+UsrDateStr.substring(0, TempDatePos);
  };

// Function: finds 'th' (etc.) for a number
function AddTH(usernumber)
  {
  var AddTHend = "th";
  if (usernumber % 10 > 0 & usernumber % 10 < 4) // Last digit is 1..3
    {
    if (usernumber < 11 || usernumber > 13)      // Not 11..13
      {
      if (usernumber % 10 == 1)
        {
        AddTHend="st";
        }
      else
        {
        if (usernumber % 10 == 2)
          {
          AddTHend="nd";
          }
        else
          {
          if (usernumber % 10 == 3) AddTHend="rd";
          };
        };
      };
    };
  return AddTHend;
  };

// Useful general variables and constants
var OneDayDateSerial = Date.parse('2 January 2000')-Date.parse('1 January 2000');  
var MouseHoverInstruction = 'Hover mouse over an item to see it defined here';
var MouseActive = ' dummy=\"'+MouseHoverInstruction+'\" onmouseover=\"MouseoverDisplay.innerText=this.descriptive\" onmouseout=\"MouseoverDisplay.innerText=this.dummy\"';
var MainHolyDayIX = 0;
var OtherHolyDayIX = 0;
var DescriptiveString = '';
var DisplayStyle = '';
var tdString = '';
var MonthNumber = 0;
var DayNumber = 0;
var DayNumberLimit = 0;
var DayOffset = 0;

// These are essential Bahai names that contain upper-ASCII characters or '
var EscapedBahai = 'Bah%E1%27i';
var EscapedBahaullah = 'Bah%E1%27%FAll%E1h';
var EscapedBab = 'B%E1b';
var EscapedNawRuz = 'Naw-R%FAz';
var EscapedRidvan = 'Ridv%E1n';

// Some useful date-based variables
var NowDate = new Date();                                                             // Get the time/date right now date
var TodayDate = new Date(NowDate.getFullYear(),NowDate.getMonth(),NowDate.getDate()); // Zero the hms to leave the date only
var TodayDateSerial = Date.parse(TodayDate);                                          // Convert it into a datenumber
var ThisYearYearNum = NowDate.getFullYear();                                          // Extract the year part and...
do                                
  {
  var StartYearDate = new Date(ThisYearYearNum,2,21);                                 // Start-of-year date
  var EndYearDate = new Date(ThisYearYearNum+1,2,20);                                 // End-of-year date
  ThisYearYearNum -= 1;
  }
while (StartYearDate > TodayDate);                                                    // ...set that to the year in which this Bahai year began (i.e. the 21st March prior to Today)
var StartYearDateSerial = Date.parse(StartYearDate);                                  // Start-of-year as a date serial
var EndYearDateSerial = Date.parse(EndYearDate);                                      // End-of-year as a date serial
var EndYearYearNum = EndYearDate.getFullYear();                                       // Western year number for end-of-year
var StartYearYearNum = StartYearDate.getFullYear();                                   // Western year number for start-of-year
var BahaiCalendarStartDate = new Date(1844,2,21);                                     // Base-date for the Bahai Calendar
var BahaiYearNumber = 1 + StartYearDate.getFullYear() - BahaiCalendarStartDate.getFullYear();
var IncrementalDayDateSerial = StartYearDateSerial;                                   // Counter that we will increment each day


// Details of the months.  NB: MonthData 19 is the Intercalary and MonthData 20 is Month 19 (!)
function MonthObject(NameBahai, NameWestern, DatesWesternStart, DatesWesternEnd)
  {
  this.NameBahai         = NameBahai;
  this.NameWestern       = NameWestern;
  this.DatesWesternStart = DatesWesternStart;
  this.DatesWesternEnd   = DatesWesternEnd;
  };

var MonthData = new Array();
MonthData[01] = new MonthObject(unescape('Bah%E1'),         'Splendor',         '21 March '+StartYearYearNum,     '8 April '+StartYearYearNum);
MonthData[02] = new MonthObject(unescape('Jal%E1l'),        'Glory',            '9 April '+StartYearYearNum,      '27 April '+StartYearYearNum);
MonthData[03] = new MonthObject(unescape('Jam%E1l'),        'Beauty',           '28 April '+StartYearYearNum,     '16 May '+StartYearYearNum);
MonthData[04] = new MonthObject(unescape('%27Azam%E1t'),    'Grandeur',         '17 May '+StartYearYearNum,       '4 June '+StartYearYearNum);
MonthData[05] = new MonthObject(unescape('N%FAr'),          'Light',            '5 June '+StartYearYearNum,       '23 June '+StartYearYearNum);
MonthData[06] = new MonthObject(unescape('Rahmat'),         'Mercy',            '24 June '+StartYearYearNum,      '12 July '+StartYearYearNum);
MonthData[07] = new MonthObject(unescape('Kalim%E1t'),      'Words',            '13 July '+StartYearYearNum,      '31 July '+StartYearYearNum);
MonthData[08] = new MonthObject(unescape('Kam%E1l'),        'Perfection',       '1 August '+StartYearYearNum,     '19 August '+StartYearYearNum);
MonthData[09] = new MonthObject(unescape('Asm%E1%27'),      'Names',            '20 August '+StartYearYearNum,    '7 September '+StartYearYearNum);
MonthData[10] = new MonthObject(unescape('%27Izzat'),       'Might',            '8 September '+StartYearYearNum,  '26 September '+StartYearYearNum);
MonthData[11] = new MonthObject(unescape('Mash%EDyyat'),    'Will',             '27 September '+StartYearYearNum, '15 October '+StartYearYearNum);
MonthData[12] = new MonthObject(unescape('%27Ilm'),         'Knowledge',        '16 October '+StartYearYearNum,   '3 November '+StartYearYearNum);
MonthData[13] = new MonthObject(unescape('Qudrat'),         'Power',            '4 November '+StartYearYearNum,   '22 November '+StartYearYearNum);
MonthData[14] = new MonthObject(unescape('Qawl'),           'Speech',           '23 November '+StartYearYearNum,  '11 December '+StartYearYearNum);
MonthData[15] = new MonthObject(unescape('Mas%E1%27il'),    'Questions',        '12 December '+StartYearYearNum,  '30 December '+StartYearYearNum);
MonthData[16] = new MonthObject(unescape('Sharaf'),         'Honour',           '31 December '+StartYearYearNum,  '18 January '+EndYearYearNum);
MonthData[17] = new MonthObject(unescape('Sult%E1n'),       'Sovereignty',      '19 January '+EndYearYearNum,     '6 February '+EndYearYearNum);
MonthData[18] = new MonthObject(unescape('Mulk'),           'Dominion',         '7 February '+EndYearYearNum,     '25 February '+EndYearYearNum);
MonthData[19] = new MonthObject(unescape('Ayy%E1m-i-H%E1'), 'Intercalary Days', '26 February '+EndYearYearNum,    '1 March '+EndYearYearNum);
MonthData[20] = new MonthObject(unescape('%27Al%E1'),       'Loftiness',        '2 March '+EndYearYearNum,        '20 March '+EndYearYearNum);


// Function: Returns the month name in both Bahai and Western forms
function MonthNameBoth(UsrMonthNumber)
  {
  with (MonthData[UsrMonthNumber]) return NameBahai+' ('+NameWestern+')';
  };

// Single-date events
function EventObject(DateSerial,DayName)
  {
  this.DateSerial = DateSerial;
  this.DayName    = DayName;
  };

// Main holy days
var MainHolyDayData = new Array();
MainHolyDayData[01] = new EventObject(StartYearDateSerial+(OneDayDateSerial*0),   unescape('Feast of '+EscapedNawRuz));
MainHolyDayData[02] = new EventObject(StartYearDateSerial+(OneDayDateSerial*31),  unescape('First day of '+EscapedRidvan));
MainHolyDayData[03] = new EventObject(StartYearDateSerial+(OneDayDateSerial*39),  unescape('Ninth day of '+EscapedRidvan));
MainHolyDayData[04] = new EventObject(StartYearDateSerial+(OneDayDateSerial*42),  unescape('Twelfth day of '+EscapedRidvan));
MainHolyDayData[05] = new EventObject(StartYearDateSerial+(OneDayDateSerial*63),  unescape('Declaration of the '+EscapedBab));
MainHolyDayData[06] = new EventObject(StartYearDateSerial+(OneDayDateSerial*69),  unescape('Ascension of '+EscapedBahaullah));
MainHolyDayData[07] = new EventObject(StartYearDateSerial+(OneDayDateSerial*110), unescape('Martyrdom of the '+EscapedBab));
MainHolyDayData[08] = new EventObject(StartYearDateSerial+(OneDayDateSerial*213), unescape('Birth of the '+EscapedBab));
MainHolyDayData[09] = new EventObject(StartYearDateSerial+(OneDayDateSerial*236), unescape('Birth of of '+EscapedBahaullah));

// Other holy days
var OtherHolyDayData = new Array();
OtherHolyDayData[01] = new EventObject(StartYearDateSerial+(OneDayDateSerial*250), unescape("Day of the Covenant"));
OtherHolyDayData[02] = new EventObject(StartYearDateSerial+(OneDayDateSerial*252), unescape("Ascension of %27Abdu%27l-Bah%E1"));

// Date-range events
function RangeObject(First,Last)
  {
  this.First = First;
  this.Last  = Last;
  };

// Ridvan
var RidvanData = new RangeObject(StartYearDateSerial+(OneDayDateSerial*31), StartYearDateSerial+(OneDayDateSerial*42));

// the fast.  Note that the Fast is defined by reference to the year-end to avoid problems with leap years.
var FastData = new RangeObject(EndYearDateSerial-(OneDayDateSerial*18), EndYearDateSerial-(OneDayDateSerial*0));

// main month process
function ProcessOneMonth(MonthNumber)
  {
  with (MonthData[MonthNumber])
    {
  
    // Start of this row
    document.writeln('<tr>');
  
    // Set up the year-dependent things
    if (IncrementalDayDateSerial > EndYearDateSerial)
      {
      var TestDateSerial = IncrementalDayDateSerial - (OneDayDateSerial + EndYearDateSerial - StartYearDateSerial);
      var DateIndicatorText = ':'+(BahaiYearNumber+1);
      var DateIndicatorStyle = ' style=\"'+StyleBlank+'\"';
      var DisplayYearNum = 1 + BahaiYearNumber;
      }
    else
      {
      var TestDateSerial = IncrementalDayDateSerial;
      var DateIndicatorText = '';
      var DateIndicatorStyle = '';
      var DisplayYearNum = BahaiYearNumber;
      };

    // Bahai month name
    var DescriptiveText = MonthNameBoth(MonthNumber)+DateIndicatorText+' '+DatesWesternStart+'-'+DatesWesternEnd;
    document.writeln('<th align=\"left\"'+DateIndicatorStyle+' title=\"'+DescriptiveText+'\" descriptive=\"'+DescriptiveText+'\"'+MouseActive+'>'+NameBahai+DateIndicatorText+'<\/th>');
  
    // Deal with the Intercalary days: set DayNumberLimit for each month
    if (MonthNumber==19) 
      {
      if (EndYearYearNum % 4 == 0) // Calculate the number of Intercalary days, based on whether EndYearYearNum is a leap year
        {
        DayNumberLimit = 6; 
        }
      else
        {
        DayNumberLimit = 5; 
        };
      }
    else 
      {
      DayNumberLimit = 20;
      };
  
    for(DayNumber=1; DayNumber<DayNumberLimit; DayNumber++) 
      {
  
      // Initialise variables and start the table cell tag
      DescriptiveString = '';
      tdString = '<td align=\"right\"';

      if ((IncrementalDayDateSerial <= EndYearDateSerial)&&(IncrementalDayDateSerial >= StartYearDateSerial))
        {
        DisplayStyle = StyleNormal;
        }
      else
        {
        DisplayStyle = StyleBlank;
        };
    
    
      // Start-of-month feast
      if (DayNumber == 1)
        {
        if (MonthNumber!=19) 
          {
          DisplayStyle = StyleMonthFeast;
          DescriptiveString += ' Feast of '+MonthNameBoth(MonthNumber)+'. ';
          };
        };

    
      // Intercalary days
      if (MonthNumber==19) DisplayStyle = StyleIntercalary;
    
    
      // Ridvan
      with (RidvanData)
        {
        if ((TestDateSerial >= First)&&(TestDateSerial <= Last))
          {
          DisplayStyle = StyleRidvan;
          DescriptiveString += ' Period of '+unescape(EscapedRidvan)+'. ';
          };
        };
    
    
      // Fast
      with (FastData)
        {
        if ((TestDateSerial >= First)&&(TestDateSerial <= Last))
          {
          DisplayStyle = StyleFast;
          DescriptiveString += ' Period of the Fast. ';
          };
        };
    
  
      // Main Holy Days
      for (MainHolyDayIX=1; MainHolyDayIX<10; MainHolyDayIX++)
        {
        with (MainHolyDayData[MainHolyDayIX])
          {
          if (TestDateSerial == DateSerial)
            {
            DisplayStyle = StyleMainHolyDay;
            DescriptiveString = ' Holy Day - '+DayName+'. '+DescriptiveString;
            };
          };
        };
    
      // Other Holy Days
      for (OtherHolyDayIX=1; OtherHolyDayIX<3; OtherHolyDayIX++)
        {
        with (OtherHolyDayData[OtherHolyDayIX])
          {
          if (TestDateSerial == DateSerial)
            {
            DisplayStyle = StyleOtherHolyDay;
            DescriptiveString = ' '+DayName+'. '+DescriptiveString;
            };
          };
        };
  
    
      DayOffset = (1+(TestDateSerial-StartYearDateSerial)/OneDayDateSerial);
      DescriptiveString += ' '+DayNumber+AddTH(DayNumber)+' day of '+MonthNameBoth(MonthNumber)+' & '+DayOffset+AddTH(DayOffset)+' of '+unescape(EscapedBahai)+' year '+DisplayYearNum+'. '+DatePartStr(new Date(IncrementalDayDateSerial))+'. ';
    
      // Today
      if (IncrementalDayDateSerial == TodayDateSerial)
        {
        DisplayStyle = StyleToday;
        DateDisplayLink.innerText = 'Today: '+DescriptiveString;
        DescriptiveString += ' Today';
        };
    
      tdString += ' style=\"'+DisplayStyle+'\" title=\"'+DescriptiveString+'\" descriptive=\"'+DescriptiveString+'\"'+MouseActive+'>';
    
      if (DayNumber<10) 
        {
        tdString += '0' + DayNumber;
        }
      else
        {
        tdString += DayNumber;
        };
    
      document.writeln(tdString + '<\/td>');
    
      // Update IncrementalDayDateSerial last
      IncrementalDayDateSerial += OneDayDateSerial;
      TestDateSerial += OneDayDateSerial;
      };

    // Fill in the space for the remaining days (actually only does anything for Intercalary)
    if (DayNumberLimit < 19)
      {
      document.write('<td style=\"'+StyleBlank+'\" colspan=\"'+(20-DayNumberLimit)+'\">&nbsp;<\/td>');
      };
    
    // Western month name 
    var DescriptiveText = MonthNameBoth(MonthNumber)+DateIndicatorText+' '+DatesWesternStart+'-'+DatesWesternEnd;
    document.writeln('<th align=\"right\"'+DateIndicatorStyle+' title=\"'+DescriptiveText+'\" descriptive=\"'+DescriptiveText+'\"'+MouseActive+'>'+NameWestern+DateIndicatorText+'<\/th>');

    // End of this row
    document.writeln('<\/tr>');
    };
  };

/*  --------------  */
/*  -- PROGRAM  --  */
/*  --------------  */

// Display the headline
var DescriptiveText = unescape(EscapedBahai)+' Year Number '+BahaiYearNumber+', '+DatePartStr(StartYearDate)+' to '+DatePartStr(EndYearDate);
document.writeln('<h1 title=\"'+DescriptiveText+'\" descriptive=\"'+DescriptiveText+'\"'+MouseActive+'>Year&nbsp;'+BahaiYearNumber+'&nbsp;BE<br><small><a id=\"DateDisplayLink\">Today is =PlaceMarker=<\/a><\/small><\/h1>');

// Start the table
document.write('<table cellpadding=\"3\" border=\"1\" width=\"100%\">');

// Process each of the months
for(LoopMonthNumber=1; LoopMonthNumber<21; LoopMonthNumber++)
  {
  ProcessOneMonth(LoopMonthNumber);
  };
for(LoopMonthNumber=1; LoopMonthNumber<(NumExtraMonths+1); LoopMonthNumber++)
  {
  ProcessOneMonth(LoopMonthNumber);
  };

// Finish the table
document.writeln('<\/table>');

// Show the Key
document.write('<p align=\"center\"><b>Key<\/b>: ');
document.write('<span title=\"see description below\" descriptive=\"Main Holy Day: '+unescape(EscapedBahai)+'s observe eleven holy days each year. These include days associated with the lives of '+unescape(EscapedBahaullah)+' and the '+unescape(EscapedBab)+', as well as the '+unescape(EscapedBahai)+' new year, on March 21. On these nine main holy days, '+unescape(EscapedBahai)+'s abstain from work.\" style=\"'+StyleMainHolyDay+'\"'+MouseActive+'>&nbsp;Main Holy Day&nbsp;<\/span> ');
document.write('<span title=\"see description below\" descriptive=\"Other Holy Day: '+unescape(EscapedBahai)+'s observe eleven holy days each year; nine main holy days and these two others. On these other two holy days, '+unescape(EscapedBahai)+'s do not abstain from work.\" style=\"'+StyleOtherHolyDay+'\"'+MouseActive+'>&nbsp;Other Holy Day&nbsp;<\/span> ');
document.write('<span title=\"see description below\" descriptive=\"Day of '+unescape(EscapedRidvan)+': The most important of the other holidays is '+unescape(EscapedRidvan)+', a twelve-day period that commemorates '+unescape(EscapedBahaullah+'%27')+'s declaration of His mission.\" style=\"'+StyleRidvan+'\"'+MouseActive+'>&nbsp;Day of '+unescape(EscapedRidvan)+'&nbsp;<\/span> ');
document.write('<span title=\"see description below\" descriptive=\"Feast Day: The regular gathering that promotes and sustains the unity of the local '+unescape(EscapedBahai)+' community, and always contains three elements: spiritual devotion, administrative consultation, and social fellowship, thus combining religious worship with grassroots governance and social enjoyment.\" style=\"'+StyleMonthFeast+'\"'+MouseActive+'>&nbsp;Feast Day&nbsp;<\/span> ');
document.write('<span title=\"see description below\" descriptive=\"Period of the Fast: As has been the case with other revealed religions, the '+unescape(EscapedBahai)+' Faith sees great value in the practice of fasting as a discipline for the soul. '+unescape(EscapedBahaullah)+' designated a nineteen-day period each year when adult '+unescape(EscapedBahai)+'s fast from sunrise to sunset each day.\" style=\"'+StyleFast+'\"'+MouseActive+'>&nbsp;Period of the Fast&nbsp;<\/span> ');
document.write('<span title=\"see description below\" descriptive=\"'+MonthNameBoth(19)+': a period of four (five in leap years) days devoted to social gatherings, acts of charity, and the exchange of gifts with friends and family.\" style=\"'+StyleIntercalary+'\"'+MouseActive+'>&nbsp;'+MonthData[19].NameBahai+'&nbsp;<\/span> ');
document.write('<span title=\"see description below\" descriptive=\"'+DateDisplayLink.innerText+'\" style=\"'+StyleToday+'\"'+MouseActive+'>&nbsp;Today <\/span>&nbsp;');
document.write('<span title=\"see description below\" descriptive=\"Normal Day\" style=\"'+StyleNormal+'\"'+MouseActive+'>&nbsp;Normal Day&nbsp;<\/span> ');
document.writeln('<\/p>');

// Position the mouseover display
document.writeln('<p align=\"center\" id=\"MouseoverDisplay\">'+MouseHoverInstruction+'<\/p>');

/* Copyright (c) Burgh House Limited 2011 all rights reserved */

