Timeformat bigger then 24 hours [duplicate] - c#

I am trying to store a .Net TimeSpan in SQL server 2008 R2.
EF Code First seems to be suggesting it should be stored as a Time(7) in SQL.
However TimeSpan in .Net can handle longer periods than 24 hours.
What is the best way to handle storing .Net TimeSpan in SQL server?

I'd store it in the database as a BIGINT and I'd store the number of ticks (eg. TimeSpan.Ticks property).
That way, if I wanted to get a TimeSpan object when I retrieve it, I could just do TimeSpan.FromTicks(value) which would be easy.

Thanks for the advice. As there is no equivalent in SQL server. I simply created a 2nd field which converted the TimeSpan to ticks and stored that in the DB. I then prevented storing the TimeSpan
public Int64 ValidityPeriodTicks { get; set; }
[NotMapped]
public TimeSpan ValidityPeriod
{
get { return TimeSpan.FromTicks(ValidityPeriodTicks); }
set { ValidityPeriodTicks = value.Ticks; }
}

If you don't have to store more than 24 hours you can just store time, since SQL Server 2008 and later the mapping is
time (SQL Server) <-> TimeSpan(.NET)
No conversions needed if you only need to store 24 hours or less.
Source: http://msdn.microsoft.com/en-us/library/cc716729(v=vs.110).aspx
But, if you want to store more than 24h, you are going to need to store it in ticks, retrieve the data and then convert to TimeSpan. For example
int timeData = yourContext.yourTable.FirstOrDefault();
TimeSpan ts = TimeSpan.FromMilliseconds(timeData);

There isn't a direct equivalent. Just store it numerically, e.g. number of seconds or something appropriate to your required accuracy.

There are multiple ways how to present a timespan in the database.
time
This datatype is supported since SQL Server 2008 and is the prefered way to store a TimeSpan. There is no mapping needed. It also works well with SQL code.
public TimeSpan ValidityPeriod { get; set; }
However, as stated in the original question, this datatype is limited to 24 hours.
datetimeoffset
The datetimeoffset datatype maps directly to System.DateTimeOffset. It's used to express the offset between a datetime/datetime2 to UTC, but you can also use it for TimeSpan.
However, since the datatype suggests a very specific semantic, so you should also consider other options.
datetime / datetime2
One approach might be to use the datetime or datetime2 types. This is best in scenarios where you need to process the values in the database directly, ie. for views, stored procedures, or reports. The drawback is that you need to substract the value DateTime(1900,01,01,00,00,00) from the date to get back the timespan in your business logic.
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
Another approach might be to convert the TimeSpan into ticks and use the bigint datatype. However, this approach has the drawback that it's cumbersome to use in SQL queries.
public long ValidityPeriod { get; set; }
[NotMapped]
public TimeSpan ValidityPeriodTimeSpan
{
get { return TimeSpan.FromTicks(ValidityPeriod); }
set { ValidityPeriod = value.Ticks; }
}
varchar(N)
This is best for cases where the value should be readable by humans. You might also use this format in SQL queries by utilizing the CONVERT(datetime, ValidityPeriod) function. Dependent on the required precision, you will need between 8 and 25 characters.
public string ValidityPeriod { get; set; }
[NotMapped]
public TimeSpan ValidityPeriodTimeSpan
{
get { return TimeSpan.Parse(ValidityPeriod); }
set { ValidityPeriod = value.ToString("HH:mm:ss"); }
}
Bonus: Period and Duration
Using a string, you can also store NodaTime datatypes, especially Duration and Period. The first is basically the same as a TimeSpan, while the later respects that some days and months are longer or shorter than others (ie. January has 31 days and February has 28 or 29; some days are longer or shorter because of daylight saving time). In such cases, using a TimeSpan is the wrong choice.
You can use this code to convert Periods:
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)));
}
}
And then use it like
public string ValidityPeriod { get; set; }
[NotMapped]
public Period ValidityPeriodPeriod
{
get => ValidityPeriod.ToPeriod();
set => ValidityPeriod = value.ToString();
}
I really like NodaTime and it often saves me from tricky bugs and lots of headache. The drawback here is that you really can't use it in SQL queries and need to do calculations in-memory.
CLR User-Defined Type
You also have the option to use a custom datatype and support a custom TimeSpan class directly. See CLR User-Defined Types for details.
The drawback here is that the datatype might not behave well with SQL Reports. Also, some versions of SQL Server (Azure, Linux, Data Warehouse) are not supported.
Value Conversions
Starting with EntityFramework Core 2.1, you have the option to use Value Conversions.
However, when using this, EF will not be able to convert many queries into SQL, causing queries to run in-memory; potentially transfering lots and lots of data to your application.
So at least for now, it might be better not to use it, and just map the query result with Automapper.

