Emulating impact of SQL storage on C# DateTime - c#

I have uncovered a unfortunate side-effect that storing and rehydrating a C# DateTime from a SQL Server datetime column alters it slightly, so that the equality operator is no longer valid.
For example, using EF:
FileInfo fi = new FileInfo("file.txt");
Entity.FileTimeUtc = fi.LastWriteTimeUtc;
we find that
(Entity.FileTimeUtc == fi.LastWriteTimeUtc) // true
but if we save that entity and then reload it from SQL Server, we find that
(Entity.FileTimeUtc == fi.LastWriteTimeUtc) // false
I understand that a process of rounding has happened here (if only by a few milliseconds) due to differing internal storage formats between the .NET DateTime and the SQL datetime.
What I am looking for is a process that will reliably emulate this conversion, and align native DateTime values to those which have been stored and rehydrated from a SQL datetime field, to make the equality test valid again.

That is because SQL Server's DateTime type counts time in 3- and 4-millisecond "ticks", with some very odd "rounding" rules.
See my answer to the question, "How does SqlDateTime do its precision reduction?" for details on exactly what those rounding rules are.
That will allow you to do the exact same conversion in .Net.
Also, I believe that if converting your C#/.Net DateTime values to a System.Data.SqlTypes.SqlDateTime will do the same conversions (but I can't swear to that — it's been a while since I had to wrangle with that).

Related

How to handle timezone difference

