adodb-time.inc.php 42.7 KB
Newer Older
Penny Leach's avatar
Penny Leach committed
1
<?php
2
/*
Penny Leach's avatar
Penny Leach committed
3
ADOdb Date Library, part of the ADOdb abstraction library
4

5
6
7
Latest version is available at http://adodb.org/

@version   v5.20.14  06-Jan-2019
8
9
@copyright (c) 2000-2013 John Lim (jlim#natsoft.com). All rights reserved.
@copyright (c) 2014      Damien Regad, Mark Newnham and the ADOdb community
Penny Leach's avatar
Penny Leach committed
10
11

PHP native date functions use integer timestamps for computations.
12
13
14
15
Because of this, dates are restricted to the years 1901-2038 on Unix
and 1970-2038 on Windows due to integer overflow for dates beyond
those years. This library overcomes these limitations by replacing the
native function's signed integers (normally 32-bits) with PHP floating
Penny Leach's avatar
Penny Leach committed
16
17
18
19
point numbers (normally 64-bits).

Dates from 100 A.D. to 3000 A.D. and later
have been tested. The minimum is 100 A.D. as <100 will invoke the
20
21
2 => 4 digit year conversion. The maximum is billions of years in the
future, but this is a theoretical limit as the computation of that year
Penny Leach's avatar
Penny Leach committed
22
23
24
25
would take too long with the current implementation of adodb_mktime().

This library replaces native functions as follows:

26
<pre>
Penny Leach's avatar
Penny Leach committed
27
	getdate()  with  adodb_getdate()
28
	date()     with  adodb_date()
Penny Leach's avatar
Penny Leach committed
29
30
31
32
33
34
	gmdate()   with  adodb_gmdate()
	mktime()   with  adodb_mktime()
	gmmktime() with  adodb_gmmktime()
	strftime() with  adodb_strftime()
	strftime() with  adodb_gmstrftime()
</pre>
35

Penny Leach's avatar
Penny Leach committed
36
The parameters are identical, except that adodb_date() accepts a subset
37
38
of date()'s field formats. Mktime() will convert from local time to GMT,
and date() will convert from GMT to local time, but daylight savings is
Penny Leach's avatar
Penny Leach committed
39
40
41
42
43
44
45
46
not handled currently.

This library is independant of the rest of ADOdb, and can be used
as standalone code.

PERFORMANCE

For high speed, this library uses the native date functions where
47
possible, and only switches to PHP code when the dates fall outside
Penny Leach's avatar
Penny Leach committed
48
49
50
51
the 32-bit signed integer range.

GREGORIAN CORRECTION

52
53
54
Pope Gregory shortened October of A.D. 1582 by ten days. Thursday,
October 4, 1582 (Julian) was followed immediately by Friday, October 15,
1582 (Gregorian).
Penny Leach's avatar
Penny Leach committed
55
56
57

Since 0.06, we handle this correctly, so:

58
adodb_mktime(0,0,0,10,15,1582) - adodb_mktime(0,0,0,10,4,1582)
Penny Leach's avatar
Penny Leach committed
59
60
61
62
63
64
	== 24 * 3600 (1 day)

=============================================================================

COPYRIGHT

65
(c) 2003-2014 John Lim and released under BSD-style license except for code by
Penny Leach's avatar
Penny Leach committed
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
jackbbs, which includes adodb_mktime, adodb_get_gmt_diff, adodb_is_leap_year
and originally found at http://www.php.net/manual/en/function.mktime.php

=============================================================================

BUG REPORTS

These should be posted to the ADOdb forums at

	http://phplens.com/lens/lensforum/topics.php?id=4

=============================================================================

FUNCTION DESCRIPTIONS

81
82
83
** FUNCTION adodb_time()

Returns the current time measured in the number of seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) as an unsigned integer.
Penny Leach's avatar
Penny Leach committed
84
85
86
87

** FUNCTION adodb_getdate($date=false)

Returns an array containing date information, as getdate(), but supports
88
89
90
91
dates greater than 1901 to 2038. The local date/time format is derived from a
heuristic the first time adodb_getdate is called.


Penny Leach's avatar
Penny Leach committed
92
93
94
95
96
97
98
99
100
** FUNCTION adodb_date($fmt, $timestamp = false)

Convert a timestamp to a formatted local date. If $timestamp is not defined, the
current timestamp is used. Unlike the function date(), it supports dates
outside the 1901 to 2038 range.

The format fields that adodb_date supports:

<pre>
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
	a - "am" or "pm"
	A - "AM" or "PM"
	d - day of the month, 2 digits with leading zeros; i.e. "01" to "31"
	D - day of the week, textual, 3 letters; e.g. "Fri"
	F - month, textual, long; e.g. "January"
	g - hour, 12-hour format without leading zeros; i.e. "1" to "12"
	G - hour, 24-hour format without leading zeros; i.e. "0" to "23"
	h - hour, 12-hour format; i.e. "01" to "12"
	H - hour, 24-hour format; i.e. "00" to "23"
	i - minutes; i.e. "00" to "59"
	j - day of the month without leading zeros; i.e. "1" to "31"
	l (lowercase 'L') - day of the week, textual, long; e.g. "Friday"
	L - boolean for whether it is a leap year; i.e. "0" or "1"
	m - month; i.e. "01" to "12"
	M - month, textual, 3 letters; e.g. "Jan"
	n - month without leading zeros; i.e. "1" to "12"
	O - Difference to Greenwich time in hours; e.g. "+0200"
	Q - Quarter, as in 1, 2, 3, 4
	r - RFC 2822 formatted date; e.g. "Thu, 21 Dec 2000 16:01:07 +0200"
	s - seconds; i.e. "00" to "59"
	S - English ordinal suffix for the day of the month, 2 characters;
	   			i.e. "st", "nd", "rd" or "th"
Penny Leach's avatar
Penny Leach committed
123
	t - number of days in the given month; i.e. "28" to "31"
124
125
126
127
128
129
130
131
132
	T - Timezone setting of this machine; e.g. "EST" or "MDT"
	U - seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
	w - day of the week, numeric, i.e. "0" (Sunday) to "6" (Saturday)
	Y - year, 4 digits; e.g. "1999"
	y - year, 2 digits; e.g. "99"
	z - day of the year; i.e. "0" to "365"
	Z - timezone offset in seconds (i.e. "-43200" to "43200").
	   			The offset for timezones west of UTC is always negative,
				and for those east of UTC is always positive.
Penny Leach's avatar
Penny Leach committed
133
134
135
136
</pre>

Unsupported:
<pre>
137
	B - Swatch Internet time
Penny Leach's avatar
Penny Leach committed
138
	I (capital i) - "1" if Daylight Savings Time, "0" otherwise.
139
	W - ISO-8601 week number of year, weeks starting on Monday
Penny Leach's avatar
Penny Leach committed
140
141
142
143
144
145
146
147
148

</pre>


** FUNCTION adodb_date2($fmt, $isoDateString = false)
Same as adodb_date, but 2nd parameter accepts iso date, eg.

  adodb_date2('d-M-Y H:i','2003-12-25 13:01:34');

149

Penny Leach's avatar
Penny Leach committed
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
** FUNCTION adodb_gmdate($fmt, $timestamp = false)

Convert a timestamp to a formatted GMT date. If $timestamp is not defined, the
current timestamp is used. Unlike the function date(), it supports dates
outside the 1901 to 2038 range.


** FUNCTION adodb_mktime($hr, $min, $sec[, $month, $day, $year])

Converts a local date to a unix timestamp.  Unlike the function mktime(), it supports
dates outside the 1901 to 2038 range. All parameters are optional.


** FUNCTION adodb_gmmktime($hr, $min, $sec [, $month, $day, $year])

Converts a gmt date to a unix timestamp.  Unlike the function gmmktime(), it supports
dates outside the 1901 to 2038 range. Differs from gmmktime() in that all parameters
are currently compulsory.

** FUNCTION adodb_gmstrftime($fmt, $timestamp = false)
Convert a timestamp to a formatted GMT date.

** FUNCTION adodb_strftime($fmt, $timestamp = false)

174
Convert a timestamp to a formatted local date. Internally converts $fmt into
Penny Leach's avatar
Penny Leach committed
175
176
177
178
179
180
181
adodb_date format, then echo result.

For best results, you can define the local date format yourself. Define a global
variable $ADODB_DATE_LOCALE which is an array, 1st element is date format using
adodb_date syntax, and 2nd element is the time format, also in adodb_date syntax.

    eg. $ADODB_DATE_LOCALE = array('d/m/Y','H:i:s');
182

Penny Leach's avatar
Penny Leach committed
183
184
185
	Supported format codes:

<pre>
186
187
188
189
190
191
192
193
	%a - abbreviated weekday name according to the current locale
	%A - full weekday name according to the current locale
	%b - abbreviated month name according to the current locale
	%B - full month name according to the current locale
	%c - preferred date and time representation for the current locale
	%d - day of the month as a decimal number (range 01 to 31)
	%D - same as %m/%d/%y
	%e - day of the month as a decimal number, a single digit is preceded by a space (range ' 1' to '31')
Penny Leach's avatar
Penny Leach committed
194
	%h - same as %b
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
	%H - hour as a decimal number using a 24-hour clock (range 00 to 23)
	%I - hour as a decimal number using a 12-hour clock (range 01 to 12)
	%m - month as a decimal number (range 01 to 12)
	%M - minute as a decimal number
	%n - newline character
	%p - either `am' or `pm' according to the given time value, or the corresponding strings for the current locale
	%r - time in a.m. and p.m. notation
	%R - time in 24 hour notation
	%S - second as a decimal number
	%t - tab character
	%T - current time, equal to %H:%M:%S
	%x - preferred date representation for the current locale without the time
	%X - preferred time representation for the current locale without the date
	%y - year as a decimal number without a century (range 00 to 99)
	%Y - year as a decimal number including the century
	%Z - time zone or name or abbreviation
	%% - a literal `%' character
</pre>
Penny Leach's avatar
Penny Leach committed
213
214
215

	Unsupported codes:
<pre>
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
	%C - century number (the year divided by 100 and truncated to an integer, range 00 to 99)
	%g - like %G, but without the century.
	%G - The 4-digit year corresponding to the ISO week number (see %V).
	     This has the same format and value as %Y, except that if the ISO week number belongs
		 to the previous or next year, that year is used instead.
	%j - day of the year as a decimal number (range 001 to 366)
	%u - weekday as a decimal number [1,7], with 1 representing Monday
	%U - week number of the current year as a decimal number, starting
	    with the first Sunday as the first day of the first week
	%V - The ISO 8601:1988 week number of the current year as a decimal number,
	     range 01 to 53, where week 1 is the first week that has at least 4 days in the
		 current year, and with Monday as the first day of the week. (Use %G or %g for
		 the year component that corresponds to the week number for the specified timestamp.)
	%w - day of the week as a decimal, Sunday being 0
	%W - week number of the current year as a decimal number, starting with the
	     first Monday as the first day of the first week
Penny Leach's avatar
Penny Leach committed
232
233
234
235
236
237
238
239
240
</pre>

=============================================================================

NOTES

Useful url for generating test timestamps:
	http://www.4webhelp.net/us/timestamp.php

241
Possible future optimizations include
Penny Leach's avatar
Penny Leach committed
242

243
244
a. Using an algorithm similar to Plauger's in "The Standard C Library"
(page 428, xttotm.c _Ttotm() function). Plauger's algorithm will not
Penny Leach's avatar
Penny Leach committed
245
246
247
248
249
250
251
work outside 32-bit signed range, so i decided not to implement it.

b. Implement daylight savings, which looks awfully complicated, see
	http://webexhibits.org/daylightsaving/


CHANGELOG
252
253
254
255
- 16 Jan 2011 0.36
Added adodb_time() which returns current time. If > 2038, will return as float

- 7 Feb 2011 0.35
256
Changed adodb_date to be symmetric with adodb_mktime. See $jan1_71. fix for bc.
257
258
259

- 13 July 2010 0.34
Changed adodb_get_gm_diff to use DateTimeZone().
260
261
262
263
264

- 11 Feb 2008 0.33
* Bug in 0.32 fix for hour handling. Fixed.

- 1 Feb 2008 0.32
265
* Now adodb_mktime(0,0,0,12+$m,20,2040) works properly.
266
267
268
269
270

- 10 Jan 2008 0.31
* Now adodb_mktime(0,0,0,24,1,2037) works correctly.

- 15 July 2007 0.30
271
Added PHP 5.2.0 compatability fixes.
272
273
 * gmtime behaviour for 1970 has changed. We use the actual date if it is between 1970 to 2038 to get the
 * timezone, otherwise we use the current year as the baseline to retrieve the timezone.
274
 * Also the timezone's in php 5.2.* support historical data better, eg. if timezone today was +8, but
275
   in 1970 it was +7:30, then php 5.2 return +7:30, while this library will use +8.
276
277
 *

Penny Leach's avatar
Penny Leach committed
278
279
280
281
- 19 March 2006 0.24
Changed strftime() locale detection, because some locales prepend the day of week to the date when %c is used.

- 10 Feb 2006 0.23
282
PHP5 compat: when we detect PHP5, the RFC2822 format for gmt 0000hrs is changed from -0000 to +0000.
Penny Leach's avatar
Penny Leach committed
283
284
285
286
287
288
289
290
291
292
293
294
295
	In PHP4, we will still use -0000 for 100% compat with PHP4.

- 08 Sept 2005 0.22
In adodb_date2(), $is_gmt not supported properly. Fixed.

- 18 July  2005 0.21
In PHP 4.3.11, the 'r' format has changed. Leading 0 in day is added. Changed for compat.
Added support for negative months in adodb_mktime().

- 24 Feb 2005 0.20
Added limited strftime/gmstrftime support. x10 improvement in performance of adodb_date().

- 21 Dec 2004 0.17
296
In adodb_getdate(), the timestamp was accidentally converted to gmt when $is_gmt is false.
Penny Leach's avatar
Penny Leach committed
297
298
299
300
301
302
303
304
Also adodb_mktime(0,0,0) did not work properly. Both fixed thx Mauro.

- 17 Nov 2004 0.16
Removed intval typecast in adodb_mktime() for secs, allowing:
	 adodb_mktime(0,0,0 + 2236672153,1,1,1934);
Suggested by Ryan.

- 18 July 2004 0.15
305
All params in adodb_mktime were formerly compulsory. Now only the hour, min, secs is compulsory.
Penny Leach's avatar
Penny Leach committed
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
This brings it more in line with mktime (still not identical).

- 23 June 2004 0.14

Allow you to define your own daylights savings function, adodb_daylight_sv.
If the function is defined (somewhere in an include), then you can correct for daylights savings.

In this example, we apply daylights savings in June or July, adding one hour. This is extremely
unrealistic as it does not take into account time-zone, geographic location, current year.

function adodb_daylight_sv(&$arr, $is_gmt)
{
	if ($is_gmt) return;
	$m = $arr['mon'];
	if ($m == 6 || $m == 7) $arr['hours'] += 1;
}

323
This is only called by adodb_date() and not by adodb_mktime().
Penny Leach's avatar
Penny Leach committed
324
325

The format of $arr is
326
327
328
329
Array (
   [seconds] => 0
   [minutes] => 0
   [hours] => 0
Penny Leach's avatar
Penny Leach committed
330
331
   [mday] => 1      # day of month, eg 1st day of the month
   [mon] => 2       # month (eg. Feb)
332
   [year] => 2102
Penny Leach's avatar
Penny Leach committed
333
334
335
   [yday] => 31     # days in current year
   [leap] =>        # true if leap year
   [ndays] => 28    # no of days in current month
336
337
   )

Penny Leach's avatar
Penny Leach committed
338
339
340
341
342
343
344
345

- 28 Apr 2004 0.13
Fixed adodb_date to properly support $is_gmt. Thx to Dimitar Angelov.

- 20 Mar 2004 0.12
Fixed month calculation error in adodb_date. 2102-June-01 appeared as 2102-May-32.

- 26 Oct 2003 0.11
346
Because of daylight savings problems (some systems apply daylight savings to
Penny Leach's avatar
Penny Leach committed
347
348
349
January!!!), changed adodb_get_gmt_diff() to ignore daylight savings.

- 9 Aug 2003 0.10
350
Fixed bug with dates after 2038.
Penny Leach's avatar
Penny Leach committed
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
See http://phplens.com/lens/lensforum/msgs.php?id=6980

- 1 July 2003 0.09
Added support for Q (Quarter).
Added adodb_date2(), which accepts ISO date in 2nd param

- 3 March 2003 0.08
Added support for 'S' adodb_date() format char. Added constant ADODB_ALLOW_NEGATIVE_TS
if you want PHP to handle negative timestamps between 1901 to 1969.

- 27 Feb 2003 0.07
All negative numbers handled by adodb now because of RH 7.3+ problems.
See http://bugs.php.net/bug.php?id=20048&edit=2

- 4 Feb 2003 0.06
Fixed a typo, 1852 changed to 1582! This means that pre-1852 dates
are now correctly handled.

- 29 Jan 2003 0.05

Leap year checking differs under Julian calendar (pre 1582). Also
leap year code optimized by checking for most common case first.

We also handle month overflow correctly in mktime (eg month set to 13).

Day overflow for less than one month's days is supported.

- 28 Jan 2003 0.04

380
Gregorian correction handled. In PHP5, we might throw an error if
Penny Leach's avatar
Penny Leach committed
381
382
383
384
385
386
mktime uses invalid dates around 5-14 Oct 1582. Released with ADOdb 3.10.
Added limbo 5-14 Oct 1582 check, when we set to 15 Oct 1582.

- 27 Jan 2003 0.03

Fixed some more month problems due to gmt issues. Added constant ADODB_DATE_VERSION.
387
Fixed calculation of days since start of year for <1970.
Penny Leach's avatar
Penny Leach committed
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404

- 27 Jan 2003 0.02

Changed _adodb_getdate() to inline leap year checking for better performance.
Fixed problem with time-zones west of GMT +0000.

- 24 Jan 2003 0.01

First implementation.
*/


