Qual é o tipo de SQL correto para armazenar um .Net Timespan com valores> 24:00:00?

196

Estou tentando armazenar um .Net TimeSpanno SQL server 2008 R2.

O código EF First parece sugerir que ele deve ser armazenado como Time(7)no SQL.

No entanto TimeSpan, o .Net pode lidar com períodos maiores que 24 horas.

Qual é a melhor maneira de lidar com o armazenamento de .Net TimeSpanno SQL server?

GraemeMiller
fonte
15
Estou usando-o para armazenar a duração de eventos recorrentes. Portanto, eu queria capturar o comprimento do independente caso da data
GraemeMiller
1
Relacionado, não duplicado. Eu escrevi os dois. Um é sobre o Code First e como mudar o mapa para o TimeSpan. O outro é sobre o mapeamento real do tipo .Net Timespan to SQL.
GraemeMiller

Respostas:

222

Eu o armazenaria no banco de dados como BIGINTe armazenaria o número de ticks (por exemplo, propriedade TimeSpan.Ticks ).

Dessa forma, se eu quisesse obter um objeto TimeSpan ao recuperá-lo, poderia fazer TimeSpan.FromTicks (value) o que seria fácil.

Tom Chantler
fonte
3
Como você lidaria com os cálculos no sql, digamos que você precisava calcular quantas horas ele contém?
Peter
10
Eu provavelmente converter os carrapatos em um objeto momento como este: SELECT CAST(DATEADD(MILLISECOND, @Ticks/CAST(10000 AS BIGINT), '1900-01-01') AS TIME). A '1900-01-01'data não importa, é claro, é apenas a terceira variável requerida pela DATEADD(...)função. Lembre-se de que há 100 nanossegundos em um tick, mas se você usar, DATEADD(NANOSECOND...é provável que haja um estouro, portanto, usando milissegundos. Lembre-se também de que você deve verificar esse fato usando C # TimeSpan.TicksPerMillisecond(deve ser 10000) para ter certeza.
Tom Chantler
Uma opção é armazená-lo como uma string, e você pode carregá-lo usando TimeSpan.Parse (texto). não é o ideal de uma perspectiva tamanho ou SQL querys mas pode ser analisado em TSQL, se necessário
Walter Vehoeven
65

Obrigado pelo conselho. Como não há equivalente no servidor SQL. Eu simplesmente criei um segundo campo que convertia o TimeSpan em ticks e o armazenava no banco de dados. Impedi então o armazenamento do TimeSpan

public Int64 ValidityPeriodTicks { get; set; }

[NotMapped]
public TimeSpan ValidityPeriod
{
    get { return TimeSpan.FromTicks(ValidityPeriodTicks); }
    set { ValidityPeriodTicks = value.Ticks; }
}
GraemeMiller
fonte
6
Também para qualquer pessoa usando EF Núcleo - em 2,1 você pode usar conversões de valor e TimeSpanToTicksConverter para mapear intervalos de tempo para carrapatos no banco de dados de forma transparente
GraemeMiller
30

Se você não precisar armazenar mais de 24 horas, poderá apenas armazenar o tempo , desde o SQL Server 2008 e posterior, o mapeamento é

time (SQL Server) <-> TimeSpan(.NET)

Não são necessárias conversões se você precisar armazenar apenas 24 horas ou menos.

Fonte: http://msdn.microsoft.com/en-us/library/cc716729(v=vs.110).aspx

Mas , se você deseja armazenar mais de 24 horas, precisará armazená-lo em ticks, recuperar os dados e depois converter para TimeSpan. Por exemplo

int timeData = yourContext.yourTable.FirstOrDefault();
TimeSpan ts = TimeSpan.FromMilliseconds(timeData);
Alejandro del Río
fonte
23
Como o OP diz, o tipo de dados "tempo" no SQL Server suporta apenas até 24h, ele quer store> 24h
MichelZ
11
Além disso, o TimeSpan (.NET) pode ser negativo, enquanto o Time (SQL Server) não.
Edward
11
Há uma grande diferença entre um tempo e uma duração. A hora representa a hora em um determinado dia, enquanto a duração é a diferença entre dois momentos. Compare com um local (tempo) e distância (duração).
Ramon de Klein
3
^ Exatamente. - O Timetipo SQL não representa uma duração, mas a parte Time de um valor DateTime; é uma péssima escolha para TimeSpan.
precisa saber é o seguinte
19

Não há um equivalente direto. Apenas armazene-o numericamente, por exemplo, número de segundos ou algo apropriado à sua precisão necessária.

fearofawhackplanet
fonte
ie armazene-o como um flutuador e use `TimeSpan.FromSeconds` conforme msdn.microsoft.com/en-us/library/…
CAD bloke
7

Sei que essa é uma pergunta antiga, mas queria garantir que outras duas opções fossem anotadas.

Como você não pode armazenar um TimeSpan maior que 24 horas em um campo de tipo de dados sql de tempo; algumas outras opções podem ser.

  1. Use um varchar (xx) para armazenar o ToString do TimeSpan. O benefício disso é que a precisão não precisa ser inserida no tipo de dados ou no cálculo (segundos x milissegundos x dias x quinzenas) Tudo que você precisa é usar TimeSpan.Parse / TryParse. Isto é o que eu faria.

  2. Use uma segunda data, datetime ou datetimeoffset, que armazene o resultado da primeira data + intervalo de tempo. A leitura do banco de dados é uma questão de TimeSpan x = SecondDate - FirstDate. O uso dessa opção o protegerá para que outras bibliotecas de acesso a dados não .NET acessem os mesmos dados, mas não entendam o TimeSpans; caso você tenha esse ambiente.

Rick
fonte
1
A opção 2 parece que pode ser útil de vez em quando. thx
rahicks
3

Para ser consistente com o que provavelmente é a fonte mais provável de gerar um intervalo de tempo (calculando a diferença de 2 vezes ou data e hora), convém armazenar um .NET TimeSpancomo um tipo de SQL Server DateTime.

Isso ocorre porque no SQL Server, a diferença de 2 DateTime'( Castto Float' e depois Castvolta para a DateTime) é simplesmente uma DateTimerelativa a 1º de janeiro de 1900. Ex. Uma diferença de +0,1 segundo seria 1º de janeiro de 1900 00: 00: 00.100 e -0,1 segundo seria 31 de dezembro de 1899 23: 59: 59.900.