I have my aplication which uses BST time zone(UTC+1) and another application where i am using the value from the above specified application . On my second server the timezone is UTC.
Because of this when i get value in a datetime property there is a diufference of one hour and because of that the value is going wrong.
I wont be able to adjust the timezone on both the servers . How can i convert the value to a BST time zone in my second application using .net.
public DateTime? EmploymentStartDate { get; set; }
This is my property in my model.
the value from my first server is 29/8/2001 00:00:00 when it comes to the second server where the timezone is UTC it is 28/9/2001 23:00:00 .
It's simple, you must consider all the DateTime always in UTC, so simply call ToUniversalTime() to convert every instance in UTC timezone:
DateTime dt = DateTime.Now;
dt.ToUniversalTime();
I agree with Daniel A. White. You should store everything as UTC and convert the date to local date on the client.
Look this https://msdn.microsoft.com/en-us/en-en/library/system.timezoneinfo.converttimefromutc(v=vs.100).aspx
private DateTime? _employmentStartDate;
public DateTime? EmploymentStartDate
{
get
{
return _employmentStartDate != null ? TimeZoneInfo.ConvertTimeFromUtc(_employmentStartDate.Value, TimeZoneInfo.Local) : new DateTime();
}
set
{
_employmentStartDate = TimeZoneInfo.ConvertTimeToUtc((DateTime)value);
}
}
Also consult this previous StackOverflow post:
Should MySQL have its timezone set to UTC?
This article (and the numerous other articles and web-pages that it links to) discusses MySQL, but the concerns and techniques are similar for any database and programming language.
While your (the OP's) question seems to be about how to convert time information between time zones, the discussion in the comments has grown hot on the subject of choosing a format for storage. First things first – to convert your UTC time to BST you can use TimeZoneInfo.ConvertTimeFromUtc. This method “converts a Coordinated Universal Time (UTC) to the time in a specified time zone.”
Here’s an example:
var bstZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Daylight Time");
bstTime = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, bstZone);
NB 1: If what you really want is local time on the server when executing, obviously you should rather use ToLocalTime(), and that will yield the same result if that local time happens to be BST (and the Kind property of the DateTime variable is not DateTimeKind.Local).
NB 2: To the best of my knowledge, the TimeZoneInfo class referrers to "British Summer Time" as "GMT Daylight Time", and I’m assuming that’s what you refer to as BST. But don’t take my word for it – you need to make sure yourself that you have the right time zone ID.
Now, about storing:
TL;DR
A time signature without time zone information is unreliable information. Given this premise, there is however no true “right or wrong” time zone to use for storing. While most people might argue that UTC is to be considered a best practice, first of all that information (that it's in UTC) should still be stored explicitly alongside the timestamp, and secondly, there may be practical reasons why another format is preferable in a specific scenario.
When performing arithmetic calculations on time values you should use universal time representations to avoid hiccups with regard to daylight saving practices and such.
Be weary of complications that might arise from serializing and de-serializing mechanisms making the wrong assumptions about the information you provide, e.g. System.XML.Serialization or a database engine.
Obviously, when presenting your time information (that is, when creating a string representation suitable for interpretation by humans), you should do so in whatever format the user should be expecting. Regardless of how you persist your values, conversions (with offset) may be necessary to do so.
Check out these links for more information:
Converting Times Between Time Zones
Coding Best Practices Using DateTime in the .NET Framework

Setting format of DateTime - how do I keep the value in DateTime format?

I have a DateTime being created in the format dd/MM/yyyy HH:mm:ss. I am writing code that interacts with a third-party SOAP library that requires a DateTime variable, in the format yyyy-MM-dd HH:mm:ss.
How do I change the way the information is stored in the DateTime variable, for the purpose of the call to the third-party SOAP library, i.e. no system-wide changes to dates?
I have investigated CultureInfo, which is mildly confusing and possibly too permanent a solution; the only time I need the DateTime changing is for an instance of this single call.
As an explanation, the library has a function GetOrders(DateTime startDate, DateTime endDate, TradingRoleCodeType roleType, OrderStatusCodeType statusType). When attempting to perform the function with DateTimes as created, it generates an error "Sorry, the end date was missing, invalid, or before the start date. must be in YYYY-MM-DD or YYYY-MM-DD HH:MI:SS format, and after the start date.". Given the format that is passed in as dd/MM/yyyy HH:mm:ss, I'd think this may be the problem.
I have a DateTime being created in the format dd/MM/yyyy HH:ii:ss
No, you do not. You have a DateTime. It has no format. It is a number - which is well documented, you know, in the documentation. The string form is never used in a stored DateTime, only when generating the string for presentation.
How do I change the way the information is stored in the DateTime
variable, for the purpose of the call to the third-party SOAP library,
i.e. no system-wide changes to dates?
You do not. I would suggest you talk to your SOAP library - and it is not SOAP btw., IIRC the format you give as example is not valid in SOAP. Yes, bad news. Someone wants Pseudo-Soap.
http://www.w3schools.com/schema/schema_dtypes_date.asp
describes all valid date, time and datetime formats and yours is NOT there.
You can change the default format on a thread level back and forth, so one solution is to set it before calls into the soap library. Another one is to have someone fix the SOAP layer to accept standard formats.
You can create a dummy date :
public class SomeClass
{
[XmlIgnore]
public DateTime SomeDate { get; set; }
[XmlElement("SomeDate")]
public string SomeDateString
{
get { return this.SomeDate.ToString("yyyy-MM-dd HH:mm:ss"); }
set { this.SomeDate = DateTime.Parse(value); }
}
}
Source : Force XmlSerializer to serialize DateTime as 'YYYY-MM-DD hh:mm:ss' --kbrimington
As it turns out, the problem - as some have pointed out - is not to do with the variable being a DateTime, nor its "format" which is not a "format", but is certainly the representation of the information in a method to be understood.
The basic issue with the information was a DateTime comparison between standard time and UTC time. The third-party library examined the DateTime as a UTC DateTime, which when at the right time of year to be caught with a difference in times can cause a problem comparing a DateTime; despite being presented as after the reference time to the user, the time is actually before the reference time when being calculated, meaning the comparison fails.
The main takeaway for this question is to interrogate the information being passed to functions, if you don't have access to third-party library code nor access to documentation with sufficient detail, and errors are occurring when interacting with said third-party code.
Particularly, test various use cases to determine what variable values cause a failure and which cause successful execution of code; identify a pattern, and then test specific use cases that confirm the pattern. From there, determine the actual error that is occurring and code to fix the issue.
In the case of DateTimes, where the code understands DateTimeKinds such as C#, remember to test the different DateTimeKinds to establish whether they can be a part of the problem; its not happened to me often, but it has happened (as evidenced by this question).
Finally, error codes don't help much, and can lead to poor questions and poor advice; trial and error appears to be the best in cases similar to this.
You don't need to change how it's stored, as already mentioned above.
You need to format is as a string according to ISO8601, which is what your SOAP service expects datetime parameter to be.
Check How to parse and generate DateTime objects in ISO 8601 format
and
Given a DateTime object, how do I get an ISO 8601 date in string format?

TimeZoneInfo works differently in two bits of code

I have some code in my UI layer, which is supposed to take a DateTime, which is in UTC, and convert it to a local date time:
In my Data layer, I simply do this:
private DateTime ConvertToLocal(DateTime dt)
{
if (_currentTimeZoneUser == string.Empty)
{
var u = new UserData(_userId).GetUser(_userId);
_currentTimeZoneUser = u.TimeZoneId;
}
var reply = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(dt, _currentTimeZoneUser);
return reply;
}
What that does is check if _currentTimeZoneUser is set. If not, get the zimezone from the user table, and then does a conversion.
This code is working, and I get a valid result.
I then copied the code to my UI layer (As I need to do a conversion there as well, for a data grid), but 'reply' always equals 'dt'.
I googled, and noticed that I should be doing it a slightly different way. So I change my UI method to this:
public static DateTime GetLocalDateTime(DateTime serverTime)
{
var timeZoneId = HttpContext.Current.Session["TimeZoneId"].ToString();
TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
var reply = TimeZoneInfo.ConvertTimeFromUtc(serverTime, cstZone);
return reply;
}
and it works!
I can't see why it works in my data layer, but in the UI, I need to change the code.
Am I doing something wrong with my time conversion code in one of the methods?
If I'm understanding you correctly, your question boils down to the difference between ConvertTimeBySystemTimeZoneId and ConvertTimeFromUtc.
First, you need to understand that any time zone conversion operations involving DateTime may have behavioral differences depending on the value of the .Kind of DateTime you are giving it. When you look at the documentation for each of these methods (here and here), you will find a chart that describes the behavior for each of the three different kinds (Utc,Local, and Unspecified).
This is a sore point in .Net, which is why libraries like Noda Time exist. You can read more in these two articles:
What's wrong with DateTime Anyway?
The case against DateTime.Now
The actual reason for the specific problem is that you probably passed in a DateTime who's .Kind is Unspecified. In the ConvertTimeBySystemTimeZoneId method, that will be treated as if it were Local, while in the ConvertTimeFromUtc method it will be treated as if it were Utc.
There are two solutions.
The first is what you already found - use the ConvertTimeFromUtc method. You should do this in the server code also.
The second solution is to set the .Kind to Utc when you load the value from your database. Somewhere you probably have code like this:
foo.MyDateTime = (DateTime) dataReader["MyDateTime"]
Which would change to this:
foo.MyDateTime = DateTime.SpecifyKind(
(DateTime) dataReader["MyDateTime"],
DateTimeKind.Utc);
I'm assuming you are doing a direct ADO.Net call with a DataReader response. Adjust accordingly for whatever you are actually doing.

Milliseconds in my DateTime changes when stored in SQL Server

I have a date time that I generate like this:
DateTime myDateTime = DateTime.Now;
I then store it in the database (in a DateTime typed column) with Entity Framework. I then retrieve it with OData (WCF Data Services).
When it goes in the TimeOfDay value is: 09:30:03.0196095
When it comes out the TimeOfDay value is: 09:30:03.0200000
The net effect of this makes it so that the Milliseconds are seen as 19 before it is saved and 20 after it is re-loaded.
So when I do a compare later in my code, it fails where it should be equal.
Does SQL Server not have as much precision as .NET? Or is it Entity Framework or OData that is messing this up?
I will just truncate off the milliseconds (I don't really need them). But I would like to know why this is happening.
This really depends on the version of SQL server you are using.
The resolution of the date time field is to 3 decimal places: For example: 2011-06-06 23:59:59.997 and is only accuracte to within 3.33 ms.
In your case, 09:30:03.0196095 is being rounded up to 09:30:03.020 on storage.
Beginning with SQL 2008, other data types were added to provide more detail, such as datetime2 which has up to 7 decimal places and is accurate to within 100ns.
See the following for more information:
http://karaszi.com/the-ultimate-guide-to-the-datetime-datatypes
I think your best bet is to provide the rounding to the second PRIOR to storing it in SQL server if the milliseconds is unimportant.
This is due to the precision of the SQL datetime type. According to msdn:
Datetime values are rounded to increments of .000, .003, or .007 seconds
Look at the Rounding of datetime Fractional Second Precision section of this msdn page and you'll understand how the rounding is done.
As indicated by others, you can use datetime2 instead of datetime to have a better precision:
datetime time range is 00:00:00 through 23:59:59.997
datetime2 time range is 00:00:00 through 23:59:59.9999999
For those who do not have the ability to use DateTime2 in SQL (ex: like me using tables that are generated by a separate system that would be expensive to change for this single issue), there is a simple code modification that will do the rounding for you.
Reference System.Data and import the System.Data.SqlTypes namespace. You can then use the SqlDateTime structure to do the conversion for you:
DateTime someDate = new SqlDateTime(DateTime.Now).Value;
This will convert the value into SQL ticks, and then back into .NET ticks, including the loss of precision. :)
A word of warning, this will lose the Kind of the original DateTime structure (i.e. Utc, Local). This conversion is also not simply rounding, there is a complete conversion including tick calculations, MaxTime changes, etc.. So don't use this if you are relying on specific indicators in DateTime as they could be lost.
The precision of DateTime in SQL Server is milliseconds (.fff). So 0.0196 would round to 0.020. If you can use datetime2, you get a higher precision.

.NET DateTime to SqlDateTime Conversion

While converting .NET DateTime (when is default(DateTime)) to SqlDateTime should I always check if the .NET date is between SqlDateTime.MinValue and SqlDateTime.MaxValue [or] Is there a good way to do this.
Is it possible that the date could actually be outside that range? Does it come from user input? If the answer to either of these questions is yes, then you should always check - otherwise you're leaving your application prone to error.
You can format your date for inclusion in an SQL statement rather easily:
var sqlFormattedDate = myDateTime.Date.ToString("yyyy-MM-dd HH:mm:ss");
If you are checking for DBNULL, converting a SQL Datetime to a .NET DateTime should not be a problem. However, you can run into problems converting a .NET DateTime to a valid SQL DateTime.
SQL Server does not recognize dates prior to 1/1/1753. Thats the year England adopted the Gregorian Calendar. Usually checking for DateTime.MinValue is sufficient, but if you suspect that the data could have years before the 18th century, you need to make another check or use a different data type. (I often wonder what Museums use in their databases)
Checking for max date is not really necessary, SQL Server and .NET DateTime both have a max date of 12/31/9999 It may be a valid business rule but it won't cause a problem.
Also please remember resolutions [quantum of time] are different.
http://msdn.microsoft.com/en-us/library/system.data.sqltypes.sqldatetime.aspx
SQL one is 3.33 ms and .net one is 100 ns.
on my quest to do this with entitie, i stumbled over here, just hitting back to post what i've found out...
when using EF4, "a sql's" datetime column can be filled from .NET's DateTime using BitConverter.
EntitieObj.thetime = BitConverter.GetBytes(DateTime.Now.ToBinary());
also Fakrudeen's link brought me further... thank you.
-To compare only the date part, you can do:
var result = db.query($"SELECT * FROM table WHERE date >= '{fromDate.ToString("yyyy-MM-dd")}' and date <= '{toDate.ToString("yyyy-MM-dd"}'");
var sqlCommand = new SqlCommand("SELECT * FROM mytable WHERE start_time >= #StartTime");
sqlCommand.Parameters.Add("#StartTime", SqlDbType.DateTime);
sqlCommand.Parameters("#StartTime").Value = MyDateObj;

Categories

Resources