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.
Related
I'm trying to get ahold of this timezone issue we are having. We would like to store all DateTimes in UTC, and then convert the DateTime to the user's timezone.
We decided to use NodaTime for this, as it seems like the right approach. However, we are experiencing an issue with it.
This is how we convert the DateTime to UTC (note - I hardcoded the usersTimeZone for now):
public static DateTime ConvertLocaltoUTC(this DateTime dateTime)
{
LocalDateTime localDateTime = LocalDateTime.FromDateTime(dateTime);
IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
var usersTimezoneId = "Europe/Copenhagen";
var usersTimezone = timeZoneProvider[usersTimezoneId];
var zonedDbDateTime = usersTimezone.AtLeniently(localDateTime);
var returnThis = zonedDbDateTime.ToDateTimeUtc();
return zonedDbDateTime.ToDateTimeUtc();
}
And here is how we convert it back:
public static DateTime ConvertUTCtoLocal(this DateTime dateTime)
{
Instant instant = Instant.FromDateTimeUtc(dateTime);
IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
var usersTimezoneId = "Europe/Copenhagen"; //just an example
var usersTimezone = timeZoneProvider[usersTimezoneId];
var usersZonedDateTime = instant.InZone(usersTimezone);
return usersZonedDateTime.ToDateTimeUnspecified();
}
However, when we convert it back to local time, it throws an exception:
Argument Exception: Invalid DateTime.Kind for Instant.FromDateTimeUtc
at the first line of ConvertUTCtoLocal().
An example of the DateTime could be: "9/18/2017 5:28:46 PM" - yes this has been through the ConvertLocalToUTC method.
Am I providing an incorrect format? What am I doing wrong here?
The exception you show:
Argument Exception: Invalid DateTime.Kind for Instant.FromDateTimeUtc
Is thrown from this code:
Instant instant = Instant.FromDateTimeUtc(dateTime);
It means that dateTime.Kind needs to be DateTimeKind.Utc to be convertible to an Instant, and for whatever reason it is not.
If you look at the result of your ConvertLocaltoUTC method, you'll find that it does have .Kind == DateTimeKind.Utc.
So, the problem lies elsewhere in your code, wherever you created the dateTime you're passing in to ConvertUTCtoLocal.
You may find the solution could be any of the following:
You might need to call DateTime.SpecifyKind to set the kind to UTC, but be careful to only do this when your values are actually UTC and it's just not setting the kind. For example, use this when loading a UTC-based DateTime from a database.
You might need to call .ToUniversalTime(), but be careful to only do this if the machine's local time zone is relevant to your situation. For example, do this in desktop or mobile apps where a UI control is picking a date, but you meant it to mean UTC instead of local time.
You might need to change how you parse strings into DateTime values, such as by passing DateTimeStyles.RoundTripKind to a DateTime.Parse call (or any of its variants. For example, do this if you are reading data from text, csv, etc.
If you want to avoid having to decide, don't write functions that take DateTime as input or give DateTime as output. Instead, use DateTimeOffset, or use Noda-Time types like Instant, LocalDateTime, etc. as early as possible.
This is what worked for me:
Instant instant = Instant.FromDateTimeUtc(DateTime.SpecifyKind(datetime, DateTimeKind.Utc));
I regularly seem to have to work with converting a DateTimeOffset value to another timezone also in DateTimeOffset. the big headache has been the fact that the TimeZoneInfo class convert method returns DateTime, so I end up having to convert the data again when I it to a DateTimeOffset type.
To over come this, I cam up with an extension method for the DateTimeOffset:
This has to exist in a class.
public static DateTimeOffset ToNewTimeZone(this DateTimeOffset value, string timeZone)
{
value = value.UtcDateTime;
TimeZoneInfo tzObject = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
//Using the GetUtcOffset means that the TimeZoneInfo class is responsible for calculating the Daylight savings time.
DateTimeOffset ret = value.ToOffset(tzObject.GetUtcOffset(value));
return ret;
}
It works extremely well under every test case I have come up with, but it seems inelegant to me. Which is usually a sign that there is already a better solution. However I have yet to find one.
I create a .NET Fiddle to show my work. https://dotnetfiddle.net/LLl1Za
Lines 42 and 43 highlight this code in light of my other experiments above it.
Just so that we have context, I cannot change the DB to store data in UTC only, and just change the zone for the client screens. The project is too big and expansive to make this change now. There a many articles that talk about other timezone handling issues, but just not this specific scenario.
Is there a better way to do this?
I don't see a reason why you should implement it yourself. You should use TimeZoneInfo.ConvertTime() to change the timezone of a DateTimeOffset.
Please refer to https://msdn.microsoft.com/en-us/library/bb396765%28v=vs.110%29.aspx
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?
So,
I'm storing a DateTime object as a private member on an object. This private member is set on object creation like so:
this.mCreateDate = DateTime.UtcNow.ToUniversalTime();
Now, later on in the application, I want to see how long the object has been alive for. This could be many weeks (this is a long running web app).
I'm getting the object lifetime like so:
DateTime now = DateTime.UtcNow.ToUniversalTime();
TimeSpan objectLifetime = now.Subtract(Foo.CreateDate);
// Output formatted time span
Does this all look correct?
First, calling ToUniversalTime() on DateTime.UtcNow is redundant, since .net 2.0, no conversion happens if the "Kind" of the source DateTime object is "Utc".
Other than that, this looks fine.
This seems correct, and you're following the golden rule of DateTime: Use UTC, display in local!
Your approach seems correct to me although I think it is more appropriate to use the DateTimeOffset structure, which is new since .NET 3.5.
I quote Anthony Moore:
Use DateTimeOffset whenever you are referring to an exact point in time. For example, use it to calculate "now", transaction times, file change times, logging event times, etc. If the time zone is not known, use it with UTC. These uses are much more common than the scenarios where DateTime is preferred, so this should be considered the default.
Use DateTime for any cases where the absolute point in time does not apply: e.g. store opening times that apply across time zones.
Use DateTime for interop scenarios where the information is a Date and Time without information about the time zone, e.g. OLE Automation, databases, existing .NET APIs that use DateTime, etc.
Use DateTime with a 00:00:00 time component to represent whole dates, e.g. Date of irth.
Use TimeSpan to represent times of day without a date.
So you could simply write:
this.mCreateDate = DateTimeOffset.Now;
//...
TimeSpan lifeTime = DateTimeOffset.Now - Foo.CreateDate;
If I run a snippet like:
bool areTheyTheSame = DateTime.UtcNow == DateTime.Now
what will I get? Does the DateTime returned know its timezone such that I can compare?
My specific problem is that I'm trying to build a cache-like API. If it takes a DateTime AbsoluteExpiration, do I have to enforce that users of my API know whether to give me a UTC time or a timezone-based time?
[Edit] This SO question is extremely relevant to my issue as well: Cache.Add absolute expiration - UTC based or not?
[Edit] Just to clarify for future readers, the DateTimeKind is what is different. The Undefined DateTimeKind's are often a problem, which is what you get when you pull one out of a database, for instance. Set the DateTimeKind in the DateTime constructor...
[Edit] JonSkeet wrote a lovely blog post condemning this behavior and offering a solution: http://noda-time.blogspot.co.uk/2011/08/what-wrong-with-datetime-anyway.html
Did you actually try the snippet yourself?
They're different, and a straight comparison doesn't account for the difference, but you can convert local to UTC by calling ToUniversalTime.
var now = DateTime.Now;
var utcNow = DateTime.UtcNow;
Console.WriteLine(now); // 12/07/2010 16:44:16
Console.WriteLine(utcNow); // 12/07/2010 15:44:16
Console.WriteLine(now.ToUniversalTime()); // 12/07/2010 15:44:16
Console.WriteLine(utcNow.ToUniversalTime()); // 12/07/2010 15:44:16
Console.WriteLine(now == utcNow); // False
Console.WriteLine(now.ToUniversalTime() == utcNow); // True
Console.WriteLine(utcNow.ToUniversalTime() == utcNow); // True
Also be wary of taking a local DateTime and calling .ToUniversalTime() when daylight savings comes around.
See the note in the remarks section here: http://msdn.microsoft.com/en-us/library/system.timezone.touniversaltime.aspx
DateTime.Now returns the system time while DateTime.UtcNow returns the UTC time.