/* Initialization */

/*
	Version Number
*/
405
define('ADODB_DATE_VERSION',0.35);
406
407

$ADODB_DATETIME_CLASS = (PHP_VERSION >= 5.2);
Penny Leach's avatar
Penny Leach committed
408
409

/*
410
	This code was originally for windows. But apparently this problem happens
Penny Leach's avatar
Penny Leach committed
411
	also with Linux, RH 7.3 and later!
412

Penny Leach's avatar
Penny Leach committed
413
414
415
	glibc-2.2.5-34 and greater has been changed to return -1 for dates <
	1970.  This used to work.  The problem exists with RedHat 7.3 and 8.0
	echo (mktime(0, 0, 0, 1, 1, 1960));  // prints -1
416

Penny Leach's avatar
Penny Leach committed
417
418
419
420
421
422
423
	References:
	 http://bugs.php.net/bug.php?id=20048&edit=2
	 http://lists.debian.org/debian-glibc/2002/debian-glibc-200205/msg00010.html
*/

if (!defined('ADODB_ALLOW_NEGATIVE_TS')) define('ADODB_NO_NEGATIVE_TS',1);

424
425
426
if (!DEFINED('ADODB_FUTURE_DATE_CUTOFF_YEARS'))
	DEFINE('ADODB_FUTURE_DATE_CUTOFF_YEARS',200);