Para converter um .NET TimeSpanem um DateTimetipo de servidor SQL , primeiro você o converteria em um DateTimetipo .NET adicionando-o a 1 DateTimede janeiro de 1900. É claro que, quando você o lê no .NET a partir do SQL Server, primeiro leia-o em um .NET DateTimee subtraia em 1º de janeiro de 1900 para convertê-lo em .NET TimeSpan.

Para casos de uso em que os intervalos de tempo estão sendo gerados a partir do SQL Server DateTimee dentro do SQL Server (ou seja, via T-SQL) e o SQL Server é anterior a 2016, dependendo de suas necessidades de alcance e precisão, pode não ser prático armazená-las como milissegundos (para não mencionar Ticks) porque o IntTipo retornado por DateDiff(vs. o BigIntdo SS 2016 + DateDiff_Big) excede após aproximadamente 24 dias em milissegundos e aproximadamente 67 anos. de segundos. Visto que esta solução lidará com intervalos de tempo com precisão de até 0,1 segundos e de -147 a +8,099 anos.

ADVERTÊNCIAS:

  1. Isso só funcionaria se a diferença em relação a 1º de janeiro de 1900 resultasse em um valor dentro do intervalo de um DateTimetipo de servidor SQL (1º de janeiro de 1753 a 31 de dezembro de 9999, também conhecido como -147 a +8.099 anos). Não precisamos nos preocupar tanto quanto o TimeSpanlado .NET , pois ele pode armazenar de 29 a +29 k anos. Não mencionei o SQL Server DateTime2Type (cujo intervalo, no lado negativo, é muito maior que o do SQL Server DateTime), porque: a) não pode ser convertido em numérico por meio de um simples Casteb) DateTimedeve ser suficiente para a grande maioria dos casos de uso.

  2. SQL Server DateTimediferenças calculadas através do Cast- de - Float- e - método de volta não parece ser precisos para além 0,1 segundos.

