Time for another Date?

Published 15 May 09 03:34 PM | MartinBell

This was first published on the Technet Industry Insiders blog on May 27, 2008, but I thought I’d bring it up-to-date and that it would also be useful to have it on my new blog as well!!

As a DBA I wear many hats! Wearing my database architect hat, I 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), but 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.1 14:13:12.12 14:13:12.123
         
time(4) time(5) time(6) time(7)  
14:13:12.1235 14:13:12.12346 14:13:12.123457 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

datetime2 (2)

(22,2)

6

0-2

datetime2 (3)

(23,3)

7

3-4

datetime2 (4)

(24,4)

7

3-4

datetime2 (5)

(25,5)

8

5-7

datetime2 (6)

(26,6)

8

5-7

datetime2 (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 2008-05-29 14:13:12.1 2008-05-29 14:13:12.12 2008-05-29 14:13:12.123
         
datetime2(4) datetime2(5) datetime2(6) datetime2(7)  
2008-05-29 14:13:12.1235 2008-05-29 14:13:12.12346 2008-05-29 14:13:12.123457 2008-05-29 14:13:12.1234567  
 

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

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)

9

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

Abbreviations

microsecond

mcs

nanosecond

ns

TZoffset

tz

ISO_WEEK

isowk, isoww

For example:

DECLARE @datetime2 datetime2(7) = '2008-05-29T14:13:12.1234567',
        @datetimeoffset datetimeoffset = '2008-05-29T14:13:12.1234567+02:15' 

/* Another new feature in SQL Server 2008 is initialising on declaration*/ 

/* 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) = '2008-05-29T14:13:12.1234567',
    @datetime2_2 datetime2 = '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) = '2008-05-29T14:13:12.1234567',
    @datetimeoffset datetimeoffset = '2008-05-29T14:13:12.1234567',
    @date 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
2009-05-15 15:21:25.5240000 2009-05-15 15:21:25.5240000 +01:00 2009-05-15 14:21:25.5240000

This reflects that we are currently on British Summer Time. If you change your computer’s timezone to pacific time (GMT – 8:00) the results would be something like:

SysDateTime SysDateTimeOffset SysUTCDateTime
2009-05-15 07:24:42.8360000 2009-05-15 07:24:42.8360000 -07:00 2009-05-15 14:24:42.8360000

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 )

So back in British Summer time we get:

SELECT SYSDATETIMEOFFSET() as [Current Time (BST)]
    ,SWITCHOFFSET(SYSDATETIMEOFFSET(), '-07:00' ) AS [Pacific Summer Time]

Returns:

Current Time (BST) Pacific Summer Time
2009-05-15 15:28:29.2590000 +01:00 2009-05-15 07:28:29.2590000 -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
2009-05-15 15:30:33.2410000 2009-05-15 15:30:33.2410000 2009-05-15 15:30:33.2410000

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 SQL Server 2008. These will help improve storage and data 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

Filed under:

Comments

# Dew Drop - May 16, 2009 | Alvin Ashcraft's Morning Dew said on May 17, 2009 03:52 AM:

Pingback from  Dew Drop - May 16, 2009 | Alvin Ashcraft's Morning Dew

# Martin Bell UK SQL Server MVP said on March 15, 2010 10:34 PM:

At the VBUG meeting in Manchester on 3rd March, I was scheduled to talk about Table Valued Parameters

This Blog

SQL Blogs

Syndication