Penny Leach's avatar
Penny Leach committed
427
428
function adodb_date_test_date($y1,$m,$d=13)
{
429
430
	$h = round(rand()% 24);
	$t = adodb_mktime($h,0,0,$m,$d,$y1);
Penny Leach's avatar
Penny Leach committed
431
	$rez = adodb_date('Y-n-j H:i:s',$t);
432
433
434
435
	if ($h == 0) $h = '00';
	else if ($h < 10) $h = '0'.$h;
	if ("$y1-$m-$d $h:00:00" != $rez) {
		print "<b>$y1 error, expected=$y1-$m-$d $h:00:00, adodb=$rez</b><br>";
Penny Leach's avatar
Penny Leach committed
436
437
438
439
440
441
442
443
444
		return false;
	}
	return true;
}

function adodb_date_test_strftime($fmt)
{
	$s1 = strftime($fmt);
	$s2 = adodb_strftime($fmt);
445

Penny Leach's avatar
Penny Leach committed
446
	if ($s1 == $s2) return true;
447

448
	echo "error for $fmt,  strftime=$s1, adodb=$s2<br>";
Penny Leach's avatar
Penny Leach committed
449
450
451
452
453
	return false;
}

/**
	 Test Suite
454
*/
Penny Leach's avatar
Penny Leach committed
455
456
function adodb_date_test()
{
457

458
459
	for ($m=-24; $m<=24; $m++)
		echo "$m :",adodb_date('d-m-Y',adodb_mktime(0,0,0,1+$m,20,2040)),"<br>";
460

Penny Leach's avatar
Penny Leach committed
461
462
463
464
	error_reporting(E_ALL);
	print "<h4>Testing adodb_date and adodb_mktime. version=".ADODB_DATE_VERSION.' PHP='.PHP_VERSION."</h4>";
	@set_time_limit(0);
	$fail = false;
465

Penny Leach's avatar
Penny Leach committed
466
467
	// This flag disables calling of PHP native functions, so we can properly test the code
	if (!defined('ADODB_TEST_DATES')) define('ADODB_TEST_DATES',1);
468

469
	$t = time();
470
471


472
473
474
475
476
	$fmt = 'Y-m-d H:i:s';
	echo '<pre>';
	echo 'adodb: ',adodb_date($fmt,$t),'<br>';
	echo 'php  : ',date($fmt,$t),'<br>';
	echo '</pre>';
477

Penny Leach's avatar
Penny Leach committed
478
479
480
	adodb_date_test_strftime('%Y %m %x %X');
	adodb_date_test_strftime("%A %d %B %Y");
	adodb_date_test_strftime("%H %M S");
481

Penny Leach's avatar
Penny Leach committed
482
483
	$t = adodb_mktime(0,0,0);
	if (!(adodb_date('Y-m-d') == date('Y-m-d'))) print 'Error in '.adodb_mktime(0,0,0).'<br>';
484

Penny Leach's avatar
Penny Leach committed
485
486
	$t = adodb_mktime(0,0,0,6,1,2102);
	if (!(adodb_date('Y-m-d',$t) == '2102-06-01')) print 'Error in '.adodb_date('Y-m-d',$t).'<br>';
487

Penny Leach's avatar
Penny Leach committed
488
489
	$t = adodb_mktime(0,0,0,2,1,2102);
	if (!(adodb_date('Y-m-d',$t) == '2102-02-01')) print 'Error in '.adodb_date('Y-m-d',$t).'<br>';
490
491


Penny Leach's avatar
Penny Leach committed
492
493
494
495
	print "<p>Testing gregorian <=> julian conversion<p>";
	$t = adodb_mktime(0,0,0,10,11,1492);
	//http://www.holidayorigins.com/html/columbus_day.html - Friday check
	if (!(adodb_date('D Y-m-d',$t) == 'Fri 1492-10-11')) print 'Error in Columbus landing<br>';
496

Penny Leach's avatar
Penny Leach committed
497
498
	$t = adodb_mktime(0,0,0,2,29,1500);
	if (!(adodb_date('Y-m-d',$t) == '1500-02-29')) print 'Error in julian leap years<br>';
499

Penny Leach's avatar
Penny Leach committed
500
501
	$t = adodb_mktime(0,0,0,2,29,1700);
	if (!(adodb_date('Y-m-d',$t) == '1700-03-01')) print 'Error in gregorian leap years<br>';
502

Penny Leach's avatar
Penny Leach committed
503
504
505
506
	print  adodb_mktime(0,0,0,10,4,1582).' ';
	print adodb_mktime(0,0,0,10,15,1582);
	$diff = (adodb_mktime(0,0,0,10,15,1582) - adodb_mktime(0,0,0,10,4,1582));
	if ($diff != 3600*24) print " <b>Error in gregorian correction = ".($diff/3600/24)." days </b><br>";
507

Penny Leach's avatar
Penny Leach committed
508
509
	print " 15 Oct 1582, Fri=".(adodb_dow(1582,10,15) == 5 ? 'Fri' : '<b>Error</b>')."<br>";
	print " 4 Oct 1582, Thu=".(adodb_dow(1582,10,4) == 4 ? 'Thu' : '<b>Error</b>')."<br>";
510

Penny Leach's avatar
Penny Leach committed
511
	print "<p>Testing overflow<p>";
512

Penny Leach's avatar
Penny Leach committed
513
514
515
516
517
518
519
520
521
522
523
524
	$t = adodb_mktime(0,0,0,3,33,1965);
	if (!(adodb_date('Y-m-d',$t) == '1965-04-02')) print 'Error in day overflow 1 <br>';
	$t = adodb_mktime(0,0,0,4,33,1971);
	if (!(adodb_date('Y-m-d',$t) == '1971-05-03')) print 'Error in day overflow 2 <br>';
	$t = adodb_mktime(0,0,0,1,60,1965);
	if (!(adodb_date('Y-m-d',$t) == '1965-03-01')) print 'Error in day overflow 3 '.adodb_date('Y-m-d',$t).' <br>';
	$t = adodb_mktime(0,0,0,12,32,1965);
	if (!(adodb_date('Y-m-d',$t) == '1966-01-01')) print 'Error in day overflow 4 '.adodb_date('Y-m-d',$t).' <br>';
	$t = adodb_mktime(0,0,0,12,63,1965);
	if (!(adodb_date('Y-m-d',$t) == '1966-02-01')) print 'Error in day overflow 5 '.adodb_date('Y-m-d',$t).' <br>';
	$t = adodb_mktime(0,0,0,13,3,1965);
	if (!(adodb_date('Y-m-d',$t) == '1966-01-03')) print 'Error in mth overflow 1 <br>';
525

Penny Leach's avatar
Penny Leach committed
526
527
528
529
530
531
532
533
	print "Testing 2-digit => 4-digit year conversion<p>";
	if (adodb_year_digit_check(00) != 2000) print "Err 2-digit 2000<br>";
	if (adodb_year_digit_check(10) != 2010) print "Err 2-digit 2010<br>";
	if (adodb_year_digit_check(20) != 2020) print "Err 2-digit 2020<br>";
	if (adodb_year_digit_check(30) != 2030) print "Err 2-digit 2030<br>";
	if (adodb_year_digit_check(40) != 1940) print "Err 2-digit 1940<br>";
	if (adodb_year_digit_check(50) != 1950) print "Err 2-digit 1950<br>";
	if (adodb_year_digit_check(90) != 1990) print "Err 2-digit 1990<br>";
534

Penny Leach's avatar
Penny Leach committed
535
536
	// Test string formating
	print "<p>Testing date formating</p>";
537

Penny Leach's avatar
Penny Leach committed
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
	$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';
	$s1 = date($fmt,0);
	$s2 = adodb_date($fmt,0);
	if ($s1 != $s2) {
		print " date() 0 failed<br>$s1<br>$s2<br>";
	}
	flush();
	for ($i=100; --$i > 0; ) {

		$ts = 3600.0*((rand()%60000)+(rand()%60000))+(rand()%60000);
		$s1 = date($fmt,$ts);
		$s2 = adodb_date($fmt,$ts);
		//print "$s1 <br>$s2 <p>";
		$pos = strcmp($s1,$s2);

		if (($s1) != ($s2)) {
			for ($j=0,$k=strlen($s1); $j < $k; $j++) {
				if ($s1[$j] != $s2[$j]) {
					print substr($s1,$j).' ';
					break;
				}
			}
560
			print "<b>Error date(): $ts<br><pre>
Penny Leach's avatar
Penny Leach committed
561
562
563
564
&nbsp; \"$s1\" (date len=".strlen($s1).")
&nbsp; \"$s2\" (adodb_date len=".strlen($s2).")</b></pre><br>";
			$fail = true;
		}
565

Penny Leach's avatar
Penny Leach committed
566
567
568
569
570
571
572
573
574
575
576
577
		$a1 = getdate($ts);
		$a2 = adodb_getdate($ts);
		$rez = array_diff($a1,$a2);
		if (sizeof($rez)>0) {
			print "<b>Error getdate() $ts</b><br>";
				print_r($a1);
			print "<br>";
				print_r($a2);
			print "<p>";
			$fail = true;
		}
	}
578

Penny Leach's avatar
Penny Leach committed
579
580
581
582
583
584
585
	// Test generation of dates outside 1901-2038
	print "<p>Testing random dates between 100 and 4000</p>";
	adodb_date_test_date(100,1);
	for ($i=100; --$i >= 0;) {
		$y1 = 100+rand(0,1970-100);
		$m = rand(1,12);
		adodb_date_test_date($y1,$m);
586

Penny Leach's avatar
Penny Leach committed
587
588
589
590
591
592
593
594
595
596
		$y1 = 3000-rand(0,3000-1970);
		adodb_date_test_date($y1,$m);
	}
	print '<p>';
	$start = 1960+rand(0,10);
	$yrs = 12;
	$i = 365.25*86400*($start-1970);
	$offset = 36000+rand(10000,60000);
	$max = 365*$yrs*86400;
	$lastyear = 0;
597

Penny Leach's avatar
Penny Leach committed
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
	// we generate a timestamp, convert it to a date, and convert it back to a timestamp
	// and check if the roundtrip broke the original timestamp value.
	print "Testing $start to ".($start+$yrs).", or $max seconds, offset=$offset: ";
	$cnt = 0;
	for ($max += $i; $i < $max; $i += $offset) {
		$ret = adodb_date('m,d,Y,H,i,s',$i);
		$arr = explode(',',$ret);
		if ($lastyear != $arr[2]) {
			$lastyear = $arr[2];
			print " $lastyear ";
			flush();
		}
		$newi = adodb_mktime($arr[3],$arr[4],$arr[5],$arr[0],$arr[1],$arr[2]);
		if ($i != $newi) {
			print "Error at $i, adodb_mktime returned $newi ($ret)";
			$fail = true;
			break;
		}
		$cnt += 1;
	}
	echo "Tested $cnt dates<br>";
	if (!$fail) print "<p>Passed !</p>";
	else print "<p><b>Failed</b> :-(</p>";
}

