1<?php
2/**
3 * ADOdb Date Library.
4 *
5 * PHP native date functions use integer timestamps for computations.
6 * Because of this, dates are restricted to the years 1901-2038 on Unix
7 * and 1970-2038 on Windows due to integer overflow for dates beyond
8 * those years. This library overcomes these limitations by replacing the
9 * native function's signed integers (normally 32-bits) with PHP floating
10 * point numbers (normally 64-bits).
11 *
12 * Dates from 100 A.D. to 3000 A.D. and later have been tested.
13 * The minimum is 100 A.D. as <100 will invoke the 2 => 4 digit year
14 * conversion. The maximum is billions of years in the future, but this
15 * is a theoretical limit as the computation of that year would take too
16 * long with the current implementation of adodb_mktime().
17 *
18 * Replaces native functions as follows:
19 * - getdate()  with  adodb_getdate()
20 * - date()     with  adodb_date()
21 * - gmdate()   with  adodb_gmdate()
22 * - mktime()   with  adodb_mktime()
23 * - gmmktime() with  adodb_gmmktime()
24 * - strftime() with  adodb_strftime()
25 * - strftime() with  adodb_gmstrftime()
26 *
27 * The parameters are identical, except that adodb_date() accepts a subset
28 * of date()'s field formats. Mktime() will convert from local time to GMT,
29 * and date() will convert from GMT to local time, but daylight savings is
30 * not handled currently.
31 *
32 * To improve performance, the native date functions are used whenever
33 * possible, the library only switches to PHP code when the dates fall outside
34 * of the 32-bit signed integer range.
35 *
36 * This library is independent of the rest of ADOdb, and can be used
37 * as standalone code.
38 *
39 * GREGORIAN CORRECTION
40 *
41 * Pope Gregory shortened October of A.D. 1582 by ten days. Thursday,
42 * October 4, 1582 (Julian) was followed immediately by Friday, October 15,
43 * 1582 (Gregorian). We handle this correctly, so:
44 * adodb_mktime(0, 0, 0, 10, 15, 1582) - adodb_mktime(0, 0, 0, 10, 4, 1582)
45 * == 24 * 3600 (1 day)
46 *
47 * This file is part of ADOdb, a Database Abstraction Layer library for PHP.
48 *
49 * @package ADOdb
50 * @link https://adodb.org Project's web site and documentation
51 * @link https://github.com/ADOdb/ADOdb Source code and issue tracker
52 *
53 * The ADOdb Library is dual-licensed, released under both the BSD 3-Clause
54 * and the GNU Lesser General Public Licence (LGPL) v2.1 or, at your option,
55 * any later version. This means you can use it in proprietary products.
56 * See the LICENSE.md file distributed with this source code for details.
57 * @license BSD-3-Clause
58 * @license LGPL-2.1-or-later
59 *
60 * @copyright 2003-2013 John Lim
61 * @copyright 2014 Damien Regad, Mark Newnham and the ADOdb community
62 */
63
64/*
65=============================================================================
66
67FUNCTION DESCRIPTIONS
68
69** FUNCTION adodb_time()
70
71Returns the current time measured in the number of seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) as an unsigned integer.
72
73** FUNCTION adodb_getdate($date=false)
74
75Returns an array containing date information, as getdate(), but supports
76dates greater than 1901 to 2038. The local date/time format is derived from a
77heuristic the first time adodb_getdate is called.
78
79
80** FUNCTION adodb_date($fmt, $timestamp = false)
81
82Convert a timestamp to a formatted local date. If $timestamp is not defined, the
83current timestamp is used. Unlike the function date(), it supports dates
84outside the 1901 to 2038 range.
85
86The format fields that adodb_date supports:
87
88<pre>
89	a - "am" or "pm"
90	A - "AM" or "PM"
91	d - day of the month, 2 digits with leading zeros; i.e. "01" to "31"
92	D - day of the week, textual, 3 letters; e.g. "Fri"
93	F - month, textual, long; e.g. "January"
94	g - hour, 12-hour format without leading zeros; i.e. "1" to "12"
95	G - hour, 24-hour format without leading zeros; i.e. "0" to "23"
96	h - hour, 12-hour format; i.e. "01" to "12"
97	H - hour, 24-hour format; i.e. "00" to "23"
98	i - minutes; i.e. "00" to "59"
99	j - day of the month without leading zeros; i.e. "1" to "31"
100	l (lowercase 'L') - day of the week, textual, long; e.g. "Friday"
101	L - boolean for whether it is a leap year; i.e. "0" or "1"
102	m - month; i.e. "01" to "12"
103	M - month, textual, 3 letters; e.g. "Jan"
104	n - month without leading zeros; i.e. "1" to "12"
105	O - Difference to Greenwich time in hours; e.g. "+0200"
106	Q - Quarter, as in 1, 2, 3, 4
107	r - RFC 2822 formatted date; e.g. "Thu, 21 Dec 2000 16:01:07 +0200"
108	s - seconds; i.e. "00" to "59"
109	S - English ordinal suffix for the day of the month, 2 characters;
110	   			i.e. "st", "nd", "rd" or "th"
111	t - number of days in the given month; i.e. "28" to "31"
112	T - Timezone setting of this machine; e.g. "EST" or "MDT"
113	U - seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
114	w - day of the week, numeric, i.e. "0" (Sunday) to "6" (Saturday)
115	Y - year, 4 digits; e.g. "1999"
116	y - year, 2 digits; e.g. "99"
117	z - day of the year; i.e. "0" to "365"
118	Z - timezone offset in seconds (i.e. "-43200" to "43200").
119	   			The offset for timezones west of UTC is always negative,
120				and for those east of UTC is always positive.
121</pre>
122
123Unsupported:
124<pre>
125	B - Swatch Internet time
126	I (capital i) - "1" if Daylight Savings Time, "0" otherwise.
127	W - ISO-8601 week number of year, weeks starting on Monday
128
129</pre>
130
131
132** FUNCTION adodb_date2($fmt, $isoDateString = false)
133Same as adodb_date, but 2nd parameter accepts iso date, eg.
134
135  adodb_date2('d-M-Y H:i','2003-12-25 13:01:34');
136
137
138** FUNCTION adodb_gmdate($fmt, $timestamp = false)
139
140Convert a timestamp to a formatted GMT date. If $timestamp is not defined, the
141current timestamp is used. Unlike the function date(), it supports dates
142outside the 1901 to 2038 range.
143
144
145** FUNCTION adodb_mktime($hr, $min, $sec[, $month, $day, $year])
146
147Converts a local date to a unix timestamp.  Unlike the function mktime(), it supports
148dates outside the 1901 to 2038 range. All parameters are optional.
149
150
151** FUNCTION adodb_gmmktime($hr, $min, $sec [, $month, $day, $year])
152
153Converts a gmt date to a unix timestamp.  Unlike the function gmmktime(), it supports
154dates outside the 1901 to 2038 range. Differs from gmmktime() in that all parameters
155are currently compulsory.
156
157** FUNCTION adodb_gmstrftime($fmt, $timestamp = false)
158Convert a timestamp to a formatted GMT date.
159
160** FUNCTION adodb_strftime($fmt, $timestamp = false)
161
162Convert a timestamp to a formatted local date. Internally converts $fmt into
163adodb_date format, then echo result.
164
165For best results, you can define the local date format yourself. Define a global
166variable $ADODB_DATE_LOCALE which is an array, 1st element is date format using
167adodb_date syntax, and 2nd element is the time format, also in adodb_date syntax.
168
169    eg. $ADODB_DATE_LOCALE = array('d/m/Y','H:i:s');
170
171	Supported format codes:
172
173<pre>
174	%a - abbreviated weekday name according to the current locale
175	%A - full weekday name according to the current locale
176	%b - abbreviated month name according to the current locale
177	%B - full month name according to the current locale
178	%c - preferred date and time representation for the current locale
179	%d - day of the month as a decimal number (range 01 to 31)
180	%D - same as %m/%d/%y
181	%e - day of the month as a decimal number, a single digit is preceded by a space (range ' 1' to '31')
182	%h - same as %b
183	%H - hour as a decimal number using a 24-hour clock (range 00 to 23)
184	%I - hour as a decimal number using a 12-hour clock (range 01 to 12)
185	%m - month as a decimal number (range 01 to 12)
186	%M - minute as a decimal number
187	%n - newline character
188	%p - either `am' or `pm' according to the given time value, or the corresponding strings for the current locale
189	%r - time in a.m. and p.m. notation
190	%R - time in 24 hour notation
191	%S - second as a decimal number
192	%t - tab character
193	%T - current time, equal to %H:%M:%S
194	%x - preferred date representation for the current locale without the time
195	%X - preferred time representation for the current locale without the date
196	%y - year as a decimal number without a century (range 00 to 99)
197	%Y - year as a decimal number including the century
198	%Z - time zone or name or abbreviation
199	%% - a literal `%' character
200</pre>
201
202	Unsupported codes:
203<pre>
204	%C - century number (the year divided by 100 and truncated to an integer, range 00 to 99)
205	%g - like %G, but without the century.
206	%G - The 4-digit year corresponding to the ISO week number (see %V).
207	     This has the same format and value as %Y, except that if the ISO week number belongs
208		 to the previous or next year, that year is used instead.
209	%j - day of the year as a decimal number (range 001 to 366)
210	%u - weekday as a decimal number [1,7], with 1 representing Monday
211	%U - week number of the current year as a decimal number, starting
212	    with the first Sunday as the first day of the first week
213	%V - The ISO 8601:1988 week number of the current year as a decimal number,
214	     range 01 to 53, where week 1 is the first week that has at least 4 days in the
215		 current year, and with Monday as the first day of the week. (Use %G or %g for
216		 the year component that corresponds to the week number for the specified timestamp.)
217	%w - day of the week as a decimal, Sunday being 0
218	%W - week number of the current year as a decimal number, starting with the
219	     first Monday as the first day of the first week
220</pre>
221
222=============================================================================
223
224NOTES
225
226Useful url for generating test timestamps:
227	http://www.4webhelp.net/us/timestamp.php
228
229Possible future optimizations include
230
231a. Using an algorithm similar to Plauger's in "The Standard C Library"
232(page 428, xttotm.c _Ttotm() function). Plauger's algorithm will not
233work outside 32-bit signed range, so i decided not to implement it.
234
235b. Implement daylight savings, which looks awfully complicated, see
236	http://webexhibits.org/daylightsaving/
237
238
239CHANGELOG
240- 16 Jan 2011 0.36
241Added adodb_time() which returns current time. If > 2038, will return as float
242
243- 7 Feb 2011 0.35
244Changed adodb_date to be symmetric with adodb_mktime. See $jan1_71. fix for bc.
245
246- 13 July 2010 0.34
247Changed adodb_get_gm_diff to use DateTimeZone().
248
249- 11 Feb 2008 0.33
250* Bug in 0.32 fix for hour handling. Fixed.
251
252- 1 Feb 2008 0.32
253* Now adodb_mktime(0,0,0,12+$m,20,2040) works properly.
254
255- 10 Jan 2008 0.31
256* Now adodb_mktime(0,0,0,24,1,2037) works correctly.
257
258- 15 July 2007 0.30
259Added PHP 5.2.0 compatibility fixes.
260 * gmtime behaviour for 1970 has changed. We use the actual date if it is between 1970 to 2038 to get the
261 * timezone, otherwise we use the current year as the baseline to retrieve the timezone.
262 * Also the timezone's in php 5.2.* support historical data better, eg. if timezone today was +8, but
263   in 1970 it was +7:30, then php 5.2 return +7:30, while this library will use +8.
264 *
265
266- 19 March 2006 0.24
267Changed strftime() locale detection, because some locales prepend the day of week to the date when %c is used.
268
269- 10 Feb 2006 0.23
270PHP5 compat: when we detect PHP5, the RFC2822 format for gmt 0000hrs is changed from -0000 to +0000.
271	In PHP4, we will still use -0000 for 100% compat with PHP4.
272
273- 08 Sept 2005 0.22
274In adodb_date2(), $is_gmt not supported properly. Fixed.
275
276- 18 July  2005 0.21
277In PHP 4.3.11, the 'r' format has changed. Leading 0 in day is added. Changed for compat.
278Added support for negative months in adodb_mktime().
279
280- 24 Feb 2005 0.20
281Added limited strftime/gmstrftime support. x10 improvement in performance of adodb_date().
282
283- 21 Dec 2004 0.17
284In adodb_getdate(), the timestamp was accidentally converted to gmt when $is_gmt is false.
285Also adodb_mktime(0,0,0) did not work properly. Both fixed thx Mauro.
286
287- 17 Nov 2004 0.16
288Removed intval typecast in adodb_mktime() for secs, allowing:
289	 adodb_mktime(0,0,0 + 2236672153,1,1,1934);
290Suggested by Ryan.
291
292- 18 July 2004 0.15
293All params in adodb_mktime were formerly compulsory. Now only the hour, min, secs is compulsory.
294This brings it more in line with mktime (still not identical).
295
296- 23 June 2004 0.14
297
298Allow you to define your own daylights savings function, adodb_daylight_sv.
299If the function is defined (somewhere in an include), then you can correct for daylights savings.
300
301In this example, we apply daylights savings in June or July, adding one hour. This is extremely
302unrealistic as it does not take into account time-zone, geographic location, current year.
303
304function adodb_daylight_sv(&$arr, $is_gmt)
305{
306	if ($is_gmt) return;
307	$m = $arr['mon'];
308	if ($m == 6 || $m == 7) $arr['hours'] += 1;
309}
310
311This is only called by adodb_date() and not by adodb_mktime().
312
313The format of $arr is
314Array (
315   [seconds] => 0
316   [minutes] => 0
317   [hours] => 0
318   [mday] => 1      # day of month, eg 1st day of the month
319   [mon] => 2       # month (eg. Feb)
320   [year] => 2102
321   [yday] => 31     # days in current year
322   [leap] =>        # true if leap year
323   [ndays] => 28    # no of days in current month
324   )
325
326
327- 28 Apr 2004 0.13
328Fixed adodb_date to properly support $is_gmt. Thx to Dimitar Angelov.
329
330- 20 Mar 2004 0.12
331Fixed month calculation error in adodb_date. 2102-June-01 appeared as 2102-May-32.
332
333- 26 Oct 2003 0.11
334Because of daylight savings problems (some systems apply daylight savings to
335January!!!), changed adodb_get_gmt_diff() to ignore daylight savings.
336
337- 9 Aug 2003 0.10
338Fixed bug with dates after 2038.
339See PHPLens Issue No: 6980
340
341- 1 July 2003 0.09
342Added support for Q (Quarter).
343Added adodb_date2(), which accepts ISO date in 2nd param
344
345- 3 March 2003 0.08
346Added support for 'S' adodb_date() format char. Added constant ADODB_ALLOW_NEGATIVE_TS
347if you want PHP to handle negative timestamps between 1901 to 1969.
348
349- 27 Feb 2003 0.07
350All negative numbers handled by adodb now because of RH 7.3+ problems.
351See http://bugs.php.net/bug.php?id=20048&edit=2
352
353- 4 Feb 2003 0.06
354Fixed a typo, 1852 changed to 1582! This means that pre-1852 dates
355are now correctly handled.
356
357- 29 Jan 2003 0.05
358
359Leap year checking differs under Julian calendar (pre 1582). Also
360leap year code optimized by checking for most common case first.
361
362We also handle month overflow correctly in mktime (eg month set to 13).
363
364Day overflow for less than one month's days is supported.
365
366- 28 Jan 2003 0.04
367
368Gregorian correction handled. In PHP5, we might throw an error if
369mktime uses invalid dates around 5-14 Oct 1582. Released with ADOdb 3.10.
370Added limbo 5-14 Oct 1582 check, when we set to 15 Oct 1582.
371
372- 27 Jan 2003 0.03
373
374Fixed some more month problems due to gmt issues. Added constant ADODB_DATE_VERSION.
375Fixed calculation of days since start of year for <1970.
376
377- 27 Jan 2003 0.02
378
379Changed _adodb_getdate() to inline leap year checking for better performance.
380Fixed problem with time-zones west of GMT +0000.
381
382- 24 Jan 2003 0.01
383
384First implementation.
385*/
386
387
388/* Initialization */
389
390/*
391	Version Number
392*/
393define('ADODB_DATE_VERSION',0.35);
394
395/*
396	This code was originally for windows. But apparently this problem happens
397	also with Linux, RH 7.3 and later!
398
399	glibc-2.2.5-34 and greater has been changed to return -1 for dates <
400	1970.  This used to work.  The problem exists with RedHat 7.3 and 8.0
401	echo (mktime(0, 0, 0, 1, 1, 1960));  // prints -1
402
403	References:
404	 http://bugs.php.net/bug.php?id=20048&edit=2
405	 http://lists.debian.org/debian-glibc/2002/debian-glibc-200205/msg00010.html
406*/
407
408if (!defined('ADODB_ALLOW_NEGATIVE_TS')) define('ADODB_NO_NEGATIVE_TS',1);
409
410if (!DEFINED('ADODB_FUTURE_DATE_CUTOFF_YEARS'))
411	DEFINE('ADODB_FUTURE_DATE_CUTOFF_YEARS',200);
412
413function adodb_date_test_date($y1,$m,$d=13)
414{
415	$h = round(rand()% 24);
416	$t = adodb_mktime($h,0,0,$m,$d,$y1);
417	$rez = adodb_date('Y-n-j H:i:s',$t);
418	if ($h == 0) $h = '00';
419	else if ($h < 10) $h = '0'.$h;
420	if ("$y1-$m-$d $h:00:00" != $rez) {
421		print "<b>$y1 error, expected=$y1-$m-$d $h:00:00, adodb=$rez</b><br>";
422		return false;
423	}
424	return true;
425}
426
427function adodb_date_test_strftime($fmt)
428{
429	$s1 = strftime($fmt);
430	$s2 = adodb_strftime($fmt);
431
432	if ($s1 == $s2) return true;
433
434	echo "error for $fmt,  strftime=$s1, adodb=$s2<br>";
435	return false;
436}
437
438/**
439	 Test Suite
440*/
441function adodb_date_test()
442{
443
444	for ($m=-24; $m<=24; $m++)
445		echo "$m :",adodb_date('d-m-Y',adodb_mktime(0,0,0,1+$m,20,2040)),"<br>";
446
447	error_reporting(E_ALL);
448	print "<h4>Testing adodb_date and adodb_mktime. version=".ADODB_DATE_VERSION.' PHP='.PHP_VERSION."</h4>";
449	@set_time_limit(0);
450	$fail = false;
451
452	// This flag disables calling of PHP native functions, so we can properly test the code
453	if (!defined('ADODB_TEST_DATES')) define('ADODB_TEST_DATES',1);
454
455	$t = time();
456
457
458	$fmt = 'Y-m-d H:i:s';
459	echo '<pre>';
460	echo 'adodb: ',adodb_date($fmt,$t),'<br>';
461	echo 'php  : ',date($fmt,$t),'<br>';
462	echo '</pre>';
463
464	adodb_date_test_strftime('%Y %m %x %X');
465	adodb_date_test_strftime("%A %d %B %Y");
466	adodb_date_test_strftime("%H %M S");
467
468	$t = adodb_mktime(0,0,0);
469	if (!(adodb_date('Y-m-d') == date('Y-m-d'))) print 'Error in '.adodb_mktime(0,0,0).'<br>';
470
471	$t = adodb_mktime(0,0,0,6,1,2102);
472	if (!(adodb_date('Y-m-d',$t) == '2102-06-01')) print 'Error in '.adodb_date('Y-m-d',$t).'<br>';
473
474	$t = adodb_mktime(0,0,0,2,1,2102);
475	if (!(adodb_date('Y-m-d',$t) == '2102-02-01')) print 'Error in '.adodb_date('Y-m-d',$t).'<br>';
476
477
478	print "<p>Testing gregorian <=> julian conversion<p>";
479	$t = adodb_mktime(0,0,0,10,11,1492);
480	//http://www.holidayorigins.com/html/columbus_day.html - Friday check
481	if (!(adodb_date('D Y-m-d',$t) == 'Fri 1492-10-11')) print 'Error in Columbus landing<br>';
482
483	$t = adodb_mktime(0,0,0,2,29,1500);
484	if (!(adodb_date('Y-m-d',$t) == '1500-02-29')) print 'Error in julian leap years<br>';
485
486	$t = adodb_mktime(0,0,0,2,29,1700);
487	if (!(adodb_date('Y-m-d',$t) == '1700-03-01')) print 'Error in gregorian leap years<br>';
488
489	print  adodb_mktime(0,0,0,10,4,1582).' ';
490	print adodb_mktime(0,0,0,10,15,1582);
491	$diff = (adodb_mktime(0,0,0,10,15,1582) - adodb_mktime(0,0,0,10,4,1582));
492	if ($diff != 3600*24) print " <b>Error in gregorian correction = ".($diff/3600/24)." days </b><br>";
493
494	print " 15 Oct 1582, Fri=".(adodb_dow(1582,10,15) == 5 ? 'Fri' : '<b>Error</b>')."<br>";
495	print " 4 Oct 1582, Thu=".(adodb_dow(1582,10,4) == 4 ? 'Thu' : '<b>Error</b>')."<br>";
496
497	print "<p>Testing overflow<p>";
498
499	$t = adodb_mktime(0,0,0,3,33,1965);
500	if (!(adodb_date('Y-m-d',$t) == '1965-04-02')) print 'Error in day overflow 1 <br>';
501	$t = adodb_mktime(0,0,0,4,33,1971);
502	if (!(adodb_date('Y-m-d',$t) == '1971-05-03')) print 'Error in day overflow 2 <br>';
503	$t = adodb_mktime(0,0,0,1,60,1965);
504	if (!(adodb_date('Y-m-d',$t) == '1965-03-01')) print 'Error in day overflow 3 '.adodb_date('Y-m-d',$t).' <br>';
505	$t = adodb_mktime(0,0,0,12,32,1965);
506	if (!(adodb_date('Y-m-d',$t) == '1966-01-01')) print 'Error in day overflow 4 '.adodb_date('Y-m-d',$t).' <br>';
507	$t = adodb_mktime(0,0,0,12,63,1965);
508	if (!(adodb_date('Y-m-d',$t) == '1966-02-01')) print 'Error in day overflow 5 '.adodb_date('Y-m-d',$t).' <br>';
509	$t = adodb_mktime(0,0,0,13,3,1965);
510	if (!(adodb_date('Y-m-d',$t) == '1966-01-03')) print 'Error in mth overflow 1 <br>';
511
512	print "Testing 2-digit => 4-digit year conversion<p>";
513	if (adodb_year_digit_check(00) != 2000) print "Err 2-digit 2000<br>";
514	if (adodb_year_digit_check(10) != 2010) print "Err 2-digit 2010<br>";
515	if (adodb_year_digit_check(20) != 2020) print "Err 2-digit 2020<br>";
516	if (adodb_year_digit_check(30) != 2030) print "Err 2-digit 2030<br>";
517	if (adodb_year_digit_check(40) != 1940) print "Err 2-digit 1940<br>";
518	if (adodb_year_digit_check(50) != 1950) print "Err 2-digit 1950<br>";
519	if (adodb_year_digit_check(90) != 1990) print "Err 2-digit 1990<br>";
520
521	// Test string formatting
522	print "<p>Testing date formatting</p>";
523
524	$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\C2822 r s t U w y Y z Z 2003';
525	$s1 = date($fmt,0);
526	$s2 = adodb_date($fmt,0);
527	if ($s1 != $s2) {
528		print " date() 0 failed<br>$s1<br>$s2<br>";
529	}
530	flush();
531	for ($i=100; --$i > 0; ) {
532
533		$ts = 3600.0*((rand()%60000)+(rand()%60000))+(rand()%60000);
534		$s1 = date($fmt,$ts);
535		$s2 = adodb_date($fmt,$ts);
536		//print "$s1 <br>$s2 <p>";
537		$pos = strcmp($s1,$s2);
538
539		if (($s1) != ($s2)) {
540			for ($j=0,$k=strlen($s1); $j < $k; $j++) {
541				if ($s1[$j] != $s2[$j]) {
542					print substr($s1,$j).' ';
543					break;
544				}
545			}
546			print "<b>Error date(): $ts<br><pre>
547&nbsp; \"$s1\" (date len=".strlen($s1).")
548&nbsp; \"$s2\" (adodb_date len=".strlen($s2).")</b></pre><br>";
549			$fail = true;
550		}
551
552		$a1 = getdate($ts);
553		$a2 = adodb_getdate($ts);
554		$rez = array_diff($a1,$a2);
555		if (sizeof($rez)>0) {
556			print "<b>Error getdate() $ts</b><br>";
557				print_r($a1);
558			print "<br>";
559				print_r($a2);
560			print "<p>";
561			$fail = true;
562		}
563	}
564
565	// Test generation of dates outside 1901-2038
566	print "<p>Testing random dates between 100 and 4000</p>";
567	adodb_date_test_date(100,1);
568	for ($i=100; --$i >= 0;) {
569		$y1 = 100+rand(0,1970-100);
570		$m = rand(1,12);
571		adodb_date_test_date($y1,$m);
572
573		$y1 = 3000-rand(0,3000-1970);
574		adodb_date_test_date($y1,$m);
575	}
576	print '<p>';
577	$start = 1960+rand(0,10);
578	$yrs = 12;
579	$i = 365.25*86400*($start-1970);
580	$offset = 36000+rand(10000,60000);
581	$max = 365*$yrs*86400;
582	$lastyear = 0;
583
584	// we generate a timestamp, convert it to a date, and convert it back to a timestamp
585	// and check if the roundtrip broke the original timestamp value.
586	print "Testing $start to ".($start+$yrs).", or $max seconds, offset=$offset: ";
587	$cnt = 0;
588	for ($max += $i; $i < $max; $i += $offset) {
589		$ret = adodb_date('m,d,Y,H,i,s',$i);
590		$arr = explode(',',$ret);
591		if ($lastyear != $arr[2]) {
592			$lastyear = $arr[2];
593			print " $lastyear ";
594			flush();
595		}
596		$newi = adodb_mktime($arr[3],$arr[4],$arr[5],$arr[0],$arr[1],$arr[2]);
597		if ($i != $newi) {
598			print "Error at $i, adodb_mktime returned $newi ($ret)";
599			$fail = true;
600			break;
601		}
602		$cnt += 1;
603	}
604	echo "Tested $cnt dates<br>";
605	if (!$fail) print "<p>Passed !</p>";
606	else print "<p><b>Failed</b> :-(</p>";
607}
608
609function adodb_time()
610{
611	$d = new DateTime();
612	return $d->format('U');
613}
614
615/**
616	Returns day of week, 0 = Sunday,... 6=Saturday.
617	Algorithm from PEAR::Date_Calc
618*/
619function adodb_dow($year, $month, $day)
620{
621/*
622Pope Gregory removed 10 days - October 5 to October 14 - from the year 1582 and
623proclaimed that from that time onwards 3 days would be dropped from the calendar
624every 400 years.
625
626Thursday, October 4, 1582 (Julian) was followed immediately by Friday, October 15, 1582 (Gregorian).
627*/
628	if ($year <= 1582) {
629		if ($year < 1582 ||
630			($year == 1582 && ($month < 10 || ($month == 10 && $day < 15)))) $greg_correction = 3;
631		 else
632			$greg_correction = 0;
633	} else
634		$greg_correction = 0;
635
636	if($month > 2)
637	    $month -= 2;
638	else {
639	    $month += 10;
640	    $year--;
641	}
642
643	$day =  floor((13 * $month - 1) / 5) +
644	        $day + ($year % 100) +
645	        floor(($year % 100) / 4) +
646	        floor(($year / 100) / 4) - 2 *
647	        floor($year / 100) + 77 + $greg_correction;
648
649	return $day - 7 * floor($day / 7);
650}
651
652
653/**
654 Checks for leap year, returns true if it is. No 2-digit year check. Also
655 handles julian calendar correctly.
656*/
657function _adodb_is_leap_year($year)
658{
659	if ($year % 4 != 0) return false;
660
661	if ($year % 400 == 0) {
662		return true;
663	// if gregorian calendar (>1582), century not-divisible by 400 is not leap
664	} else if ($year > 1582 && $year % 100 == 0 ) {
665		return false;
666	}
667
668	return true;
669}
670
671
672/**
673 checks for leap year, returns true if it is. Has 2-digit year check
674*/
675function adodb_is_leap_year($year)
676{
677	return  _adodb_is_leap_year(adodb_year_digit_check($year));
678}
679
680/**
681	Fix 2-digit years. Works for any century.
682 	Assumes that if 2-digit is more than 30 years in future, then previous century.
683*/
684function adodb_year_digit_check($y)
685{
686	if ($y < 100) {
687
688		$yr = (integer) date("Y");
689		$century = (integer) ($yr /100);
690
691		if ($yr%100 > 50) {
692			$c1 = $century + 1;
693			$c0 = $century;
694		} else {
695			$c1 = $century;
696			$c0 = $century - 1;
697		}
698		$c1 *= 100;
699		// if 2-digit year is less than 30 years in future, set it to this century
700		// otherwise if more than 30 years in future, then we set 2-digit year to the prev century.
701		if (($y + $c1) < $yr+30) $y = $y + $c1;
702		else $y = $y + $c0*100;
703	}
704	return $y;
705}
706
707function adodb_get_gmt_diff_ts($ts)
708{
709	if (0 <= $ts && $ts <= 0x7FFFFFFF) { // check if number in 32-bit signed range) {
710		$arr = getdate($ts);
711		$y = $arr['year'];
712		$m = $arr['mon'];
713		$d = $arr['mday'];
714		return adodb_get_gmt_diff($y,$m,$d);
715	} else {
716		return adodb_get_gmt_diff(false,false,false);
717	}
718
719}
720
721/**
722 get local time zone offset from GMT. Does not handle historical timezones before 1970.
723*/
724function adodb_get_gmt_diff($y,$m,$d)
725{
726	static $TZ,$tzo;
727
728	if (!defined('ADODB_TEST_DATES')) $y = false;
729	else if ($y < 1970 || $y >= 2038) $y = false;
730
731	if ($y !== false) {
732		$dt = new DateTime();
733		$dt->setISODate($y,$m,$d);
734		if (empty($tzo)) {
735			$tzo = new DateTimeZone(date_default_timezone_get());
736		#	$tzt = timezone_transitions_get( $tzo );
737		}
738		return -$tzo->getOffset($dt);
739	} else {
740		if (isset($TZ)) return $TZ;
741		$y = date('Y');
742		/*
743		if (function_exists('date_default_timezone_get') && function_exists('timezone_offset_get')) {
744			$tzonename = date_default_timezone_get();
745			if ($tzonename) {
746				$tobj = new DateTimeZone($tzonename);
747				$TZ = -timezone_offset_get($tobj,new DateTime("now",$tzo));
748			}
749		}
750		*/
751		if (empty($TZ)) $TZ = mktime(0,0,0,12,2,$y) - gmmktime(0,0,0,12,2,$y);
752	}
753	return $TZ;
754}
755
756/**
757	Returns an array with date info.
758*/
759function adodb_getdate($d=false,$fast=false)
760{
761	if ($d === false) return getdate();
762	if (!defined('ADODB_TEST_DATES')) {
763		if ((abs($d) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
764			if (!defined('ADODB_NO_NEGATIVE_TS') || $d >= 0) // if windows, must be +ve integer
765				return @getdate($d);
766		}
767	}
768	return _adodb_getdate($d);
769}
770
771/*
772// generate $YRS table for _adodb_getdate()
773function adodb_date_gentable($out=true)
774{
775
776	for ($i=1970; $i >= 1600; $i-=10) {
777		$s = adodb_gmmktime(0,0,0,1,1,$i);
778		echo "$i => $s,<br>";
779	}
780}
781adodb_date_gentable();
782
783for ($i=1970; $i > 1500; $i--) {
784
785echo "<hr />$i ";
786	adodb_date_test_date($i,1,1);
787}
788
789*/
790
791
792$_month_table_normal = array("",31,28,31,30,31,30,31,31,30,31,30,31);
793$_month_table_leaf = array("",31,29,31,30,31,30,31,31,30,31,30,31);
794
795function adodb_validdate($y,$m,$d)
796{
797global $_month_table_normal,$_month_table_leaf;
798
799	if (_adodb_is_leap_year($y)) $marr = $_month_table_leaf;
800	else $marr = $_month_table_normal;
801
802	if ($m > 12 || $m < 1) return false;
803
804	if ($d > 31 || $d < 1) return false;
805
806	if ($marr[$m] < $d) return false;
807
808	if ($y < 1000 || $y > 3000) return false;
809
810	return true;
811}
812
813/**
814	Low-level function that returns the getdate() array. We have a special
815	$fast flag, which if set to true, will return fewer array values,
816	and is much faster as it does not calculate dow, etc.
817*/
818function _adodb_getdate($origd=false,$fast=false,$is_gmt=false)
819{
820static $YRS;
821global $_month_table_normal,$_month_table_leaf, $_adodb_last_date_call_failed;
822
823	$_adodb_last_date_call_failed = false;
824
825	$d =  $origd - ($is_gmt ? 0 : adodb_get_gmt_diff_ts($origd));
826	$_day_power = 86400;
827	$_hour_power = 3600;
828	$_min_power = 60;
829
830	$cutoffDate = time() + (60 * 60 * 24 * 365 * ADODB_FUTURE_DATE_CUTOFF_YEARS);
831
832	if ($d > $cutoffDate)
833	{
834		$d = $cutoffDate;
835		$_adodb_last_date_call_failed = true;
836	}
837
838	if ($d < -12219321600) $d -= 86400*10; // if 15 Oct 1582 or earlier, gregorian correction
839
840	$_month_table_normal = array("",31,28,31,30,31,30,31,31,30,31,30,31);
841	$_month_table_leaf = array("",31,29,31,30,31,30,31,31,30,31,30,31);
842
843	$d366 = $_day_power * 366;
844	$d365 = $_day_power * 365;
845
846	if ($d < 0) {
847
848		if (empty($YRS)) $YRS = array(
849			1970 => 0,
850			1960 => -315619200,
851			1950 => -631152000,
852			1940 => -946771200,
853			1930 => -1262304000,
854			1920 => -1577923200,
855			1910 => -1893456000,
856			1900 => -2208988800,
857			1890 => -2524521600,
858			1880 => -2840140800,
859			1870 => -3155673600,
860			1860 => -3471292800,
861			1850 => -3786825600,
862			1840 => -4102444800,
863			1830 => -4417977600,
864			1820 => -4733596800,
865			1810 => -5049129600,
866			1800 => -5364662400,
867			1790 => -5680195200,
868			1780 => -5995814400,
869			1770 => -6311347200,
870			1760 => -6626966400,
871			1750 => -6942499200,
872			1740 => -7258118400,
873			1730 => -7573651200,
874			1720 => -7889270400,
875			1710 => -8204803200,
876			1700 => -8520336000,
877			1690 => -8835868800,
878			1680 => -9151488000,
879			1670 => -9467020800,
880			1660 => -9782640000,
881			1650 => -10098172800,
882			1640 => -10413792000,
883			1630 => -10729324800,
884			1620 => -11044944000,
885			1610 => -11360476800,
886			1600 => -11676096000);
887
888		if ($is_gmt) $origd = $d;
889		// The valid range of a 32bit signed timestamp is typically from
890		// Fri, 13 Dec 1901 20:45:54 GMT to Tue, 19 Jan 2038 03:14:07 GMT
891		//
892
893		# old algorithm iterates through all years. new algorithm does it in
894		# 10 year blocks
895
896		/*
897		# old algo
898		for ($a = 1970 ; --$a >= 0;) {
899			$lastd = $d;
900
901			if ($leaf = _adodb_is_leap_year($a)) $d += $d366;
902			else $d += $d365;
903
904			if ($d >= 0) {
905				$year = $a;
906				break;
907			}
908		}
909		*/
910
911		$lastsecs = 0;
912		$lastyear = 1970;
913		foreach($YRS as $year => $secs) {
914			if ($d >= $secs) {
915				$a = $lastyear;
916				break;
917			}
918			$lastsecs = $secs;
919			$lastyear = $year;
920		}
921
922		$d -= $lastsecs;
923		if (!isset($a)) $a = $lastyear;
924
925		//echo ' yr=',$a,' ', $d,'.';
926
927		for (; --$a >= 0;) {
928			$lastd = $d;
929
930			if ($leaf = _adodb_is_leap_year($a)) $d += $d366;
931			else $d += $d365;
932
933			if ($d >= 0) {
934				$year = $a;
935				break;
936			}
937		}
938		/**/
939
940		$secsInYear = 86400 * ($leaf ? 366 : 365) + $lastd;
941
942		$d = $lastd;
943		$mtab = ($leaf) ? $_month_table_leaf : $_month_table_normal;
944		for ($a = 13 ; --$a > 0;) {
945			$lastd = $d;
946			$d += $mtab[$a] * $_day_power;
947			if ($d >= 0) {
948				$month = $a;
949				$ndays = $mtab[$a];
950				break;
951			}
952		}
953
954		$d = $lastd;
955		$day = $ndays + ceil(($d+1) / ($_day_power));
956
957		$d += ($ndays - $day+1)* $_day_power;
958		$hour = floor($d/$_hour_power);
959
960	} else {
961		for ($a = 1970 ;; $a++) {
962			$lastd = $d;
963
964			if ($leaf = _adodb_is_leap_year($a)) $d -= $d366;
965			else $d -= $d365;
966			if ($d < 0) {
967				$year = $a;
968				break;
969			}
970		}
971		$secsInYear = $lastd;
972		$d = $lastd;
973		$mtab = ($leaf) ? $_month_table_leaf : $_month_table_normal;
974		for ($a = 1 ; $a <= 12; $a++) {
975			$lastd = $d;
976			$d -= $mtab[$a] * $_day_power;
977			if ($d < 0) {
978				$month = $a;
979				$ndays = $mtab[$a];
980				break;
981			}
982		}
983		$d = $lastd;
984		$day = ceil(($d+1) / $_day_power);
985		$d = $d - ($day-1) * $_day_power;
986		$hour = floor($d /$_hour_power);
987	}
988
989	$d -= $hour * $_hour_power;
990	$min = floor($d/$_min_power);
991	$secs = $d - $min * $_min_power;
992	if ($fast) {
993		return array(
994		'seconds' => $secs,
995		'minutes' => $min,
996		'hours' => $hour,
997		'mday' => $day,
998		'mon' => $month,
999		'year' => $year,
1000		'yday' => floor($secsInYear/$_day_power),
1001		'leap' => $leaf,
1002		'ndays' => $ndays
1003		);
1004	}
1005
1006
1007	$dow = adodb_dow($year,$month,$day);
1008
1009	return array(
1010		'seconds' => $secs,
1011		'minutes' => $min,
1012		'hours' => $hour,
1013		'mday' => $day,
1014		'wday' => $dow,
1015		'mon' => $month,
1016		'year' => $year,
1017		'yday' => floor($secsInYear/$_day_power),
1018		'weekday' => gmdate('l',$_day_power*(3+$dow)),
1019		'month' => gmdate('F',mktime(0,0,0,$month,2,1971)),
1020		0 => $origd
1021	);
1022}
1023
1024/**
1025 * Compute timezone offset.
1026 *
1027 * @param int  $gmt     Time offset from GMT, in seconds
1028 * @param bool $ignored Param leftover from removed PHP4-compatibility code
1029 *                      kept to avoid altering function signature.
1030 * @return string
1031 */
1032function adodb_tz_offset($gmt, $ignored=true)
1033{
1034	$zhrs = abs($gmt) / 3600;
1035	$hrs = floor($zhrs);
1036	return sprintf('%s%02d%02d', ($gmt <= 0) ? '+' : '-', $hrs, ($zhrs - $hrs) * 60);
1037}
1038
1039
1040function adodb_gmdate($fmt,$d=false)
1041{
1042	return adodb_date($fmt,$d,true);
1043}
1044
1045// accepts unix timestamp and iso date format in $d
1046function adodb_date2($fmt, $d=false, $is_gmt=false)
1047{
1048	if ($d !== false) {
1049		if (!preg_match(
1050			"|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ -]?(([0-9]{1,2}):?([0-9]{1,2}):?([0-9\.]{1,4}))?|",
1051			($d), $rr)) return adodb_date($fmt,false,$is_gmt);
1052
1053		if ($rr[1] <= 100 && $rr[2]<= 1) return adodb_date($fmt,false,$is_gmt);
1054
1055		// h-m-s-MM-DD-YY
1056		if (!isset($rr[5])) $d = adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1],false,$is_gmt);
1057		else $d = @adodb_mktime($rr[5],$rr[6],$rr[7],$rr[2],$rr[3],$rr[1],false,$is_gmt);
1058	}
1059
1060	return adodb_date($fmt,$d,$is_gmt);
1061}
1062
1063
1064/**
1065	Return formatted date based on timestamp $d
1066*/
1067function adodb_date($fmt,$d=false,$is_gmt=false)
1068{
1069	static $daylight;
1070	static $jan1_1971;
1071
1072	if (!isset($daylight)) {
1073		$daylight = function_exists('adodb_daylight_sv');
1074		if (empty($jan1_1971)) $jan1_1971 = mktime(0,0,0,1,1,1971); // we only use date() when > 1970 as adodb_mktime() only uses mktime() when > 1970
1075	}
1076
1077	if ($d === false) return ($is_gmt)? @gmdate($fmt): @date($fmt);
1078	if (!defined('ADODB_TEST_DATES')) {
1079
1080		/*
1081		* Format 'Q' is an ADOdb custom format, not supported in PHP
1082		* so if there is a 'Q' in the format, we force it to use our
1083		* function. There is a trivial overhead in this
1084		*/
1085
1086		if ((abs($d) <= 0x7FFFFFFF) && strpos($fmt,'Q') === false)
1087		{ // check if number in 32-bit signed range
1088
1089			if (!defined('ADODB_NO_NEGATIVE_TS') || $d >= $jan1_1971) // if windows, must be +ve integer
1090				return ($is_gmt)? @gmdate($fmt,$d): @date($fmt,$d);
1091
1092		}
1093	}
1094	$_day_power = 86400;
1095
1096	$arr = _adodb_getdate($d,true,$is_gmt);
1097
1098	if ($daylight) adodb_daylight_sv($arr, $is_gmt);
1099
1100	$year = $arr['year'];
1101	$month = $arr['mon'];
1102	$day = $arr['mday'];
1103	$hour = $arr['hours'];
1104	$min = $arr['minutes'];
1105	$secs = $arr['seconds'];
1106
1107	$max = strlen($fmt);
1108	$dates = '';
1109
1110	/*
1111		at this point, we have the following integer vars to manipulate:
1112		$year, $month, $day, $hour, $min, $secs
1113	*/
1114	for ($i=0; $i < $max; $i++) {
1115		switch($fmt[$i]) {
1116		case 'e':
1117			$dates .= date('e');
1118			break;
1119		case 'T':
1120			$dt = new DateTime();
1121			$dt->SetDate($year,$month,$day);
1122			$dates .= $dt->Format('T');
1123			break;
1124		// YEAR
1125		case 'L': $dates .= $arr['leap'] ? '1' : '0'; break;
1126		case 'r': // Thu, 21 Dec 2000 16:01:07 +0200
1127
1128			// 4.3.11 uses '04 Jun 2004'
1129			// 4.3.8 uses  ' 4 Jun 2004'
1130			$dates .= gmdate('D',$_day_power*(3+adodb_dow($year,$month,$day))).', '
1131				. ($day<10?'0'.$day:$day) . ' '.date('M',mktime(0,0,0,$month,2,1971)).' '.$year.' ';
1132
1133			if ($hour < 10) $dates .= '0'.$hour; else $dates .= $hour;
1134
1135			if ($min < 10) $dates .= ':0'.$min; else $dates .= ':'.$min;
1136
1137			if ($secs < 10) $dates .= ':0'.$secs; else $dates .= ':'.$secs;
1138
1139			$gmt = adodb_get_gmt_diff($year,$month,$day);
1140
1141			$dates .= ' '.adodb_tz_offset($gmt);
1142			break;
1143
1144		case 'Y': $dates .= $year; break;
1145		case 'y': $dates .= substr($year,strlen($year)-2,2); break;
1146		// MONTH
1147		case 'm': if ($month<10) $dates .= '0'.$month; else $dates .= $month; break;
1148		case 'Q':
1149			$dates .= ceil($month / 3);
1150			break;
1151		case 'n': $dates .= $month; break;
1152		case 'M': $dates .= date('M',mktime(0,0,0,$month,2,1971)); break;
1153		case 'F': $dates .= date('F',mktime(0,0,0,$month,2,1971)); break;
1154		// DAY
1155		case 't': $dates .= $arr['ndays']; break;
1156		case 'z': $dates .= $arr['yday']; break;
1157		case 'w': $dates .= adodb_dow($year,$month,$day); break;
1158		case 'W':
1159			$dates .= sprintf('%02d',ceil( $arr['yday'] / 7) - 1);
1160			break;
1161		case 'l': $dates .= gmdate('l',$_day_power*(3+adodb_dow($year,$month,$day))); break;
1162		case 'D': $dates .= gmdate('D',$_day_power*(3+adodb_dow($year,$month,$day))); break;
1163		case 'j': $dates .= $day; break;
1164		case 'd': if ($day<10) $dates .= '0'.$day; else $dates .= $day; break;
1165		case 'S':
1166			$d10 = $day % 10;
1167			if ($d10 == 1) $dates .= 'st';
1168			else if ($d10 == 2 && $day != 12) $dates .= 'nd';
1169			else if ($d10 == 3) $dates .= 'rd';
1170			else $dates .= 'th';
1171			break;
1172
1173		// HOUR
1174		case 'Z':
1175			$dates .= ($is_gmt) ? 0 : -adodb_get_gmt_diff($year,$month,$day); break;
1176		case 'O':
1177			$gmt = ($is_gmt) ? 0 : adodb_get_gmt_diff($year,$month,$day);
1178
1179			$dates .= adodb_tz_offset($gmt);
1180			break;
1181
1182		case 'H':
1183			if ($hour < 10) $dates .= '0'.$hour;
1184			else $dates .= $hour;
1185			break;
1186		case 'h':
1187			if ($hour > 12) $hh = $hour - 12;
1188			else {
1189				if ($hour == 0) $hh = '12';
1190				else $hh = $hour;
1191			}
1192
1193			if ($hh < 10) $dates .= '0'.$hh;
1194			else $dates .= $hh;
1195			break;
1196
1197		case 'G':
1198			$dates .= $hour;
1199			break;
1200
1201		case 'g':
1202			if ($hour > 12) $hh = $hour - 12;
1203			else {
1204				if ($hour == 0) $hh = '12';
1205				else $hh = $hour;
1206			}
1207			$dates .= $hh;
1208			break;
1209		// MINUTES
1210		case 'i': if ($min < 10) $dates .= '0'.$min; else $dates .= $min; break;
1211		// SECONDS
1212		case 'U': $dates .= $d; break;
1213		case 's': if ($secs < 10) $dates .= '0'.$secs; else $dates .= $secs; break;
1214		// AM/PM
1215		// Note 00:00 to 11:59 is AM, while 12:00 to 23:59 is PM
1216		case 'a':
1217			if ($hour>=12) $dates .= 'pm';
1218			else $dates .= 'am';
1219			break;
1220		case 'A':
1221			if ($hour>=12) $dates .= 'PM';
1222			else $dates .= 'AM';
1223			break;
1224		default:
1225			$dates .= $fmt[$i]; break;
1226		// ESCAPE
1227		case "\\":
1228			$i++;
1229			if ($i < $max) $dates .= $fmt[$i];
1230			break;
1231		}
1232	}
1233	return $dates;
1234}
1235
1236/**
1237	Returns a timestamp given a GMT/UTC time.
1238	Note that $is_dst is not implemented and is ignored.
1239*/
1240function adodb_gmmktime($hr,$min,$sec,$mon=false,$day=false,$year=false,$is_dst=false)
1241{
1242	return adodb_mktime($hr,$min,$sec,$mon,$day,$year,$is_dst,true);
1243}
1244
1245/**
1246	Return a timestamp given a local time. Originally by jackbbs.
1247	Note that $is_dst is not implemented and is ignored.
1248
1249	Not a very fast algorithm - O(n) operation. Could be optimized to O(1).
1250*/
1251function adodb_mktime($hr,$min,$sec,$mon=false,$day=false,$year=false,$is_dst=false,$is_gmt=false)
1252{
1253	if (!defined('ADODB_TEST_DATES')) {
1254
1255		if ($mon === false) {
1256			return $is_gmt? @gmmktime($hr,$min,$sec): @mktime($hr,$min,$sec);
1257		}
1258
1259		// for windows, we don't check 1970 because with timezone differences,
1260		// 1 Jan 1970 could generate negative timestamp, which is illegal
1261		$usephpfns = (1970 < $year && $year < 2038
1262			|| !defined('ADODB_NO_NEGATIVE_TS') && (1901 < $year && $year < 2038)
1263			);
1264
1265
1266		if ($usephpfns && ($year + $mon/12+$day/365.25+$hr/(24*365.25) >= 2038)) $usephpfns = false;
1267
1268		if ($usephpfns) {
1269				return $is_gmt ?
1270					@gmmktime($hr,$min,$sec,$mon,$day,$year):
1271					@mktime($hr,$min,$sec,$mon,$day,$year);
1272		}
1273	}
1274
1275	$gmt_different = ($is_gmt) ? 0 : adodb_get_gmt_diff($year,$mon,$day);
1276
1277	/*
1278	# disabled because some people place large values in $sec.
1279	# however we need it for $mon because we use an array...
1280	$hr = intval($hr);
1281	$min = intval($min);
1282	$sec = intval($sec);
1283	*/
1284	$mon = intval($mon);
1285	$day = intval($day);
1286	$year = intval($year);
1287
1288
1289	$year = adodb_year_digit_check($year);
1290
1291	if ($mon > 12) {
1292		$y = floor(($mon-1)/ 12);
1293		$year += $y;
1294		$mon -= $y*12;
1295	} else if ($mon < 1) {
1296		$y = ceil((1-$mon) / 12);
1297		$year -= $y;
1298		$mon += $y*12;
1299	}
1300
1301	$_day_power = 86400;
1302	$_hour_power = 3600;
1303	$_min_power = 60;
1304
1305	$_month_table_normal = array("",31,28,31,30,31,30,31,31,30,31,30,31);
1306	$_month_table_leaf = array("",31,29,31,30,31,30,31,31,30,31,30,31);
1307
1308	$_total_date = 0;
1309	if ($year >= 1970) {
1310		for ($a = 1970 ; $a <= $year; $a++) {
1311			$leaf = _adodb_is_leap_year($a);
1312			if ($leaf == true) {
1313				$loop_table = $_month_table_leaf;
1314				$_add_date = 366;
1315			} else {
1316				$loop_table = $_month_table_normal;
1317				$_add_date = 365;
1318			}
1319			if ($a < $year) {
1320				$_total_date += $_add_date;
1321			} else {
1322				for($b=1;$b<$mon;$b++) {
1323					$_total_date += $loop_table[$b];
1324				}
1325			}
1326		}
1327		$_total_date +=$day-1;
1328		$ret = $_total_date * $_day_power + $hr * $_hour_power + $min * $_min_power + $sec + $gmt_different;
1329
1330	} else {
1331		for ($a = 1969 ; $a >= $year; $a--) {
1332			$leaf = _adodb_is_leap_year($a);
1333			if ($leaf == true) {
1334				$loop_table = $_month_table_leaf;
1335				$_add_date = 366;
1336			} else {
1337				$loop_table = $_month_table_normal;
1338				$_add_date = 365;
1339			}
1340			if ($a > $year) { $_total_date += $_add_date;
1341			} else {
1342				for($b=12;$b>$mon;$b--) {
1343					$_total_date += $loop_table[$b];
1344				}
1345			}
1346		}
1347		$_total_date += $loop_table[$mon] - $day;
1348
1349		$_day_time = $hr * $_hour_power + $min * $_min_power + $sec;
1350		$_day_time = $_day_power - $_day_time;
1351		$ret = -( $_total_date * $_day_power + $_day_time - $gmt_different);
1352		if ($ret < -12220185600) $ret += 10*86400; // if earlier than 5 Oct 1582 - gregorian correction
1353		else if ($ret < -12219321600) $ret = -12219321600; // if in limbo, reset to 15 Oct 1582.
1354	}
1355	//print " dmy=$day/$mon/$year $hr:$min:$sec => " .$ret;
1356	return $ret;
1357}
1358
1359function adodb_gmstrftime($fmt, $ts=false)
1360{
1361	return adodb_strftime($fmt,$ts,true);
1362}
1363
1364// hack - convert to adodb_date
1365function adodb_strftime($fmt, $ts=false,$is_gmt=false)
1366{
1367global $ADODB_DATE_LOCALE;
1368
1369	if (!defined('ADODB_TEST_DATES')) {
1370		if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
1371			if (!defined('ADODB_NO_NEGATIVE_TS') || $ts >= 0) // if windows, must be +ve integer
1372				return ($is_gmt)? @gmstrftime($fmt,$ts): @strftime($fmt,$ts);
1373
1374		}
1375	}
1376
1377	if (empty($ADODB_DATE_LOCALE)) {
1378	/*
1379		$tstr = strtoupper(gmstrftime('%c',31366800)); // 30 Dec 1970, 1 am
1380		$sep = substr($tstr,2,1);
1381		$hasAM = strrpos($tstr,'M') !== false;
1382	*/
1383		# see PHPLens Issue No: 14865 for reasoning, and changelog for version 0.24
1384		$dstr = gmstrftime('%x',31366800); // 30 Dec 1970, 1 am
1385		$sep = substr($dstr,2,1);
1386		$tstr = strtoupper(gmstrftime('%X',31366800)); // 30 Dec 1970, 1 am
1387		$hasAM = strrpos($tstr,'M') !== false;
1388
1389		$ADODB_DATE_LOCALE = array();
1390		$ADODB_DATE_LOCALE[] =  strncmp($tstr,'30',2) == 0 ? 'd'.$sep.'m'.$sep.'y' : 'm'.$sep.'d'.$sep.'y';
1391		$ADODB_DATE_LOCALE[]  = ($hasAM) ? 'h:i:s a' : 'H:i:s';
1392
1393	}
1394	$inpct = false;
1395	$fmtdate = '';
1396	for ($i=0,$max = strlen($fmt); $i < $max; $i++) {
1397		$ch = $fmt[$i];
1398		if ($ch == '%') {
1399			if ($inpct) {
1400				$fmtdate .= '%';
1401				$inpct = false;
1402			} else
1403				$inpct = true;
1404		} else if ($inpct) {
1405
1406			$inpct = false;
1407			switch($ch) {
1408			case '0':
1409			case '1':
1410			case '2':
1411			case '3':
1412			case '4':
1413			case '5':
1414			case '6':
1415			case '7':
1416			case '8':
1417			case '9':
1418			case 'E':
1419			case 'O':
1420				/* ignore format modifiers */
1421				$inpct = true;
1422				break;
1423
1424			case 'a': $fmtdate .= 'D'; break;
1425			case 'A': $fmtdate .= 'l'; break;
1426			case 'h':
1427			case 'b': $fmtdate .= 'M'; break;
1428			case 'B': $fmtdate .= 'F'; break;
1429			case 'c': $fmtdate .= $ADODB_DATE_LOCALE[0].$ADODB_DATE_LOCALE[1]; break;
1430			case 'C': $fmtdate .= '\C?'; break; // century
1431			case 'd': $fmtdate .= 'd'; break;
1432			case 'D': $fmtdate .= 'm/d/y'; break;
1433			case 'e': $fmtdate .= 'j'; break;
1434			case 'g': $fmtdate .= '\g?'; break; //?
1435			case 'G': $fmtdate .= '\G?'; break; //?
1436			case 'H': $fmtdate .= 'H'; break;
1437			case 'I': $fmtdate .= 'h'; break;
1438			case 'j': $fmtdate .= '?z'; $parsej = true; break; // wrong as j=1-based, z=0-basd
1439			case 'm': $fmtdate .= 'm'; break;
1440			case 'M': $fmtdate .= 'i'; break;
1441			case 'n': $fmtdate .= "\n"; break;
1442			case 'p': $fmtdate .= 'a'; break;
1443			case 'r': $fmtdate .= 'h:i:s a'; break;
1444			case 'R': $fmtdate .= 'H:i:s'; break;
1445			case 'S': $fmtdate .= 's'; break;
1446			case 't': $fmtdate .= "\t"; break;
1447			case 'T': $fmtdate .= 'H:i:s'; break;
1448			case 'u': $fmtdate .= '?u'; $parseu = true; break; // wrong strftime=1-based, date=0-based
1449			case 'U': $fmtdate .= '?U'; $parseU = true; break;// wrong strftime=1-based, date=0-based
1450			case 'x': $fmtdate .= $ADODB_DATE_LOCALE[0]; break;
1451			case 'X': $fmtdate .= $ADODB_DATE_LOCALE[1]; break;
1452			case 'w': $fmtdate .= '?w'; $parseu = true; break; // wrong strftime=1-based, date=0-based
1453			case 'W': $fmtdate .= '?W'; $parseU = true; break;// wrong strftime=1-based, date=0-based
1454			case 'y': $fmtdate .= 'y'; break;
1455			case 'Y': $fmtdate .= 'Y'; break;
1456			case 'Z': $fmtdate .= 'T'; break;
1457			}
1458		} else if (('A' <= ($ch) && ($ch) <= 'Z' ) || ('a' <= ($ch) && ($ch) <= 'z' ))
1459			$fmtdate .= "\\".$ch;
1460		else
1461			$fmtdate .= $ch;
1462	}
1463	//echo "fmt=",$fmtdate,"<br>";
1464	if ($ts === false) $ts = time();
1465	$ret = adodb_date($fmtdate, $ts, $is_gmt);
1466	return $ret;
1467}
1468
1469/**
1470* Returns the status of the last date calculation and whether it exceeds
1471* the limit of ADODB_FUTURE_DATE_CUTOFF_YEARS
1472*
1473* @return boolean
1474*/
1475function adodb_last_date_status()
1476{
1477	global $_adodb_last_date_call_failed;
1478
1479	return $_adodb_last_date_call_failed;
1480}
1481