Tom
fonte
Eu esqueci que tinha lido esse Q muito menos que escrevi esse A e estava procurando um A novamente. Comecei a ler este A e pensei: (Uau, esta é a melhor resposta até agora!). : D
Tom
3

Existem várias maneiras de apresentar um intervalo de tempo no banco de dados.

Tempo

Esse tipo de dados é suportado desde o SQL Server 2008 e é a maneira preferida de armazenar um TimeSpan. Não há mapeamento necessário. Também funciona bem com o código SQL.

public TimeSpan ValidityPeriod { get; set; }

No entanto, conforme declarado na pergunta original, esse tipo de dados é limitado a 24 horas.

datetimeoffset

O datetimeoffsettipo de dados é mapeado diretamente para System.DateTimeOffset. É usado para expressar o deslocamento entre a datetime/ datetime2UTC, mas você também pode usá-lo para TimeSpan.

No entanto, como o tipo de dados sugere uma semântica muito específica, você também deve considerar outras opções.

datetime / datetime2

Uma abordagem pode ser usar os tipos datetimeou datetime2. Isso é melhor nos cenários em que você precisa processar os valores no banco de dados diretamente, ou seja. para visualizações, procedimentos armazenados ou relatórios. A desvantagem é que você precisa subtrair o valor DateTime(1900,01,01,00,00,00)da data para recuperar o período de tempo em sua lógica de negócios.

public DateTime ValidityPeriod { get; set; }

[NotMapped]
public TimeSpan ValidityPeriodTimeSpan
{
    get { return ValidityPeriod - DateTime(1900,01,01,00,00,00); }
    set { ValidityPeriod = DateTime(1900,01,01,00,00,00) + value; }
}

bigint

Outra abordagem pode ser converter o TimeSpan em ticks e usar o biginttipo de dados. No entanto, essa abordagem tem a desvantagem de ser difícil de usar em consultas SQL.

public long ValidityPeriod { get; set; }

[NotMapped]
public TimeSpan ValidityPeriodTimeSpan
{
    get { return TimeSpan.FromTicks(ValidityPeriod); }
    set { ValidityPeriod = value.Ticks; }
}

varchar (N)

Isso é melhor para casos em que o valor deve ser legível por humanos. Você também pode usar esse formato em consultas SQL utilizando a CONVERT(datetime, ValidityPeriod)função Dependendo da precisão exigida, você precisará entre 8 e 25 caracteres.

public string ValidityPeriod { get; set; }

[NotMapped]
public TimeSpan ValidityPeriodTimeSpan
{
    get { return TimeSpan.Parse(ValidityPeriod); }
    set { ValidityPeriod = value.ToString("HH:mm:ss"); }
}

Bônus: Período e Duração

Usando uma string, você também pode armazenar tipos de dados NodaTime , especialmente Duratione Period. O primeiro é basicamente o mesmo que um TimeSpan, enquanto o segundo respeita que alguns dias e meses são mais longos ou mais curtos que outros (ou seja, janeiro tem 31 dias e fevereiro tem 28 ou 29; alguns dias são mais longos ou mais curtos devido ao horário de verão ) Nesses casos, o uso de um TimeSpan é a escolha errada.

Você pode usar este código para converter períodos:

using NodaTime;
using NodaTime.Serialization.JsonNet;