623
624
625
626
627
628
function adodb_time()
{
	$d = new DateTime();
	return $d->format('U');
}

Penny Leach's avatar
Penny Leach committed
629
/**
630
	Returns day of week, 0 = Sunday,... 6=Saturday.
Penny Leach's avatar
Penny Leach committed
631
632
633
634
635
	Algorithm from PEAR::Date_Calc
*/
function adodb_dow($year, $month, $day)
{
/*
636
637
Pope Gregory removed 10 days - October 5 to October 14 - from the year 1582 and
proclaimed that from that time onwards 3 days would be dropped from the calendar
Penny Leach's avatar
Penny Leach committed
638
639
every 400 years.

640
Thursday, October 4, 1582 (Julian) was followed immediately by Friday, October 15, 1582 (Gregorian).
Penny Leach's avatar
Penny Leach committed
641
642
*/
	if ($year <= 1582) {
643
		if ($year < 1582 ||
Penny Leach's avatar
Penny Leach committed
644
645
646
647
648
			($year == 1582 && ($month < 10 || ($month == 10 && $day < 15)))) $greg_correction = 3;
		 else
			$greg_correction = 0;
	} else
		$greg_correction = 0;
649

Penny Leach's avatar
Penny Leach committed
650
651
652
653
654
655
	if($month > 2)
	    $month -= 2;
	else {
	    $month += 10;
	    $year--;
	}
656

Penny Leach's avatar
Penny Leach committed
657
658
659
660
661
	$day =  floor((13 * $month - 1) / 5) +
	        $day + ($year % 100) +
	        floor(($year % 100) / 4) +
	        floor(($year / 100) / 4) - 2 *
	        floor($year / 100) + 77 + $greg_correction;
662

Penny Leach's avatar
Penny Leach committed
663
664
665
666
667
	return $day - 7 * floor($day / 7);
}


