diff --git a/test/functional/sqlsrv/sqlsrv_ae_datetimes_as_strings.phpt b/test/functional/sqlsrv/sqlsrv_ae_datetimes_as_strings.phpt new file mode 100644 index 00000000..1ad1cecd --- /dev/null +++ b/test/functional/sqlsrv/sqlsrv_ae_datetimes_as_strings.phpt @@ -0,0 +1,628 @@ +--TEST-- +Test various date and time types with AE and ReturnDatesAsStrings set to true +--SKIPIF-- + +--FILE-- +date_format($datetime, 'Y-m-d'), + 'time'=>date_format($datetime, 'H:i:s.u'), + 'datetime'=>date_format($datetime, 'Y-m-d H:i:s.v'), + 'datetime2'=>date_format($datetime, 'Y-m-d H:i:s.u'), + 'datetimeoffset'=>date_format($datetime, 'Y-m-d H:i:s.u P'), + 'smalldatetime'=>date_format($datetime, 'Y-m-d H:i').":00", + ); + + CompareDateTimeObject($dateTimeType, $expectedDateTime, $datetimeArray); + } + + // retrieve date time fields without explicitly requesting the type + echo "Select fields with no type information provided:\n"; + + $stmt = sqlsrv_query($conn, "SELECT * FROM [$tableName]"); + if ($stmt === false) { + fatalError("Select from $tableName failed"); + } + + while (sqlsrv_fetch($stmt)) { + $idnum = sqlsrv_get_field($stmt, 0); + $datetime = sqlsrv_get_field($stmt, 1); + + if ($returnDatesAsStrings == true) { + if (!is_string($datetime)) { + fatalError("String for date expected, not a string"); + } + + CompareDateTimeString($dateTimeType, $expectedDateTime, $datetime); + } else { // ReturnDatesAsStrings is false + if (!($datetime instanceof DateTime)) { + fatalError("DateTime object expected, not a DateTime"); + } + + $datetimeArray = array('date'=>date_format($datetime, 'Y-m-d'), + 'time'=>date_format($datetime, 'H:i:s.u'), + 'datetime'=>date_format($datetime, 'Y-m-d H:i:s.v'), + 'datetime2'=>date_format($datetime, 'Y-m-d H:i:s.u'), + 'datetimeoffset'=>date_format($datetime, 'Y-m-d H:i:s.u P'), + 'smalldatetime'=>date_format($datetime, 'Y-m-d H:i').":00", + ); + + CompareDateTimeObject($dateTimeType, $expectedDateTime, $datetimeArray); + } + } + + // retrieve date time fields as default + echo "Select using fetch_array:\n"; + + $stmt = sqlsrv_query($conn, "SELECT * FROM [$tableName]"); + if ($stmt === false) { + fatalError("Select from $tableName failed"); + } + + while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC)) { + $idnum = $row[0]; + + if ($returnDatesAsStrings == true) { + if (!is_string($row[1])) { + fatalError("String for date expected, not a string"); + } + + CompareDateTimeString($dateTimeType, $expectedDateTime, $row[1]); + } else { // ReturnDatesAsStrings is false + if (!($row[1] instanceof DateTime)) { + fatalError("DateTime object expected, not a DateTime"); + } + + $datetimeArray = array('date'=>date_format($datetime, 'Y-m-d'), + 'time'=>date_format($datetime, 'H:i:s.u'), + 'datetime'=>date_format($datetime, 'Y-m-d H:i:s.v'), + 'datetime2'=>date_format($datetime, 'Y-m-d H:i:s.u'), + 'datetimeoffset'=>date_format($datetime, 'Y-m-d H:i:s.u P'), + 'smalldatetime'=>date_format($datetime, 'Y-m-d H:i').":00", + ); + + CompareDateTimeObject($dateTimeType, $expectedDateTime, $datetimeArray); + } + } +} + +// The date used for the test will be Januray 31, 2002, or 2002/01/31. +// This will sidestep issues involving the use of two digit years. +// Time is 23:59:29.049876. User can substitute any values they wish for date +// and time, except for values that would cause rollovers to the next +// second/minute/hour/day/month/year because there is no logic in this test +// to handle rollovers. This warning applies to +// 1. datetime when $frac is '999', because datetime is accurate to .000, .003, +// or .007 s, and 999 would roll over to the next second when inserted. +// 2. smalldatetime when $second is >= 30, because smalldatetime rounds to the +// nearest minute, and that may cause this test to fail if it rolls over to the next day. +$year = '2002'; +$month = '01'; +$month_name = 'January'; +$month_abbr = 'Jan'; +$day = '31'; +$hour = '23'; +$hour12 = '11'; +$meridian = 'PM'; +$minute = '59'; +$second = '29'; +$frac = '049'; +$frac2 = '876'; +$tz_correction = '+08:00'; + +// The datetime type is accurate to .000, .003, or .007 second, so adjust +// $frac appropriately for that type. Do not use '999' +$frac_rounded = $frac; +if ($frac[2] == '2' or $frac[2] == '4') $frac_rounded[2] = '3'; +elseif ($frac[2] == '5' or $frac[2] == '6' or $frac[2] == '8') $frac_rounded[2] = '7'; +elseif ($frac[2] == '1') $frac_rounded[2] = '0'; +elseif ($frac[2] == '9') +{ + // Get as integer and add one, then get as string back, prepend '0' if result is less than 100 + $frac_int = intval($frac); + $frac_int += 1; + $frac_rounded = $frac_int < 100 ? '0'.strval($frac_int) : strval($frac_int); +} + +// This is the array of dates/times/timezones to test against. They have +// different numbers of trailing zeroes to match the precision of the +// SQL Server date and time types, but only up to microseconds (0.000001 s) +// because that is PHP's maximum precision when formatting times with +// date_format() (time, datetime2, and datetimeoffset go up to 0.0000001 s precision.) +// This allows direct string comparisons when the DateTime objects retrieved from +// a table are formatted as strings with date_format(). However, when returning +// dates as strings using ReturnDatesAsStrings set to true, the returned +// data defaults to SQL Server type precision, so for comparisons some zeroes +// have to be added or removed from the values below. +$expectedDateTime = array('date'=>array($year."-".$month."-".$day), + 'time'=>array($hour.":".$minute.":".$second.".".$frac_rounded."000", + $hour.":".$minute.":".$second.".".$frac.$frac2, + $hour.":".$minute.":".$second.".".$frac."000", + $hour.":".$minute.":".$second.".000000", + $hour.":".$minute.":00.000000"), + 'datetime'=>array($year."-".$month."-".$day." ".$hour.":".$minute.":".$second.".".$frac_rounded, + $year."-".$month."-".$day." ".$hour.":".$minute.":".$second.".".$frac, + $year."-".$month."-".$day." ".$hour.":".$minute.":".$second.".000", + $year."-".$month."-".$day." ".$hour.":".$minute.":00.000"), + 'datetime2'=>array($year."-".$month."-".$day." ".$hour.":".$minute.":".$second.".".$frac_rounded."000", + $year."-".$month."-".$day." ".$hour.":".$minute.":".$second.".".$frac.$frac2, + $year."-".$month."-".$day." ".$hour.":".$minute.":".$second.".".$frac."000", + $year."-".$month."-".$day." ".$hour.":".$minute.":".$second.".000000", + $year."-".$month."-".$day." ".$hour.":".$minute.":00.000000"), + 'datetimeoffset'=>array($year."-".$month."-".$day." ".$hour.":".$minute.":".$second.".".$frac_rounded."000 ".$tz_correction, + $year."-".$month."-".$day." ".$hour.":".$minute.":".$second.".".$frac.$frac2." ".$tz_correction, + $year."-".$month."-".$day." ".$hour.":".$minute.":".$second.".".$frac."000 ".$tz_correction, + $year."-".$month."-".$day." ".$hour.":".$minute.":".$second.".000000 ".$tz_correction, + $year."-".$month."-".$day." ".$hour.":".$minute.":00.000000 ".$tz_correction), + 'smalldatetime'=>array($year."-".$month."-".$day." ".$hour.":".$minute.":00"), + ); + +// These formats are for the ODBC driver with types specified in sqlsrv_prepare() +$date_formats = array($year."-".$month."-".$day + ); +$time_formats = array($hour.":".$minute.":".$second, + $hour.":".$minute.":".$second.".".$frac, + $hour.":".$minute.":".$second.".".$frac.$frac2 + ); + +// These formats are not accepted by either the ODBC driver or by PHP, but +// can possibly be wrangled in sqlsrv_prepare() using strings instead of +// the dedicated date and time types. +$date_formats_nonODBC = array($year."/".$month."/".$day, + $month."/".$day."/".$year, + $month."-".$day."-".$year, + $day."/".$month."/".$year, + $day."-".$month."-".$year, + $month_name." ".$day.", ".$year, + $day."-".$month_name."-".$year, + $day."-".$month_abbr."-".$year + ); +$time_formats_nonODBC = array($hour12.":".$minute." ".$meridian, + $hour12.":".$minute.":".$second." ".$meridian, + $hour12.":".$minute.":".$second.".".$frac." ".$meridian, + $hour12.":".$minute.":".$second.".".$frac.$frac2." ".$meridian, + $hour.":".$minute + ); + +// Create arrays containing the ODBC-standard formats, and larger arrays +// containing the non-standard formats, for the supported SQL Server +// date and time types. +$date_formats_all = array_merge($date_formats, $date_formats_nonODBC); +$time_formats_all = array_merge($time_formats, $time_formats_nonODBC); + +$datetime_formats_all = array(); +$datetime2_formats_all = array(); +$datetimeoffset_formats_all = array(); +$datetimesmall_formats_all = array(); + +$SZ_TIME_all = sizeof($time_formats_all); +$SZ_DATE_all = sizeof($date_formats_all); +$SZ_DATETIME_all = $SZ_TIME_all*$SZ_DATE_all; + +// Create compound date/time/timezone arrays corresponding to the SQL Server +// date/time types by concatenating the dates and times from above. For the +// datetime type, remove the extra precision of $frac2. For the smalldatetime +// type, remove the extra precision of $frac and $frac2. If the numerical +// string in $frac and/or $frac2 is found elsewhere in the date/time, the data +// will be garbled. For example, if the year is 2002 and $frac2 is 002, the +// code below will remove any instances of '002' in the datetime and +// smalldatetime strings, producing garbage for those types. User must be +// cognizant of this when testing different dates and times. +for ($i=0; $i<$SZ_DATE_all; $i++) +{ + for ($j=0; $j<$SZ_TIME_all; $j++) + { + $datetime_formats_all[] = str_replace($frac2, "", $date_formats_all[$i]." ".$time_formats_all[$j]); + $datetime2_formats_all[] = $date_formats_all[$i]." ".$time_formats_all[$j]; + $datetimeoffset_formats_all[] = $date_formats_all[$i]." ".$time_formats_all[$j].$tz_correction; + if (str_replace(".".$frac.$frac2, "", $date_formats_all[$i]." ".$time_formats_all[$j]) == ($date_formats_all[$i]." ".$time_formats_all[$j])) { + $datetimesmall_formats_all[] = str_replace(".".$frac, "", $date_formats_all[$i]." ".$time_formats_all[$j]); + } else { + $datetimesmall_formats_all[] = str_replace(".".$frac.$frac2, "", $date_formats_all[$i]." ".$time_formats_all[$j]); + } + } +} + +date_default_timezone_set('Canada/Pacific'); +sqlsrv_configure('WarningsReturnAsErrors', 1); +sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL); +sqlsrv_configure('LogSubsystems', SQLSRV_LOG_SYSTEM_OFF); + +$returnDatesAsStrings = true; + +$conn = AE\connect(array('ReturnDatesAsStrings' => $returnDatesAsStrings)); + +InsertDatesAndOrTimes($conn, 'date', $date_formats_all, $SZ_DATE_all, SQLSRV_SQLTYPE_DATE); +InsertDatesAndOrTimes($conn, 'time', $time_formats_all, $SZ_TIME_all, SQLSRV_SQLTYPE_TIME); +InsertDatesAndOrTimes($conn, 'datetime', $datetime_formats_all, $SZ_DATETIME_all, SQLSRV_SQLTYPE_DATETIME); +InsertDatesAndOrTimes($conn, 'datetime2', $datetime2_formats_all, $SZ_DATETIME_all, SQLSRV_SQLTYPE_DATETIME2); +InsertDatesAndOrTimes($conn, 'datetimeoffset', $datetimeoffset_formats_all, $SZ_DATETIME_all, SQLSRV_SQLTYPE_DATETIMEOFFSET); +InsertDatesAndOrTimes($conn, 'smalldatetime', $datetimesmall_formats_all, $SZ_DATETIME_all, SQLSRV_SQLTYPE_SMALLDATETIME); + +FetchDatesAndOrTimes($conn, 'date', $expectedDateTime, $returnDatesAsStrings); +FetchDatesAndOrTimes($conn, 'time', $expectedDateTime, $returnDatesAsStrings); +FetchDatesAndOrTimes($conn, 'datetime', $expectedDateTime, $returnDatesAsStrings); +FetchDatesAndOrTimes($conn, 'datetime2', $expectedDateTime, $returnDatesAsStrings); +FetchDatesAndOrTimes($conn, 'datetimeoffset', $expectedDateTime, $returnDatesAsStrings); +FetchDatesAndOrTimes($conn, 'smalldatetime', $expectedDateTime, $returnDatesAsStrings); + +sqlsrv_close($conn); + +$returnDatesAsStrings = false; + +$conn = AE\connect(array('ReturnDatesAsStrings' => $returnDatesAsStrings)); + +InsertDatesAndOrTimes($conn, 'date', $date_formats_all, $SZ_DATE_all, SQLSRV_SQLTYPE_DATE); +InsertDatesAndOrTimes($conn, 'time', $time_formats_all, $SZ_TIME_all, SQLSRV_SQLTYPE_TIME); +InsertDatesAndOrTimes($conn, 'datetime', $datetime_formats_all, $SZ_DATETIME_all, SQLSRV_SQLTYPE_DATETIME); +InsertDatesAndOrTimes($conn, 'datetime2', $datetime2_formats_all, $SZ_DATETIME_all, SQLSRV_SQLTYPE_DATETIME2); +InsertDatesAndOrTimes($conn, 'datetimeoffset', $datetimeoffset_formats_all, $SZ_DATETIME_all, SQLSRV_SQLTYPE_DATETIMEOFFSET); +InsertDatesAndOrTimes($conn, 'smalldatetime', $datetimesmall_formats_all, $SZ_DATETIME_all, SQLSRV_SQLTYPE_SMALLDATETIME); + +FetchDatesAndOrTimes($conn, 'date', $expectedDateTime, $returnDatesAsStrings); +FetchDatesAndOrTimes($conn, 'time', $expectedDateTime, $returnDatesAsStrings); +FetchDatesAndOrTimes($conn, 'datetime', $expectedDateTime, $returnDatesAsStrings); +FetchDatesAndOrTimes($conn, 'datetime2', $expectedDateTime, $returnDatesAsStrings); +FetchDatesAndOrTimes($conn, 'datetimeoffset', $expectedDateTime, $returnDatesAsStrings); +FetchDatesAndOrTimes($conn, 'smalldatetime', $expectedDateTime, $returnDatesAsStrings); + +sqlsrv_close($conn); + +?> +--EXPECT-- +Select fields as strings: +Select fields as DateTime objects: +Select fields with no type information provided: +Select using fetch_array: +Select fields as strings: +Select fields as DateTime objects: +Select fields with no type information provided: +Select using fetch_array: +Select fields as strings: +Select fields as DateTime objects: +Select fields with no type information provided: +Select using fetch_array: +Select fields as strings: +Select fields as DateTime objects: +Select fields with no type information provided: +Select using fetch_array: +Select fields as strings: +Select fields as DateTime objects: +Select fields with no type information provided: +Select using fetch_array: +Select fields as strings: +Select fields as DateTime objects: +Select fields with no type information provided: +Select using fetch_array: +Select fields as strings: +Select fields as DateTime objects: +Select fields with no type information provided: +Select using fetch_array: +Select fields as strings: +Select fields as DateTime objects: +Select fields with no type information provided: +Select using fetch_array: +Select fields as strings: +Select fields as DateTime objects: +Select fields with no type information provided: +Select using fetch_array: +Select fields as strings: +Select fields as DateTime objects: +Select fields with no type information provided: +Select using fetch_array: +Select fields as strings: +Select fields as DateTime objects: +Select fields with no type information provided: +Select using fetch_array: +Select fields as strings: +Select fields as DateTime objects: +Select fields with no type information provided: +Select using fetch_array: \ No newline at end of file