]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/adodb/adodb-time.inc.php
Reformat code
[SourceForge/phpwiki.git] / lib / WikiDB / adodb / adodb-time.inc.php
1 <?php
2 /**
3 ADOdb Date Library, part of the ADOdb abstraction library
4 Download: http://php.weblogs.com/adodb_date_time_library
5
6 PHP native date functions use integer timestamps for computations.
7 Because of this, dates are restricted to the years 1901-2038 on Unix
8 and 1970-2038 on Windows due to integer overflow for dates beyond
9 those years. This library overcomes these limitations by replacing the
10 native function's signed integers (normally 32-bits) with PHP floating
11 point numbers (normally 64-bits).
12
13 Dates from 100 A.D. to 3000 A.D. and later
14 have been tested. The minimum is 100 A.D. as <100 will invoke the
15 2 => 4 digit year conversion. The maximum is billions of years in the
16 future, but this is a theoretical limit as the computation of that year
17 would take too long with the current implementation of adodb_mktime().
18
19 This library replaces native functions as follows:
20
21 <pre>
22 getdate()  with  adodb_getdate()
23 date()     with  adodb_date()
24 gmdate()   with  adodb_gmdate()
25 mktime()   with  adodb_mktime()
26 gmmktime() with  adodb_gmmktime()
27 </pre>
28
29 The parameters are identical, except that adodb_date() accepts a subset
30 of date()'s field formats. Mktime() will convert from local time to GMT,
31 and date() will convert from GMT to local time, but daylight savings is
32 not handled currently.
33
34 This library is independant of the rest of ADOdb, and can be used
35 as standalone code.
36
37 PERFORMANCE
38
39 For high speed, this library uses the native date functions where
40 possible, and only switches to PHP code when the dates fall outside
41 the 32-bit signed integer range.
42
43 GREGORIAN CORRECTION
44
45 Pope Gregory shortened October of A.D. 1582 by ten days. Thursday,
46 October 4, 1582 (Julian) was followed immediately by Friday, October 15,
47 1582 (Gregorian).
48
49 Since 0.06, we handle this correctly, so:
50
51 adodb_mktime(0,0,0,10,15,1582) - adodb_mktime(0,0,0,10,4,1582)
52 == 24 * 3600 (1 day)
53
54 =============================================================================
55
56 COPYRIGHT
57
58 (c) 2003 John Lim and released under BSD-style license except for code by jackbbs,
59 which includes adodb_mktime, adodb_get_gmt_diff, adodb_is_leap_year
60 and originally found at http://www.php.net/manual/en/function.mktime.php
61
62 =============================================================================
63
64 BUG REPORTS
65
66 These should be posted to the ADOdb forums at
67
68 http://phplens.com/lens/lensforum/topics.php?id=4
69
70 =============================================================================
71
72 FUNCTION DESCRIPTIONS
73
74 FUNCTION adodb_getdate($date=false)
75
76 Returns an array containing date information, as getdate(), but supports
77 dates greater than 1901 to 2038.
78
79 FUNCTION adodb_date($fmt, $timestamp = false)
80
81 Convert a timestamp to a formatted local date. If $timestamp is not defined, the
82 current timestamp is used. Unlike the function date(), it supports dates
83 outside the 1901 to 2038 range.
84
85 The format fields that adodb_date supports:
86
87 <pre>
88 a - "am" or "pm"
89 A - "AM" or "PM"
90 d - day of the month, 2 digits with leading zeros; i.e. "01" to "31"
91 D - day of the week, textual, 3 letters; e.g. "Fri"
92 F - month, textual, long; e.g. "January"
93 g - hour, 12-hour format without leading zeros; i.e. "1" to "12"
94 G - hour, 24-hour format without leading zeros; i.e. "0" to "23"
95 h - hour, 12-hour format; i.e. "01" to "12"
96 H - hour, 24-hour format; i.e. "00" to "23"
97 i - minutes; i.e. "00" to "59"
98 j - day of the month without leading zeros; i.e. "1" to "31"
99 l (lowercase 'L') - day of the week, textual, long; e.g. "Friday"
100 L - boolean for whether it is a leap year; i.e. "0" or "1"
101 m - month; i.e. "01" to "12"
102 M - month, textual, 3 letters; e.g. "Jan"
103 n - month without leading zeros; i.e. "1" to "12"
104 O - Difference to Greenwich time in hours; e.g. "+0200"
105 Q - Quarter, as in 1, 2, 3, 4
106 r - RFC 822 formatted date; e.g. "Thu, 21 Dec 2000 16:01:07 +0200"
107 s - seconds; i.e. "00" to "59"
108 S - English ordinal suffix for the day of the month, 2 characters;
109 i.e. "st", "nd", "rd" or "th"
110 t - number of days in the given month; i.e. "28" to "31"
111 T - Timezone setting of this machine; e.g. "EST" or "MDT"
112 U - seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
113 w - day of the week, numeric, i.e. "0" (Sunday) to "6" (Saturday)
114 Y - year, 4 digits; e.g. "1999"
115 y - year, 2 digits; e.g. "99"
116 z - day of the year; i.e. "0" to "365"
117 Z - timezone offset in seconds (i.e. "-43200" to "43200").
118 The offset for timezones west of UTC is always negative,
119 and for those east of UTC is always positive.
120 </pre>
121
122 Unsupported:
123 <pre>
124 B - Swatch Internet time
125 I (capital i) - "1" if Daylight Savings Time, "0" otherwise.
126 W - ISO-8601 week number of year, weeks starting on Monday
127
128 </pre>
129
130 FUNCTION adodb_date2($fmt, $isoDateString = false)
131 Same as adodb_date, but 2nd parameter accepts iso date, eg.
132
133 adodb_date2('d-M-Y H:i','2003-12-25 13:01:34');
134
135 FUNCTION adodb_gmdate($fmt, $timestamp = false)
136
137 Convert a timestamp to a formatted GMT date. If $timestamp is not defined, the
138 current timestamp is used. Unlike the function date(), it supports dates
139 outside the 1901 to 2038 range.
140
141 FUNCTION adodb_mktime($hr, $min, $sec, $month, $day, $year)
142
143 Converts a local date to a unix timestamp.  Unlike the function mktime(), it supports
144 dates outside the 1901 to 2038 range. Differs from mktime() in that all parameters
145 are currently compulsory.
146
147 FUNCTION adodb_gmmktime($hr, $min, $sec, $month, $day, $year)
148
149 Converts a gmt date to a unix timestamp.  Unlike the function gmmktime(), it supports
150 dates outside the 1901 to 2038 range. Differs from gmmktime() in that all parameters
151 are currently compulsory.
152
153 =============================================================================
154
155 NOTES
156
157 Useful url for generating test timestamps:
158 http://www.4webhelp.net/us/timestamp.php
159
160 Possible future optimizations include
161
162 a. Using an algorithm similar to Plauger's in "The Standard C Library"
163 (page 428, xttotm.c _Ttotm() function). Plauger's algorithm will not
164 work outside 32-bit signed range, so i decided not to implement it.
165
166 b. Iterate over a block of years (say 12) when searching for the
167 correct year.
168
169 c. Implement daylight savings, which looks awfully complicated, see
170 http://webexhibits.org/daylightsaving/
171
172 CHANGELOG
173 - 20 Mar 2004 0.12
174 Fixed month calculation error in adodb_date. 2102-June-01 appeared as 2102-May-32.
175
176 - 26 Oct 2003 0.11
177 Because of daylight savings problems (some systems apply daylight savings to
178 January!!!), changed adodb_get_gmt_diff() to ignore daylight savings.
179
180 - 9 Aug 2003 0.10
181 Fixed bug with dates after 2038.
182 See http://phplens.com/lens/lensforum/msgs.php?id=6980
183
184 - 1 July 2003 0.09
185 Added support for Q (Quarter).
186 Added adodb_date2(), which accepts ISO date in 2nd param
187
188 - 3 March 2003 0.08
189 Added support for 'S' adodb_date() format char. Added constant ADODB_ALLOW_NEGATIVE_TS
190 if you want PHP to handle negative timestamps between 1901 to 1969.
191
192 - 27 Feb 2003 0.07
193 All negative numbers handled by adodb now because of RH 7.3+ problems.
194 See http://bugs.php.net/bug.php?id=20048&edit=2
195
196 - 4 Feb 2003 0.06
197 Fixed a typo, 1852 changed to 1582! This means that pre-1852 dates
198 are now correctly handled.
199
200 - 29 Jan 2003 0.05
201
202 Leap year checking differs under Julian calendar (pre 1582). Also
203 leap year code optimized by checking for most common case first.
204
205 We also handle month overflow correctly in mktime (eg month set to 13).
206
207 Day overflow for less than one month's days is supported.
208
209 - 28 Jan 2003 0.04
210
211 Gregorian correction handled. In PHP5, we might throw an error if
212 mktime uses invalid dates around 5-14 Oct 1582. Released with ADOdb 3.10.
213 Added limbo 5-14 Oct 1582 check, when we set to 15 Oct 1582.
214
215 - 27 Jan 2003 0.03
216
217 Fixed some more month problems due to gmt issues. Added constant ADODB_DATE_VERSION.
218 Fixed calculation of days since start of year for <1970.
219
220 - 27 Jan 2003 0.02
221
222 Changed _adodb_getdate() to inline leap year checking for better performance.
223 Fixed problem with time-zones west of GMT +0000.
224
225 - 24 Jan 2003 0.01
226
227 First implementation.
228  */
229
230
231 /* Initialization */
232
233 /*
234     Version Number
235 */
236 define('ADODB_DATE_VERSION', 0.12);
237
238 /*
239     We check for Windows as only +ve ints are accepted as dates on Windows.
240
241     Apparently this problem happens also with Linux, RH 7.3 and later!
242
243     glibc-2.2.5-34 and greater has been changed to return -1 for dates <
244     1970.  This used to work.  The problem exists with RedHat 7.3 and 8.0
245     echo (mktime(0, 0, 0, 1, 1, 1960));  // prints -1
246
247     References:
248      http://bugs.php.net/bug.php?id=20048&edit=2
249      http://lists.debian.org/debian-glibc/2002/debian-glibc-200205/msg00010.html
250 */
251
252 if (!defined('ADODB_ALLOW_NEGATIVE_TS')) define('ADODB_NO_NEGATIVE_TS', 1);
253
254 function adodb_date_test_date($y1, $m)
255 {
256     //print " $y1/$m ";
257     $t = adodb_mktime(0, 0, 0, $m, 13, $y1);
258     if ("$y1-$m-13 00:00:00" != adodb_date('Y-n-d H:i:s', $t)) {
259         print "<b>$y1 error</b><br>";
260         return false;
261     }
262     return true;
263 }
264
265 /**
266 Test Suite
267  */
268 function adodb_date_test()
269 {
270
271     error_reporting(E_ALL);
272     print "<h4>Testing adodb_date and adodb_mktime. version=" . ADODB_DATE_VERSION . "</h4>";
273     set_time_limit(0);
274     $fail = false;
275
276     // This flag disables calling of PHP native functions, so we can properly test the code
277     if (!defined('ADODB_TEST_DATES')) define('ADODB_TEST_DATES', 1);
278
279     $t = adodb_mktime(0, 0, 0, 6, 1, 2102);
280     if (!(adodb_date('Y-m-d', $t) == '2102-06-01')) print 'Error in ' . adodb_date('Y-m-d', $t) . '<br>';
281
282     $t = adodb_mktime(0, 0, 0, 2, 1, 2102);
283     if (!(adodb_date('Y-m-d', $t) == '2102-02-01')) print 'Error in ' . adodb_date('Y-m-d', $t) . '<br>';
284
285
286     print "<p>Testing gregorian <=> julian conversion<p>";
287     $t = adodb_mktime(0, 0, 0, 10, 11, 1492);
288     //http://www.holidayorigins.com/html/columbus_day.html - Friday check
289     if (!(adodb_date('D Y-m-d', $t) == 'Fri 1492-10-11')) print 'Error in Columbus landing<br>';
290
291     $t = adodb_mktime(0, 0, 0, 2, 29, 1500);
292     if (!(adodb_date('Y-m-d', $t) == '1500-02-29')) print 'Error in julian leap years<br>';
293
294     $t = adodb_mktime(0, 0, 0, 2, 29, 1700);
295     if (!(adodb_date('Y-m-d', $t) == '1700-03-01')) print 'Error in gregorian leap years<br>';
296
297     print  adodb_mktime(0, 0, 0, 10, 4, 1582) . ' ';
298     print adodb_mktime(0, 0, 0, 10, 15, 1582);
299     $diff = (adodb_mktime(0, 0, 0, 10, 15, 1582) - adodb_mktime(0, 0, 0, 10, 4, 1582));
300     if ($diff != 3600 * 24) print " <b>Error in gregorian correction = " . ($diff / 3600 / 24) . " days </b><br>";
301
302     print " 15 Oct 1582, Fri=" . (adodb_dow(1582, 10, 15) == 5 ? 'Fri' : '<b>Error</b>') . "<br>";
303     print " 4 Oct 1582, Thu=" . (adodb_dow(1582, 10, 4) == 4 ? 'Thu' : '<b>Error</b>') . "<br>";
304
305     print "<p>Testing overflow<p>";
306
307     $t = adodb_mktime(0, 0, 0, 3, 33, 1965);
308     if (!(adodb_date('Y-m-d', $t) == '1965-04-02')) print 'Error in day overflow 1 <br>';
309     $t = adodb_mktime(0, 0, 0, 4, 33, 1971);
310     if (!(adodb_date('Y-m-d', $t) == '1971-05-03')) print 'Error in day overflow 2 <br>';
311     $t = adodb_mktime(0, 0, 0, 1, 60, 1965);
312     if (!(adodb_date('Y-m-d', $t) == '1965-03-01')) print 'Error in day overflow 3 ' . adodb_date('Y-m-d', $t) . ' <br>';
313     $t = adodb_mktime(0, 0, 0, 12, 32, 1965);
314     if (!(adodb_date('Y-m-d', $t) == '1966-01-01')) print 'Error in day overflow 4 ' . adodb_date('Y-m-d', $t) . ' <br>';
315     $t = adodb_mktime(0, 0, 0, 12, 63, 1965);
316     if (!(adodb_date('Y-m-d', $t) == '1966-02-01')) print 'Error in day overflow 5 ' . adodb_date('Y-m-d', $t) . ' <br>';
317     $t = adodb_mktime(0, 0, 0, 13, 3, 1965);
318     if (!(adodb_date('Y-m-d', $t) == '1966-01-03')) print 'Error in mth overflow 1 <br>';
319
320     print "Testing 2-digit => 4-digit year conversion<p>";
321     if (adodb_year_digit_check(00) != 2000) print "Err 2-digit 2000<br>";
322     if (adodb_year_digit_check(10) != 2010) print "Err 2-digit 2010<br>";
323     if (adodb_year_digit_check(20) != 2020) print "Err 2-digit 2020<br>";
324     if (adodb_year_digit_check(30) != 2030) print "Err 2-digit 2030<br>";
325     if (adodb_year_digit_check(40) != 1940) print "Err 2-digit 1940<br>";
326     if (adodb_year_digit_check(50) != 1950) print "Err 2-digit 1950<br>";
327     if (adodb_year_digit_check(90) != 1990) print "Err 2-digit 1990<br>";
328
329     // Test string formating
330     print "<p>Testing date formating</p>";
331     $fmt = '\d\a\t\e T Y-m-d H:i:s a A d D F g G h H i j l L m M n O \R\F\C822 r s t U w y Y z Z 2003';
332     $s1 = date($fmt, 0);
333     $s2 = adodb_date($fmt, 0);
334     if ($s1 != $s2) {
335         print " date() 0 failed<br>$s1<br>$s2<br>";
336     }
337     flush();
338     for ($i = 100; --$i > 0;) {
339
340         $ts = 3600.0 * ((rand() % 60000) + (rand() % 60000)) + (rand() % 60000);
341         $s1 = date($fmt, $ts);
342         $s2 = adodb_date($fmt, $ts);
343         //print "$s1 <br>$s2 <p>";
344         $pos = strcmp($s1, $s2);
345
346         if (($s1) != ($s2)) {
347             for ($j = 0, $k = strlen($s1); $j < $k; $j++) {
348                 if ($s1[$j] != $s2[$j]) {
349                     print substr($s1, $j) . ' ';
350                     break;
351                 }
352             }
353             print "<b>Error date(): $ts<br><pre>
354 &nbsp; \"$s1\" (date len=" . strlen($s1) . ")
355 &nbsp; \"$s2\" (adodb_date len=" . strlen($s2) . ")</b></pre><br>";
356             $fail = true;
357         }
358
359         $a1 = getdate($ts);
360         $a2 = adodb_getdate($ts);
361         $rez = array_diff($a1, $a2);
362         if (sizeof($rez) > 0) {
363             print "<b>Error getdate() $ts</b><br>";
364             print_r($a1);
365             print "<br>";
366             print_r($a2);
367             print "<p>";
368             $fail = true;
369         }
370     }
371
372     // Test generation of dates outside 1901-2038
373     print "<p>Testing random dates between 100 and 4000</p>";
374     adodb_date_test_date(100, 1);
375     for ($i = 100; --$i >= 0;) {
376         $y1 = 100 + rand(0, 1970 - 100);
377         $m = rand(1, 12);
378         adodb_date_test_date($y1, $m);
379
380         $y1 = 3000 - rand(0, 3000 - 1970);
381         adodb_date_test_date($y1, $m);
382     }
383     print '<p>';
384     $start = 1960 + rand(0, 10);
385     $yrs = 12;
386     $i = 365.25 * 86400 * ($start - 1970);
387     $offset = 36000 + rand(10000, 60000);
388     $max = 365 * $yrs * 86400;
389     $lastyear = 0;
390
391     // we generate a timestamp, convert it to a date, and convert it back to a timestamp
392     // and check if the roundtrip broke the original timestamp value.
393     print "Testing $start to " . ($start + $yrs) . ", or $max seconds, offset=$offset: ";
394
395     for ($max += $i; $i < $max; $i += $offset) {
396         $ret = adodb_date('m,d,Y,H,i,s', $i);
397         $arr = explode(',', $ret);
398         if ($lastyear != $arr[2]) {
399             $lastyear = $arr[2];
400             print " $lastyear ";
401             flush();
402         }
403         $newi = adodb_mktime($arr[3], $arr[4], $arr[5], $arr[0], $arr[1], $arr[2]);
404         if ($i != $newi) {
405             print "Error at $i, adodb_mktime returned $newi ($ret)";
406             $fail = true;
407             break;
408         }
409     }
410
411     if (!$fail) print "<p>Passed !</p>";
412     else print "<p><b>Failed</b> :-(</p>";
413 }
414
415 /**
416 Returns day of week, 0 = Sunday,... 6=Saturday.
417 Algorithm from PEAR::Date_Calc
418  */
419 function adodb_dow($year, $month, $day)
420 {
421     /*
422     Pope Gregory removed 10 days - October 5 to October 14 - from the year 1582 and
423     proclaimed that from that time onwards 3 days would be dropped from the calendar
424     every 400 years.
425
426     Thursday, October 4, 1582 (Julian) was followed immediately by Friday, October 15, 1582 (Gregorian).
427     */
428     if ($year <= 1582) {
429         if ($year < 1582 ||
430             ($year == 1582 && ($month < 10 || ($month == 10 && $day < 15)))
431         ) $greg_correction = 3;
432         else
433             $greg_correction = 0;
434     } else
435         $greg_correction = 0;
436
437     if ($month > 2)
438         $month -= 2;
439     else {
440         $month += 10;
441         $year--;
442     }
443
444     $day = (floor((13 * $month - 1) / 5) +
445         $day + ($year % 100) +
446         floor(($year % 100) / 4) +
447         floor(($year / 100) / 4) - 2 *
448         floor($year / 100) + 77);
449
450     return (($day - 7 * floor($day / 7))) + $greg_correction;
451 }
452
453
454 /**
455 Checks for leap year, returns true if it is. No 2-digit year check. Also
456 handles julian calendar correctly.
457  */
458 function _adodb_is_leap_year($year)
459 {
460     if ($year % 4 != 0) return false;
461
462     if ($year % 400 == 0) {
463         return true;
464         // if gregorian calendar (>1582), century not-divisible by 400 is not leap
465     } elseif ($year > 1582 && $year % 100 == 0) {
466         return false;
467     }
468
469     return true;
470 }
471
472 /**
473 checks for leap year, returns true if it is. Has 2-digit year check
474  */
475 function adodb_is_leap_year($year)
476 {
477     return _adodb_is_leap_year(adodb_year_digit_check($year));
478 }
479
480 /**
481 Fix 2-digit years. Works for any century.
482 Assumes that if 2-digit is more than 30 years in future, then previous century.
483  */
484 function adodb_year_digit_check($y)
485 {
486     if ($y < 100) {
487
488         $yr = (integer)date("Y");
489         $century = (integer)($yr / 100);
490
491         if ($yr % 100 > 50) {
492             $c1 = $century + 1;
493             $c0 = $century;
494         } else {
495             $c1 = $century;
496             $c0 = $century - 1;
497         }
498         $c1 *= 100;
499         // if 2-digit year is less than 30 years in future, set it to this century
500         // otherwise if more than 30 years in future, then we set 2-digit year to the prev century.
501         if (($y + $c1) < $yr + 30) $y = $y + $c1;
502         else $y = $y + $c0 * 100;
503     }
504     return $y;
505 }
506
507 /**
508 get local time zone offset from GMT
509  */
510 function adodb_get_gmt_diff()
511 {
512     static $TZ;
513     if (isset($TZ)) return $TZ;
514
515     $TZ = mktime(0, 0, 0, 1, 2, 1970, 0) - gmmktime(0, 0, 0, 1, 2, 1970, 0);
516     return $TZ;
517 }
518
519 /**
520 Returns an array with date info.
521  */
522 function adodb_getdate($d = false, $fast = false)
523 {
524     if ($d === false) return getdate();
525     if (!defined('ADODB_TEST_DATES')) {
526         if ((abs($d) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
527             if (!defined('ADODB_NO_NEGATIVE_TS') || $d >= 0) // if windows, must be +ve integer
528                 return @getdate($d);
529         }
530     }
531     return _adodb_getdate($d);
532 }
533
534 /**
535 Low-level function that returns the getdate() array. We have a special
536 $fast flag, which if set to true, will return fewer array values,
537 and is much faster as it does not calculate dow, etc.
538  */
539 function _adodb_getdate($origd = false, $fast = false, $is_gmt = false)
540 {
541     $d = $origd - ($is_gmt ? 0 : adodb_get_gmt_diff());
542
543     $_day_power = 86400;
544     $_hour_power = 3600;
545     $_min_power = 60;
546
547     if ($d < -12219321600) $d -= 86400 * 10; // if 15 Oct 1582 or earlier, gregorian correction
548
549     $_month_table_normal = array("", 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
550     $_month_table_leaf = array("", 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
551
552     if ($d < 0) {
553         $origd = $d;
554         // The valid range of a 32bit signed timestamp is typically from
555         // Fri, 13 Dec 1901 20:45:54 GMT to Tue, 19 Jan 2038 03:14:07 GMT
556         for ($a = 1970; --$a >= 0;) {
557             $lastd = $d;
558
559             if ($leaf = _adodb_is_leap_year($a)) {
560                 $d += $_day_power * 366;
561             } else
562                 $d += $_day_power * 365;
563             if ($d >= 0) {
564                 $year = $a;
565                 break;
566             }
567         }
568
569         $secsInYear = 86400 * ($leaf ? 366 : 365) + $lastd;
570
571         $d = $lastd;
572         $mtab = ($leaf) ? $_month_table_leaf : $_month_table_normal;
573         for ($a = 13; --$a > 0;) {
574             $lastd = $d;
575             $d += $mtab[$a] * $_day_power;
576             if ($d >= 0) {
577                 $month = $a;
578                 $ndays = $mtab[$a];
579                 break;
580             }
581         }
582
583         $d = $lastd;
584         $day = $ndays + ceil(($d + 1) / ($_day_power));
585
586         $d += ($ndays - $day + 1) * $_day_power;
587         $hour = floor($d / $_hour_power);
588
589     } else {
590
591         for ($a = 1970; ; $a++) {
592             $lastd = $d;
593
594             if ($leaf = _adodb_is_leap_year($a)) {
595                 $d -= $_day_power * 366;
596             } else
597                 $d -= $_day_power * 365;
598             if ($d < 0) {
599                 $year = $a;
600                 break;
601             }
602         }
603         $secsInYear = $lastd;
604         $d = $lastd;
605         $mtab = ($leaf) ? $_month_table_leaf : $_month_table_normal;
606         for ($a = 1; $a <= 12; $a++) {
607             $lastd = $d;
608             $d -= $mtab[$a] * $_day_power;
609             if ($d < 0) {
610                 $month = $a;
611                 $ndays = $mtab[$a];
612                 break;
613             }
614         }
615         $d = $lastd;
616         $day = ceil(($d + 1) / $_day_power);
617         $d = $d - ($day - 1) * $_day_power;
618         $hour = floor($d / $_hour_power);
619     }
620
621     $d -= $hour * $_hour_power;
622     $min = floor($d / $_min_power);
623     $secs = $d - $min * $_min_power;
624     if ($fast) {
625         return array(
626             'seconds' => $secs,
627             'minutes' => $min,
628             'hours' => $hour,
629             'mday' => $day,
630             'mon' => $month,
631             'year' => $year,
632             'yday' => floor($secsInYear / $_day_power),
633             'leap' => $leaf,
634             'ndays' => $ndays
635         );
636     }
637
638
639     $dow = adodb_dow($year, $month, $day);
640
641     return array(
642         'seconds' => $secs,
643         'minutes' => $min,
644         'hours' => $hour,
645         'mday' => $day,
646         'wday' => $dow,
647         'mon' => $month,
648         'year' => $year,
649         'yday' => floor($secsInYear / $_day_power),
650         'weekday' => gmdate('l', $_day_power * (3 + $dow)),
651         'month' => gmdate('F', mktime(0, 0, 0, $month, 2, 1971)),
652         0 => $origd
653     );
654 }
655
656 function adodb_gmdate($fmt, $d = false)
657 {
658     return adodb_date($fmt, $d, true);
659 }
660
661 function adodb_date2($fmt, $d = false, $is_gmt = false)
662 {
663     if ($d !== false) {
664         if (!preg_match(
665             "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ -]?(([0-9]{1,2}):?([0-9]{1,2}):?([0-9\.]{1,4}))?|",
666             ($d), $rr)
667         ) return adodb_date($fmt, false, $is_gmt);
668
669         if ($rr[1] <= 100 && $rr[2] <= 1) return adodb_date($fmt, false, $is_gmt);
670
671         // h-m-s-MM-DD-YY
672         if (!isset($rr[5])) $d = adodb_mktime(0, 0, 0, $rr[2], $rr[3], $rr[1]);
673         else $d = @adodb_mktime($rr[5], $rr[6], $rr[7], $rr[2], $rr[3], $rr[1]);
674     }
675
676     return adodb_date($fmt, $d, $is_gmt);
677 }
678
679 /**
680 Return formatted date based on timestamp $d
681  */
682 function adodb_date($fmt, $d = false, $is_gmt = false)
683 {
684     if ($d === false) return date($fmt);
685     if (!defined('ADODB_TEST_DATES')) {
686         if ((abs($d) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
687             if (!defined('ADODB_NO_NEGATIVE_TS') || $d >= 0) // if windows, must be +ve integer
688                 return @date($fmt, $d);
689         }
690     }
691     $_day_power = 86400;
692
693     $arr = _adodb_getdate($d, true, $is_gmt);
694     $year = $arr['year'];
695     $month = $arr['mon'];
696     $day = $arr['mday'];
697     $hour = $arr['hours'];
698     $min = $arr['minutes'];
699     $secs = $arr['seconds'];
700
701     $max = strlen($fmt);
702     $dates = '';
703
704     /*
705         at this point, we have the following integer vars to manipulate:
706         $year, $month, $day, $hour, $min, $secs
707     */
708     for ($i = 0; $i < $max; $i++) {
709         switch ($fmt[$i]) {
710             case 'T':
711                 $dates .= date('T');
712                 break;
713             // YEAR
714             case 'L':
715                 $dates .= $arr['leap'] ? '1' : '0';
716                 break;
717             case 'r': // Thu, 21 Dec 2000 16:01:07 +0200
718
719                 $dates .= gmdate('D', $_day_power * (3 + adodb_dow($year, $month, $day))) . ', '
720                     . ($day < 10 ? ' ' . $day : $day) . ' ' . date('M', mktime(0, 0, 0, $month, 2, 1971)) . ' ' . $year . ' ';
721
722                 if ($hour < 10) $dates .= '0' . $hour; else $dates .= $hour;
723
724                 if ($min < 10) $dates .= ':0' . $min; else $dates .= ':' . $min;
725
726                 if ($secs < 10) $dates .= ':0' . $secs; else $dates .= ':' . $secs;
727
728                 $gmt = adodb_get_gmt_diff();
729                 $dates .= sprintf(' %s%04d', ($gmt < 0) ? '+' : '-', abs($gmt) / 36);
730                 break;
731
732             case 'Y':
733                 $dates .= $year;
734                 break;
735             case 'y':
736                 $dates .= substr($year, strlen($year) - 2, 2);
737                 break;
738             // MONTH
739             case 'm':
740                 if ($month < 10) $dates .= '0' . $month; else $dates .= $month;
741                 break;
742             case 'Q':
743                 $dates .= ($month + 3) >> 2;
744                 break;
745             case 'n':
746                 $dates .= $month;
747                 break;
748             case 'M':
749                 $dates .= date('M', mktime(0, 0, 0, $month, 2, 1971));
750                 break;
751             case 'F':
752                 $dates .= date('F', mktime(0, 0, 0, $month, 2, 1971));
753                 break;
754             // DAY
755             case 't':
756                 $dates .= $arr['ndays'];
757                 break;
758             case 'z':
759                 $dates .= $arr['yday'];
760                 break;
761             case 'w':
762                 $dates .= adodb_dow($year, $month, $day);
763                 break;
764             case 'l':
765                 $dates .= gmdate('l', $_day_power * (3 + adodb_dow($year, $month, $day)));
766                 break;
767             case 'D':
768                 $dates .= gmdate('D', $_day_power * (3 + adodb_dow($year, $month, $day)));
769                 break;
770             case 'j':
771                 $dates .= $day;
772                 break;
773             case 'd':
774                 if ($day < 10) $dates .= '0' . $day; else $dates .= $day;
775                 break;
776             case 'S':
777                 $d10 = $day % 10;
778                 if ($d10 == 1) $dates .= 'st';
779                 else if ($d10 == 2) $dates .= 'nd';
780                 else if ($d10 == 3) $dates .= 'rd';
781                 else $dates .= 'th';
782                 break;
783
784             // HOUR
785             case 'Z':
786                 $dates .= ($is_gmt) ? 0 : -adodb_get_gmt_diff();
787                 break;
788             case 'O':
789                 $gmt = ($is_gmt) ? 0 : adodb_get_gmt_diff();
790                 $dates .= sprintf('%s%04d', ($gmt < 0) ? '+' : '-', abs($gmt) / 36);
791                 break;
792
793             case 'H':
794                 if ($hour < 10) $dates .= '0' . $hour;
795                 else $dates .= $hour;
796                 break;
797             case 'h':
798                 if ($hour > 12) $hh = $hour - 12;
799                 else {
800                     if ($hour == 0) $hh = '12';
801                     else $hh = $hour;
802                 }
803
804                 if ($hh < 10) $dates .= '0' . $hh;
805                 else $dates .= $hh;
806                 break;
807
808             case 'G':
809                 $dates .= $hour;
810                 break;
811
812             case 'g':
813                 if ($hour > 12) $hh = $hour - 12;
814                 else {
815                     if ($hour == 0) $hh = '12';
816                     else $hh = $hour;
817                 }
818                 $dates .= $hh;
819                 break;
820             // MINUTES
821             case 'i':
822                 if ($min < 10) $dates .= '0' . $min; else $dates .= $min;
823                 break;
824             // SECONDS
825             case 'U':
826                 $dates .= $d;
827                 break;
828             case 's':
829                 if ($secs < 10) $dates .= '0' . $secs; else $dates .= $secs;
830                 break;
831             // AM/PM
832             // Note 00:00 to 11:59 is AM, while 12:00 to 23:59 is PM
833             case 'a':
834                 if ($hour >= 12) $dates .= 'pm';
835                 else $dates .= 'am';
836                 break;
837             case 'A':
838                 if ($hour >= 12) $dates .= 'PM';
839                 else $dates .= 'AM';
840                 break;
841             default:
842                 $dates .= $fmt[$i];
843                 break;
844             // ESCAPE
845             case "\\":
846                 $i++;
847                 if ($i < $max) $dates .= $fmt[$i];
848                 break;
849         }
850     }
851     return $dates;
852 }
853
854 /**
855 Returns a timestamp given a GMT/UTC time.
856 Note that $is_dst is not implemented and is ignored.
857  */
858 function adodb_gmmktime($hr, $min, $sec, $mon, $day, $year, $is_dst = false)
859 {
860     return adodb_mktime($hr, $min, $sec, $mon, $day, $year, $is_dst, true);
861 }
862
863 /**
864 Return a timestamp given a local time. Originally by jackbbs.
865 Note that $is_dst is not implemented and is ignored.
866  */
867 function adodb_mktime($hr, $min, $sec, $mon, $day, $year, $is_dst = false, $is_gmt = false)
868 {
869     if (!defined('ADODB_TEST_DATES')) {
870         // for windows, we don't check 1970 because with timezone differences,
871         // 1 Jan 1970 could generate negative timestamp, which is illegal
872         if (!defined('ADODB_NO_NEGATIVE_TS') || ($year >= 1971))
873             if (1901 < $year && $year < 2038)
874                 return @mktime($hr, $min, $sec, $mon, $day, $year);
875     }
876
877     $gmt_different = ($is_gmt) ? 0 : adodb_get_gmt_diff();
878
879     $hr = intval($hr);
880     $min = intval($min);
881     $sec = intval($sec);
882     $mon = intval($mon);
883     $day = intval($day);
884     $year = intval($year);
885
886     $year = adodb_year_digit_check($year);
887
888     if ($mon > 12) {
889         $y = floor($mon / 12);
890         $year += $y;
891         $mon -= $y * 12;
892     }
893
894     $_day_power = 86400;
895     $_hour_power = 3600;
896     $_min_power = 60;
897
898     $_month_table_normal = array("", 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
899     $_month_table_leaf = array("", 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
900
901     $_total_date = 0;
902     if ($year >= 1970) {
903         for ($a = 1970; $a <= $year; $a++) {
904             $leaf = _adodb_is_leap_year($a);
905             if ($leaf == true) {
906                 $loop_table = $_month_table_leaf;
907                 $_add_date = 366;
908             } else {
909                 $loop_table = $_month_table_normal;
910                 $_add_date = 365;
911             }
912             if ($a < $year) {
913                 $_total_date += $_add_date;
914             } else {
915                 for ($b = 1; $b < $mon; $b++) {
916                     $_total_date += $loop_table[$b];
917                 }
918             }
919         }
920         $_total_date += $day - 1;
921         $ret = $_total_date * $_day_power + $hr * $_hour_power + $min * $_min_power + $sec + $gmt_different;
922
923     } else {
924         for ($a = 1969; $a >= $year; $a--) {
925             $leaf = _adodb_is_leap_year($a);
926             if ($leaf == true) {
927                 $loop_table = $_month_table_leaf;
928                 $_add_date = 366;
929             } else {
930                 $loop_table = $_month_table_normal;
931                 $_add_date = 365;
932             }
933             if ($a > $year) {
934                 $_total_date += $_add_date;
935             } else {
936                 for ($b = 12; $b > $mon; $b--) {
937                     $_total_date += $loop_table[$b];
938                 }
939             }
940         }
941         $_total_date += $loop_table[$mon] - $day;
942
943         $_day_time = $hr * $_hour_power + $min * $_min_power + $sec;
944         $_day_time = $_day_power - $_day_time;
945         $ret = -($_total_date * $_day_power + $_day_time - $gmt_different);
946         if ($ret < -12220185600) $ret += 10 * 86400; // if earlier than 5 Oct 1582 - gregorian correction
947         else if ($ret < -12219321600) $ret = -12219321600; // if in limbo, reset to 15 Oct 1582.
948     }
949     //print " dmy=$day/$mon/$year $hr:$min:$sec => " .$ret;
950     return $ret;
951 }