Time for a Date?
As a DBA I wear many hats! If I had to choose a hat for my data architect role it would probably be a nice white panama, the sort you see in the Agatha Christie re-runs on ITV3, but I digress!.
As a data architect you extol the virtues of “correctly data typing” in your design, such as not using varchar when char would be more appropriate, declaring a smallint or tinyint where a column can only be an integer less than or equal to 32,767 or 255 (respectively), so when it comes to dates and times SQL Server has, until till now, forced you to compromise.
If you use the 8 byte datetime data type for only storing a date, the time portion will default to midnight, and if you only store a time the date portion will default to 1901-01-01 e.g.
Running the query:
SELECT CAST('20080101' AS datetime) as DateOnly
,CAST('14:00:00.000' AS datetime) as TimeOnly
Returns:
DateOnly | TimeOnly |
2008-01-01 00:00:00.000 | 1900-01-01 14:00:00.000 |
The datetime data type can hold date values from January 1, 1753, through December 31, 9999 and time values from 00:00:00 through 23:59:59.997 rounded to increments of .000, .003, or .007 seconds, which means that if you want better accuracy you will have to resort to holding your dates as character data, or as an offset, or possibly in SQL 2005 as a .NET data type, which in themselves can cause issues and added complexity. The smalldatetime data type uses 4 bytes to store dates at a lower precision than datetime. This gives it a date range of January 1, 1900, through June 6, 2079 and a time accuracy of 1 minute.
So using either the datetime or smalldatetime data types for storing only a date or a time, are using excessive storage and may not fulfil your requirements. Fortunately in SQL Server 2008 several new data types have been added which will help to resolve these issues. These data types are time, date, datetime2 and datetimeoffset and I will look at each of these separately
Time
The time data type has a range between 00:00:00.0000000 to 23:59:59.9999999 and will use 3 to 5 bytes depending on the precision. This makes the time accurate to 100 nanoseconds.
Specified scale | Result (precision, scale) | Column length (bytes) | Fractional seconds precision |
time | (16,7) | 5 | 7 |
time(0) | (8,0) | 3 | 0-2 |
time(1) | (10,1) | 3 | 0-2 |
time(2) | (11,2) | 3 | 0-2 |
time(3) | (12,3) | 4 | 3-4 |
time(4) | (13,4) | 4 | 3-4 |
time(5) | (14,5) | 5 | 5-7 |
time(6) | (15,6) | 5 | 5-7 |
time(7) | (16,7) | 5 | 5-7 |
So the queries:
SELECT CAST('14:13:12.1234567' AS time) AS [time]
,CAST('14:13:12.1234567' AS time(0) ) AS [time(0)]
,CAST('14:13:12.1234567' AS time(1) ) AS [time(1)]
,CAST('14:13:12.1234567' AS time(2) ) AS [time(2)]
,CAST('14:13:12.1234567' AS time(3) ) AS [time(3)]
, CAST('14:13:12.1234567' AS time(4) ) AS [time(4)]
,CAST('14:13:12.1234567' AS time(5) ) AS [time(5)]
,CAST('14:13:12.1234567' AS time(6) ) AS [time(6)]
,CAST('14:13:12.1234567' AS time(7) ) AS [time(7)]
Returns:
time | time(0) | time(1) | time(2) | time(3) |
14:13:12.1234567 | 14:13:12 | 14:13:12.1000000 | 14:13:12.1200000 | 14:13:12.1230000 |
time(4) | time(5) | time(6) | time(7) | |
14:13:12.1235000 | 14:13:12.1234600 | 14:13:12.1234570 | 14:13:12.1234567 |
As you can see the default format for a time data type is hh:mm:ss[.nnnnnnn]
Date
The date data type has a range 0001-01-01 to 9999-12-31 with the fixed granularity of 1 day. This will take 3 bytes of storage. The default format for a date data type is YYYY-MM-DD.
Datetime2
A variable precision date and time data type with a range 0001-01-01 00:00:00.0000000 to 9999-12-31 23:59:59.9999999 using between 6 to 8 bytes for storage
Specified scale | Result (precision, scale) | Column length (bytes) | Fractional seconds precision |
Datetime2 | (27,7) | 8 | 7 |
Datetime2(0) | (19,0) | 6 | 0-2 |
Datetime2(1) | (21,1) | 6 | 0-2 |
Datetime(2) | (22,2) | 6 | 0-2 |
Datetime(3) | (23,3) | 7 | 3-4 |
Datetime(4) | (24,4) | 7 | 3-4 |
Datetime(5) | (25,5) | 8 | 5-7 |
Datetime(6) | (26,6) | 8 | 5-7 |
Datetime(7) | (27,7) | 8 | 5-7 |
So the queries:
SELECT CAST('2008-05-29 14:13:12.1234567' AS datetime2) AS [datetime2]
,CAST('2008-05-29 14:13:12.1234567' AS datetime2 (0) ) AS [datetime2(0)]
,CAST('2008-05-29 14:13:12.1234567' AS datetime2 (1) ) AS [datetime2(1)]
,CAST('2008-05-29 14:13:12.1234567' AS datetime2 (2) ) AS [datetime2(2)]
,CAST('2008-05-29 14:13:12.1234567' AS datetime2 (3) ) AS [datetime2(3)]
, CAST('2008-05-29 14:13:12.1234567' AS datetime2 (4) ) AS [datetime2(4)]
,CAST('2008-05-29 14:13:12.1234567' AS datetime2 (5) ) AS [datetime2(5)]
,CAST('2008-05-29 14:13:12.1234567' AS datetime2 (6) ) AS [datetime2(6)]
,CAST('2008-05-29 14:13:12.1234567' AS datetime2(7) ) AS [datetime2(7)]
Returns:
datetime2 | datetime2(0) | datetime2(1) | datetime2(2) | datetime2(3) |
2008-05-29 14:13:12.1234567 | 2008-05-29 14:13:12.0000000 | 2008-05-29 14:13:12.1000000 | 2008-05-29 14:13:12.1200000 | 2008-05-29 14:13:12.1230000 |
datetime2(4) | datetime2(5) | datetime2(6) | datetime2(7) | |
2008-05-29 14:13:12.1235000 | 2008-05-29 14:13:12.1234600 | 2008-05-29 14:13:12.1234570 | 2008-05-29 14:13:12.1234567 |
Note: This gives the wrong precision when using the February CTP.
Like the datetime data type the datatime2 and datetimeoffet data type will default a time portion to midnight 00:00:00.000 if not specified i.e.
SELECT CAST('2008-05-29’ AS datetime2) AS [datetime2]
Returns:
datetime2
2008-05-29 00:00:00.0000000
Note: This gives the wrong precision when using the February CTP.
Datetimeoffset
A timezone aware, variable precision date and time data type with a range 0001-01-01 00:00:00.0000000 to 9999-12-31 23:59:59.9999999 using between 8 to 10 bytes for storage. The timezone offset is based on a 24hr clock and can have the range -14:00 through +14:00. The two ISO 8601 formats YYYY-MM-DDThh:mm:ss[.nnnnnnn][{+|-}hh:mm] and YYYY-MM-DDThh:mm:ss[.nnnnnnn]Z (UTC) are valid string representations of the datetimeoffset data type
Specified scale | Result(precision, scale) | Column length (bytes) | Fractional seconds precision |
datetimeoffset | (34,7) | 10 | 7 |
datetimeoffset (0) | (26,0) | 8 | 0-2 |
datetimeoffset(1) | (28,1) | 8 | 0-2 |
datetimeoffset(2) | (29,2) | 8 | 0-2 |
datetimeoffset(3) | (30,3) | 9 | 3-4 |
datetimeoffset(4) | (31,4) | 10 | 3-4 |
datetimeoffset(5) | (32,5) | 10 | 5-7 |
datetimeoffset(6) | (33,6) | 10 | 5-7 |
datetimeoffset(7) | (34,7) | 10 | 5-7 |
The following are all the same moment:
SELECT CAST('2008-05-29 16:28:12.1234567 +02:15' AS datetimeoffset(7)) AS 'datetimeoffset'
,CAST('2008-05-29T11:58:12.1234567-02:15' AS datetimeoffset(7)) AS 'datetimeoffset IS08601'
,CAST('2008-05-29 14:13:12.1234567Z' AS datetimeoffset(7)) AS 'datetimeoffset UTC'
For more information on ISO 8601 date formats see http://en.wikipedia.org/wiki/ISO_8601
Datepart types
With the addition of these new more precise data types, it makes sense that the functions such as DATEPART have also been updated to reflect the higher accuracy.
Datepart | Abbreviation |
microsecond | mcs |
nanosecond | ns |
TZoffset | tz |
ISO_WEEK | isowk, isoww |
For example:
DECLARE @datetime2 datetime2(7), @datetimeoffset datetimeoffset
SET @datetime2 = '2008-05-29T14:13:12.1234567'
SET @datetimeoffset = '2008-05-29T14:13:12.1234567+02:15'
/* Using full names */
SELECT DATEPART ( microsecond, @datetime2 ) AS [Microsecond]
,DATEPART ( nanosecond, @datetime2 ) AS [Nanosecond]
,DATEPART ( TZoffset, @datetimeoffset ) AS [TZoffset]
,DATEPART ( ISO_WEEK, @datetime2 ) AS [ISO_Week]
/* Using abbreviations */
SELECT DATEPART ( mcs, @datetime2 ) AS [Microsecond]
,DATEPART ( ns, @datetime2 ) AS [Nanosecond]
,DATEPART ( tz, @datetimeoffset ) AS [TZoffset]
,DATEPART ( isowk, @datetime2 ) AS [ISO_Week]
Both queries return the result set
Microsecond | Nanosecond | TZoffset | ISO_Week |
123456 | 123456700 | 135 | 22 |
TZoffset requires a datetimeoffset value to return a non-null value according to Books Online although the February CTP 0 is returned instead. The Timezone offset value is in minutes so 2 hours 15 minutes is (2x60)+15=135
ISO_WEEK returns the week for the ISO 8601 week-date system. Each week is associated with the year in which the Thursday in the week occurs. i.e. week 1 of 2008 (2008W01) ran from Monday 31 December 2007 to Sunday, 6 January 2008.
DECLARE @datetime2_1 datetime2(7), @datetime2_2 datetime2
SELECT @datetime2_1 = '2008-05-29T14:13:12.1234567',
@datetime2_2 = '2008-05-29T14:13:12.1234578'
SELECT DATEDIFF( mcs, @datetime2_1, @datetime2_2 ) AS [Microseconds Difference]
,DATEDIFF( ns, @datetime2_1, @datetime2_2 ) AS [Nanoseconds Difference]
Returns:
Microseconds Difference | Nanoseconds Difference |
1 | 1100 |
ISO_WEEK is not supported as a datepart in the datediff function.
DAY, MONTH and YEAR changes
The functions DAY, MONTH and YEAR can be used on datetime2, dateoffset and date datatypes but not time
DECLARE @datetime2 datetime2(7), @datetimeoffset datetimeoffset, @date date
SELECT @datetime2 = '2008-05-29T14:13:12.1234567'
,@datetimeoffset = '2008-05-29T14:13:12.1234567'
,@date = '2008-05-29'
SELECT DAY(@datetime2)
,DAY(@datetimeoffset)
,DAY(@date)
,MONTH(@datetime2)
,MONTH(@datetimeoffset)
,MONTH(@date)
,YEAR(@datetime2)
,YEAR(@datetimeoffset)
,YEAR(@date)
High Precision System Date and Time functions
Additional non-deterministic high precision functions have added to return the more accurate data types
- SYSDATETIME returns the current database time stamp as datetime2(7)
- SYSDATETIMEOFFSET similar to SYSDATETIME but also includes the database timezone offset as a datetimeoffset(7)
- SYSUTCDATETIME returns the current database time stamp as datetime2(7) as a UTC time.
SELECT SYSDATETIME() AS [SysDateTime]
,SYSDATETIMEOFFSET() AS [SysDateTimeOffset]
,SYSUTCDATETIME() AS [SysUTCDateTime]
Returns:
SysDateTime | SysDateTimeOffset | SysUTCDateTime |
2008-05-29 15:28:51.1202384 | 2008-05-29 15:28:51.1202384 +01:00 | 2008-05-29 14:28:51.1202384 |
This reflects that we are currently on British Summer Time. If you change the timezone to pacific time (GMT – 8:00) the results would be something like:
SysDateTime | SysDateTimeOffset | SysUTCDateTime |
2008-05-29 07:29:47.6967536 | 2008-05-29 07:29:47.6967536 -07:00 | 2008-05-29 14:29:47.6967536 |
Note: This gives the wrong precision/format when using the February CTP.
DATE and Time modification functions
SWITCHOFFSET will return a datetimeoffset value into the time zone offset specified. The syntax of the function is :
- SWITCHOFFSET ( DATETIMEOFFSET, time_zone )
SELECT SYSDATETIMEOFFSET() as [Current Time (BST)]
,SWITCHOFFSET(SYSDATETIMEOFFSET(), '-07:00' ) AS [Pacific Summer Time]
Returns:
Current Time (BST) | Pacific Summer Time |
2008-05-29 21:32:10.4515504 +01:00 | 2008-05-29 13:32:10.4515504 -07:00 |
TODATETIMEOFFSET converts a local date and time value and a time zone offset to a datetimeoffset UTC value. The syntax of the function is:
- TODATETIMEOFFSET ( datetime, time_zone )
CREATE TABLE #tmpdts (datetimecol1 datetime2, datetimecol2 datetime2, datetimecol3 datetime2 )
INSERT INTO #tmpdts ( datetimecol1, datetimecol2, datetimecol3 )
VALUES( SYSDATETIME(), SYSDATETIME(), SYSDATETIME())
UPDATE #tmpdts
SET datetimecol2 = TODATETIMEOFFSET(datetimecol2, '-07:00' )
,datetimecol3 = TODATETIMEOFFSET(datetimecol3, -420 )
SELECT datetimecol1, datetimecol2, datetimecol3 FROM #tmpdts
DROP TABLE #tmpdts
Returns:
datetimecol1 | datetimecol2 | datetimecol3 |
2008-05-29 21:47:36.6533632 +01:00 | 2008-05-29 13:47:36.6533632 -07:00 | 2008-05-29 13:47:36.6533632 -07:00 |
Both of these functions are deterministic
Looking Forward
As you can see there has been a great deal of improvements and added functionality available to the database architect and designer in the next release of SQL Server. These will help improve storage and accuracy when using the new precision data types for date and time.
For more information on these new data types and functions check out the SQL Server 2008 Books Online at http://msdn.microsoft.com/en-us/library/ms180878(SQL.100).aspx