I know this is an old question, but I wanted to make sure a couple of other options are noted.
Since you can't store a TimeSpan greater than 24 hours in a time sql datatype field; a couple of other options might be.
Use a varchar(xx) to store the ToString of the TimeSpan. The benefit of this is the precision doesn't have to be baked into the datatype or the calculation, (seconds vs milliseconds vs days vs fortnights) All you need to to is use TimeSpan.Parse/TryParse. This is what I would do.
Use a second date, datetime or datetimeoffset, that stores the result of first date + timespan. Reading from the db is a matter of TimeSpan x = SecondDate - FirstDate. Using this option will protect you for other non .NET data access libraries access the same data but not understanding TimeSpans; in case you have such an environment.

Now, with EF Core you can convert data type transparently in your AppDbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// i.e. Store TimeSpan as string (custom)
modelBuilder
.Entity<YourClass>()
.Property(x => x.YourTimeSpan)
.HasConversion(
timeSpan => timeSpan.ToString(), // To DB
timeSpanString => TimeSpan.Parse(timeSpanString) // From DB
);
// i.e. Store TimeSpan as string (using TimeSpanToStringConverter)
modelBuilder
.Entity<YourClass>()
.Property(x => x.YourTimeSpan)
.HasConversion(new TimeSpanToStringConverter());
// i.e. Store TimeSpan as number of ticks (custom)
modelBuilder
.Entity<YourClass>()
.Property(x => x.YourTimeSpan)
.HasConversion(
timeSpan => timeSpan.Ticks, // To DB
timeSpanString => TimeSpan.FromTicks(timeSpanString) // From DB
);
// i.e. Store TimeSpan as number of ticks (using TimeSpanToTicksConverter)
modelBuilder
.Entity<YourClass>()
.Property(x => x.YourTimeSpan)
.HasConversion(new TimeSpanToTicksConverter());
}

