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:
77 # append end date to begin date if available
81 $Date .=
" - ".$EndDate;
84 # strip off any leading or trailing whitespace
87 # bail out if we don't have anything to parse
88 if (strlen($Date) < 1) {
return; }
90 # check for and strip out inferred indicators ("[" and "]")
92 if (preg_match(
"/\\[/", $Date))
95 $Date = preg_replace(
"/[\\[\\]]/",
"", $Date);
98 # check for and strip off copyright indicator (leading "c")
99 if (preg_match(
"/^c/", $Date))
102 $Date = preg_replace(
"/^c/",
"", $Date);
105 # check for and strip off continuous indicator (trailing "-")
106 if (preg_match(
"/\\-$/", $Date))
109 $Date = preg_replace(
"/\\-$/",
"", $Date);
112 # strip out any times
113 $Date = preg_replace(
"/[0-9]{1,2}:[0-9]{2,2}[:]?[0-9]{0,2}/",
"", $Date);
116 $Date = strtolower($Date);
118 # A regex to match short and long month names:
119 $MonthRegex =
"(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may".
120 "|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?".
121 "|nov(?:ember)?|dec(?:ember)?)";
123 # Here we'll construct a template regex for dates
124 # We want a single regex that covers all the different formats
125 # of date we understand, with the various components of the
126 # date pulled out using named subexpressions (eg: (?P<name>)).
127 # Annoyingly, we can't re-use the same name for subexpressions
128 # that will never both be matched at once.
129 # So, we have to number them (year1, year2, etc) and figure
130 # out which one did match.
131 # Use XX_ThingNumber in the parameterized subexpressions
133 # We'll use string substitutions later to convert the XX_ to
137 # Matched formats are separated by |, as
this isn
't used in any of the formats
138 # First alternative will match the following formats:
139 # 1999-09-19 | 19990909 | 1999-09 | 199909 | 1999
140 "(?:(?P<XX_year1>\d{4})(?:-?(?P<XX_month1>\d{1,2})(?:-?(?P<XX_day1>\d{1,2}))?)?)".
141 # Second alternative will match the following formats:
142 # 09-19-1999 | 19-09-1999 | 09/19/01 | 09-19-01
143 "|(?:(?P<XX_month2>\d{1,2})[\/-](?P<XX_day2>\d{1,2})[\/-](?P<XX_year2>(?:\d{2,4})))".
144 # Third alternative will match the following formats:
145 # 09-Sep-1999 | 09 Sep 1999 | Sep-1999 | Sep 1999
146 "|(?:(?:(?P<XX_day3>\d+)[ -])?(?P<XX_month3>".$MonthRegex.")[ -](?P<XX_year3>\d{4}))".
147 # Fourth alternative will match the following formats:
148 # Sep 9 1999 | September 9th, 1999
149 "|(?:(?P<XX_month4>".$MonthRegex.") (?P<XX_day4>\d{1,2})(?:(?:st|nd|rd|th|),)? (?P<XX_year4>\d{4}))".
152 # If more formats are added, bump this.
153 $NumberOfDateRegexes = 4;
155 # Construct the begin and end regexes for the date range
156 $BeginRegex = str_replace('XX
','Begin
', $DateRegex );
157 $EndRegex = str_replace('XX
','End
', $DateRegex );
159 # Glue them together, making the second one optional,
160 # and do the matching.
161 if ( preg_match("/".$BeginRegex.
162 "(?:(?:(?: - )|,)".$EndRegex.")?/",
165 # Pull out the Begin and End data from the matches array:
166 foreach( array("Begin","End") as $Time )
169 # Extract the matching elements from the regex parse
170 '$
'.$Time.'Day = $this->ExtractMatchData($Matches,
"'.
171 $Time.'_day", $NumberOfDateRegexes );
' .
172 '$
'.$Time.'Month = $this->ExtractMatchData($Matches,
"'.
173 $Time.'_month",$NumberOfDateRegexes );
' .
174 '$
'.$Time.'Year = $this->ExtractMatchData($Matches,
"'.
175 $Time.'_year", $NumberOfDateRegexes );
' .
176 # Convert named months to month numbers:
177 'if ( isset($
'.$Time.'Month) &&
' .
178 ' !is_numeric($
'.$Time.'Month))
' .
180 ' $
'.$Time.'Month=$MonthNames[$
'.$Time.'Month];
' .
182 # Handle 2-digit years
183 'if ( isset($
'.$Time.'Year) &&
' .
184 ' strlen($
'.$Time.'Year)==2)
' .
186 ' $
'.$Time.'Year += ($
'.$Time.'Year>50)?1900:2000;
' .
188 # Deal with D-M-Y format, where we can
189 'if ( isset($
'.$Time.'Month) && $
'.$Time.'Month>12)
' .
191 ' $Tmp = $
'.$Time.'Month;
' .
192 ' $
'.$Time.'Month = $
'.$Time.'Day;
' .
193 ' $
'.$Time.'Day = $Tmp;
' .
199 # use current month if begin day but no begin month specified
200 if (isset($BeginDay) && !isset($BeginMonth))
202 $BeginMonth = date("m");
205 # use current year if begin month but no begin year specified
206 if (isset($BeginMonth) && !isset($BeginYear))
208 $BeginYear = date("Y");
211 # use begin year if end month but no end year specified
212 if (isset($EndMonth) && !isset($EndYear))
214 $EndYear = $BeginYear;
217 # After we've shuffled around the numbers, check the result to see
if
218 # it looks valid, dropping that which doesn't.
219 foreach( array(
"Begin",
"End") as $Time)
222 # Discard invalid looking dates
223 'if ( isset($'.$Time.
'Year) && !($'.$Time.
'Year >=1)) ' .
224 ' { unset($'.$Time.
'Year); } ' .
225 'if ( isset($'.$Time.
'Month) && ' .
226 ' !( $'.$Time.
'Month>=1 && $'.$Time.
'Month<=12)) ' .
227 ' { unset($'.$Time.
'Month); } ' .
228 'if ( isset($'.$Time.
'Day) && ' .
229 ' !( $'.$Time.
'Day >=1 && $'.$Time.
'Day <=31)) ' .
230 ' { unset($'.$Time.
'Day); } '
234 # if no begin date found and begin date value is not illegal
236 && ($BeginDate !=
"0000-00-00")
237 && ($BeginDate !=
"0000-00-00 00:00:00"))
239 # try system call to parse incoming date
240 $UDateStamp = strtotime($BeginDate);
241 if ($this->DebugLevel > 1) { print(
"Date: calling strtotime to parse BeginDate \"".$BeginDate.
"\" -- strtotime returned \"".$UDateStamp.
"\"<br>\n"); }
243 # if system call was able to parse date
244 if (($UDateStamp != -1) && ($UDateStamp !== FALSE))
246 # set begin date to value returned by system call
253 # if end date value supplied and no end date found and end date value is not illegal
254 if (($EndDate != NULL) && !isset(
$EndYear)
255 && ($EndDate !=
"0000-00-00")
256 && ($EndDate !=
"0000-00-00 00:00:00"))
258 # try system call to parse incoming date
259 $UDateStamp = strtotime($EndDate);
261 # if system call was able to parse date
262 if (($UDateStamp != -1) && ($UDateStamp !== FALSE))
264 # set begin date to value returned by system call
267 $EndDay = date(
"j", $UDateStamp);
271 # if end date is before begin date
279 # swap begin and end dates
291 # if precision value supplied by caller
294 # use supplied precision value
299 # save new precision value
309 # save new date values
310 if ($this->DebugLevel > 1) { print(
"Date: BeginYear = $BeginYear<br>\n"); }
311 if ($this->DebugLevel > 1) { print(
"Date: BeginMonth = $BeginMonth<br>\n"); }
312 if ($this->DebugLevel > 1) { print(
"Date: BeginDay = $BeginDay<br>\n"); }
313 if ($this->DebugLevel > 1) { print(
"Date: EndYear = $EndYear<br>\n"); }
314 if ($this->DebugLevel > 1) { print(
"Date: EndMonth = $EndMonth<br>\n"); }
315 if ($this->DebugLevel > 1) { print(
"Date: EndDay = $EndDay<br>\n"); }
316 if ($this->DebugLevel > 1) { print(
"Date: Precision = ".$this->
FormattedPrecision().
"<br>\n"); }
325 # return value suitable for display
328 # if begin year available
332 # start with begin year
335 # if begin month available
339 $DateString .=
"-".$this->BeginMonth;
341 # if begin day available
345 $DateString .=
"-".$this->BeginDay;
349 # if end year available
355 # separate dates with comma
360 # separate dates with dash
361 $DateString .=
" - ";
367 # if end month available
371 $DateString .=
"-".$this->EndMonth;
373 # if end day available
377 $DateString .=
"-".$this->EndDay;
383 # if date is open-ended
386 # add dash to indicate open-ended
391 # if copyright flag is set
394 # add on copyright indicator
395 $DateString =
"c".$DateString;
398 # if flag is set indicating date was inferred
401 # add on inferred indicators
402 $DateString =
"[".$DateString.
"]";
406 # return formatted date string to caller
410 # return date in format specified like PHP date() format parameter
425 return date($Format, mktime(0, 0, 0, $Month, $Day, $Year));
428 # get begin date/time (or end if requested) formatted for SQL DATETIME field
431 return $this->
PFormatted(
"Y-m-d H:i:s", $ReturnEndDate);
434 # return begin time in ISO 8601 format
437 # start out assuming date will be empty
440 # if begin year available
443 # start with begin year
444 $DateString = sprintf(
"%04d", $this->BeginYear);
446 # if begin month available
450 $DateString .= sprintf(
"-%02d", $this->BeginMonth);
452 # if begin day available
456 $DateString .= sprintf(
"-%02d", $this->BeginDay);
461 # return ISO 8601 formatted date string to caller
465 # return values in UTC instead of local time (NOT IMPLEMENTED)
468 # if not currently in UTC
469 if ($this->InUTC != TRUE)
474 # set flag to indicate we are in UTC
479 # return values in local time instead of UTC (NOT IMPLEMENTED)
482 # if currently in UTC
485 # adjust date to local time
488 # set flag to indicate we are in local time
489 $this->InUTC = FALSE;
493 # return normalized values (suitable for storing via SQL)
496 # build date string based on current precision
501 if ($this->
Precision & DATEPRE_BEGINMONTH)
503 $DateFormat =
"%04d-%02d-%02d";
507 $DateFormat =
"%04d-%02d-01";
512 $DateFormat =
"%04d-01-01";
515 $DateString = sprintf($DateFormat,
516 $this->BeginYear, $this->BeginMonth, $this->BeginDay);
523 # return date string to caller
528 # build date string based on current precision
535 $DateFormat =
"%04d-%02d-%02d";
539 $DateFormat =
"%04d-%02d-00";
544 $DateFormat =
"%04d-00-00";
547 $DateString = sprintf($DateFormat,
548 $this->EndYear, $this->EndMonth, $this->EndDay);
555 # return date string to caller
559 # get or set precision value (combination of boolean flags)
562 if ($NewPrecision != NULL) { $this->
Precision = $NewPrecision; }
566 # return text of SQL condition for records that match date
567 function SqlCondition($FieldName, $EndFieldName = NULL, $Operator =
"=")
569 # if no date value is set
572 # if operator is equals
573 if ($Operator ==
"=")
575 # construct conditional that will find null dates
576 $Condition =
"(".$FieldName.
" IS NULL OR ".$FieldName.
" < '0000-01-01 00:00:01')";
580 # construct conditional that will find non-null dates
581 $Condition =
"(".$FieldName.
" > '0000-01-01 00:00:00')";
586 # use begin field name as end if no end field specified
587 if ($EndFieldName == NULL) { $EndFieldName = $FieldName; }
589 # determine begin and end of range
634 if ($this->
Precision & DATEPRE_BEGINMONTH)
657 # construct SQL condition
661 $Condition =
" ${FieldName} > ${RangeEnd} ";
665 $Condition =
" ${FieldName} > ${RangeBegin} ";
669 $Condition =
" ${FieldName} <= ${RangeBegin} ";
673 $Condition =
" ${FieldName} <= ${RangeEnd} ";
677 $Condition =
" (${FieldName} <= ${RangeBegin}"
678 .
" OR ${FieldName} > ${RangeEnd}) ";
683 $Condition =
" (${FieldName} > ${RangeBegin}"
684 .
" AND ${FieldName} <= ${RangeEnd}) ";
689 # return condition to caller
693 # return string containing printable version of precision flags
710 $String = preg_replace(
"/^\\|/",
"", $String);
715 # ---- PRIVATE INTERFACE -------------------------------------------------
726 # Return the first non-empty parameterized subexpression match
727 # Expects a match array from preg_match()
728 # Expects a number of array elements, eg. match1, match2, match3
729 # Checks each element and returns the first non-empty one
730 # If they are all empty, NULL is returned
731 private function ExtractMatchData( $Matches, $Member, $Max )
733 for( $i=1; $i<=$Max; $i++ )
735 if (isset($Matches[$Member.$i]) && strlen($Matches[$Member.$i])>0)
737 return $Matches[$Member.$i];
744 # date precision flags
745 define(
"DATEPRE_BEGINYEAR", 1);
746 define(
"DATEPRE_BEGINMONTH", 2);
747 define(
"DATEPRE_BEGINDAY", 4);
748 define(
"DATEPRE_BEGINDECADE", 8);
749 define(
"DATEPRE_BEGINCENTURY",16);
750 define(
"DATEPRE_ENDYEAR", 32);
751 define(
"DATEPRE_ENDMONTH", 64);
752 define(
"DATEPRE_ENDDAY", 128);
753 define(
"DATEPRE_ENDDECADE", 256);
754 define(
"DATEPRE_ENDCENTURY", 512);
755 define(
"DATEPRE_INFERRED", 1024);
756 define(
"DATEPRE_COPYRIGHT", 2048);
757 define(
"DATEPRE_CONTINUOUS", 4096);
758 define(
"DATEPRE_SEPARATE", 8192);
759 define(
"DATEPRE_UNSURE", 16384);
FormattedForSql($ReturnEndDate=FALSE)
SqlCondition($FieldName, $EndFieldName=NULL, $Operator="=")
const DATEPRE_BEGINDECADE
Precision($NewPrecision=NULL)
PFormatted($Format, $ReturnEndDate=FALSE)
Date($BeginDate, $EndDate=NULL, $Precision=NULL, $DebugLevel=0)
FormattedPrecision($Precision=NULL)