minJD=$date;
$this->maxJD=$date;
list($this->y, $this->m, $this->d)=$this->JDtoYMD($date);
return;
}
// Construct from an array (of three gedcom-style strings: "1900", "feb", "4")
if (is_array($date)) {
$this->d=(int)$date[2];
if (!is_null($this->MONTH_TO_NUM($date[1]))) {
$this->m=$this->MONTH_TO_NUM($date[1]);
} else {
$this->m=0;
$this->d=0;
}
$this->y=$this->ExtractYear($date[0]);
$this->SetJDfromYMD();
return;
}
// Construct from an equivalent xxxxDate object
if ($this->CALENDAR_ESCAPE()==$date->CALENDAR_ESCAPE()) {
// NOTE - can't copy whole object - need to be able to copy Hebrew to Jewish, etc.
$this->y=$date->y;
$this->m=$date->m;
$this->d=$date->d;
$this->minJD=$date->minJD;
$this->maxJD=$date->maxJD;
return;
}
// ...else construct an inequivalent xxxxDate object
if ($date->y==0) {
// Incomplete date - convert on basis of anniversary in current year
$today=$date->TodayYMD();
$jd=$date->YMDtoJD($today[0], $date->m, $date->d==0?$today[2]:$date->d);
} else {
// Complete date
$jd=floor(($date->maxJD+$date->minJD)/2);
}
list($this->y, $this->m, $this->d)=$this->JDtoYMD($jd);
// New date has same precision as original date
if ($date->y==0) $this->y=0;
if ($date->m==0) $this->m=0;
if ($date->d==0) $this->d=0;
$this->SetJDfromYMD();
}
// Set the object's JD from a potentially incomplete YMD
function SetJDfromYMD() {
if ($this->y==0) {
$this->minJD=0;
$this->maxJD=0;
} else
if ($this->m==0) {
$this->minJD=$this->YMDtoJD($this->y, 1, 1);
$this->maxJD=$this->YMDtoJD($this->NextYear($this->y), 1, 1)-1;
} else {
if ($this->d==0) {
list($ny,$nm)=$this->NextMonth();
$this->minJD=$this->YMDtoJD($this->y, $this->m, 1);
$this->maxJD=$this->YMDtoJD($ny, $nm, 1)-1;
} else {
$this->minJD=$this->YMDtoJD($this->y, $this->m, $this->d);
$this->maxJD=$this->minJD;
}
}
}
// Calendars are defined in terms of the following static functions.
// They should redefine them as necessary.
static function CALENDAR_ESCAPE() {
return '@#DUNKNOWN@';
}
static function NUM_MONTHS() {
return 12;
}
static function MONTH_TO_NUM($m) {
static $months=array(''=>0, 'jan'=>1, 'feb'=>2, 'mar'=>3, 'apr'=>4, 'may'=>5, 'jun'=>6, 'jul'=>7, 'aug'=>8, 'sep'=>9, 'oct'=>10, 'nov'=>11, 'dec'=>12);
if (isset($months[$m])) {
return $months[$m];
} else {
return null;
}
}
// We put these in the base class, to save duplicating it in the Julian and Gregorian calendars
static function NUM_TO_MONTH_NOMINATIVE($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('NOMINATIVE', 'January');
case 2: return i18n::translate_c('NOMINATIVE', 'February');
case 3: return i18n::translate_c('NOMINATIVE', 'March');
case 4: return i18n::translate_c('NOMINATIVE', 'April');
case 5: return i18n::translate_c('NOMINATIVE', 'May');
case 6: return i18n::translate_c('NOMINATIVE', 'June');
case 7: return i18n::translate_c('NOMINATIVE', 'July');
case 8: return i18n::translate_c('NOMINATIVE', 'August');
case 9: return i18n::translate_c('NOMINATIVE', 'September');
case 10: return i18n::translate_c('NOMINATIVE', 'October');
case 11: return i18n::translate_c('NOMINATIVE', 'November');
case 12: return i18n::translate_c('NOMINATIVE', 'December');
default: return '';
}
}
static function NUM_TO_MONTH_GENITIVE($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('GENITIVE', 'January');
case 2: return i18n::translate_c('GENITIVE', 'February');
case 3: return i18n::translate_c('GENITIVE', 'March');
case 4: return i18n::translate_c('GENITIVE', 'April');
case 5: return i18n::translate_c('GENITIVE', 'May');
case 6: return i18n::translate_c('GENITIVE', 'June');
case 7: return i18n::translate_c('GENITIVE', 'July');
case 8: return i18n::translate_c('GENITIVE', 'August');
case 9: return i18n::translate_c('GENITIVE', 'September');
case 10: return i18n::translate_c('GENITIVE', 'October');
case 11: return i18n::translate_c('GENITIVE', 'November');
case 12: return i18n::translate_c('GENITIVE', 'December');
default: return '';
}
}
static function NUM_TO_MONTH_LOCATIVE($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('LOCATIVE', 'January');
case 2: return i18n::translate_c('LOCATIVE', 'February');
case 3: return i18n::translate_c('LOCATIVE', 'March');
case 4: return i18n::translate_c('LOCATIVE', 'April');
case 5: return i18n::translate_c('LOCATIVE', 'May');
case 6: return i18n::translate_c('LOCATIVE', 'June');
case 7: return i18n::translate_c('LOCATIVE', 'July');
case 8: return i18n::translate_c('LOCATIVE', 'August');
case 9: return i18n::translate_c('LOCATIVE', 'September');
case 10: return i18n::translate_c('LOCATIVE', 'October');
case 11: return i18n::translate_c('LOCATIVE', 'November');
case 12: return i18n::translate_c('LOCATIVE', 'December');
default: return '';
}
}
static function NUM_TO_MONTH_INSTRUMENTAL($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('INSTRUMENTAL', 'January');
case 2: return i18n::translate_c('INSTRUMENTAL', 'February');
case 3: return i18n::translate_c('INSTRUMENTAL', 'March');
case 4: return i18n::translate_c('INSTRUMENTAL', 'April');
case 5: return i18n::translate_c('INSTRUMENTAL', 'May');
case 6: return i18n::translate_c('INSTRUMENTAL', 'June');
case 7: return i18n::translate_c('INSTRUMENTAL', 'July');
case 8: return i18n::translate_c('INSTRUMENTAL', 'August');
case 9: return i18n::translate_c('INSTRUMENTAL', 'September');
case 10: return i18n::translate_c('INSTRUMENTAL', 'October');
case 11: return i18n::translate_c('INSTRUMENTAL', 'November');
case 12: return i18n::translate_c('INSTRUMENTAL', 'December');
default: return '';
}
}
static function NUM_TO_SHORT_MONTH($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('Abbreviation for January', 'Jan');
case 2: return i18n::translate_c('Abbreviation for February', 'Feb');
case 3: return i18n::translate_c('Abbreviation for March', 'Mar');
case 4: return i18n::translate_c('Abbreviation for April', 'Apr');
case 5: return i18n::translate_c('Abbreviation for May', 'May');
case 6: return i18n::translate_c('Abbreviation for June', 'Jun');
case 7: return i18n::translate_c('Abbreviation for July', 'Jul');
case 8: return i18n::translate_c('Abbreviation for August', 'Aug');
case 9: return i18n::translate_c('Abbreviation for September', 'Sep');
case 10: return i18n::translate_c('Abbreviation for October', 'Oct');
case 11: return i18n::translate_c('Abbreviation for November', 'Nov');
case 12: return i18n::translate_c('Abbreviation for December', 'Dec');
default: return '';
}
}
static function NUM_TO_GEDCOM_MONTH($n, $leap_year) {
switch ($n) {
case 1: return 'JAN';
case 2: return 'FEB';
case 3: return 'MAR';
case 4: return 'APR';
case 5: return 'MAY';
case 6: return 'JUN';
case 7: return 'JUL';
case 8: return 'AUG';
case 9: return 'SEP';
case 10: return 'OCT';
case 11: return 'NOV';
case 12: return 'DEC';
default: return '';
}
}
static function CAL_START_JD() {
return 0; // @#DJULIAN@ 01 JAN 4713B.C.
}
static function CAL_END_JD() {
return 99999999;
}
static function NUM_DAYS_OF_WEEK() {
return 7;
}
static function LONG_DAYS_OF_WEEK($n) {
switch ($n) {
case 0: return i18n::translate('Monday');
case 1: return i18n::translate('Tuesday');
case 2: return i18n::translate('Wednesday');
case 3: return i18n::translate('Thursday');
case 4: return i18n::translate('Friday');
case 5: return i18n::translate('Saturday');
case 6: return i18n::translate('Sunday');
}
}
static function SHORT_DAYS_OF_WEEK($n) {
switch ($n) {
case 0: return i18n::translate('Mon');
case 1: return i18n::translate('Tue');
case 2: return i18n::translate('Wed');
case 3: return i18n::translate('Thu');
case 4: return i18n::translate('Fri');
case 5: return i18n::translate('Sat');
case 6: return i18n::translate('Sun');
}
}
static function YMDtoJD($y, $m, $d) {
return 0;
}
static function JDtoYMD($j) {
return array(0, 0, 0);
}
// Most years are 1 more than the previous, but not always (e.g. 1BC->1AD)
static function NextYear($y) {
return $y+1;
}
// Calendars that use suffixes, etc. (e.g. 'B.C.') or OS/NS notation should redefine this.
function ExtractYear($year) {
return (int)$year;
}
// Leap years may have extra days, extra months, etc.
function IsLeapYear() {
return false;
}
// Compare two dates - helper function for sorting by date
static function Compare($d1, $d2) {
if ($d1->maxJD < $d2->minJD)
return -1;
if ($d2->minJD > $d1->maxJD)
return 1;
return 0;
}
// How long between an event and a given julian day
// Return result as either a number of years or
// a gedcom-style age string.
// bool $full: true=gedcom style, false=just years
// int $jd: date for calculation
// TODO: JewishDate needs to redefine this to cope with leap months
function GetAge($full, $jd, $warn_on_negative=true) {
if ($this->y==0 || $jd==0) {
return '';
}
if ($this->minJD < $jd && $this->maxJD > $jd) {
return '';
}
if ($this->minJD==$jd) {
return $full?'':'0';
}
if ($warn_on_negative && $jd<$this->minJD) {
return '
';
}
list($y,$m,$d)=$this->JDtoYMD($jd);
$dy=$y-$this->y;
$dm=$m-max($this->m,1);
$dd=$d-max($this->d,1);
if ($dd<0) {
$dd+=$this->DaysInMonth();
$dm--;
}
if ($dm<0) {
$dm+=$this->NUM_MONTHS();
$dy--;
}
// Not a full age? Then just the years
if (!$full)
return $dy;
// Age in years?
if ($dy>1)
return $dy.'y';
$dm+=$dy*$this->NUM_MONTHS();
// Age in months?
if ($dm>1)
return $dm.'m';
// Age in days?
return ($jd-$this->minJD)."d";
}
// Convert a date from one calendar to another.
function convert_to_cal($calendar) {
switch ($calendar) {
case 'gregorian':
return new GregorianDate($this);
case 'julian':
return new JulianDate($this);
case 'jewish':
if (WT_LOCALE!='he')
return new JewishDate($this);
// no break
case 'hebrew':
return new HebrewDate($this);
case 'french':
return new FrenchRDate($this);
case 'arabic':
if (WT_LOCALE!='ar')
return new ArabicDate($this);
// no break
case 'hijri':
return new HijriDate($this);
default:
return $this;
}
}
// Is this date within the valid range of the calendar
function InValidRange() {
return $this->minJD>=$this->CAL_START_JD() && $this->maxJD<=$this->CAL_END_JD();
}
// How many days in the current month
function DaysInMonth() {
list($ny,$nm)=$this->NextMonth();
return $this->YMDtoJD($ny, $nm, 1) - $this->YMDtoJD($this->y, $this->m, 1);
}
// How many days in the current week
function DaysInWeek() {
return $this->NUM_DAYS_OF_WEEK();
}
// Format a date
// $format - format string: the codes are specified in http://php.net/date
function Format($format, $qualifier='') {
// Don't show exact details for inexact dates
if (!$this->d) {
// The comma is for US "M D, Y" dates
$format=preg_replace('/%[djlDNSwz][,]?/', '', $format);
}
if (!$this->m) {
$format=str_replace(array('%F', '%m', '%M', '%n', '%t'), '', $format);
}
if (!$this->y) {
$format=str_replace(array('%t', '%L', '%G', '%y', '%Y'), '', $format);
}
// If we've trimmed the format, also trim the punctuation
if (!$this->d || !$this->m || !$this->y) {
$format=trim($format, ',. ;/-');
}
if ($this->d && preg_match('/%[djlDNSwz]/', $format)) {
// If we have a day-number *and* we are being asked to display it, then genitive
$case='GENITIVE';
} else {
switch ($qualifier) {
case '':
case 'int':
case 'est':
case 'cal': $case='NOMINATIVE'; break;
case 'to':
case 'abt':
case 'from': $case='GENITIVE'; break;
case 'aft': $case='LOCATIVE'; break;
case 'bef':
case 'bet':
case 'and': $case='INSTRUMENTAL'; break;
}
}
// Build up the formated date, character at a time
preg_match_all('/%[^%]/', $format, $matches);
foreach ($matches[0] as $match) {
switch ($match) {
case '%d': $format=str_replace($match, $this->FormatDayZeros(), $format); break;
case '%j': $format=str_replace($match, $this->FormatDay(), $format); break;
case '%l': $format=str_replace($match, $this->FormatLongWeekday(), $format); break;
case '%D': $format=str_replace($match, $this->FormatShortWeekday(), $format); break;
case '%N': $format=str_replace($match, $this->FormatISOWeekday(), $format); break;
case '%S': $format=str_replace($match, $this->FormatOrdinalSuffix(), $format); break;
case '%w': $format=str_replace($match, $this->FormatNumericWeekday(), $format); break;
case '%z': $format=str_replace($match, $this->FormatDayOfYear(), $format); break;
case '%F': $format=str_replace($match, $this->FormatLongMonth($case), $format); break;
case '%m': $format=str_replace($match, $this->FormatMonthZeros(), $format); break;
case '%M': $format=str_replace($match, $this->FormatShortMonth(), $format); break;
case '%n': $format=str_replace($match, $this->FormatMonth(), $format); break;
case '%t': $format=str_replace($match, $this->DaysInMonth(), $format); break;
case '%L': $format=str_replace($match, (int)$this->IsLeapYear(), $format); break;
case '%Y': $format=str_replace($match, $this->FormatLongYear(), $format); break;
case '%y': $format=str_replace($match, $this->FormatShortYear(), $format); break;
// These 4 extensions are useful for re-formatting gedcom dates.
case '%@': $format=str_replace($match, $this->CALENDAR_ESCAPE(), $format); break;
case '%A': $format=str_replace($match, $this->FormatGedcomDay(), $format); break;
case '%O': $format=str_replace($match, $this->FormatGedcomMonth(), $format); break;
case '%E': $format=str_replace($match, $this->FormatGedcomYear(), $format); break;
}
}
return $format;
}
// Functions to extract bits of the date in various formats. Individual calendars
// will want to redefine some of these.
function FormatDayZeros() {
if ($this->d<10)
return '0'.$this->d;
else
return $this->d;
}
function FormatDay() {
return $this->d;
}
function FormatLongWeekday() {
return $this->LONG_DAYS_OF_WEEK($this->minJD % $this->NUM_DAYS_OF_WEEK());
}
function FormatShortWeekday() {
return $this->SHORT_DAYS_OF_WEEK($this->minJD % $this->NUM_DAYS_OF_WEEK());
}
function FormatISOWeekday() {
return $this->minJD % 7 + 1;
}
function FormatOrdinalSuffix() {
$func="ordinal_suffix_".WT_LOCALE;
if (function_exists($func))
return $func($this->d);
else
return '';
}
function FormatNumericWeekday() {
return ($this->minJD + 1) % $this->NUM_DAYS_OF_WEEK();
}
function FormatDayOfYear() {
return $this->minJD - $this->YMDtoJD($this->y, 1, 1);
}
function FormatMonth() {
return $this->m;
}
function FormatMonthZeros() {
if ($this->m > 9)
return $this->m;
else
return '0'.$this->m;
}
function FormatLongMonth($case='NOMINATIVE') {
switch ($case) {
case 'GENITIVE': return $this->NUM_TO_MONTH_GENITIVE ($this->m, $this->IsLeapYear());
case 'NOMINATIVE': return $this->NUM_TO_MONTH_NOMINATIVE ($this->m, $this->IsLeapYear());
case 'LOCATIVE': return $this->NUM_TO_MONTH_LOCATIVE ($this->m, $this->IsLeapYear());
case 'INSTRUMENTAL': return $this->NUM_TO_MONTH_INSTRUMENTAL($this->m, $this->IsLeapYear());
}
}
function FormatShortMonth() {
return $this->NUM_TO_SHORT_MONTH($this->m, $this->IsLeapYear());
}
// NOTE Short year is NOT a 2-digit year. It is for calendars such as hebrew
// which have a 3-digit form of 4-digit years.
function FormatShortYear() {
return $this->y;
}
function FormatGedcomDay() {
if ($this->d==0)
return '';
else
return sprintf('%02d', $this->d);
}
function FormatGedcomMonth() {
return $this->NUM_TO_GEDCOM_MONTH($this->m, $this->IsLeapYear());
}
function FormatGedcomYear() {
if ($this->y==0)
return '';
else
return sprintf('%04d', $this->y);
}
function FormatLongYear() {
return $this->y;
}
// Calendars with leap-months should redefine this.
function NextMonth() {
return array(
$this->m==$this->NUM_MONTHS() ? $this->NextYear($this->y) : $this->y,
($this->m%$this->NUM_MONTHS())+1
);
}
// Convert a decimal number to roman numerals
static function NumToRoman($num) {
static $lookup=array(1000=>'M', '900'=>'CM', '500'=>'D', 400=>'CD', 100=>'C', 90=>'XC', 50=>'L', 40=>'XL', 10=>'X', 9=>'IX', 5=>'V', 4=>'IV', 1=>'I');
if ($num<1) return $num;
$roman='';
foreach ($lookup as $key=>$value)
while ($num>=$key) {
$roman.=$value;
$num-=$key;
}
return $roman;
}
// Convert a roman numeral to decimal
static function RomanToNum($roman) {
static $lookup=array(1000=>'M', '900'=>'CM', '500'=>'D', 400=>'CD', 100=>'C', 90=>'XC', 50=>'L', 40=>'XL', 10=>'X', 9=>'IX', 5=>'V', 4=>'IV', 1=>'I');
$num=0;
foreach ($lookup as $key=>$value)
if (strpos($roman, $value)===0) {
$num+=$key;
$roman=substr($roman, strlen($value));
}
return $num;
}
// Get today's date in the current calendar
function TodayYMD() {
return $this->JDtoYMD(GregorianDate::YMDtoJD(date('Y'), date('n'), date('j')));
}
function Today() {
$tmp=clone $this;
$ymd=$tmp->TodayYMD();
$tmp->y=$ymd[0];
$tmp->m=$ymd[1];
$tmp->d=$ymd[2];
$tmp->SetJDfromYMD();
return $tmp;
}
// Create a URL that links this date to the WT calendar
function CalendarURL($date_fmt="") {
global $DATE_FORMAT;
if (empty($date_fmt)) {
$date_fmt=$DATE_FORMAT;
}
$URL='calendar.php?cal='.$this->CALENDAR_ESCAPE();
$action="year";
if (strpos($date_fmt, "Y")!==false
|| strpos($date_fmt, "y")!==false) {
$URL.='&year='.$this->FormatGedcomYear();
}
if (strpos($date_fmt, "F")!==false
|| strpos($date_fmt, "M")!==false
|| strpos($date_fmt, "m")!==false
|| strpos($date_fmt, "n")!==false) {
$URL.='&month='.$this->FormatGedcomMonth();
if ($this->m>0)
$action="calendar";
}
if (strpos($date_fmt, "d")!==false
|| strpos($date_fmt, "D")!==false
|| strpos($date_fmt, "j")!==false) {
$URL.='&day='.$this->FormatGedcomDay();
if ($this->d>0)
$action="today";
}
return $URL.'&action='.$action;
}
} // class CalendarDate
////////////////////////////////////////////////////////////////////////////////
// Definitions for the Gregorian calendar
////////////////////////////////////////////////////////////////////////////////
class GregorianDate extends CalendarDate {
static function CALENDAR_ESCAPE() {
return '@#DGREGORIAN@';
}
static function CAL_START_JD() {
return 2299161; // 15 OCT 1582
}
function IsLeapYear() {
return $this->y%4==0 && $this->y%100!=0 || $this->y%400==0;
}
static function YMDtoJD($y, $m, $d) {
if ($y<0) // 0=1BC, -1=2BC, etc.
++$y;
$a=floor((14-$m)/12);
$y=$y+4800-$a;
$m=$m+12*$a-3;
return $d+floor((153*$m+2)/5)+365*$y+floor($y/4)-floor($y/100)+floor($y/400)-32045;
}
static function JDtoYMD($j) {
$a=$j+32044;
$b=floor((4*$a+3)/146097);
$c=$a-floor($b*146097/4);
$d=floor((4*$c+3)/1461);
$e=$c-floor((1461*$d)/4);
$m=floor((5*$e+2)/153);
$day=$e-floor((153*$m+2)/5)+1;
$month=$m+3-12*floor($m/10);
$year=$b*100+$d-4800+floor($m/10);
if ($year<1) // 0=1BC, -1=2BC, etc.
--$year;
return array($year, $month, $day);
}
} // class GregorianDate
////////////////////////////////////////////////////////////////////////////////
// Definitions for the Julian Proleptic calendar
// (Proleptic means we extend it backwards, prior to its introduction in 46BC)
////////////////////////////////////////////////////////////////////////////////
class JulianDate extends CalendarDate {
var $new_old_style=false;
static function CALENDAR_ESCAPE() {
return '@#DJULIAN@';
}
static function NextYear($y) {
if ($y==-1)
return 1;
else
return $y+1;
}
function IsLeapYear() {
return $this->y%4==0;
}
static function YMDtoJD($y, $m, $d) {
if ($y<0) // 0=1BC, -1=2BC, etc.
++$y;
$a=floor((14-$m)/12);
$y=$y+4800-$a;
$m=$m+12*$a-3;
return $d+floor((153*$m+2)/5)+365*$y+floor($y/4)-32083;
}
static function JDtoYMD($j) {
$c=$j+32082;
$d=floor((4*$c+3)/1461);
$e=$c-floor(1461*$d/4);
$m=floor((5*$e+2)/153);
$day=$e-floor((153*$m+2)/5)+1;
$month=$m+3-12*floor($m/10);
$year=$d-4800+floor($m/10);
if ($year<1) // 0=1BC, -1=2BC, etc.
--$year;
return array($year, $month, $day);
}
// Process new-style/old-style years and years BC
function ExtractYear($year) {
if (preg_match('/^(\d\d\d\d) \/ \d{1,4}$/', $year, $match)) { // Assume the first year is correct
$this->new_old_style=true;
return $match[1]+1;
} else
if (preg_match('/^(\d+) b ?c$/', $year, $match))
return -$match[1];
else
return (int)$year;
}
function FormatLongYear() {
if ($this->y<0) {
// I18N: Number of years "before christ"
return i18n::translate('%d B.C.', -$this->y);
} else {
if ($this->new_old_style) {
return sprintf('%d/%02d', $this->y-1, $this->y % 100);
} else
return $this->y;
}
}
function FormatGedcomYear() {
if ($this->y<0)
return sprintf('%04dB.C.', -$this->y);
else
if ($this->new_old_style) {
return sprintf('%04d/%02d', $this->y-1, $this->y % 100);
} else
return sprintf('%04d', $this->y);
}
} // class JulianDate
////////////////////////////////////////////////////////////////////////////////
// Definitions for the Jewish calendar
////////////////////////////////////////////////////////////////////////////////
class JewishDate extends CalendarDate {
static function CALENDAR_ESCAPE() {
return '@#DHEBREW@';
}
static function MONTH_TO_NUM($m) {
static $months=array(''=>0, 'tsh'=>1, 'csh'=>2, 'ksl'=>3, 'tvt'=>4, 'shv'=>5, 'adr'=>6, 'ads'=>7, 'nsn'=>8, 'iyr'=>9, 'svn'=>10, 'tmz'=>11, 'aav'=>12, 'ell'=>13);
if (isset($months[$m])) {
return $months[$m];
} else {
return null;
}
}
static function NUM_TO_MONTH_NOMINATIVE($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('NOMINATIVE', 'Tishrei');
case 2: return i18n::translate_c('NOMINATIVE', 'Heshvan');
case 3: return i18n::translate_c('NOMINATIVE', 'Kislev');
case 4: return i18n::translate_c('NOMINATIVE', 'Tevet');
case 5: return i18n::translate_c('NOMINATIVE', 'Shevat');
case 6: if ($leap_year) return i18n::translate_c('NOMINATIVE', 'Adar'); else return i18n::translate_c('NOMINATIVE', 'Adar I');
case 7: return i18n::translate_c('NOMINATIVE', 'Adar II');
case 8: return i18n::translate_c('NOMINATIVE', 'Nissan');
case 9: return i18n::translate_c('NOMINATIVE', 'Iyar');
case 10: return i18n::translate_c('NOMINATIVE', 'Sivan');
case 11: return i18n::translate_c('NOMINATIVE', 'Tamuz');
case 12: return i18n::translate_c('NOMINATIVE', 'Av');
case 13: return i18n::translate_c('NOMINATIVE', 'Elul');
default: return '';
}
}
static function NUM_TO_MONTH_GENITIVE($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('GENITIVE', 'Tishrei');
case 2: return i18n::translate_c('GENITIVE', 'Heshvan');
case 3: return i18n::translate_c('GENITIVE', 'Kislev');
case 4: return i18n::translate_c('GENITIVE', 'Tevet');
case 5: return i18n::translate_c('GENITIVE', 'Shevat');
case 6: if ($leap_year) return i18n::translate_c('GENITIVE', 'Adar'); else return i18n::translate_c('GENITIVE', 'Adar I');
case 7: return i18n::translate_c('GENITIVE', 'Adar II');
case 8: return i18n::translate_c('GENITIVE', 'Nissan');
case 9: return i18n::translate_c('GENITIVE', 'Iyar');
case 10: return i18n::translate_c('GENITIVE', 'Sivan');
case 11: return i18n::translate_c('GENITIVE', 'Tamuz');
case 12: return i18n::translate_c('GENITIVE', 'Av');
case 13: return i18n::translate_c('GENITIVE', 'Elul');
default: return '';
}
}
static function NUM_TO_MONTH_LOCATIVE($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('LOCATIVE', 'Tishrei');
case 2: return i18n::translate_c('LOCATIVE', 'Heshvan');
case 3: return i18n::translate_c('LOCATIVE', 'Kislev');
case 4: return i18n::translate_c('LOCATIVE', 'Tevet');
case 5: return i18n::translate_c('LOCATIVE', 'Shevat');
case 6: if ($leap_year) return i18n::translate_c('LOCATIVE', 'Adar'); else return i18n::translate_c('LOCATIVE', 'Adar I');
case 7: return i18n::translate_c('LOCATIVE', 'Adar II');
case 8: return i18n::translate_c('LOCATIVE', 'Nissan');
case 9: return i18n::translate_c('LOCATIVE', 'Iyar');
case 10: return i18n::translate_c('LOCATIVE', 'Sivan');
case 11: return i18n::translate_c('LOCATIVE', 'Tamuz');
case 12: return i18n::translate_c('LOCATIVE', 'Av');
case 13: return i18n::translate_c('LOCATIVE', 'Elul');
default: return '';
}
}
static function NUM_TO_MONTH_INSTRUMENTAL($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('INSTRUMENTAL', 'Tishrei');
case 2: return i18n::translate_c('INSTRUMENTAL', 'Heshvan');
case 3: return i18n::translate_c('INSTRUMENTAL', 'Kislev');
case 4: return i18n::translate_c('INSTRUMENTAL', 'Tevet');
case 5: return i18n::translate_c('INSTRUMENTAL', 'Shevat');
case 6: if ($leap_year) return i18n::translate_c('INSTRUMENTAL', 'Adar'); else return i18n::translate_c('INSTRUMENTAL', 'Adar I');
case 7: return i18n::translate_c('INSTRUMENTAL', 'Adar II');
case 8: return i18n::translate_c('INSTRUMENTAL', 'Nissan');
case 9: return i18n::translate_c('INSTRUMENTAL', 'Iyar');
case 10: return i18n::translate_c('INSTRUMENTAL', 'Sivan');
case 11: return i18n::translate_c('INSTRUMENTAL', 'Tamuz');
case 12: return i18n::translate_c('INSTRUMENTAL', 'Av');
case 13: return i18n::translate_c('INSTRUMENTAL', 'Elul');
default: return '';
}
}
static function NUM_TO_SHORT_MONTH($n, $leap_year) {
// TODO: Do these have short names?
switch ($n) {
case 1: return i18n::translate('Tishrei');
case 2: return i18n::translate('Heshvan');
case 3: return i18n::translate('Kislev');
case 4: return i18n::translate('Tevet');
case 5: return i18n::translate('Shevat');
case 6: if ($leap_year) return i18n::translate('Adar'); else return i18n::translate('Adar I');
case 7: return i18n::translate('Adar II');
case 8: return i18n::translate('Nissan');
case 9: return i18n::translate('Iyar');
case 10: return i18n::translate('Sivan');
case 11: return i18n::translate('Tamuz');
case 12: return i18n::translate('Av');
case 13: return i18n::translate('Elul');
default: return '';
}
}
static function NUM_TO_GEDCOM_MONTH($n, $leap_year) {
// TODO: Do these have short names in English?
switch ($n) {
case 1: return 'TSH';
case 2: return 'CSH';
case 3: return 'KSL';
case 4: return 'TVT';
case 5: return 'SHV';
case 6: return 'ADR';
case 7: return 'ADS';
case 8: return 'NSN';
case 9: return 'IYR';
case 10: return 'SVN';
case 11: return 'TMZ';
case 12: return 'AAV';
case 13: return 'ELL';
default: return '';
}
}
static function NUM_MONTHS() {
return 13;
}
static function CAL_START_JD() {
return 347998; // 01 TSH 0001 = @#JULIAN@ 7 OCT 3761B.C.
}
function NextMonth() {
if ($this->m==6 && !$this->IsLeapYear())
return array($this->y, 8);
else
return array($this->y+($this->m==13?1:0), ($this->m%13)+1);
}
function IsLeapYear() {
return ((7*$this->y+1)%19)<7;
}
// TODO implement this function locally
static function YMDtoJD($y, $mh, $d) {
if (function_exists('JewishToJD'))
return JewishToJD($mh, $d, $y);
else
return 0;
}
// TODO implement this function locally
static function JDtoYMD($j) {
if (function_exists('JdToJewish'))
list($m, $d, $y)=explode('/', JDToJewish($j));
else
list($m, $d, $y)=array(0, 0, 0);
return array($y, $m, $d);
}
} // class JewishDate
////////////////////////////////////////////////////////////////////////////////
// Definitions for the Hebrew calendar.
// NOTE - this is the same as the Jewish Calendar, but displays dates in hebrew
// rather than the local language.
////////////////////////////////////////////////////////////////////////////////
class HebrewDate extends JewishDate {
const GERSHAYIM="״";
const GERSH="׳";
const ALAFIM="אלפים";
function FormatDayZeros() {
return $this->NumToHebrew($this->d);
}
function FormatDay() {
return $this->NumToHebrew($this->d);
}
static function LONG_DAYS_OF_WEEK($n) {
// Do not translate these - they are supposed to be hebrew, whatever language is shown.
switch ($n) {
case 0: return 'שני';
case 1: return 'שלישי';
case 2: return 'רביעי';
case 3: return 'חמישי';
case 4: return 'ששי';
case 5: return 'שבת';
case 6: return 'ראשון';
}
}
static function SHORT_DAYS_OF_WEEK($n) {
// TODO: Do these have short names?
return LONG_DAYS_OF_WEEK($n);
}
static function NUM_TO_MONTH_NOMINATIVE($n, $leap_year) {
// Do not translate these - they are supposed to be hebrew, whatever language is shown.
switch ($n) {
case 1: return 'תשרי';
case 2: return 'חשוון';
case 3: return 'כסלו';
case 4: return 'טבת';
case 5: return 'שבט';
case 6: if ($leap_year) return 'אדר א׳'; else return 'אדר';
case 7: return 'אדר ב׳';
case 8: return 'ניסן';
case 9: return 'איי';
case 10: return 'סיוון';
case 11: return 'תמוז';
case 12: return 'אב';
case 13: return 'אלול';
default: return '';
}
}
static function NUM_TO_MONTH_GENITIVE($n, $leap_year) {
// Hebrew does not have genitive forms
return self::NUM_TO_MONTH_NOMINATIVE($n, $leap_year);
}
static function NUM_TO_MONTH_LOCATIVE($n, $leap_year) {
// Hebrew does not have locative forms
return self::NUM_TO_MONTH_NOMINATIVE($n, $leap_year);
}
static function NUM_TO_MONTH_INSTRUMENTAL($n, $leap_year) {
// Hebrew does not have instrumental forms
return self::NUM_TO_MONTH_NOMINATIVE($n, $leap_year);
}
static function NUM_TO_SHORT_MONTH($n, $leap_year) {
// TODO: Do these have short names?
return self::NUM_TO_MONTH_NOMINATIVE($n, $leap_year);
}
function FormatShortYear() {
return $this->NumToHebrew($this->y%1000);
}
function FormatLongYear() {
return $this->NumToHebrew($this->y);
}
// Convert a decimal number to hebrew - like roman numerals, but with extra punctuation
// and special rules.
static function NumToHebrew($num) {
global $DISPLAY_JEWISH_THOUSANDS;
static $jHundreds = array("", "ק", "ר", "ש", "ת", "תק", "תר","תש", "תת", "תתק");
static $jTens = array("", "י", "כ", "ל", "מ", "נ", "ס", "ע", "פ", "צ");
static $jTenEnds = array("", "י", "ך", "ל", "ם", "ן", "ס", "ע", "ף", "ץ");
static $tavTaz = array("ט״ו", "ט״ז");
static $jOnes = array("", "א", "ב", "ג", "ד", "ה", "ו", "ז", "ח", "ט");
$shortYear = $num %1000; //discard thousands
//next check for all possible single Hebrew digit years
$singleDigitYear=($shortYear < 11 || ($shortYear <100 && $shortYear % 10 == 0) || ($shortYear <= 400 && $shortYear % 100 ==0));
$thousands = $num / 1000; //get # thousands
$sb = "";
//append thousands to String
if($num % 1000 == 0) { // in year is 5000, 4000 etc
$sb .= $jOnes[$thousands];
$sb .= self::GERSH;
$sb .= " ";
$sb .= self::ALAFIM; //add # of thousands plus word thousand (overide alafim boolean)
} else if($DISPLAY_JEWISH_THOUSANDS) { // if alafim boolean display thousands
$sb .= $jOnes[$thousands];
$sb .= self::GERSH; //append thousands quote
$sb .= " ";
}
$num = $num % 1000; //remove 1000s
$hundreds = $num / 100; // # of hundreds
$sb .= $jHundreds[$hundreds]; //add hundreds to String
$num = $num % 100; //remove 100s
if($num == 15) { //special case 15
$sb .= $tavTaz[0];
} else if($num == 16) { //special case 16
$sb .= $tavTaz[1];
} else {
$tens = $num / 10;
if($num % 10 == 0) { // if evenly divisable by 10
if($singleDigitYear == false) {
$sb .= $jTenEnds[$tens]; // use end letters so that for example 5750 will end with an end nun
} else {
$sb .= $jTens[$tens]; // use standard letters so that for example 5050 will end with a regular nun
}
} else {
$sb .= $jTens[$tens];
$num = $num % 10;
$sb .= $jOnes[$num];
}
}
if ($singleDigitYear == true) {
$sb .= self::GERSH; //append single quote
} else { // append double quote before last digit
$pos1 = strlen($sb)-2;
$sb = substr($sb, 0, $pos1) . self::GERSHAYIM . substr($sb, $pos1);
$sb = str_replace(self::GERSHAYIM . self::GERSHAYIM, self::GERSHAYIM, $sb); //replace double gershayim with single instance
}
return $sb;
}
} // class HebrewDate
////////////////////////////////////////////////////////////////////////////////
// Definitions for the French Republican calendar
////////////////////////////////////////////////////////////////////////////////
class FrenchRDate extends CalendarDate {
static function CALENDAR_ESCAPE() {
return '@#DFRENCH R@';
}
static function MONTH_TO_NUM($m) {
static $months=array(''=>0, 'vend'=>1, 'brum'=>2, 'frim'=>3, 'nivo'=>4, 'pluv'=>5, 'vent'=>6, 'germ'=>7, 'flor'=>8, 'prai'=>9, 'mess'=>10, 'ther'=>11, 'fruc'=>12, 'comp'=>13);
if (isset($months[$m])) {
return $months[$m];
} else {
return null;
}
}
static function NUM_TO_MONTH_NOMINATIVE($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('NOMINATIVE', 'Vendémiaire');
case 2: return i18n::translate_c('NOMINATIVE', 'Brumaire');
case 3: return i18n::translate_c('NOMINATIVE', 'Frimaire');
case 4: return i18n::translate_c('NOMINATIVE', 'Nivôse');
case 5: return i18n::translate_c('NOMINATIVE', 'Pluviôse');
case 6: return i18n::translate_c('NOMINATIVE', 'Ventôse');
case 7: return i18n::translate_c('NOMINATIVE', 'Germinal');
case 8: return i18n::translate_c('NOMINATIVE', 'Floréal');
case 9: return i18n::translate_c('NOMINATIVE', 'Prairial');
case 10: return i18n::translate_c('NOMINATIVE', 'Messidor');
case 11: return i18n::translate_c('NOMINATIVE', 'Thermidor');
case 12: return i18n::translate_c('NOMINATIVE', 'Fructidor');
case 13: return i18n::translate_c('NOMINATIVE', 'jours complémentaires');
}
}
static function NUM_TO_MONTH_GENITIVE($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('GENITIVE', 'Vendémiaire');
case 2: return i18n::translate_c('GENITIVE', 'Brumaire');
case 3: return i18n::translate_c('GENITIVE', 'Frimaire');
case 4: return i18n::translate_c('GENITIVE', 'Nivôse');
case 5: return i18n::translate_c('GENITIVE', 'Pluviôse');
case 6: return i18n::translate_c('GENITIVE', 'Ventôse');
case 7: return i18n::translate_c('GENITIVE', 'Germinal');
case 8: return i18n::translate_c('GENITIVE', 'Floréal');
case 9: return i18n::translate_c('GENITIVE', 'Prairial');
case 10: return i18n::translate_c('GENITIVE', 'Messidor');
case 11: return i18n::translate_c('GENITIVE', 'Thermidor');
case 12: return i18n::translate_c('GENITIVE', 'Fructidor');
case 13: return i18n::translate_c('GENITIVE', 'jours complémentaires');
}
}
static function NUM_TO_MONTH_LOCATIVE($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('LOCATIVE', 'Vendémiaire');
case 2: return i18n::translate_c('LOCATIVE', 'Brumaire');
case 3: return i18n::translate_c('LOCATIVE', 'Frimaire');
case 4: return i18n::translate_c('LOCATIVE', 'Nivôse');
case 5: return i18n::translate_c('LOCATIVE', 'Pluviôse');
case 6: return i18n::translate_c('LOCATIVE', 'Ventôse');
case 7: return i18n::translate_c('LOCATIVE', 'Germinal');
case 8: return i18n::translate_c('LOCATIVE', 'Floréal');
case 9: return i18n::translate_c('LOCATIVE', 'Prairial');
case 10: return i18n::translate_c('LOCATIVE', 'Messidor');
case 11: return i18n::translate_c('LOCATIVE', 'Thermidor');
case 12: return i18n::translate_c('LOCATIVE', 'Fructidor');
case 13: return i18n::translate_c('LOCATIVE', 'jours complémentaires');
}
}
static function NUM_TO_MONTH_INSTRUMENTAL($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('INSTRUMENTAL', 'Vendémiaire');
case 2: return i18n::translate_c('INSTRUMENTAL', 'Brumaire');
case 3: return i18n::translate_c('INSTRUMENTAL', 'Frimaire');
case 4: return i18n::translate_c('INSTRUMENTAL', 'Nivôse');
case 5: return i18n::translate_c('INSTRUMENTAL', 'Pluviôse');
case 6: return i18n::translate_c('INSTRUMENTAL', 'Ventôse');
case 7: return i18n::translate_c('INSTRUMENTAL', 'Germinal');
case 8: return i18n::translate_c('INSTRUMENTAL', 'Floréal');
case 9: return i18n::translate_c('INSTRUMENTAL', 'Prairial');
case 10: return i18n::translate_c('INSTRUMENTAL', 'Messidor');
case 11: return i18n::translate_c('INSTRUMENTAL', 'Thermidor');
case 12: return i18n::translate_c('INSTRUMENTAL', 'Fructidor');
case 13: return i18n::translate_c('INSTRUMENTAL', 'jours complémentaires');
}
}
static function NUM_TO_SHORT_MONTH($n, $leap_year) {
// TODO: Do these have short names?
return self::NUM_TO_MONTH_NOMINATIVE($n, $leap_year);
}
static function NUM_TO_GEDCOM_MONTH($n, $leap_year) {
switch ($n) {
case 1: return 'VEND';
case 2: return 'BRUM';
case 3: return 'FRIM';
case 4: return 'NIVO';
case 5: return 'PLUV';
case 6: return 'VENT';
case 7: return 'GERM';
case 8: return 'FLOR';
case 9: return 'PRAI';
case 10: return 'MESS';
case 11: return 'THER';
case 12: return 'FRUC';
case 13: return 'COMP';
}
}
static function NUM_MONTHS() {
return 13;
}
static function LONG_DAYS_OF_WEEK($n) {
switch ($n) {
case 0: return i18n::translate('Primidi');
case 1: return i18n::translate('Duodi');
case 2: return i18n::translate('Tridi');
case 3: return i18n::translate('Quartidi');
case 4: return i18n::translate('Quintidi');
case 5: return i18n::translate('Sextidi');
case 6: return i18n::translate('Septidi');
case 7: return i18n::translate('Octidi');
case 8: return i18n::translate('Nonidi');
case 9: return i18n::translate('Decidi');
}
}
static function SHORT_DAYS_OF_WEEK($n) {
// TODO: Do these have short names?
return self::LONG_DAYS_OF_WEEK($n);
}
static function NUM_DAYS_OF_WEEK() {
return 10; // A "metric" week of 10 unimaginatively named days.
}
static function CAL_START_JD() {
return 2375840; // 22 SEP 1792 = 01 VEND 0001
}
static function CAL_END_JD() {
return 2380687; // 31 DEC 1805 = 10 NIVO 0014
}
// Leap years were based on astronomical observations. Only years 3, 7 and 11
// were ever observed. Moves to a gregorian-like (fixed) system were proposed
// but never implemented. These functions are valid over the range years 1-14.
function IsLeapYear() {
return $this->y%4==3;
}
static function YMDtoJD($y, $m, $d) {
return 2375444+$d+$m*30+$y*365+floor($y/4);
}
static function JDtoYMD($j) {
$y=floor(($j-2375109)*4/1461)-1;
$m=floor(($j-2375475-$y*365-floor($y/4))/30)+1;
$d=$j-2375444-$m*30-$y*365-floor($y/4);
return array($y, $m, $d);
}
// Years were written using roman numerals
function FormatLongYear() {
return $this->NumToRoman($this->y);
}
} // class FrenchRDate
////////////////////////////////////////////////////////////////////////////////
// Definitions for the Hijri calendar. Note that these are "theoretical" dates.
// "True" dates are based on local lunar observations, and can be a +/- one day.
////////////////////////////////////////////////////////////////////////////////
class HijriDate extends CalendarDate {
static function CALENDAR_ESCAPE() {
return '@#DHIJRI@';
}
static function MONTH_TO_NUM($m) {
static $months=array(''=>0, 'muhar'=>1, 'safar'=>2, 'rabia'=>3, 'rabit'=>4, 'jumaa'=>5, 'jumat'=>6, 'rajab'=>7, 'shaab'=>8, 'ramad'=>9, 'shaww'=>10, 'dhuaq'=>11, 'dhuah'=>12);
if (isset($months[$m])) {
return $months[$m];
} else {
return null;
}
}
static function NUM_TO_MONTH_NOMINATIVE($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('NOMINATIVE', 'Muharram');
case 2: return i18n::translate_c('NOMINATIVE', 'Safar');
case 3: return i18n::translate_c('NOMINATIVE', 'Rabi\' al-awwal');
case 4: return i18n::translate_c('NOMINATIVE', 'Rabi\' al-thani');
case 5: return i18n::translate_c('NOMINATIVE', 'Jumada al-awwal');
case 6: return i18n::translate_c('NOMINATIVE', 'Jumada al-thani');
case 7: return i18n::translate_c('NOMINATIVE', 'Rajab');
case 8: return i18n::translate_c('NOMINATIVE', 'Sha\'aban');
case 9: return i18n::translate_c('NOMINATIVE', 'Ramadan');
case 10: return i18n::translate_c('NOMINATIVE', 'Shawwal');
case 11: return i18n::translate_c('NOMINATIVE', 'Dhu al-Qi\'dah');
case 12: return i18n::translate_c('NOMINATIVE', 'Dhu al-Hijjah');
default: return '';
}
}
static function NUM_TO_MONTH_GENITIVE($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('GENITIVE', 'Muharram');
case 2: return i18n::translate_c('GENITIVE', 'Safar');
case 3: return i18n::translate_c('GENITIVE', 'Rabi\' al-awwal');
case 4: return i18n::translate_c('GENITIVE', 'Rabi\' al-thani');
case 5: return i18n::translate_c('GENITIVE', 'Jumada al-awwal');
case 6: return i18n::translate_c('GENITIVE', 'Jumada al-thani');
case 7: return i18n::translate_c('GENITIVE', 'Rajab');
case 8: return i18n::translate_c('GENITIVE', 'Sha\'aban');
case 9: return i18n::translate_c('GENITIVE', 'Ramadan');
case 10: return i18n::translate_c('GENITIVE', 'Shawwal');
case 11: return i18n::translate_c('GENITIVE', 'Dhu al-Qi\'dah');
case 12: return i18n::translate_c('GENITIVE', 'Dhu al-Hijjah');
default: return '';
}
}
static function NUM_TO_MONTH_LOCATIVE($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('LOCATIVE', 'Muharram');
case 2: return i18n::translate_c('LOCATIVE', 'Safar');
case 3: return i18n::translate_c('LOCATIVE', 'Rabi\' al-awwal');
case 4: return i18n::translate_c('LOCATIVE', 'Rabi\' al-thani');
case 5: return i18n::translate_c('LOCATIVE', 'Jumada al-awwal');
case 6: return i18n::translate_c('LOCATIVE', 'Jumada al-thani');
case 7: return i18n::translate_c('LOCATIVE', 'Rajab');
case 8: return i18n::translate_c('LOCATIVE', 'Sha\'aban');
case 9: return i18n::translate_c('LOCATIVE', 'Ramadan');
case 10: return i18n::translate_c('LOCATIVE', 'Shawwal');
case 11: return i18n::translate_c('LOCATIVE', 'Dhu al-Qi\'dah');
case 12: return i18n::translate_c('LOCATIVE', 'Dhu al-Hijjah');
default: return '';
}
}
static function NUM_TO_MONTH_INSTRUMENTAL($n, $leap_year) {
switch ($n) {
case 1: return i18n::translate_c('INSTRUMENTAL', 'Muharram');
case 2: return i18n::translate_c('INSTRUMENTAL', 'Safar');
case 3: return i18n::translate_c('INSTRUMENTAL', 'Rabi\' al-awwal');
case 4: return i18n::translate_c('INSTRUMENTAL', 'Rabi\' al-thani');
case 5: return i18n::translate_c('INSTRUMENTAL', 'Jumada al-awwal');
case 6: return i18n::translate_c('INSTRUMENTAL', 'Jumada al-thani');
case 7: return i18n::translate_c('INSTRUMENTAL', 'Rajab');
case 8: return i18n::translate_c('INSTRUMENTAL', 'Sha\'aban');
case 9: return i18n::translate_c('INSTRUMENTAL', 'Ramadan');
case 10: return i18n::translate_c('INSTRUMENTAL', 'Shawwal');
case 11: return i18n::translate_c('INSTRUMENTAL', 'Dhu al-Qi\'dah');
case 12: return i18n::translate_c('INSTRUMENTAL', 'Dhu al-Hijjah');
default: return '';
}
}
static function NUM_TO_SHORT_MONTH($n, $leap_year) {
// TODO: Do these have short names?
return $this->NUM_TO_MONTH_NOMINATIVE($n, $leap_year);
}
static function NUM_TO_GEDCOM_MONTH($n, $leap_year) {
switch ($n) {
case 1: return 'MUHAR';
case 2: return 'SAFAR';
case 3: return 'RABIA';
case 4: return 'RABIT';
case 5: return 'JUMAA';
case 6: return 'JUMAT';
case 7: return 'RAJAB';
case 8: return 'SHAAB';
case 9: return 'RAMAD';
case 10: return 'SHAWW';
case 11: return 'DHUAQ';
case 12: return 'DHUAH';
default: return '';
}
}
static function CAL_START_JD() {
return 1948440; // @#DHIJRI@ 1 MUHAR 0001 = @#JULIAN@ 16 JUL 0622
}
function IsLeapYear() {
return ((11*$this->y+14)%30)<11;
}
static function YMDtoJD($y, $m, $d) {
return $d+29*($m-1)+floor((6*$m-1)/11)+$y*354+floor((3+11*$y)/30)+1948085;
}
static function JDtoYMD($j) {
$y=floor((30*($j-1948440)+10646)/10631);
$m=floor((11*($j-$y*354-floor((3+11*$y)/30)-1948086)+330)/325);
$d=$j-29*($m-1)-floor((6*$m-1)/11)-$y*354-floor((3+11*$y)/30)-1948085;
return array($y, $m, $d);
}
} // class HijriDate
////////////////////////////////////////////////////////////////////////////////
// Definitions for the Arabic calendar.
// NOTE - this is the same as the Hijri Calendar, but displays dates in arabic
// rather than the local language.
////////////////////////////////////////////////////////////////////////////////
class ArabicDate extends HijriDate {
static $ARABIC_DAYS=array("الأثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعه", "السبت", "الأحد");
static function NUM_TO_MONTH_NOMINATIVE($n, $leap_year) {
// Do not translate these - they are supposed to be arabic, whatever language is shown.
switch ($n) {
case 1: return 'محرّم';
case 2: return 'صفر';
case 3: return 'ربيع الأول';
case 4: return 'ربيع الثانى';
case 5: return 'جمادى الأول';
case 6: return 'جمادى الثاني';
case 7: return 'رجب';
case 8: return 'شعبان';
case 9: return 'رمضان';
case 10: return 'شوّال';
case 11: return 'ذو القعدة';
case 12: return 'ذو الحجة';
default: return '';
}
}
static function NUM_TO_MONTH_GENITIVE($n, $leap_year) {
// Arabic does not have genitive forms
return self::NUM_TO_MONTH_NOMINATIVE($n, $leap_year);
}
static function NUM_TO_MONTH_LOCATIVE($n, $leap_year) {
// Arabic does not have locative forms
return self::NUM_TO_MONTH_NOMINATIVE($n, $leap_year);
}
static function NUM_TO_MONTH_INSTRUMENTAL($n, $leap_year) {
// Arabic does not have instrumental forms
return self::NUM_TO_MONTH_NOMINATIVE($n, $leap_year);
}
static function NUM_TO_SHORT_MONTH($n, $leap_year) {
// TODO: Do these have short names?
return self::NUM_TO_MONTH_NOMINATIVE($n, $leap_year);
}
function FormatLongWeekday() {
return $this->$ARABIC_DAYS[$this->minJD % $this->NUM_DAYS_OF_WEEK()];
}
function FormatShortWeekday() {
return $this->$ARABIC_DAYS[$this->minJD % $this->NUM_DAYS_OF_WEEK()];
}
} // class ArabicDate
////////////////////////////////////////////////////////////////////////////////
// Definitions for the Roman calendar
// TODO The 5.5.1 gedcom spec mentions this calendar, but gives no details of
// how it is to be represented.... This class is just a place holder so that
// webtrees won't compain if it receives one.
////////////////////////////////////////////////////////////////////////////////
class RomanDate extends CalendarDate {
static function CALENDAR_ESCAPE() {
return '@#DROMAN@';
}
function FormatGedcomYear() {
return sprintf('%04dAUC',$this->y);
}
function FormatLongYear() {
return $this->y.'AUC';
}
} // class RomanDate
////////////////////////////////////////////////////////////////////////////////
//
// GedcomDate represents the date or date range from a gedcom DATE record.
//
////////////////////////////////////////////////////////////////////////////////
class GedcomDate {
var $qual1=null; // Optional qualifier, such as BEF, FROM, ABT
var $date1=null; // The first (or only) date
var $qual2=null; // Optional qualifier, such as TO, AND
var $date2=null; // Optional second date
var $text =null; // Optional text, as included with an INTerpreted date
function __construct($date) {
// Extract any explanatory text
if (preg_match('/^(.*)( ?\(.*)$/', $date, $match)) {
$date=$match[1];
$this->text=$match[2];
}
// Ignore punctuation and normalise whitespace
$date=preg_replace(
array('/(\d+|@#[^@]+@)/', '/[\s;:.,-]+/', '/^ /', '/ $/'),
array(' $1 ', ' ', '', ''),
strtolower($date)
);
if (preg_match('/^(from|bet) (.+) (and|to) (.+)/', $date, $match)) {
$this->qual1=$match[1];
$this->date1=$this->ParseDate($match[2]);
$this->qual2=$match[3];
$this->date2=$this->ParseDate($match[4]);
} elseif (preg_match('/^(from|bet|to|and|bef|aft|cal|est|int|abt) (.+)/', $date, $match)) {
$this->qual1=$match[1];
$this->date1=$this->ParseDate($match[2]);
} else {
$this->date1=$this->ParseDate($date);
}
}
// Need to "deep-clone" nested objects
function __clone() {
$this->date1=clone $this->date1;
if (is_object($this->date2)) {
$this->date2=clone $this->date2;
}
}
// Convert an individual gedcom date string into a CalendarDate object
static function ParseDate($date) {
// Calendar escape specified? - use it
if (preg_match('/^(@#[^@]+@) ?(.*)/', $date, $match)) {
$cal=$match[1];
$date=$match[2];
} else {
$cal='';
}
// A date with a month: DM, M, MY or DMY
if (preg_match('/^(\d?\d?) ?(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|tsh|csh|ksl|tvt|shv|adr|ads|nsn|iyr|svn|tmz|aav|ell|vend|brum|frim|nivo|pluv|vent|germ|flor|prai|mess|ther|fruc|comp|muhar|safar|rabi[at]|juma[at]|rajab|shaab|ramad|shaww|dhuaq|dhuah) ?((?:\d+(?: b ?c)?|\d\d\d\d \/ \d{1,4})?)$/', $date, $match)) {
$d=$match[1];
$m=$match[2];
$y=$match[3];
} else
// A date with just a year
if (preg_match('/^(\d+(?: b ?c)?|\d\d\d\d \/ \d{1,4})$/', $date, $match)) {
$d='';
$m='';
$y=$match[1];
} else {
// An invalid date - do the best we can.
$d='';
$m='';
$y='';
// Look for a 3/4 digit year anywhere in the date
if (preg_match('/\b(\d{3,4})\b/', $date, $match)) {
$y=$match[1];
}
// Look for a month anywhere in the date
if (preg_match('/(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|tsh|csh|ksl|tvt|shv|adr|ads|nsn|iyr|svn|tmz|aav|ell|vend|brum|frim|nivo|pluv|vent|germ|flor|prai|mess|ther|fruc|comp|muhar|safar|rabi[at]|juma[at]|rajab|shaab|ramad|shaww|dhuaq|dhuah)/', $date, $match)) {
$m=$match[1];
// Look for a day number anywhere in the date
if (preg_match('/\b(\d\d?)\b/', $date, $match))
$d=$match[1];
}
}
// Unambiguous dates - override calendar escape
if (preg_match('/^(tsh|csh|ksl|tvt|shv|adr|ads|nsn|iyr|svn|tmz|aav|ell)$/', $m)) {
$cal='@#dhebrew@';
} else {
if (preg_match('/^(vend|brum|frim|nivo|pluv|vent|germ|flor|prai|mess|ther|fruc|comp)$/', $m)) {
$cal='@#dfrench r@';
} else {
if (preg_match('/^(muhar|safar|rabi[at]|juma[at]|rajab|shaab|ramad|shaww|dhuaq|dhuah)$/', $m)) {
$cal='@#dhijri@'; // This is a WT extension
} elseif (preg_match('/^\d+( b ?c)|\d\d\d\d \/ \d{1,4}$/', $y)) {
$cal='@#djulian@';
}
}
}
// Ambiguous dates - don't override calendar escape
if ($cal=='') {
if (preg_match('/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)$/', $m)) {
$cal='@#dgregorian@';
} else {
if (preg_match('/^[345]\d\d\d$/', $y)) { // Year 3000-5999
$cal='@#dhebrew@';
} else {
$cal='@#dgregorian@';
}
}
}
// Now construct an object of the correct type
switch ($cal) {
case '@#dgregorian@':
return new GregorianDate(array($y, $m, $d));
case '@#djulian@':
return new JulianDate(array($y, $m, $d));
case '@#dhebrew@':
if (WT_LOCALE=='he')
return new HebrewDate(array($y, $m, $d));
else
return new JewishDate(array($y, $m, $d));
case '@#dhijri@':
if (WT_LOCALE=='ar')
return new ArabicDate(array($y, $m, $d));
else
return new HijriDate(array($y, $m, $d));
case '@#dfrench r@':
return new FrenchRDate(array($y, $m, $d));
case '@#droman@':
return new RomanDate(array($y, $m, $d));
}
}
// Convert a date to the prefered format and calendar(s) display.
// Optionally make the date a URL to the calendar.
function Display($url=false, $date_fmt=null, $cal_fmts=null) {
global $TEXT_DIRECTION, $DATE_FORMAT, $CALENDAR_FORMAT;
// Convert dates to given calendars and given formats
if (!$date_fmt) {
$date_fmt=$DATE_FORMAT;
}
if (is_null($cal_fmts))
$cal_fmts=explode('_and_', $CALENDAR_FORMAT);
// Allow special processing for different languages
$func="date_localisation_".WT_LOCALE;
if (!function_exists($func))
$func="DefaultDateLocalisation";
// Two dates with text before, between and after
$q1=$this->qual1;
$d1=$this->date1->Format($date_fmt, $this->qual1);
$q2=$this->qual2;
if (is_null($this->date2))
$d2='';
else
$d2=$this->date2->Format($date_fmt, $this->qual2);
$q3='';
$func($q1, $d1, $q2, $d2, $q3);
// Convert to other calendars, if requested
$conv1='';
$conv2='';
foreach ($cal_fmts as $cal_fmt)
if ($cal_fmt!='none') {
$d1conv=$this->date1->convert_to_cal($cal_fmt);
if ($d1conv->InValidRange()) {
$d1tmp=$d1conv->Format($date_fmt, $this->qual1);
} else {
$d1tmp='';
}
$q1tmp=$this->qual1;
if (is_null($this->date2)) {
$d2conv=null;
$d2tmp='';
} else {
$d2conv=$this->date2->convert_to_cal($cal_fmt);
if ($d2conv->InValidRange()) {
$d2tmp=$d2conv->Format($date_fmt, $this->qual2);
} else {
$d2tmp='';
}
}
$q2tmp=$this->qual2;
$q3tmp='';
// Localise the date
$func($q1tmp, $d1tmp, $q2tmp, $d2tmp, $q3tmp);
// If the date is different to the unconverted date, add it to the date string.
if ($d1!=$d1tmp && $d1tmp!='') {
if ($url) {
if ($CALENDAR_FORMAT!="none") {
$conv1.=' ('.$d1tmp.')';
} else {
$conv1.='
'.$d1tmp.'';
}
} else {
$conv1.=' ('.$d1tmp.')';
}
}
if (!is_null($this->date2) && $d2!=$d2tmp && $d1tmp!='') {
if ($url) {
$conv2.=' ('.$d2tmp.')';
} else {
$conv2.=' ('.$d2tmp.')';
}
}
}
// Add URLs, if requested
if ($url) {
$d1=''.$d1.'';
if (!is_null($this->date2))
$d2=''.$d2.'';
}
// Localise the date
// TODO, use separate translations for nominative, genitive, etc.
switch ($q1.$q2) {
case '': $tmp=$d1.$conv1; break;
case 'abt': /* I18N: Gedcom ABT dates */ $tmp=i18n::translate('about %s', $d1.$conv1); break;
case 'cal': /* I18N: Gedcom CAL dates */ $tmp=i18n::translate('calculated %s', $d1.$conv1); break;
case 'est': /* I18N: Gedcom EST dates */ $tmp=i18n::translate('estimated %s', $d1.$conv1); break;
case 'int': /* I18N: Gedcom INT dates */ $tmp=i18n::translate('interpreted %s (%s)', $d1.$conv1, $this->text); break;
case 'bef': /* I18N: Gedcom BEF dates */ $tmp=i18n::translate('before %s', $d1.$conv1); break;
case 'aft': /* I18N: Gedcom AFT dates */ $tmp=i18n::translate('after %s', $d1.$conv1); break;
case 'from': /* I18N: Gedcom FROM dates */ $tmp=i18n::translate('from %s', $d1.$conv1); break;
case 'to': /* I18N: Gedcom TO dates */ $tmp=i18n::translate('to %s', $d1.$conv1); break;
case 'betand': /* I18N: Gedcom BET-AND dates */ $tmp=i18n::translate('between %s and %s', $d1.$conv1, $d2.$conv2); break;
case 'fromto': /* I18N: Gedcom FROM-TO dates */ $tmp=i18n::translate('from %s to %s', $d1.$conv1, $d2.$conv2); break;
default: $tmp=i18n::translate('Invalid date'); break; // e.g. BET without AND
}
// Return at least one printable character, for better formatting in tables.
if (strip_tags($tmp)=='')
return ' ';
else
return "{$tmp}";
}
// Get the earliest/latest date/JD from this date
function MinDate() {
return $this->date1;
}
function MaxDate() {
if (is_null($this->date2))
return $this->date1;
else
return $this->date2;
}
function MinJD() {
$tmp=$this->MinDate();
return $tmp->minJD;
}
function MaxJD() {
$tmp=$this->MaxDate();
return $tmp->maxJD;
}
function JD() {
return floor(($this->MinJD()+$this->MaxJD())/2);
}
// Offset this date by N years, and round to the whole year
function AddYears($n, $qual='') {
$tmp=clone $this;
$tmp->date1->y+=$n;
$tmp->date1->m=0;
$tmp->date1->d=0;
$tmp->date1->SetJDfromYMD();
$tmp->qual1=$qual;
$tmp->qual2='';
$tmp->date2=null;
return $tmp;
}
// Calculate the number of full years between two events.
// Return the result as either a number of years (for indi lists, etc.)
static function GetAgeYears($d1, $d2=null, $warn_on_negative=true) {
if (!is_object($d1)) return;
if (!is_object($d2))
return $d1->date1->GetAge(false, WT_CLIENT_JD, $warn_on_negative );
else
return $d1->date1->GetAge(false, $d2->MinJD(), $warn_on_negative);
}
// Calculate the years/months/days between two events
// Return a gedcom style age string: "1y 2m 3d" (for fact details)
static function GetAgeGedcom($d1, $d2=null, $warn_on_negative=true) {
if (is_null($d2)) {
return $d1->date1->GetAge(true, WT_CLIENT_JD, $warn_on_negative);
} else {
// If dates overlap, then can't calculate age.
if (GedcomDate::Compare($d1, $d2)) {
return $d1->date1->GetAge(true, $d2->MinJD(), $warn_on_negative);
} if (GedcomDate::Compare($d1, $d2)==0 && $d1->date1->minJD==$d2->MinJD()) {
return '0d';
} else {
return '';
}
}
}
// Static function to compare two dates.
// return <0 if $a<$b
// return >0 if $b>$a
// return 0 if dates same/overlap
// BEF/AFT sort as the day before/after.
static function Compare($a, $b) {
// Get min/max JD for each date.
switch ($a->qual1) {
case 'bef':
$amin=$a->MinJD()-1;
$amax=$amin;
break;
case 'aft':
$amax=$a->MaxJD()+1;
$amin=$amax;
break;
default:
$amin=$a->MinJD();
$amax=$a->MaxJD();
break;
}
switch ($b->qual1) {
case 'bef':
$bmin=$b->MinJD()-1;
$bmax=$bmin;
break;
case 'aft':
$bmax=$b->MaxJD()+1;
$bmin=$bmax;
break;
default:
$bmin=$b->MinJD();
$bmax=$b->MaxJD();
break;
}
if ($amax<$bmin) {
return -1;
} else {
if ($amin>$bmax && $bmax>0) {
return 1;
} else {
if ($amin<$bmin && $amax<=$bmax) {
return -1;
} elseif ($amin>$bmin && $amax>=$bmax && $bmax>0) {
return 1;
} else {
return 0;
}
}
}
}
// Check whether a gedcom date contains usable calendar date(s).
function isOK() {
return $this->MinJD() && $this->MaxJD();
}
// Calculate the gregorian year for a date. This should NOT be used internally
// within WT - we should keep the code "calendar neutral" to allow support for
// jewish/arabic users. This is only for interfacing with external entities,
// such as the ancestry.com search interface or the dated fact icons.
function gregorianYear() {
if ($this->isOK()) {
list($y)=GregorianDate::JDtoYMD($this->JD());
return $y;
} else {
return 0;
}
}
}
// Localise a date. This is a default function, and may be overridden in extras.xx.php
function DefaultDateLocalisation(&$q1, &$d1, &$q2, &$d2, &$q3) {
}