To be consistent with what is probably the most likely source of generating a time span (computing the difference of 2 times or date-times), you may want to store a .NET TimeSpan as a SQL Server DateTime Type.
This is because in SQL Server, the difference of 2 DateTime's (Cast to Float's and then Cast back to a DateTime) is simply a DateTime relative to Jan. 1, 1900. Ex. A difference of +0.1 second would be January 1, 1900 00:00:00.100 and -0.1 second would be Dec. 31, 1899 23:59:59.900.
To convert a .NET TimeSpan to a SQL Server DateTime Type, you would first convert it to a .NET DateTime Type by adding it to a DateTime of Jan. 1, 1900. Of course, when you read it into .NET from SQL Server, you would first read it into a .NET DateTime and then subtract Jan. 1, 1900 from it to convert it to a .NET TimeSpan.
For use cases where the time spans are being generated from SQL Server DateTime's and within SQL Server (i.e. via T-SQL) and SQL Server is prior to 2016, depending on your range and precision needs, it may not be practical to store them as milliseconds (not to mention Ticks) because the Int Type returned by DateDiff (vs. the BigInt from SS 2016+'s DateDiff_Big) overflows after ~24 days worth of milliseconds and ~67 yrs. of seconds. Whereas, this solution will handle time spans with precision down to 0.1 seconds and from -147 to +8,099 yrs..
WARNINGS:
This would only work if the difference relative to Jan. 1, 1900 would result in a value within the range of a SQL Server DateTime Type (Jan. 1, 1753 to Dec. 31, 9999 aka -147 to +8,099 yrs.). We don't have to worry near as much on the .NET TimeSpan side, since it can hold ~29 k to +29 k yrs. I didn't mention the SQL Server DateTime2 Type (whose range, on the negative side, is much greater than SQL Server DateTime's), because: a) it cannot be converted to a numeric via a simple Cast and b) DateTime's range should suffice for the vast majority of use cases.
SQL Server DateTime differences computed via the Cast - to - Float - and - back method does not appear to be accurate beyond 0.1 seconds.

Typically, I store a TimeSpan as a bigint populated with ticks from the TimeSpan.Ticks property as previously suggested. You can also store a TimeSpan as a varchar(26) populated with the output of TimeSpan.ToString(). The four scalar functions (ConvertFromTimeSpanString, ConvertToTimeSpanString, DateAddTicks, DateDiffTicks) that I wrote are helpful for handling TimeSpan on the SQL side and avoid the hacks that would produce artificially bounded ranges. If you can store the interval in a .NET TimeSpan at all it should work with these functions also. Additionally, the functions allow you to work with TimeSpans and 100-nanosecond ticks even when using technologies that don't include the .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 ---

I would store the timespan.TotalSeconds in a float and then retrieve it using Timespan.FromSeconds(totalSeconds).
Depending on the resolution you need you could use TotalMilliseconds, TotalMinutes, TotalDays.
You could also adjust the precision of your float in the database.
It's not an exact value... but the nice thing about this is that it's easy to read and calculate in simple queries.

Related

T-SQL SQL Server: Compare todays with future date in IF condition

I need to compare todays date with a future date using IF conditional in T-SQL SQL Server. It is contained in a store procedure which is called from C#. Also I want to return no rows if current date is not less or equal than future date.
So I have done:
DECLARE #FutureDate VARCHAR = '2017-05-20 00:00:00'
IF CAST(GETDATE() AS DATE) <= CAST(#FutureDate AS DATE)
BEGIN
....
END
ELSE
BEGIN
SELECT TOP 0 NULL
END
I call this store procedure from C#, But it is generating an error saying cannot convert into datetime. Time is not important for me, only date is important.
UPDATE:
Doing this is working and no need to convert FutureDate as DATE.
DECLARE #FutureDate DATE = '2017-05-20'
IF CAST(GETDATE() AS DATE) <= #FutureDate
BEGIN
....
Doing this is working and no need to convert FutureDate as DATE.
DECLARE #FutureDate DATE = '2017-05-20'
IF CAST(GETDATE() AS DATE) <= #FutureDate
BEGIN
....
END
ELSE
....
END

Date difference in mvc view with current date / sql server

I get date from my model and it is displayed in view
#Html.DisplayFor(modelItem => item.UpdateDate)
UpdateDate is datetime.(Format 2017-01-12 17:16:32.013)
What I want to do is, compare it with current date and show difference in time.
If the difference between current datetime and UpdatedDate is less
than 60 min then it should say "'X' Minues ago" (where 'X' is
difference in minutes)
If the difference between current datetime and UpdatedDate is more
than 60 min and less than 24 hours then it should say " 'Y' Hours 'X'
Minues ago" (where 'X' is difference in minutes and 'Y' is hours)
If the difference between current datetime and UpdatedDate is more than 24 hours then it should say " 'Z' days 'Y' Hours 'X' Minues ago" (where 'X' is difference in minutes and 'Y' is hours and 'Z' is days)
I want to do this in view. Your help is really appreciated.
Thanks
Here is how I am doing this,
TimeSpan diff = DateTime.Now - DateTime1;
string formatted = string.Format(
CultureInfo.CurrentCulture,
"{0} days, {1} hours, {2} minutes, {3} seconds",
diff.Days,
diff.Hours,
diff.Minutes,
diff.Seconds);
This works but it will prroduce o/p like
0 days 2 hours 28min (o/p needs to be 2 hours 28min)
or
0 days 0 hours 33 min ( o/p needs to be 33 min)
How do I enhance this without putting if else to check for '0' and get expected output
Well can we write a user defined function in sql server for desired format?
Create FUNCTION GetDateDiff
(
#a datetime
)
RETURNS varchar(100)
AS
BEGIN
Declare #Return varchar(100)
Set #Return = '';
if(Convert(int,(DATEDIFF(day, #a, getdate()))) >= 1)
Begin
Set #Return += Convert(varchar(10),(DATEDIFF(day, #a, getdate()))) + ' Day ';
End
if(Convert(int,(DATEDIFF(HOUR, #a, getdate()))) >= 1)
Begin
Set #Return += Convert(varchar(10),(DATEDIFF(HOUR, #a, getdate()))) + ' Hour ';
End
if(Convert(int,(DATEDIFF(MINUTE, #a, getdate()))) >= 1)
Begin
Set #Return += Convert(varchar(10),(DATEDIFF(MINUTE, #a, getdate()))) + ' Minute ';
End
return #Return
END
So I am writing this function in sql, any suggestions on improvement?
Further when day difference in greater than 1, the string should be 'days' (same for hours and minutes)

How to convert Persian date into Gregorian date in SQL Server

I have some Persian dates in my sql server table with the following format:
1394/05/14
I have to use stored procedure to convert it into Gregorian date because I need to compare it with today's date.
Does anyone know the solution? I have found some codes but they have problems in leap year and such things.
BTW I have the following code in C# but I think I have to use sql server proc because this proc should be executed with a fixed schedule.
public static DateTime ConvertToGregorianDate(string persianDate)
{
PersianCalendar pcalendar = new PersianCalendar();
int Year = int.Parse(persianDate.Split('/')[0]);
int Month = int.Parse(persianDate.Split('/')[1]);
int Day = int.Parse(persianDate.Split('/')[2]);
return new DateTime(Year, Month, Day, pcalendar);
}
Thanx in advance.
There is a project at GitHub that exactly does what you want! You just need to install its functions in your database by following the provided instructions. Then you can convert dates like below.
The main benefit of this library is that you are totally free to shape the returned result based on your needs. In fact, there is no fixed returned format.
select dbo.JalaliToGregorian('1395/06/11','/')
--returns 2016-09-01 00:00:00.000
select dbo.GregorianToJalali(GETDATE(),'yyyy MMMM dddd')
-- returns 1395 پنج شنبه مهر
select dbo.GregorianToJalali(GETDATE(),'yyyy/MM/dd HH:mm')
-- returns 1395/07/01 15:04
In the above examples suppose that GETDATE() Method in Sql server returns 2016/09/22 15:04:33!
A few different approaches
1) Use SQL CLR to run C# code from within SQL Server
2) Find or write a correct implementation of Persian<->Gregorian conversion in T-SQL
3) Run your C# code for all the dates you care about and dump the output to a file. Import that file into a table. When you need to convert, just look up the answer.
Option (3) is probably going to be the easiest, most maintainable, and best-performing solution. The nice thing about dates is that there really aren't that many of them. A calendar table for a hundred years is just kilobytes of memory, and databases are pretty good at doing lookups.
You can use bellow functions for your purpose (Iranian Calendar to Georgian Calendar) and for more information you can see here:
-- First, we need to convert Persian calendar date to Julian Calendar date
Create FUNCTION [dbo].[UDF_Persian_To_Julian](#iYear int,#iMonth int,#iDay int)
RETURNS bigint
AS
Begin
Declare #PERSIAN_EPOCH as int
Declare #epbase as bigint
Declare #epyear as bigint
Declare #mdays as bigint
Declare #Jofst as Numeric(18,2)
Declare #jdn bigint
Set #PERSIAN_EPOCH=1948321
Set #Jofst=2415020.5
If #iYear>=0
Begin
Set #epbase=#iyear-474
End
Else
Begin
Set #epbase = #iYear - 473
End
set #epyear=474 + (#epbase%2820)
If #iMonth<=7
Begin
Set #mdays=(Convert(bigint,(#iMonth) - 1) * 31)
End
Else
Begin
Set #mdays=(Convert(bigint,(#iMonth) - 1) * 30+6)
End
Set #jdn =Convert(int,#iday) + #mdays+ Cast(((#epyear * 682) - 110) / 2816 as int) + (#epyear - 1) * 365 + Cast(#epbase / 2820 as int) * 1029983 + (#PERSIAN_EPOCH - 1)
RETURN #jdn
End
Go
--Secondly, convert Julian calendar date to Gregorian to achieve the target.
Create FUNCTION [dbo].[UDF_Julian_To_Gregorian] (#jdn bigint)
Returns nvarchar(11)
as
Begin
Declare #Jofst as Numeric(18,2)
Set #Jofst=2415020.5
Return Convert(nvarchar(11),Convert(datetime,(#jdn- #Jofst),113),110)
End
Go
-- Here is the example
Select dbo.[UDF_Julian_To_Gregorian](dbo.[UDF_Persian_To_Julian](1391,1,30))
--Result is 04-18-2012
Another simple conversion is here:
SELECT FORMAT(sysdatetimeoffset() at time zone 'Iran Standard Time' , 'yyyy/MM/dd-HH:mm:ss', 'fa')
I hope you found this useful.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: MohammadSoori
-- Create date: 2021-06-21
-- Description: Convert Persian date to Miladi date.
-- =============================================
-- SELECT [dbo].[PersianToMiladi] ('1400/01/01')
-- =============================================
CREATE FUNCTION [dbo].[PersianToMiladi]
(
#PersianDate VARCHAR(10)
)
RETURNS DATE
AS
BEGIN
SET #PersianDate = RIGHT (#PersianDate, 9)
DECLARE #Year INT = SUBSTRING(#PersianDate, 1, 3)
DECLARE #Month INT = SUBSTRING(#PersianDate, 5, 2)
DECLARE #Day INT = SUBSTRING(#PersianDate, 8, 2)
DECLARE #DiffYear INT = #Year - 350
DECLARE #Days INT = #DiffYear * 365.24 +
CASE WHEN #Month < 7 THEN (#Month - 1) * 31
ELSE 186 + (#Month - 7) * 30 END + #Day
DECLARE #StartDate DATETIME = '03/21/1971'
DECLARE #ResultDate DATE = #StartDate + #Days
RETURN CONVERT(DATE, #ResultDate)
END

How to convert string to datetime in sql server

I want to count total time in hh:mm:ss format. and I have minutes in int like (465).
I made:
CONVERT(varchar, CONVERT(datetime, cast(cast(TotalMin/60 as int) as nvarchar(50)) + ' : ' + cast(TotalMin%60 as nvarchar(50))),108) AS TotalMin
but it shows below error. Not in SQL Server but when I run code in c#:
Conversion failed when converting date and/or time from character
string.
You can use this code to get the string in SQL Server. It will convert hours and minutes and add 00 for seconds as you don't have them (you're using integer value for minutes):
declare #min int = 465
select right('0' + cast(#min/60 as varchar(2)),2)
+ ':'
+ right('0' + cast((#min - (#min/60)*60) as varchar(2)),2)
+ ':00'
It will work for up to 5999 minutes (99 hours and 59 minutes).
If you need a Unicode version:
declare #min int = 465
select right(N'0' + cast(#min/60 as nvarchar(2)),2)
+ N':'
+ right(N'0' + cast((#min - (#min/60)*60) as nvarchar(2)),2)
+ N':00'
Try this:
TimeSpan t = TimeSpan.FromMinutes( TotalMin);
and see this for more
UPDATE MyTable SET MyDate = CONVERT(datetime, '2009/07/16 08:28:01', 120)
For a full discussion of CAST and CONVERT, including the different date formatting options, see the MSDN Library Link below:
http://msdn.microsoft.com/en-us/library/ms187928.aspx
This will help you
You want to multiply out to milliseconds as the fractional part is discarded.
SELECT DATEADD(ms, 121.25 * 1000, 0)
If you want it without the date portion you can use CONVERT, with style 114
SELECT CONVERT(VARCHAR, DATEADD(ms, 121.25 * 1000, 0), 114)

c# Refunding between date ranges

Basically I have an invoice lines for a below account as below:
BillID AccountID BilledFrom BillTo Days Price
38 3456 10/10/2012 10/11/2012 30 86p
39 3456 11/11/2012 11/12/2012 30 87p
40 3456 12/12/2012 30/12/2012 18 81p
The user would like to refund the customer for a partial date Range, therefore the user will need to enter a date from and date to date:
Date From: 18/10/2012 DateTo: 14/12/2012
This will cause credit lines to refund the client working out the days and price for each line. I need to return each line and show the cut off points. As you can see the ranges entered span across the 3 invoice lines.
The result needed is:
BillID AccountID BilledFrom BillTo RangeStart RangeEnd Days Price
38 3456 10/10/2012 10/11/2012 18/10/2012 10/11/2012 22 86p
39 3456 11/11/2012 11/12/2012 11/11/2012 11/12/2012 30 87p
40 3456 12/12/2012 30/12/2012 11/11/2012 11/12/2012 2 81p
The results basically will bring back the initial applicable invoice line but work out how this range fits and calculates the days against the bill from and bill to from the date range entered.
I need a function either in SQL to provide this result. Any Help would be appreciated. Thank you in advance.
try this:
declare #DateFrom date='10/18/2012'
declare #DateTo date='12/14/2012'
select T.BillID,T.AccountID,T.BilledFrom,T.BillTo,
case when BilledFrom<#DateFrom then #DateFrom else BilledFrom end [RangeStart],
case when BillTo<#DateTo then BillTo else #DateTo end [RangeEnd],DATEDIFF(D,case when BilledFrom<#DateFrom then #DateFrom else BilledFrom end ,case when BillTo<#DateTo then BillTo else #DateTo end ) [Days],Price
from t_account T
SQL Fiddle Demo
A pure T-SQL answer would be the following (assuming a table named InvoiceLine with the columns as specified in your text):
declare #from datetime
declare #to datetime
set #from = '2012-10-18'
set #to = '2012-12-14'
select
BillID, AccountID, BilledFrom, BillTo,
case when datediff(d, #from, BilledFrom)>0 then BilledFrom else #from end [RangeStart],
case when datediff(d, BillTo, #to)>0 then BillTo else #to end [RangeEnd],
[days]
+case when datediff(d, #from, BilledFrom)>0 then 0 else datediff(d, #from, BilledFrom) end
+case when datediff(d, BillTo, #to)>0 then 0 else datediff(d, BillTo, #to) end [Days],
Price
From InvoiceLine
Note that I am making some assumptions here:
The last line in your example output is wrong - the range is 12/12/2012 to 14/12/2012 (the number of days in your example is correct, however (2).
#from must be less than #to for this code to work - but you should check that anyway.
You want this to be done in T-SQL. It's not very neat, and C# is much better at this (min, max methods and date manipulation).

Categories

Resources