internal static class PeriodExtensions
{
    public static Period ToPeriod(this string input)
    {
        var js = JsonSerializer.Create(new JsonSerializerSettings());
        js.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
        var quoted = string.Concat(@"""", input, @"""");
        return js.Deserialize<Period>(new JsonTextReader(new StringReader(quoted)));
    }
}

E então use-o como

public string ValidityPeriod { get; set; }

[NotMapped]
public Period ValidityPeriodPeriod
{
    get => ValidityPeriod.ToPeriod();
    set => ValidityPeriod = value.ToString();
}

Eu realmente gosto NodaTimee muitas vezes me salva de insetos complicados e muita dor de cabeça. A desvantagem aqui é que você realmente não pode usá-lo em consultas SQL e precisa fazer cálculos na memória.

Tipo definido pelo usuário CLR

Você também tem a opção de usar um tipo de dados personalizado e suportar TimeSpandiretamente uma classe personalizada . Consulte Tipos definidos pelo usuário do CLR para obter detalhes.

A desvantagem aqui é que o tipo de dados pode não se comportar bem com os Relatórios SQL. Além disso, algumas versões do SQL Server (Azure, Linux, Data Warehouse) não são suportadas.

Conversões de valor

A partir do EntityFramework Core 2.1, você tem a opção de usar Conversões de Valor .

No entanto, ao usar isso, o EF não poderá converter muitas consultas em SQL, fazendo com que as consultas sejam executadas na memória; potencialmente transferir muitos e muitos dados para seu aplicativo.

Portanto, pelo menos por enquanto, é melhor não usá-lo e apenas mapeie o resultado da consulta com o Automapper .

MovGP0
fonte
1

Normalmente, eu armazeno um TimeSpan como um bigint preenchido com tiques da propriedade TimeSpan.Ticks, conforme sugerido anteriormente. Você também pode armazenar um TimeSpan como um varchar (26) preenchido com a saída de TimeSpan.ToString (). As quatro funções escalares (ConvertFromTimeSpanString, ConvertToTimeSpanString, DateAddTicks, DateDiffTicks) que escrevi são úteis para lidar com o TimeSpan no lado SQL e evitar os hacks que produziriam intervalos limitados artificialmente. Se você pode armazenar o intervalo em um .NET TimeSpan, ele deve funcionar com essas funções também. Além disso, as funções permitem que você trabalhe com TimeSpans e ticks de 100 nanossegundos, mesmo ao usar tecnologias que não incluem o .NET Framework.

DROP FUNCTION [dbo].[DateDiffTicks]
GO

DROP FUNCTION [dbo].[DateAddTicks]
GO

DROP FUNCTION [dbo].[ConvertToTimeSpanString]
GO

DROP FUNCTION [dbo].[ConvertFromTimeSpanString]
GO

SET ANSI_NULLS OFF
GO

SET QUOTED_IDENTIFIER OFF
GO

-- =============================================
-- Author:      James Coe
-- Create date: 2011-05-23
-- Description: Converts from a varchar(26) TimeSpan string to a bigint containing the number of 100 nanosecond ticks.
-- =============================================
/*
    [-][d.]hh:mm:ss[.fffffff] 

    "-" 
     A minus sign, which indicates a negative time interval. No sign is included for a positive time span.

    "d" 
     The number of days in the time interval. This element is omitted if the time interval is less than one day. 

    "hh" 
     The number of hours in the time interval, ranging from 0 to 23. 

    "mm" 
     The number of minutes in the time interval, ranging from 0 to 59. 

    "ss" 
     The number of seconds in the time interval, ranging from 0 to 59. 

    "fffffff" 
     Fractional seconds in the time interval. This element is omitted if the time interval does not include 
     fractional seconds. If present, fractional seconds are always expressed using seven decimal digits.
    */
CREATE FUNCTION [dbo].[ConvertFromTimeSpanString] (@timeSpan varchar(26))
RETURNS bigint
AS
BEGIN
    DECLARE @hourStart int
    DECLARE @minuteStart int
    DECLARE @secondStart int
    DECLARE @ticks bigint
    DECLARE @hours bigint
    DECLARE @minutes bigint
    DECLARE @seconds DECIMAL(9, 7)

    SET @hourStart = CHARINDEX('.', @timeSpan) + 1
    SET @minuteStart = CHARINDEX(':', @timeSpan) + 1
    SET @secondStart = CHARINDEX(':', @timespan, @minuteStart) + 1
    SET @ticks = 0

    IF (@hourStart > 1 AND @hourStart < @minuteStart)
    BEGIN
        SET @ticks = CONVERT(bigint, LEFT(@timespan, @hourstart - 2)) * 864000000000
    END
    ELSE
    BEGIN
        SET @hourStart = 1
    END

    SET @hours = CONVERT(bigint, SUBSTRING(@timespan, @hourStart, @minuteStart - @hourStart - 1))
    SET @minutes = CONVERT(bigint, SUBSTRING(@timespan, @minuteStart, @secondStart - @minuteStart - 1))
    SET @seconds = CONVERT(DECIMAL(9, 7), SUBSTRING(@timespan, @secondStart, LEN(@timeSpan) - @secondStart + 1))