/**
668
 Checks for leap year, returns true if it is. No 2-digit year check. Also
Penny Leach's avatar
Penny Leach committed
669
670
 handles julian calendar correctly.
*/
671
function _adodb_is_leap_year($year)
Penny Leach's avatar
Penny Leach committed
672
673
{
	if ($year % 4 != 0) return false;
674

Penny Leach's avatar
Penny Leach committed
675
676
677
678
679
	if ($year % 400 == 0) {
		return true;
	// if gregorian calendar (>1582), century not-divisible by 400 is not leap
	} else if ($year > 1582 && $year % 100 == 0 ) {
		return false;
680
681
	}

Penny Leach's avatar
Penny Leach committed
682
683
684
685
686
687
688
	return true;
}


/**
 checks for leap year, returns true if it is. Has 2-digit year check
*/
689
function adodb_is_leap_year($year)
Penny Leach's avatar
Penny Leach committed
690
691
692
693
694
695
696
697
{
	return  _adodb_is_leap_year(adodb_year_digit_check($year));
}

/**
	Fix 2-digit years. Works for any century.
 	Assumes that if 2-digit is more than 30 years in future, then previous century.
*/
698
function adodb_year_digit_check($y)
Penny Leach's avatar
Penny Leach committed
699
700
{
	if ($y < 100) {
701

Penny Leach's avatar
Penny Leach committed
702
703
		$yr = (integer) date("Y");
		$century = (integer) ($yr /100);
704

Penny Leach's avatar
Penny Leach committed
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
		if ($yr%100 > 50) {
			$c1 = $century + 1;
			$c0 = $century;
		} else {
			$c1 = $century;
			$c0 = $century - 1;
		}
		$c1 *= 100;
		// if 2-digit year is less than 30 years in future, set it to this century
		// otherwise if more than 30 years in future, then we set 2-digit year to the prev century.
		if (($y + $c1) < $yr+30) $y = $y + $c1;
		else $y = $y + $c0*100;
	}
	return $y;
}

721
function adodb_get_gmt_diff_ts($ts)
722
723
724
725
726
727
{
	if (0 <= $ts && $ts <= 0x7FFFFFFF) { // check if number in 32-bit signed range) {
		$arr = getdate($ts);
		$y = $arr['year'];
		$m = $arr['mon'];
		$d = $arr['mday'];
728
		return adodb_get_gmt_diff($y,$m,$d);
729
730
731
	} else {
		return adodb_get_gmt_diff(false,false,false);
	}
732

733
734
}

Penny Leach's avatar
Penny Leach committed
735
/**
736
 get local time zone offset from GMT. Does not handle historical timezones before 1970.
Penny Leach's avatar
Penny Leach committed
737
*/
738
function adodb_get_gmt_diff($y,$m,$d)
Penny Leach's avatar
Penny Leach committed
739
{
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
static $TZ,$tzo;
global $ADODB_DATETIME_CLASS;

	if (!defined('ADODB_TEST_DATES')) $y = false;
	else if ($y < 1970 || $y >= 2038) $y = false;

	if ($ADODB_DATETIME_CLASS && $y !== false) {
		$dt = new DateTime();
		$dt->setISODate($y,$m,$d);
		if (empty($tzo)) {
			$tzo = new DateTimeZone(date_default_timezone_get());
		#	$tzt = timezone_transitions_get( $tzo );
		}
		return -$tzo->getOffset($dt);
	} else {
		if (isset($TZ)) return $TZ;
		$y = date('Y');
757
758
759
760
761
762
763
		/*
		if (function_exists('date_default_timezone_get') && function_exists('timezone_offset_get')) {
			$tzonename = date_default_timezone_get();
			if ($tzonename) {
				$tobj = new DateTimeZone($tzonename);
				$TZ = -timezone_offset_get($tobj,new DateTime("now",$tzo));
			}
764
		}
765
766
		*/
		if (empty($TZ)) $TZ = mktime(0,0,0,12,2,$y) - gmmktime(0,0,0,12,2,$y);
767
	}
Penny Leach's avatar
Penny Leach committed
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
	return $TZ;
}

