5 # A Date Manipulation Object
7 # Copyright 1999-2004 Axis Data
8 # This code is free software that can be used or redistributed under the
9 # terms of Version 2 of the GNU General Public License, as published by the
10 # Free Software Foundation (http://www.fsf.org).
12 # Author: Edward Almasy (almasy@axisdata.com)
14 # Part of the AxisPHP library v1.2.5
15 # For more information see http://www.axisdata.com/AxisPHP/
20 # ---- PUBLIC INTERFACE --------------------------------------------------
28 if ($this->DebugLevel) { print(
"Date: Date(BeginDate=\"".$BeginDate.
"\" EndDate=\"".$EndDate.
"\" Precision=".$this->FormattedPrecision(
$Precision).
")<br>\n"); }
57 # Formats we need to parse:
76 # append end date to begin date if available
80 $Date .=
" - ".$EndDate;
83 # strip off any leading or trailing whitespace
86 # bail out if we don't have anything to parse
87 if (strlen($Date) < 1) {
return; }
89 # check for and strip out inferred indicators ("[" and "]")
91 if (preg_match(
"/\\[/", $Date))
94 $Date = preg_replace(
"/[\\[\\]]/",
"", $Date);
97 # check for and strip off copyright indicator (leading "c")
98 if (preg_match(
"/^c/", $Date))
101 $Date = preg_replace(
"/^c/",
"", $Date);
104 # check for and strip off continuous indicator (trailing "-")
105 if (preg_match(
"/\\-$/", $Date))
108 $Date = preg_replace(
"/\\-$/",
"", $Date);
111 # strip out any times
112 $Date = preg_replace(
"/[0-9]{1,2}:[0-9]{2,2}[:]?[0-9]{0,2}/",
"", $Date);
115 $Date = strtolower($Date);
117 # A regex to match short and long month names:
118 $MonthRegex =
"(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may".
119 "|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?".
120 "|nov(?:ember)?|dec(?:ember)?)";
122 # Here we'll construct a template regex for dates
123 # We want a single regex that covers all the different formats
124 # of date we understand, with the various components of the
125 # date pulled out using named subexpressions (eg: (?P<name>)).
126 # Annoyingly, we can't re-use the same name for subexpressions
127 # that will never both be matched at once.
128 # So, we have to number them (year1, year2, etc) and figure
129 # out which one did match.
130 # Use XX_ThingNumber in the parameterized subexpressions
132 # We'll use string substitutions later to convert the XX_ to
136 # Matched formats are separated by |, as
this isn
't used in any of the formats
137 # First alternative will match the following formats:
138 # 1999-09-19 | 19990909 | 1999-09 | 199909 | 1999
139 "(?:(?P<XX_year1>\d{4})(?:-?(?P<XX_month1>\d{1,2})(?:-?(?P<XX_day1>\d{1,2}))?)?)".
140 # Second alternative will match the following formats:
141 # 09-19-1999 | 19-09-1999 | 09/19/01 | 09-19-01
142 "|(?:(?P<XX_month2>\d{1,2})[\/-](?P<XX_day2>\d{1,2})[\/-](?P<XX_year2>(?:\d{2,4})))".
143 # Third alternative will match the following formats:
144 # 09-Sep-1999 | 09 Sep 1999 | Sep-1999 | Sep 1999
145 "|(?:(?:(?P<XX_day3>\d+)[ -])?(?P<XX_month3>".$MonthRegex.")[ -](?P<XX_year3>\d{4}))".
146 # Fourth alternative will match the following formats:
147 # Sep 9 1999 | September 9th, 1999
148 "|(?:(?P<XX_month4>".$MonthRegex.") (?P<XX_day4>\d{1,2})(?:(?:st|nd|rd|th),)? (?P<XX_year4>\d{4}))".
151 # If more formats are added, bump this.
152 $NumberOfDateRegexes = 4;
154 # Construct the begin and end regexes for the date range
155 $BeginRegex = str_replace('XX
','Begin
', $DateRegex );
156 $EndRegex = str_replace('XX
','End
', $DateRegex );
158 # Glue them together, making the second one optional,
159 # and do the matching.
160 if ( preg_match("/".$BeginRegex.
161 "(?:(?:(?: - )|,)".$EndRegex.")?/",
164 # Pull out the Begin and End data from the matches array:
165 foreach( array("Begin","End") as $Time )
168 # Extract the matching elements from the regex parse
169 '$
'.$Time.'Day = $this->ExtractMatchData($Matches,
"'.
170 $Time.'_day", $NumberOfDateRegexes );
' .
171 '$
'.$Time.'Month = $this->ExtractMatchData($Matches,
"'.
172 $Time.'_month",$NumberOfDateRegexes );
' .
173 '$
'.$Time.'Year = $this->ExtractMatchData($Matches,
"'.
174 $Time.'_year", $NumberOfDateRegexes );
' .
175 # Convert named months to month numbers:
176 'if ( isset($
'.$Time.'Month) &&
' .
177 ' !is_numeric($
'.$Time.'Month))
' .
179 ' $
'.$Time.'Month=$MonthNames[$
'.$Time.'Month];
' .
181 # Handle 2-digit years
182 'if ( isset($
'.$Time.'Year) &&
' .
183 ' strlen($
'.$Time.'Year)==2)
' .
185 ' $
'.$Time.'Year += ($
'.$Time.'Year>50)?1900:2000;
' .
187 # Deal with D-M-Y format, where we can
188 'if ( isset($
'.$Time.'Month) && $
'.$Time.'Month>12)
' .
190 ' $Tmp = $
'.$Time.'Month;
' .
191 ' $
'.$Time.'Month = $
'.$Time.'Day;
' .
192 ' $
'.$Time.'Day = $Tmp;
' .
198 # use current month if begin day but no begin month specified
199 if (isset($BeginDay) && !isset($BeginMonth))
201 $BeginMonth = date("m");
204 # use current year if begin month but no begin year specified
205 if (isset($BeginMonth) && !isset($BeginYear))
207 $BeginYear = date("Y");
210 # use begin year if end month but no end year specified
211 if (isset($EndMonth) && !isset($EndYear))
213 $EndYear = $BeginYear;
216 # After we've shuffled around the numbers, check the result to see
if
217 # it looks valid, dropping that which doesn't.
218 foreach( array(
"Begin",
"End") as $Time)
221 # Discard invalid looking dates
222 'if ( isset($'.$Time.
'Year) && !($'.$Time.
'Year >=1)) ' .
223 ' { unset($'.$Time.
'Year); } ' .
224 'if ( isset($'.$Time.
'Month) && ' .
225 ' !( $'.$Time.
'Month>=1 && $'.$Time.
'Month<=12)) ' .
226 ' { unset($'.$Time.
'Month); } ' .
227 'if ( isset($'.$Time.
'Day) && ' .
228 ' !( $'.$Time.
'Day >=1 && $'.$Time.
'Day <=31)) ' .
229 ' { unset($'.$Time.
'Day); } '
233 # if no begin date found and begin date value is not illegal
235 && ($BeginDate !=
"0000-00-00")
236 && ($BeginDate !=
"0000-00-00 00:00:00"))
238 # try system call to parse incoming date
239 $UDateStamp = strtotime($BeginDate);
240 if ($this->DebugLevel > 1) { print(
"Date: calling strtotime to parse BeginDate \"".$BeginDate.
"\" -- strtotime returned \"".$UDateStamp.
"\"<br>\n"); }
242 # if system call was able to parse date
243 if (($UDateStamp != -1) && ($UDateStamp !== FALSE))
245 # set begin date to value returned by system call
252 # if end date value supplied and no end date found and end date value is not illegal
253 if (($EndDate != NULL) && !isset(
$EndYear)
254 && ($EndDate !=
"0000-00-00")
255 && ($EndDate !=
"0000-00-00 00:00:00"))
257 # try system call to parse incoming date
258 $UDateStamp = strtotime($EndDate);
260 # if system call was able to parse date
261 if (($UDateStamp != -1) && ($UDateStamp !== FALSE))
263 # set begin date to value returned by system call
266 $EndDay = date(
"j", $UDateStamp);
270 # if end date is before begin date
278 # swap begin and end dates
290 # if precision value supplied by caller
293 # use supplied precision value
298 # save new precision value
308 # save new date values
309 if ($this->DebugLevel > 1) { print(
"Date: BeginYear = $BeginYear<br>\n"); }
310 if ($this->DebugLevel > 1) { print(
"Date: BeginMonth = $BeginMonth<br>\n"); }
311 if ($this->DebugLevel > 1) { print(
"Date: BeginDay = $BeginDay<br>\n"); }
312 if ($this->DebugLevel > 1) { print(
"Date: EndYear = $EndYear<br>\n"); }
313 if ($this->DebugLevel > 1) { print(
"Date: EndMonth = $EndMonth<br>\n"); }
314 if ($this->DebugLevel > 1) { print(
"Date: EndDay = $EndDay<br>\n"); }
315 if ($this->DebugLevel > 1) { print(
"Date: Precision = ".$this->
FormattedPrecision().
"<br>\n"); }
324 # return value suitable for display
327 # if begin year available
331 # start with begin year
334 # if begin month available
338 $DateString .=
"-".$this->BeginMonth;
340 # if begin day available
344 $DateString .=
"-".$this->BeginDay;
348 # if end year available
354 # separate dates with comma
359 # separate dates with dash
360 $DateString .=
" - ";
366 # if end month available
370 $DateString .=
"-".$this->EndMonth;
372 # if end day available
376 $DateString .=
"-".$this->EndDay;
382 # if date is open-ended
385 # add dash to indicate open-ended
390 # if copyright flag is set
393 # add on copyright indicator
394 $DateString =
"c".$DateString;
397 # if flag is set indicating date was inferred
400 # add on inferred indicators
401 $DateString =
"[".$DateString.
"]";
405 # return formatted date string to caller
409 # return date in format specified like PHP date() format parameter
424 return date($Format, mktime(0, 0, 0, $Month, $Day, $Year));
427 # get begin date/time (or end if requested) formatted for SQL DATETIME field
430 return $this->
PFormatted(
"Y-m-d H:i:s", $ReturnEndDate);
433 # return begin time in ISO 8601 format
436 # if begin year available
439 # start with begin year
440 $DateString = sprintf(
"%04d", $this->BeginYear);
442 # if begin month available
446 $DateString .= sprintf(
"-%02d", $this->BeginMonth);
448 # if begin day available
452 $DateString .= sprintf(
"-%02d", $this->BeginDay);
457 # return ISO 8601 formatted date string to caller
461 # return values in UTC instead of local time (NOT IMPLEMENTED)
464 # if not currently in UTC
465 if ($this->InUTC != TRUE)
470 # set flag to indicate we are in UTC
475 # return values in local time instead of UTC (NOT IMPLEMENTED)
478 # if currently in UTC
481 # adjust date to local time
484 # set flag to indicate we are in local time
485 $this->InUTC = FALSE;
489 # return normalized values (suitable for storing via SQL)
492 # build date string based on current precision
497 if ($this->
Precision & DATEPRE_BEGINMONTH)
499 $DateFormat =
"%04d-%02d-%02d";
503 $DateFormat =
"%04d-%02d-01";
508 $DateFormat =
"%04d-01-01";
511 $DateString = sprintf($DateFormat,
512 $this->BeginYear, $this->BeginMonth, $this->BeginDay);
519 # return date string to caller
524 # build date string based on current precision
531 $DateFormat =
"%04d-%02d-%02d";
535 $DateFormat =
"%04d-%02d-00";
540 $DateFormat =
"%04d-00-00";
543 $DateString = sprintf($DateFormat,
544 $this->EndYear, $this->EndMonth, $this->EndDay);
551 # return date string to caller
555 # get or set precision value (combination of boolean flags)
558 if ($NewPrecision != NULL) { $this->
Precision = $NewPrecision; }
562 # return text of SQL condition for records that match date
563 function SqlCondition($FieldName, $EndFieldName = NULL, $Operator =
"=")
565 # if no date value is set
568 # if operator is equals
569 if ($Operator ==
"=")
571 # construct conditional that will find null dates
572 $Condition =
"(".$FieldName.
" IS NULL OR ".$FieldName.
" < '0000-01-01 00:00:01')";
576 # construct conditional that will find non-null dates
577 $Condition =
"(".$FieldName.
" > '0000-01-01 00:00:00')";
582 # use begin field name as end if no end field specified
583 if ($EndFieldName == NULL) { $EndFieldName = $FieldName; }
585 # determine begin and end of range
630 if ($this->
Precision & DATEPRE_BEGINMONTH)
653 # construct SQL condition
657 $Condition =
" ${FieldName} > ${RangeEnd} ";
661 $Condition =
" ${FieldName} > ${RangeBegin} ";
665 $Condition =
" ${FieldName} <= ${RangeBegin} ";
669 $Condition =
" ${FieldName} <= ${RangeEnd} ";
673 $Condition =
" (${FieldName} <= ${RangeBegin}"
674 .
" OR ${FieldName} > ${RangeEnd}) ";
679 $Condition =
" (${FieldName} > ${RangeBegin}"
680 .
" AND ${FieldName} <= ${RangeEnd}) ";
685 # return condition to caller
689 # return string containing printable version of precision flags
706 $String = preg_replace(
"/^\\|/",
"", $String);
711 # ---- PRIVATE INTERFACE -------------------------------------------------
722 # Return the first non-empty parameterized subexpression match
723 # Expects a match array from preg_match()
724 # Expects a number of array elements, eg. match1, match2, match3
725 # Checks each element and returns the first non-empty one
726 # If they are all empty, NULL is returned
727 private function ExtractMatchData( $Matches, $Member, $Max )
729 for( $i=1; $i<=$Max; $i++ )
731 if (isset($Matches[$Member.$i]) && strlen($Matches[$Member.$i])>0)
733 return $Matches[$Member.$i];
740 # date precision flags
741 define(
"DATEPRE_BEGINYEAR", 1);
742 define(
"DATEPRE_BEGINMONTH", 2);
743 define(
"DATEPRE_BEGINDAY", 4);
744 define(
"DATEPRE_BEGINDECADE", 8);
745 define(
"DATEPRE_BEGINCENTURY",16);
746 define(
"DATEPRE_ENDYEAR", 32);
747 define(
"DATEPRE_ENDMONTH", 64);
748 define(
"DATEPRE_ENDDAY", 128);
749 define(
"DATEPRE_ENDDECADE", 256);
750 define(
"DATEPRE_ENDCENTURY", 512);
751 define(
"DATEPRE_INFERRED", 1024);
752 define(
"DATEPRE_COPYRIGHT", 2048);
753 define(
"DATEPRE_CONTINUOUS", 4096);
754 define(
"DATEPRE_SEPARATE", 8192);
755 define(
"DATEPRE_UNSURE", 16384);