    IF (@ticks < 0)
    BEGIN
        SET @ticks = @ticks - @hours * 36000000000
    END
    ELSE
    BEGIN
        SET @ticks = @ticks + @hours * 36000000000
    END

    IF (@ticks < 0)
    BEGIN
        SET @ticks = @ticks - @minutes * 600000000
    END
    ELSE
    BEGIN
        SET @ticks = @ticks + @minutes * 600000000
    END

    IF (@ticks < 0)
    BEGIN
        SET @ticks = @ticks - @seconds * 10000000.0
    END
    ELSE
    BEGIN
        SET @ticks = @ticks + @seconds * 10000000.0
    END

    RETURN @ticks
END
GO

-- =============================================
-- Author:      James Coe
-- Create date: 2011-05-23
-- Description: Converts from a bigint containing the number of 100 nanosecond ticks to a varchar(26) TimeSpan string.
-- =============================================
/*
[-][d.]hh:mm:ss[.fffffff] 

"-" 
 A minus sign, which indicates a negative time interval. No sign is included for a positive time span.

"d" 
 The number of days in the time interval. This element is omitted if the time interval is less than one day. 

"hh" 
 The number of hours in the time interval, ranging from 0 to 23. 

"mm" 
 The number of minutes in the time interval, ranging from 0 to 59. 

"ss" 
 The number of seconds in the time interval, ranging from 0 to 59. 

"fffffff" 
 Fractional seconds in the time interval. This element is omitted if the time interval does not include 
 fractional seconds. If present, fractional seconds are always expressed using seven decimal digits.
*/
CREATE FUNCTION [dbo].[ConvertToTimeSpanString] (@ticks bigint)
RETURNS varchar(26)
AS
BEGIN
    DECLARE @timeSpanString varchar(26)

    IF (@ticks < 0)
    BEGIN
        SET @timeSpanString = '-'
    END
    ELSE
    BEGIN
        SET @timeSpanString = ''
    END

    -- Days
    DECLARE @days bigint

    SET @days = FLOOR(ABS(@ticks / 864000000000.0))

    IF (@days > 0)
    BEGIN
        SET @timeSpanString = @timeSpanString + CONVERT(varchar(26), @days) + '.'
    END

    SET @ticks = ABS(@ticks % 864000000000)
    -- Hours
    SET @timeSpanString = @timeSpanString + RIGHT('0' + CONVERT(varchar(26), FLOOR(@ticks / 36000000000.0)), 2) + ':'
    SET @ticks = @ticks % 36000000000
    -- Minutes
    SET @timeSpanString = @timeSpanString + RIGHT('0' + CONVERT(varchar(26), FLOOR(@ticks / 600000000.0)), 2) + ':'
    SET @ticks = @ticks % 600000000
    -- Seconds
    SET @timeSpanString = @timeSpanString + RIGHT('0' + CONVERT(varchar(26), FLOOR(@ticks / 10000000.0)), 2)
    SET @ticks = @ticks % 10000000

    -- Fractional Seconds
    IF (@ticks > 0)
    BEGIN
        SET @timeSpanString = @timeSpanString + '.' + LEFT(CONVERT(varchar(26), @ticks) + '0000000', 7)
    END

    RETURN @timeSpanString
END
GO

-- =============================================
-- Author:      James Coe
-- Create date: 2011-05-23
-- Description: Adds the specified number of 100 nanosecond ticks to a date.
-- =============================================
CREATE FUNCTION [dbo].[DateAddTicks] (
    @ticks bigint
    , @starting_date datetimeoffset
    )
RETURNS datetimeoffset
AS
BEGIN
    DECLARE @dateTimeResult datetimeoffset

    IF (@ticks < 0)
    BEGIN
        -- Hours
        SET @dateTimeResult = DATEADD(HOUR, CEILING(@ticks / 36000000000.0), @starting_date)
        SET @ticks = @ticks % 36000000000
        -- Seconds
        SET @dateTimeResult = DATEADD(SECOND, CEILING(@ticks / 10000000.0), @dateTimeResult)
        SET @ticks = @ticks % 10000000
        -- Nanoseconds
        SET @dateTimeResult = DATEADD(NANOSECOND, @ticks * 100, @dateTimeResult)
    END
    ELSE
    BEGIN
        -- Hours
        SET @dateTimeResult = DATEADD(HOUR, FLOOR(@ticks / 36000000000.0), @starting_date)
        SET @ticks = @ticks % 36000000000
        -- Seconds
        SET @dateTimeResult = DATEADD(SECOND, FLOOR(@ticks / 10000000.0), @dateTimeResult)
        SET @ticks = @ticks % 10000000
        -- Nanoseconds
        SET @dateTimeResult = DATEADD(NANOSECOND, @ticks * 100, @dateTimeResult)
    END