/**
	Returns an array with date info.
*/
function adodb_getdate($d=false,$fast=false)
{
	if ($d === false) return getdate();
	if (!defined('ADODB_TEST_DATES')) {
		if ((abs($d) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
			if (!defined('ADODB_NO_NEGATIVE_TS') || $d >= 0) // if windows, must be +ve integer
				return @getdate($d);
		}
	}
	return _adodb_getdate($d);
}

/*
// generate $YRS table for _adodb_getdate()
function adodb_date_gentable($out=true)
{

	for ($i=1970; $i >= 1600; $i-=10) {
		$s = adodb_gmmktime(0,0,0,1,1,$i);
793
		echo "$i => $s,<br>";
Penny Leach's avatar
Penny Leach committed
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
	}
}
adodb_date_gentable();

for ($i=1970; $i > 1500; $i--) {

echo "<hr />$i ";
	adodb_date_test_date($i,1,1);
}

*/


$_month_table_normal = array("",31,28,31,30,31,30,31,31,30,31,30,31);
$_month_table_leaf = array("",31,29,31,30,31,30,31,31,30,31,30,31);
809

Penny Leach's avatar
Penny Leach committed
810
811
812
813
function adodb_validdate($y,$m,$d)
{
global $_month_table_normal,$_month_table_leaf;

814
815
	if (_adodb_is_leap_year($y)) $marr = $_month_table_leaf;
	else $marr = $_month_table_normal;
816

Penny Leach's avatar
Penny Leach committed
817
	if ($m > 12 || $m < 1) return false;
818

Penny Leach's avatar
Penny Leach committed
819
	if ($d > 31 || $d < 1) return false;
820

Penny Leach's avatar
Penny Leach committed
821
	if ($marr[$m] < $d) return false;
822

823
	if ($y < 1000 || $y > 3000) return false;
824

Penny Leach's avatar
Penny Leach committed
825
826
827
828
829
830
831
832
833
834
835
	return true;
}

/**
	Low-level function that returns the getdate() array. We have a special
	$fast flag, which if set to true, will return fewer array values,
	and is much faster as it does not calculate dow, etc.
*/
function _adodb_getdate($origd=false,$fast=false,$is_gmt=false)
{
static $YRS;
836
837
838
global $_month_table_normal,$_month_table_leaf, $_adodb_last_date_call_failed;

	$_adodb_last_date_call_failed = false;
Penny Leach's avatar
Penny Leach committed
839

840
	$d =  $origd - ($is_gmt ? 0 : adodb_get_gmt_diff_ts($origd));
Penny Leach's avatar
Penny Leach committed
841
842
843
	$_day_power = 86400;
	$_hour_power = 3600;
	$_min_power = 60;
844

845
846
847
848
849
850
851
852
	$cutoffDate = time() + (60 * 60 * 24 * 365 * ADODB_FUTURE_DATE_CUTOFF_YEARS);

	if ($d > $cutoffDate)
	{
		$d = $cutoffDate;
		$_adodb_last_date_call_failed = true;
	}

853
854
	if ($d < -12219321600) $d -= 86400*10; // if 15 Oct 1582 or earlier, gregorian correction

Penny Leach's avatar
Penny Leach committed
855
856
	$_month_table_normal = array("",31,28,31,30,31,30,31,31,30,31,30,31);
	$_month_table_leaf = array("",31,29,31,30,31,30,31,31,30,31,30,31);
857

Penny Leach's avatar
Penny Leach committed
858
859
	$d366 = $_day_power * 366;
	$d365 = $_day_power * 365;
860

Penny Leach's avatar
Penny Leach committed
861
	if ($d < 0) {
862

Penny Leach's avatar
Penny Leach committed
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
		if (empty($YRS)) $YRS = array(
			1970 => 0,
			1960 => -315619200,
			1950 => -631152000,
			1940 => -946771200,
			1930 => -1262304000,
			1920 => -1577923200,
			1910 => -1893456000,
			1900 => -2208988800,
			1890 => -2524521600,
			1880 => -2840140800,
			1870 => -3155673600,
			1860 => -3471292800,
			1850 => -3786825600,
			1840 => -4102444800,
			1830 => -4417977600,
			1820 => -4733596800,
			1810 => -5049129600,
			1800 => -5364662400,
			1790 => -5680195200,
			1780 => -5995814400,
			1770 => -6311347200,
			1760 => -6626966400,
			1750 => -6942499200,
			1740 => -7258118400,
			1730 => -7573651200,
			1720 => -7889270400,
			1710 => -8204803200,
			1700 => -8520336000,
			1690 => -8835868800,
			1680 => -9151488000,
			1670 => -9467020800,
			1660 => -9782640000,
			1650 => -10098172800,
			1640 => -10413792000,
			1630 => -10729324800,
			1620 => -11044944000,
			1610 => -11360476800,
			1600 => -11676096000);

		if ($is_gmt) $origd = $d;
904
		// The valid range of a 32bit signed timestamp is typically from
Penny Leach's avatar
Penny Leach committed
905
906
		// Fri, 13 Dec 1901 20:45:54 GMT to Tue, 19 Jan 2038 03:14:07 GMT
		//
907

Penny Leach's avatar
Penny Leach committed
908
909
		# old algorithm iterates through all years. new algorithm does it in
		# 10 year blocks
910

Penny Leach's avatar
Penny Leach committed
911
912
913
914
		/*
		# old algo
		for ($a = 1970 ; --$a >= 0;) {
			$lastd = $d;
915

Penny Leach's avatar
Penny Leach committed
916
917
			if ($leaf = _adodb_is_leap_year($a)) $d += $d366;
			else $d += $d365;
918

Penny Leach's avatar
Penny Leach committed
919
920
921
922
923
924
			if ($d >= 0) {
				$year = $a;
				break;
			}
		}
		*/
925

Penny Leach's avatar
Penny Leach committed
926
927
928
929
930
931
932
933
934
935
		$lastsecs = 0;
		$lastyear = 1970;
		foreach($YRS as $year => $secs) {
			if ($d >= $secs) {
				$a = $lastyear;
				break;
			}
			$lastsecs = $secs;
			$lastyear = $year;
		}
936

Penny Leach's avatar
Penny Leach committed
937
938
		$d -= $lastsecs;
		if (!isset($a)) $a = $lastyear;
939

Penny Leach's avatar
Penny Leach committed
940
		//echo ' yr=',$a,' ', $d,'.';
941

Penny Leach's avatar
Penny Leach committed
942
943
		for (; --$a >= 0;) {
			$lastd = $d;
944

Penny Leach's avatar
Penny Leach committed
945
946
			if ($leaf = _adodb_is_leap_year($a)) $d += $d366;
			else $d += $d365;
947

Penny Leach's avatar
Penny Leach committed
948
949
950
951
952
953
			if ($d >= 0) {
				$year = $a;
				break;
			}
		}
		/**/
954

Penny Leach's avatar
Penny Leach committed
955
		$secsInYear = 86400 * ($leaf ? 366 : 365) + $lastd;
956

Penny Leach's avatar
Penny Leach committed
957
958
959
960
961
962
963
964
965
966
967
		$d = $lastd;
		$mtab = ($leaf) ? $_month_table_leaf : $_month_table_normal;
		for ($a = 13 ; --$a > 0;) {
			$lastd = $d;
			$d += $mtab[$a] * $_day_power;
			if ($d >= 0) {
				$month = $a;
				$ndays = $mtab[$a];
				break;
			}
		}
968

Penny Leach's avatar
Penny Leach committed
969
970
971
972
973
		$d = $lastd;
		$day = $ndays + ceil(($d+1) / ($_day_power));

		$d += ($ndays - $day+1)* $_day_power;
		$hour = floor($d/$_hour_power);
974

Penny Leach's avatar
Penny Leach committed
975
976
977
	} else {
		for ($a = 1970 ;; $a++) {
			$lastd = $d;
978

Penny Leach's avatar
Penny Leach committed
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
			if ($leaf = _adodb_is_leap_year($a)) $d -= $d366;
			else $d -= $d365;
			if ($d < 0) {
				$year = $a;
				break;
			}
		}
		$secsInYear = $lastd;
		$d = $lastd;
		$mtab = ($leaf) ? $_month_table_leaf : $_month_table_normal;
		for ($a = 1 ; $a <= 12; $a++) {
			$lastd = $d;
			$d -= $mtab[$a] * $_day_power;
			if ($d < 0) {
				$month = $a;
				$ndays = $mtab[$a];
				break;
			}
		}
		$d = $lastd;
		$day = ceil(($d+1) / $_day_power);
		$d = $d - ($day-1) * $_day_power;
		$hour = floor($d /$_hour_power);
	}
1003

Penny Leach's avatar
Penny Leach committed
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
	$d -= $hour * $_hour_power;
	$min = floor($d/$_min_power);
	$secs = $d - $min * $_min_power;
	if ($fast) {
		return array(
		'seconds' => $secs,
		'minutes' => $min,
		'hours' => $hour,
		'mday' => $day,
		'mon' => $month,
		'year' => $year,
		'yday' => floor($secsInYear/$_day_power),
		'leap' => $leaf,
		'ndays' => $ndays
		);
	}
1020
1021


Penny Leach's avatar
Penny Leach committed
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
	$dow = adodb_dow($year,$month,$day);

	return array(
		'seconds' => $secs,
		'minutes' => $min,
		'hours' => $hour,
		'mday' => $day,
		'wday' => $dow,
		'mon' => $month,
		'year' => $year,
		'yday' => floor($secsInYear/$_day_power),
		'weekday' => gmdate('l',$_day_power*(3+$dow)),
		'month' => gmdate('F',mktime(0,0,0,$month,2,1971)),
		0 => $origd
	);
}
1038
1039
/*
		if ($isphp5)
1040
				$dates .= sprintf('%s%04d',($gmt<=0)?'+':'-',abs($gmt)/36);
1041
			else
1042
				$dates .= sprintf('%s%04d',($gmt<0)?'+':'-',abs($gmt)/36);
1043
1044
1045
1046
1047
			break;*/
function adodb_tz_offset($gmt,$isphp5)
{
	$zhrs = abs($gmt)/3600;
	$hrs = floor($zhrs);
1048
1049
	if ($isphp5)
		return sprintf('%s%02d%02d',($gmt<=0)?'+':'-',floor($zhrs),($zhrs-$hrs)*60);
1050
	else
1051
		return sprintf('%s%02d%02d',($gmt<0)?'+':'-',floor($zhrs),($zhrs-$hrs)*60);
1052
1053
}

Penny Leach's avatar
Penny Leach committed
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063

function adodb_gmdate($fmt,$d=false)
{
	return adodb_date($fmt,$d,true);
}

// accepts unix timestamp and iso date format in $d
function adodb_date2($fmt, $d=false, $is_gmt=false)
{
	if ($d !== false) {
1064
1065
		if (!preg_match(
			"|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ -]?(([0-9]{1,2}):?([0-9]{1,2}):?([0-9\.]{1,4}))?|",
Penny Leach's avatar
Penny Leach committed
1066
1067
1068
			($d), $rr)) return adodb_date($fmt,false,$is_gmt);

		if ($rr[1] <= 100 && $rr[2]<= 1) return adodb_date($fmt,false,$is_gmt);
1069

Penny Leach's avatar
Penny Leach committed
1070
1071
1072
1073
		// h-m-s-MM-DD-YY
		if (!isset($rr[5])) $d = adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1],false,$is_gmt);
		else $d = @adodb_mktime($rr[5],$rr[6],$rr[7],$rr[2],$rr[3],$rr[1],false,$is_gmt);
	}
