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