    RETURN @dateTimeResult
END
GO

-- =============================================
-- Author:      James Coe
-- Create date: 2011-05-23
-- Description:  Gets the difference between two dates in 100 nanosecond ticks.
-- =============================================
CREATE FUNCTION [dbo].[DateDiffTicks] (
    @starting_date datetimeoffset
    , @ending_date datetimeoffset
    )
RETURNS bigint
AS
BEGIN
    DECLARE @ticks bigint
    DECLARE @days bigint
    DECLARE @hours bigint
    DECLARE @minutes bigint
    DECLARE @seconds bigint

    SET @hours = DATEDIFF(HOUR, @starting_date, @ending_date)
    SET @starting_date = DATEADD(HOUR, @hours, @starting_date)
    SET @ticks = @hours * 36000000000
    SET @seconds = DATEDIFF(SECOND, @starting_date, @ending_date)
    SET @starting_date = DATEADD(SECOND, @seconds, @starting_date)
    SET @ticks = @ticks + @seconds * 10000000
    SET @ticks = @ticks + CONVERT(bigint, DATEDIFF(NANOSECOND, @starting_date, @ending_date)) / 100

    RETURN @ticks
END
GO

--- BEGIN Test Harness ---
SET NOCOUNT ON

DECLARE @dateTimeOffsetMinValue datetimeoffset
DECLARE @dateTimeOffsetMaxValue datetimeoffset
DECLARE @timeSpanMinValueString varchar(26)
DECLARE @timeSpanZeroString varchar(26)
DECLARE @timeSpanMaxValueString varchar(26)
DECLARE @timeSpanMinValueTicks bigint
DECLARE @timeSpanZeroTicks bigint
DECLARE @timeSpanMaxValueTicks bigint
DECLARE @dateTimeOffsetMinMaxDiffTicks bigint
DECLARE @dateTimeOffsetMaxMinDiffTicks bigint

SET @dateTimeOffsetMinValue = '0001-01-01T00:00:00.0000000+00:00'
SET @dateTimeOffsetMaxValue = '9999-12-31T23:59:59.9999999+00:00'
SET @timeSpanMinValueString = '-10675199.02:48:05.4775808'
SET @timeSpanZeroString = '00:00:00'
SET @timeSpanMaxValueString = '10675199.02:48:05.4775807'
SET @timeSpanMinValueTicks = -9223372036854775808
SET @timeSpanZeroTicks = 0
SET @timeSpanMaxValueTicks = 9223372036854775807
SET @dateTimeOffsetMinMaxDiffTicks = 3155378975999999999
SET @dateTimeOffsetMaxMinDiffTicks = -3155378975999999999

-- TimeSpan Conversion Tests
PRINT 'Testing TimeSpan conversions...'

DECLARE @convertToTimeSpanStringMinTicksResult varchar(26)
DECLARE @convertFromTimeSpanStringMinTimeSpanResult bigint
DECLARE @convertToTimeSpanStringZeroTicksResult varchar(26)
DECLARE @convertFromTimeSpanStringZeroTimeSpanResult bigint
DECLARE @convertToTimeSpanStringMaxTicksResult varchar(26)
DECLARE @convertFromTimeSpanStringMaxTimeSpanResult bigint

SET @convertToTimeSpanStringMinTicksResult = dbo.ConvertToTimeSpanString(@timeSpanMinValueTicks)
SET @convertFromTimeSpanStringMinTimeSpanResult = dbo.ConvertFromTimeSpanString(@timeSpanMinValueString)
SET @convertToTimeSpanStringZeroTicksResult = dbo.ConvertToTimeSpanString(@timeSpanZeroTicks)
SET @convertFromTimeSpanStringZeroTimeSpanResult = dbo.ConvertFromTimeSpanString(@timeSpanZeroString)
SET @convertToTimeSpanStringMaxTicksResult = dbo.ConvertToTimeSpanString(@timeSpanMaxValueTicks)
SET @convertFromTimeSpanStringMaxTimeSpanResult = dbo.ConvertFromTimeSpanString(@timeSpanMaxValueString)

-- Test Results
SELECT 'Convert to TimeSpan String from Ticks (Minimum)' AS Test
    , CASE 
        WHEN @convertToTimeSpanStringMinTicksResult = @timeSpanMinValueString
            THEN 'Pass'
        ELSE 'Fail'
        END AS [Test Status]
    , @timeSpanMinValueTicks AS [Ticks]
    , CONVERT(varchar(26), NULL) AS [TimeSpan String]
    , CONVERT(varchar(26), @convertToTimeSpanStringMinTicksResult) AS [Actual Result]
    , CONVERT(varchar(26), @timeSpanMinValueString) AS [Expected Result]
UNION ALL
SELECT 'Convert from TimeSpan String to Ticks (Minimum)' AS Test
    , CASE 
        WHEN @convertFromTimeSpanStringMinTimeSpanResult = @timeSpanMinValueTicks
            THEN 'Pass'
        ELSE 'Fail'
        END AS [Test Status]
    , NULL AS [Ticks]
    , @timeSpanMinValueString AS [TimeSpan String]
    , CONVERT(varchar(26), @convertFromTimeSpanStringMinTimeSpanResult) AS [Actual Result]
    , CONVERT(varchar(26), @timeSpanMinValueTicks) AS [Expected Result]
UNION ALL
SELECT 'Convert to TimeSpan String from Ticks (Zero)' AS Test
    , CASE 
        WHEN @convertToTimeSpanStringZeroTicksResult = @timeSpanZeroString
            THEN 'Pass'
        ELSE 'Fail'
        END AS [Test Status]
    , @timeSpanZeroTicks AS [Ticks]
    , CONVERT(varchar(26), NULL) AS [TimeSpan String]
    , CONVERT(varchar(26), @convertToTimeSpanStringZeroTicksResult) AS [Actual Result]
    , CONVERT(varchar(26), @timeSpanZeroString) AS [Expected Result]
UNION ALL
SELECT 'Convert from TimeSpan String to Ticks (Zero)' AS Test
    , CASE 
        WHEN @convertFromTimeSpanStringZeroTimeSpanResult = @timeSpanZeroTicks
            THEN 'Pass'
        ELSE 'Fail'
        END AS [Test Status]
    , NULL AS [Ticks]
    , @timeSpanZeroString AS [TimeSpan String]
    , CONVERT(varchar(26), @convertFromTimeSpanStringZeroTimeSpanResult) AS [Actual Result]
    , CONVERT(varchar(26), @timeSpanZeroTicks) AS [Expected Result]
UNION ALL
SELECT 'Convert to TimeSpan String from Ticks (Maximum)' AS Test
    , CASE 
        WHEN @convertToTimeSpanStringMaxTicksResult = @timeSpanMaxValueString
            THEN 'Pass'
        ELSE 'Fail'
        END AS [Test Status]
    , @timeSpanMaxValueTicks AS [Ticks]
    , CONVERT(varchar(26), NULL) AS [TimeSpan String]
    , CONVERT(varchar(26), @convertToTimeSpanStringMaxTicksResult) AS [Actual Result]
    , CONVERT(varchar(26), @timeSpanMaxValueString) AS [Expected Result]
UNION ALL
SELECT 'Convert from TimeSpan String to Ticks (Maximum)' AS Test
    , CASE 
        WHEN @convertFromTimeSpanStringMaxTimeSpanResult = @timeSpanMaxValueTicks
            THEN 'Pass'
        ELSE 'Fail'
        END AS [Test Status]
    , NULL AS [Ticks]
    , @timeSpanMaxValueString AS [TimeSpan String]
    , CONVERT(varchar(26), @convertFromTimeSpanStringMaxTimeSpanResult) AS [Actual Result]
    , CONVERT(varchar(26), @timeSpanMaxValueTicks) AS [Expected Result]

-- Ticks Date Add Test
PRINT 'Testing DateAddTicks...'

DECLARE @DateAddTicksPositiveTicksResult datetimeoffset
DECLARE @DateAddTicksZeroTicksResult datetimeoffset
DECLARE @DateAddTicksNegativeTicksResult datetimeoffset

SET @DateAddTicksPositiveTicksResult = dbo.DateAddTicks(@dateTimeOffsetMinMaxDiffTicks, @dateTimeOffsetMinValue)
SET @DateAddTicksZeroTicksResult = dbo.DateAddTicks(@timeSpanZeroTicks, @dateTimeOffsetMinValue)
SET @DateAddTicksNegativeTicksResult = dbo.DateAddTicks(@dateTimeOffsetMaxMinDiffTicks, @dateTimeOffsetMaxValue)

-- Test Results
SELECT 'Date Add with Ticks Test (Positive)' AS Test
    , CASE 
        WHEN @DateAddTicksPositiveTicksResult = @dateTimeOffsetMaxValue
            THEN 'Pass'
        ELSE 'Fail'
        END AS [Test Status]
    , @dateTimeOffsetMinMaxDiffTicks AS [Ticks]
    , @dateTimeOffsetMinValue AS [Starting Date]
    , @DateAddTicksPositiveTicksResult AS [Actual Result]
    , @dateTimeOffsetMaxValue AS [Expected Result]
UNION ALL
SELECT 'Date Add with Ticks Test (Zero)' AS Test
    , CASE 
        WHEN @DateAddTicksZeroTicksResult = @dateTimeOffsetMinValue
            THEN 'Pass'
        ELSE 'Fail'
        END AS [Test Status]
    , @timeSpanZeroTicks AS [Ticks]
    , @dateTimeOffsetMinValue AS [Starting Date]
    , @DateAddTicksZeroTicksResult AS [Actual Result]
    , @dateTimeOffsetMinValue AS [Expected Result]
UNION ALL
SELECT 'Date Add with Ticks Test (Negative)' AS Test
    , CASE 
        WHEN @DateAddTicksNegativeTicksResult = @dateTimeOffsetMinValue
            THEN 'Pass'
        ELSE 'Fail'
        END AS [Test Status]
    , @dateTimeOffsetMaxMinDiffTicks AS [Ticks]
    , @dateTimeOffsetMaxValue AS [Starting Date]
    , @DateAddTicksNegativeTicksResult AS [Actual Result]
    , @dateTimeOffsetMinValue AS [Expected Result]

-- Ticks Date Diff Test
PRINT 'Testing Date Diff Ticks...'

DECLARE @dateDiffTicksMinMaxResult bigint
DECLARE @dateDiffTicksMaxMinResult bigint

SET @dateDiffTicksMinMaxResult = dbo.DateDiffTicks(@dateTimeOffsetMinValue, @dateTimeOffsetMaxValue)
SET @dateDiffTicksMaxMinResult = dbo.DateDiffTicks(@dateTimeOffsetMaxValue, @dateTimeOffsetMinValue)

-- Test Results
SELECT 'Date Difference in Ticks Test (Min, Max)' AS Test
    , CASE 
        WHEN @dateDiffTicksMinMaxResult = @dateTimeOffsetMinMaxDiffTicks
            THEN 'Pass'
        ELSE 'Fail'
        END AS [Test Status]
    , @dateTimeOffsetMinValue AS [Starting Date]
    , @dateTimeOffsetMaxValue AS [Ending Date]
    , @dateDiffTicksMinMaxResult AS [Actual Result]
    , @dateTimeOffsetMinMaxDiffTicks AS [Expected Result]
UNION ALL
SELECT 'Date Difference in Ticks Test (Max, Min)' AS Test
    , CASE 
        WHEN @dateDiffTicksMaxMinResult = @dateTimeOffsetMaxMinDiffTicks
            THEN 'Pass'
        ELSE 'Fail'
        END AS [Test Status]
    , @dateTimeOffsetMaxValue AS [Starting Date]
    , @dateTimeOffsetMinValue AS [Ending Date]
    , @dateDiffTicksMaxMinResult AS [Actual Result]
    , @dateTimeOffsetMaxMinDiffTicks AS [Expected Result]

PRINT 'Tests Complete.'
GO
--- END Test Harness ---
JamieSee
fonte