1074

Penny Leach's avatar
Penny Leach committed
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
	return adodb_date($fmt,$d,$is_gmt);
}


/**
	Return formatted date based on timestamp $d
*/
function adodb_date($fmt,$d=false,$is_gmt=false)
{
static $daylight;
1085
global $ADODB_DATETIME_CLASS;
1086
1087
static $jan1_1971;

Penny Leach's avatar
Penny Leach committed
1088

1089
1090
1091
1092
	if (!isset($daylight)) {
		$daylight = function_exists('adodb_daylight_sv');
		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
	}
1093

Penny Leach's avatar
Penny Leach committed
1094
1095
1096
	if ($d === false) return ($is_gmt)? @gmdate($fmt): @date($fmt);
	if (!defined('ADODB_TEST_DATES')) {
		if ((abs($d) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
1097

1098
			if (!defined('ADODB_NO_NEGATIVE_TS') || $d >= $jan1_1971) // if windows, must be +ve integer
Penny Leach's avatar
Penny Leach committed
1099
1100
1101
1102
1103
				return ($is_gmt)? @gmdate($fmt,$d): @date($fmt,$d);

		}
	}
	$_day_power = 86400;
1104

Penny Leach's avatar
Penny Leach committed
1105
	$arr = _adodb_getdate($d,true,$is_gmt);
1106

Penny Leach's avatar
Penny Leach committed
1107
	if ($daylight) adodb_daylight_sv($arr, $is_gmt);
1108

Penny Leach's avatar
Penny Leach committed
1109
1110
1111
1112
1113
1114
	$year = $arr['year'];
	$month = $arr['mon'];
	$day = $arr['mday'];
	$hour = $arr['hours'];
	$min = $arr['minutes'];
	$secs = $arr['seconds'];
1115

Penny Leach's avatar
Penny Leach committed
1116
1117
	$max = strlen($fmt);
	$dates = '';
1118

Penny Leach's avatar
Penny Leach committed
1119
	$isphp5 = PHP_VERSION >= 5;
1120

Penny Leach's avatar
Penny Leach committed
1121
1122
1123
1124
1125
1126
	/*
		at this point, we have the following integer vars to manipulate:
		$year, $month, $day, $hour, $min, $secs
	*/
	for ($i=0; $i < $max; $i++) {
		switch($fmt[$i]) {
1127
1128
1129
		case 'e':
			$dates .= date('e');
			break;
1130
		case 'T':
1131
1132
1133
1134
1135
1136
1137
			if ($ADODB_DATETIME_CLASS) {
				$dt = new DateTime();
				$dt->SetDate($year,$month,$day);
				$dates .= $dt->Format('T');
			} else
				$dates .= date('T');
			break;
Penny Leach's avatar
Penny Leach committed
1138
1139
1140
		// YEAR
		case 'L': $dates .= $arr['leap'] ? '1' : '0'; break;
		case 'r': // Thu, 21 Dec 2000 16:01:07 +0200
1141

Penny Leach's avatar
Penny Leach committed
1142
1143
			// 4.3.11 uses '04 Jun 2004'
			// 4.3.8 uses  ' 4 Jun 2004'
1144
			$dates .= gmdate('D',$_day_power*(3+adodb_dow($year,$month,$day))).', '
Penny Leach's avatar
Penny Leach committed
1145
				. ($day<10?'0'.$day:$day) . ' '.date('M',mktime(0,0,0,$month,2,1971)).' '.$year.' ';
1146
1147
1148

			if ($hour < 10) $dates .= '0'.$hour; else $dates .= $hour;

Penny Leach's avatar
Penny Leach committed
1149
			if ($min < 10) $dates .= ':0'.$min; else $dates .= ':'.$min;
1150

Penny Leach's avatar
Penny Leach committed
1151
			if ($secs < 10) $dates .= ':0'.$secs; else $dates .= ':'.$secs;
1152

1153
			$gmt = adodb_get_gmt_diff($year,$month,$day);
1154

1155
			$dates .= ' '.adodb_tz_offset($gmt,$isphp5);
Penny Leach's avatar
Penny Leach committed
1156
			break;
1157

Penny Leach's avatar
Penny Leach committed
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
		case 'Y': $dates .= $year; break;
		case 'y': $dates .= substr($year,strlen($year)-2,2); break;
		// MONTH
		case 'm': if ($month<10) $dates .= '0'.$month; else $dates .= $month; break;
		case 'Q': $dates .= ($month+3)>>2; break;
		case 'n': $dates .= $month; break;
		case 'M': $dates .= date('M',mktime(0,0,0,$month,2,1971)); break;
		case 'F': $dates .= date('F',mktime(0,0,0,$month,2,1971)); break;
		// DAY
		case 't': $dates .= $arr['ndays']; break;
		case 'z': $dates .= $arr['yday']; break;
		case 'w': $dates .= adodb_dow($year,$month,$day); break;
		case 'l': $dates .= gmdate('l',$_day_power*(3+adodb_dow($year,$month,$day))); break;
		case 'D': $dates .= gmdate('D',$_day_power*(3+adodb_dow($year,$month,$day))); break;
		case 'j': $dates .= $day; break;
		case 'd': if ($day<10) $dates .= '0'.$day; else $dates .= $day; break;
1174
		case 'S':
Penny Leach's avatar
Penny Leach committed
1175
1176
1177
1178
1179
1180
			$d10 = $day % 10;
			if ($d10 == 1) $dates .= 'st';
			else if ($d10 == 2 && $day != 12) $dates .= 'nd';
			else if ($d10 == 3) $dates .= 'rd';
			else $dates .= 'th';
			break;
1181

Penny Leach's avatar
Penny Leach committed
1182
1183
		// HOUR
		case 'Z':
1184
			$dates .= ($is_gmt) ? 0 : -adodb_get_gmt_diff($year,$month,$day); break;
1185
		case 'O':
1186
			$gmt = ($is_gmt) ? 0 : adodb_get_gmt_diff($year,$month,$day);
1187

1188
			$dates .= adodb_tz_offset($gmt,$isphp5);
Penny Leach's avatar
Penny Leach committed
1189
			break;
1190
1191
1192
1193

		case 'H':
			if ($hour < 10) $dates .= '0'.$hour;
			else $dates .= $hour;
Penny Leach's avatar
Penny Leach committed
1194
			break;
1195
1196
		case 'h':
			if ($hour > 12) $hh = $hour - 12;
Penny Leach's avatar
Penny Leach committed
1197
			else {
1198
				if ($hour == 0) $hh = '12';
Penny Leach's avatar
Penny Leach committed
1199
1200
				else $hh = $hour;
			}
1201

Penny Leach's avatar
Penny Leach committed
1202
1203
1204
			if ($hh < 10) $dates .= '0'.$hh;
			else $dates .= $hh;
			break;
1205
1206

		case 'G':
Penny Leach's avatar
Penny Leach committed
1207
1208
			$dates .= $hour;
			break;
1209

Penny Leach's avatar
Penny Leach committed
1210
		case 'g':
1211
			if ($hour > 12) $hh = $hour - 12;
Penny Leach's avatar
Penny Leach committed
1212
			else {
1213
1214
				if ($hour == 0) $hh = '12';
				else $hh = $hour;
Penny Leach's avatar
Penny Leach committed
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
			}
			$dates .= $hh;
			break;
		// MINUTES
		case 'i': if ($min < 10) $dates .= '0'.$min; else $dates .= $min; break;
		// SECONDS
		case 'U': $dates .= $d; break;
		case 's': if ($secs < 10) $dates .= '0'.$secs; else $dates .= $secs; break;
		// AM/PM
		// Note 00:00 to 11:59 is AM, while 12:00 to 23:59 is PM
		case 'a':
			if ($hour>=12) $dates .= 'pm';
			else $dates .= 'am';
			break;
		case 'A':
			if ($hour>=12) $dates .= 'PM';
			else $dates .= 'AM';
			break;
		default:
			$dates .= $fmt[$i]; break;
		// ESCAPE
1236
		case "\\":
Penny Leach's avatar
Penny Leach committed
1237
1238
1239
1240
1241
1242
1243
1244
1245
			$i++;
			if ($i < $max) $dates .= $fmt[$i];
			break;
		}
	}
	return $dates;
}

/**
1246
	Returns a timestamp given a GMT/UTC time.
Penny Leach's avatar
Penny Leach committed
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
	Note that $is_dst is not implemented and is ignored.
*/
function adodb_gmmktime($hr,$min,$sec,$mon=false,$day=false,$year=false,$is_dst=false)
{
	return adodb_mktime($hr,$min,$sec,$mon,$day,$year,$is_dst,true);
}

/**
	Return a timestamp given a local time. Originally by jackbbs.
	Note that $is_dst is not implemented and is ignored.
1257

Penny Leach's avatar
Penny Leach committed
1258
1259
	Not a very fast algorithm - O(n) operation. Could be optimized to O(1).
*/
1260
function adodb_mktime($hr,$min,$sec,$mon=false,$day=false,$year=false,$is_dst=false,$is_gmt=false)
Penny Leach's avatar
Penny Leach committed
1261
1262
1263
1264
1265
1266
{
	if (!defined('ADODB_TEST_DATES')) {

		if ($mon === false) {
			return $is_gmt? @gmmktime($hr,$min,$sec): @mktime($hr,$min,$sec);
		}
1267
1268

		// for windows, we don't check 1970 because with timezone differences,
Penny Leach's avatar
Penny Leach committed
1269
		// 1 Jan 1970 could generate negative timestamp, which is illegal
1270
		$usephpfns = (1970 < $year && $year < 2038
Penny Leach's avatar
Penny Leach committed
1271
			|| !defined('ADODB_NO_NEGATIVE_TS') && (1901 < $year && $year < 2038)
1272
1273
1274
			);


1275
		if ($usephpfns && ($year + $mon/12+$day/365.25+$hr/(24*365.25) >= 2038)) $usephpfns = false;
1276

1277
		if ($usephpfns) {
Penny Leach's avatar
Penny Leach committed
1278
1279
1280
				return $is_gmt ?
					@gmmktime($hr,$min,$sec,$mon,$day,$year):
					@mktime($hr,$min,$sec,$mon,$day,$year);
1281
		}
Penny Leach's avatar
Penny Leach committed
1282
	}
1283

1284
	$gmt_different = ($is_gmt) ? 0 : adodb_get_gmt_diff($year,$mon,$day);
Penny Leach's avatar
Penny Leach committed
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295

	/*
	# disabled because some people place large values in $sec.
	# however we need it for $mon because we use an array...
	$hr = intval($hr);
	$min = intval($min);
	$sec = intval($sec);
	*/
	$mon = intval($mon);
	$day = intval($day);
	$year = intval($year);
1296
1297


Penny Leach's avatar
Penny Leach committed
1298
1299
1300
	$year = adodb_year_digit_check($year);

	if ($mon > 12) {
1301
		$y = floor(($mon-1)/ 12);
Penny Leach's avatar
Penny Leach committed
1302
1303
1304
1305
1306
1307
1308
		$year += $y;
		$mon -= $y*12;
	} else if ($mon < 1) {
		$y = ceil((1-$mon) / 12);
		$year -= $y;
		$mon += $y*12;
	}
1309

Penny Leach's avatar
Penny Leach committed
1310
1311
1312
	$_day_power = 86400;
	$_hour_power = 3600;
	$_min_power = 60;
1313

Penny Leach's avatar
Penny Leach committed
1314
1315
	$_month_table_normal = array("",31,28,31,30,31,30,31,31,30,31,30,31);
	$_month_table_leaf = array("",31,29,31,30,31,30,31,31,30,31,30,31);
1316

Penny Leach's avatar
Penny Leach committed
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
	$_total_date = 0;
	if ($year >= 1970) {
		for ($a = 1970 ; $a <= $year; $a++) {
			$leaf = _adodb_is_leap_year($a);
			if ($leaf == true) {
				$loop_table = $_month_table_leaf;
				$_add_date = 366;
			} else {
				$loop_table = $_month_table_normal;
				$_add_date = 365;
			}
1328
			if ($a < $year) {
Penny Leach's avatar
Penny Leach committed
1329
1330
1331
1332
1333
1334
1335
1336
1337
				$_total_date += $_add_date;
			} else {
				for($b=1;$b<$mon;$b++) {
					$_total_date += $loop_table[$b];
				}
			}
		}
		$_total_date +=$day-1;
		$ret = $_total_date * $_day_power + $hr * $_hour_power + $min * $_min_power + $sec + $gmt_different;
1338

Penny Leach's avatar
Penny Leach committed
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
	} else {
		for ($a = 1969 ; $a >= $year; $a--) {
			$leaf = _adodb_is_leap_year($a);
			if ($leaf == true) {
				$loop_table = $_month_table_leaf;
				$_add_date = 366;
			} else {
				$loop_table = $_month_table_normal;
				$_add_date = 365;
			}
			if ($a > $year) { $_total_date += $_add_date;
			} else {
				for($b=12;$b>$mon;$b--) {
					$_total_date += $loop_table[$b];
				}
			}
		}
		$_total_date += $loop_table[$mon] - $day;
1357

Penny Leach's avatar
Penny Leach committed
1358
1359
1360
1361
1362
		$_day_time = $hr * $_hour_power + $min