text=$match[2];
}
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 WT_Date_Calendar object
static function ParseDate($date) {
// Valid calendar escape specified? - use it
if (preg_match('/^(@#D(?:GREGORIAN|JULIAN|HEBREW|HIJRI|JALALI|FRENCH R|ROMAN|JALALI)+@) ?(.*)/', $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|FARVA|ORDIB|KHORD|TIR|MORDA|SHAHR|MEHR|ABAN|AZAR|DEY|BAHMA|ESFAN) ?((?:\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|FARVA|ORDIB|KHORD|TIR|MORDA|SHAHR|MEHR|ABAN|AZAR|DEY|BAHMA|ESFAN)/', $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
} else {
if (preg_match('/^(FARVA|ORDIB|KHORD|TIR|MORDA|SHAHR|MEHR|ABAN|AZAR|DEY|BAHMA|ESFAN)$/', $m)) {
$cal='@#DJALALI@'; // 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 WT_Date_Gregorian(array($y, $m, $d));
case '@#DJULIAN@':
return new WT_Date_Julian(array($y, $m, $d));
case '@#DHEBREW@':
return new WT_Date_Jewish(array($y, $m, $d));
case '@#DHIJRI@':
return new WT_Date_Hijri(array($y, $m, $d));
case '@#DFRENCH R@':
return new WT_Date_French(array($y, $m, $d));
case '@#DJALALI@':
return new WT_Date_Jalali(array($y, $m, $d));
case '@#DROMAN@':
return new WT_Date_Roman(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);
// 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);
// 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;
// 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
switch ($q1.$q2) {
case '': $tmp=$d1.$conv1; break;
case 'ABT': /* I18N: Gedcom ABT dates */ $tmp=WT_I18N::translate('about %s', $d1.$conv1); break;
case 'CAL': /* I18N: Gedcom CAL dates */ $tmp=WT_I18N::translate('calculated %s', $d1.$conv1); break;
case 'EST': /* I18N: Gedcom EST dates */ $tmp=WT_I18N::translate('estimated %s', $d1.$conv1); break;
case 'INT': /* I18N: Gedcom INT dates */ $tmp=WT_I18N::translate('interpreted %s (%s)', $d1.$conv1, $this->text); break;
case 'BEF': /* I18N: Gedcom BEF dates */ $tmp=WT_I18N::translate('before %s', $d1.$conv1); break;
case 'AFT': /* I18N: Gedcom AFT dates */ $tmp=WT_I18N::translate('after %s', $d1.$conv1); break;
case 'FROM': /* I18N: Gedcom FROM dates */ $tmp=WT_I18N::translate('from %s', $d1.$conv1); break;
case 'TO': /* I18N: Gedcom TO dates */ $tmp=WT_I18N::translate('to %s', $d1.$conv1); break;
case 'BETAND': /* I18N: Gedcom BET-AND dates */ $tmp=WT_I18N::translate('between %s and %s', $d1.$conv1, $d2.$conv2); break;
case 'FROMTO': /* I18N: Gedcom FROM-TO dates */ $tmp=WT_I18N::translate('from %s to %s', $d1.$conv1, $d2.$conv2); break;
default: $tmp=WT_I18N::translate('Invalid date'); break; // e.g. BET without AND
}
if ($this->text && !$q1) {
$tmp = WT_I18N::translate('%s (%s)', $tmp, $this->text);
}
// 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 (int)(($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 the age of a person, on a date.
// If $d2 is null, today's date is used.
static function getAge(WT_Date $d1, WT_Date $d2=null, $format) {
if ($d2) {
if ($d2->MaxJD()>=$d1->MinJD() && $d2->MinJD()<=$d1->MinJD()) {
// Overlapping dates
$jd=$d1->MinJD();
} else {
// Non-overlapping dates
$jd=$d2->MinJD();
}
} else {
// If second date not specified, use today’s date
$jd=WT_CLIENT_JD;
}
switch ($format) {
case 0: // Years - integer only (for statistics, rather than for display)
if ($jd && $d1->MinJD() && $d1->MinJD()<=$jd) {
return $d1->MinDate()->GetAge(false, $jd, false);
} else {
return -1;
}
case 1: // Days - integer only (for sorting, rather than for display)
if ($jd && $d1->MinJD()) {
return $jd-$d1->MinJD();
} else {
return -1;
}
case 2: // Just years, in local digits, with warning for negative/
if ($jd && $d1->MinJD()) {
if ($d1->MinJD()>$jd) {
return '';
} else {
return WT_I18N::number($d1->MinDate()->GetAge(false, $jd));
}
} else {
return ' ';
}
// TODO: combine GetAgeGedcom() into this function
}
}
// 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 (self::Compare($d1, $d2)) {
return $d1->date1->GetAge(true, $d2->MinJD(), $warn_on_negative);
} if (self::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)=WT_Date_Gregorian::JDtoYMD($this->JD());
return $y;
} else {
return 0;
}
}
}