I have a use case that I'm not sure how to solve in a nice way.
I'm currently developing a .Net Core WebApi that is receiving data from various current systems, from a cross the whole world. Which I then process and lastly I commit it to SAP through oData endpoint.
The problem I'm having is on of parameters I'm receiving in the body payload, is a DateTime. Previous I have not have any issues. But not long ago I started getting data from a other system which deliverers it in a slightly differently way.
Previously this was the format I got: 2020-09-16T16:30:00 not problem with it. But the new system looks like this: 2020-09-16T16:00:00 -05:00 Could also end in +08:00.
The problem I'm facing is that SAP needs to get in local time. But in the my code it converts this: 2020-09-16T16:00:00 -05:00 to 2020-09-16T23:00:00 when I see the incoming payload in the controller.
I have searched quite a bit to find a solution. But 99% only suggest using UTC time, which is not a option for me.
Another option is to use DateTimeOffset, which I have tried but can't the time conversion to use localTime.
My question is. Are it not possible to custom convert to strip the timezone before it hits the controller?
Generarally when you're working with datetime data that includes offsets for time zone the DateTimeOffset type is a good place to start. The sample string 2020-09-16T16:00:00 -05:00 can be passed to DateTimeOffset.Parse() to get a correct DTO value with timezone information attached. From there you can get the local time, UTC time or a DateTime value with the timezone stripped.
string source = "2020-09-16T16:00:00 -05:00";
string fmt = #"yyyy-MM-dd\THH:mm:ss zzz"
// Same as input
Console.WriteLine(DateTimeOffset.Parse(source).ToString(fmt));
// Adjusted to your local timezone
Console.WriteLine(DateTimeOffset.Parse(source).ToLocalTime().ToString(fmt));
// DateTime portion of the source, timezone offset ignored
Console.WriteLine(DateTimeOffset.Parse(source).DateTime.ToString());
Getting the UTC time is simple too via the UtcDateTime property.
It sounds like what you want is the last one - just the date and time from the inputt string with the timezone offset stripped. If you just want the corresponding local time then DateTime.Parse should give that to you directly.
The JsonSerializer class doesn't support this format for DateTimeOffset so you might have some trouble getting it converted before hitting your controller. In that case you'd need to accept a string and do the conversion by hand in your code. You also might need to investigate the TryParseExact method.
Use DateTime.Parse() , for example
string timeInString1 = "2020-09-16T16:00:00 -05:00";
DateTime moment1 = DateTime.Parse(timeInString1);
string timeInString2 = "2020-09-16T16:00:00 +08:00";
DateTime moment2 = DateTime.Parse(timeInString2);
string timeInString3 = "2020-09-16T16:30:00";
DateTime moment3 = DateTime.Parse(timeInString3);
but momen1, momen2, or moment3 is non-timezone awareness value.
I'm working in a EasyPost integration making a class library to make the use of their API simpler and I'm getting this error:
Managed Debugging Assistant 'DateTimeInvalidLocalFormat' has detected a problem in 'C:\Projects\TestClient.vshost.exe'.
Additional information: A UTC DateTime is being converted to text in a format that is only correct for local times. This can happen when calling DateTime.ToString using the 'z' format specifier, which will include a local time zone offset in the output. In that case, either use the 'Z' format specifier, which designates a UTC time, or use the 'o' format string, which is the recommended way to persist a DateTime in text. This can also occur when passing a DateTime to be serialized by XmlConvert or DataSet. If using XmlConvert.ToString, pass in XmlDateTimeSerializationMode.RoundtripKind to serialize correctly. If using DataSet, set the DateTimeMode on the DataColumn object to DataSetDateTime.Utc.
I get this error when I call the Create method in the EasyPost Shipment object. Code below:
Shipment shipment = new Shipment() {
to_address = toAddress,
from_address = fromAddress,
parcel = parcel
};
shipment.Create();
This create function probably makes a call to their REST API and is trying to convert a json response into one of their models.
To solve the error I'm trying to set the UTC as the default of my library so whenever I use DateTime.ToString() I use the DateTime.ToString("o"). I don't know if this would actually solve the problem, but I don't know how to force it (use UTC as the library default). I have tried the piece of code below, but it doesn't work
CultureInfo newCulture = CultureInfo.InvariantCulture;
Thread.CurrentThread.CurrentCulture = newCulture;
Can you help me?
I'm one of the developers on the EasyPost client libraries.
As far as I can find in some basic research, there's no (easy) way to set a default time zone for a C# application. Most of the blog posts and other SO answers I found suggest using utility functions to convert a UTC datetime object to a local datetime object when trying to display it to a string.
EasyPost's API returns all datetimes in UTC time + timezone information (ex. 2022-10-24T12:37:24-06:00), which is accounted for when the JSON is deserialized into a DateTime object in the C# client library.
I have a POCO like this:
public class BlogEntry
{
public string Title { get; set; }
public DateTime Date { get; set; }
}
Most of the time it's being hydrated from Entity Framework, but it can and will be used outside of Entity Framework.
The DateTimeKind for the Date from EF is Unspecified, which from what I read is normal.
When I cache this POCO in Redis (using the ServiceStack Redis client), it comes back with a DateTimeKind of Local.
So there is a jitter with the returned objects. The first pass (uncached) has ISO-8601 with no offset (DateTimeKind.Unspecified). The second pass (cached) is ISO-8601 with an offset (from Redis with DateTimeKind.Local).
Any way to force the ServiceStack JSON serializer to always interpret dates as a given DateTimeKind? (I know there is a "JsConfig.AppendUtcOffset" property, but whether it's true or false, the values never change for me?)
Or somewhere in the deserialization process from my typed RedisClient to make the DateTimeKind local?
I can manually change my POCO's to enforce the DateTimeKind - and that works - but I was hoping for something less error prone.
If you need something more configurable than #mythz's answer, you can force the serialization or deserialization of DateTimes to have a certain DateTimeKind by overriding the DateTime and optionally DateTime? serialization and/or deserialization methods.
Force all serialized DateTimes to be interpreted as UTC
JsConfig<DateTime>.SerializeFn = time => new DateTime(time.Ticks, DateTimeKind.Local).ToString();
You can then take this one step farther and error on deserialization if the DateTime isn't in a specified format. I started using this when I wanted to force clients to specify the timezone in all requests, but not necessarily require that it always be Utc.
JsConfig<DateTime>.DeSerializeFn = time =>
{
if (!IsInCorrectDateFormat(time))
throw new System.Runtime.Serialization.SerializationException(BadDateTime);
return ServiceStack.Text.Common.DateTimeSerializer.ParseDateTime(time);
};
The DateTimeKind offset does not get stored with Date's so by default ServiceStack serializers make the assumption that the date is local, which gets serialized as UTC and deserialized back out as Local.
You can get DateTimeKind.Unspecified to be assumed as UTC with:
JsConfig.AssumeUtc = true;
A tweak to bpruitt-goddard solution. All credit goes to him.
JsConfig<DateTime>.SerializeFn = time => new DateTime(time.Ticks, DateTimeKind.Local).ToString("o");
JsConfig<DateTime?>.SerializeFn =
time => time != null ? new DateTime(time.Value.Ticks, DateTimeKind.Local).ToString("o") : null;
JsConfig.DateHandler = DateHandler.ISO8601;
So any dates going out from service stack will be forced into a ISO8601 date format and any dates coming in will be converted automatically to C# date from the ISO8601 string.
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.
I have a website that is hosted in a different timezone than the users using the application. In addition to this, users can have a specific timezone. I was wondering how other SO users and applications approach this? The most obvious part is that inside the DB, date/times are stored in UTC. When on the server, all date/times should be dealt with in UTC. However, I see three problems that I'm trying to overcome:
Getting the current time in UTC (solved easily with DateTime.UtcNow).
Pulling date/times from the database and displaying these to the user. There are potentially lots of calls to print dates on different views. I was thinking of some layer in between the view and the controllers that could solve this issue. Or having a custom extension method on DateTime (see below). The major down side is that at every location of using a datetime in a view, the extension method must be called!
This would also add difficulty to using something like the JsonResult. You could no longer easily call Json(myEnumerable), it would have to be Json(myEnumerable.Select(transformAllDates)). Maybe AutoMapper could help in this situation?
Getting input from the user (Local to UTC). For example, POSTing a form with a date would require converting the date to UTC before. The first thing that comes to mind is creating a custom ModelBinder.
Here's the extensions that I thought of using in the views:
public static class DateTimeExtensions
{
public static DateTime UtcToLocal(this DateTime source,
TimeZoneInfo localTimeZone)
{
return TimeZoneInfo.ConvertTimeFromUtc(source, localTimeZone);
}
public static DateTime LocalToUtc(this DateTime source,
TimeZoneInfo localTimeZone)
{
source = DateTime.SpecifyKind(source, DateTimeKind.Unspecified);
return TimeZoneInfo.ConvertTimeToUtc(source, localTimeZone);
}
}
I would think that dealing with timezones would be such a common thing considering a lot of applications are now cloud-based where the server's local time could be much different than the expected time zone.
Has this been elegantly solved before? Is there anything that I'm missing? Ideas and thoughts are much appreciated.
EDIT: To clear some confusion I thought add some more details. The issue right now isn't how to store UTC times in the db, it's more about the process of going from UTC->Local and Local->UTC. As #Max Zerbini points out, it's obviously smart to put the UTC->Local code in the view, but is using the DateTimeExtensions really the answer? When getting input from the user, does it make sense to accept dates as the user's local time (since that's what JS would be using) and then use a ModelBinder to transform to UTC? The user's timezone is stored in the DB and is easily retrieved.
Not that this is a recommendation, its more sharing of a paradigm, but the most agressive way I've seen of handling timezone information in a web app (which is not exclusive to ASP.NET MVC) was the following:
All date times on the server are UTC.
That means using, like you said, DateTime.UtcNow.
Try to trust the client passing dates to the server as little as possible. For example, if you need "now", don't create a date on the client and then pass it to the server. Either create a date in your GET and pass it to the ViewModel or on POST do DateTime.UtcNow.
So far, pretty standard fare, but this is where things get 'interesting'.
If you have to accept a date from the client, then use javascript to make sure the data that you are posting to the server is in UTC. The client knows what timezone it is in, so it can with reasonable accuracy convert times into UTC.
When rendering views, they were using the HTML5 <time> element, they would never render datetimes directly in the ViewModel. It was implemented as as HtmlHelper extension, something like Html.Time(Model.when). It would render <time datetime='[utctime]' data-date-format='[datetimeformat]'></time>.
Then they would use javascript to translate UTC time into the clients local time. The script would find all the <time> elements and use the date-format data property to format the date and populate the contents of the element.
This way they never had to keep track of, store, or manage a clients timezone. The server didn't care what timezone the client was in, nor had to do any timezone translations. It simply spit out UTC and let the client convert that into something that was reasonable. Which is easy from the browser, because it knows what timezone it is in. If the client changed his/her timezone, the web application would automatically update itself. The only thing that they stored were the datetime format string for the locale of the user.
I'm not saying it was the best approach, but it was a different one that I had not seen before. Maybe you'll glean some interesting ideas from it.
After several feedbacks, here is my final solution which I think is clean and simple and covers daylight saving issues.
1 - We handle the conversion at model level. So, in the Model class, we write:
public class Quote
{
...
public DateTime DateCreated
{
get { return CRM.Global.ToLocalTime(_DateCreated); }
set { _DateCreated = value.ToUniversalTime(); }
}
private DateTime _DateCreated { get; set; }
...
}
2 - In a global helper we make our custom function "ToLocalTime":
public static DateTime ToLocalTime(DateTime utcDate)
{
var localTimeZoneId = "China Standard Time";
var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById(localTimeZoneId);
var localTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, localTimeZone);
return localTime;
}
3 - We can improve this further, by saving the timezone id in each User profile so we can retrieve from the user class instead of using constant "China Standard Time":
public class Contact
{
...
public string TimeZone { get; set; }
...
}
4 - Here we can get the list of timezone to show to user to select from a dropdownbox:
public class ListHelper
{
public IEnumerable<SelectListItem> GetTimeZoneList()
{
var list = from tz in TimeZoneInfo.GetSystemTimeZones()
select new SelectListItem { Value = tz.Id, Text = tz.DisplayName };
return list;
}
}
So, now at 9:25 AM in China, Website hosted in USA, date saved in UTC at database, here is the final result:
5/9/2013 6:25:58 PM (Server - in USA)
5/10/2013 1:25:58 AM (Database - Converted UTC)
5/10/2013 9:25:58 AM (Local - in China)
EDIT
Thanks to Matt Johnson for pointing out the weak parts of original solution, and sorry for deleting original post, but got issues getting right code display format... turned out the editor has problems mixing "bullets" with "pre code", so I removed the bulles and it was ok.
In the events section on sf4answers, users enter an address for an event, as well as a start date and an optional end date. These times are translated into a datetimeoffset in SQL server that accounts for the offset from UTC.
This is the same problem you are facing (although you are taking a different approach to it, in that you are using DateTime.UtcNow); you have a location and you need to translate a time from one timezone to another.
There are two main things I did which worked for me. First, use DateTimeOffset structure, always. It accounts for offset from UTC and if you can get that information from your client, it makes your life a little easier.
Second, when performing the translations, assuming you know the location/time zone that the client is in, you can use the public info time zone database to translate a time from UTC to another time zone (or triangulate, if you will, between two time zones). The great thing about the tz database (sometimes referred to as the Olson database) is that it accounts for the changes in time zones throughout history; getting an offset is a function of the date that you want to get the offset on (just look at the Energy Policy Act of 2005 which changed the dates when daylight savings time goes into effect in the US).
With the database in hand, you can use the ZoneInfo (tz database / Olson database) .NET API. Note that there isn't a binary distribution, you'll have to download the latest version and compile it yourself.
At the time of this writing, it currently parses all of the files in the latest data distribution (I actually ran it against the ftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gz file on September 25, 2011; in March 2017, you'd get it via https://iana.org/time-zones or from ftp://fpt.iana.org/tz/releases/tzdata2017a.tar.gz).
So on sf4answers, after getting the address, it is geocoded into a latitude/longitude combination and then sent to a third-party web service to get a timezone which corresponds to an entry in the tz database. From there, the start and end times are converted into DateTimeOffset instances with the proper UTC offset and then stored in the database.
As for dealing with it on SO and websites, it depends on the audience and what you are trying to display. If you notice, most social websites (and SO, and the events section on sf4answers) display events in relative time, or, if an absolute value is used, it's usually UTC.
However, if your audience expects local times, then using DateTimeOffset along with an extension method that takes the time zone to convert to would be just fine; the SQL data type datetimeoffset would translate to the .NET DateTimeOffset which you can then get the universal time for using the GetUniversalTime method. From there, you simply use the methods on the ZoneInfo class to convert from UTC to local time (you'll have to do a little work to get it into a DateTimeOffset, but it's simple enough to do).
Where to do the transformation? That's a cost you are going to have to pay somewhere, and there's no "best" way. I'd opt for the view though, with the timezone offset as part of the view model presented to the view. That way, if the requirements for the view change, you don't have to change your view model to accommodate the change. Your JsonResult would simply contain a model with the IEnumerable<T> and the offset.
On the input side, using a model binder? I'd say absolutely no way. You can't guarantee that all the dates (now or in the future) will have to be transformed in this way, it should be an explicit function of your controller to perform this action. Again, if the requirements change, you don't have to tweak one or many ModelBinder instances to adjust your business logic; and it is business logic, which means it should be in the controller.
This is just my opinion, I think that MVC application should separate well data presentation problem from data model management. A database can store data in local server time but it's a duty of the presentation layer to render datetime using local user timezone. This seems to me the same problem as I18N and number format for different countries.
In your case, your application should detect the Culture and timezone of the user and change the View showing different text, number and datime presentation, but the stored data can have the same format.
For output, create an display/editor template like this
#inherits System.Web.Mvc.WebViewPage<System.DateTime>
#Html.Label(Model.ToLocalTime().ToLongTimeString()))
You can bind them based on attributes on your model if you want only certain models to use those templates.
See here and here for more details on creating custom editor templates.
Alternatively, since you want it to work for both input and output, I would suggest extending a control or even creating your own. That way you can intercept both the input and outputs and convert the text/value as needed.
This link will hopefully push you in the right direction if you want to go down that path.
Either way, if you want an elegant solution, its going to be a bit of work. On the bright side, once you have done it once you can keep it in your code library for future use!
This is probably a sledgehammer to crack a nut but you could inject a layer between the UI and Business layers which transparently converts datetimes to the local time on returned object graphs, and to UTC on input datetime parameters.
I imagine this could be achieved using PostSharp or some inversion of control container.
Personally, I'd just go with explicitly converting your datetimes in the UI...
I wanted to store dates as DateTimeOffset so that I could maintain the Time Zone Offset of the user that writes to the database. However, I wanted to only use DateTime inside of the application itself.
So, local time zone in, local time zone out. Regardless of who/where/when the user is looking at the data, it will be a local time to the observer - and changes are stored as UTC + local offset.
Here is how I achieved this.
1.
Firstly, I needed to get the web client's local time zone offset and store this value on the web server:
// Sets a session variable for local time offset from UTC
function SetTimeZone() {
var now = new Date();
var offset = now.getTimezoneOffset() / 60;
var sign = offset > 0 ? "-" : "+";
var offset = "0" + offset;
offset = sign + offset + ":00";
$.ajax({
type: "post",
url: prefixWithSitePathRoot("/Home/SetTimeZone"),
data: { OffSet: offset },
datatype: "json",
traditional: true,
success: function (data) {
var data = data;
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert("SetTimeZone failed");
}
});
}
The format is intended to match that of the SQL Server DateTimeOffset type.
SetTimeZone - just sets the value of Session variable. When the user logs on, I incorporate this value into the User profile cache.
2.
As a user submits a change to the database, I filter the DateTime value through a utility class:
cmdADO.Parameters.AddWithValue("#AwardDate", (object)Utility.ConvertLocal2UTC(theContract.AwardDate, theContract.TimeOffset) ?? DBNull.Value);
The Method:
public static DateTimeOffset? ConvertLocal2UTC(DateTime? theDateTime, string TimeZoneOffset)
{
DateTimeOffset? DtOffset = null;
if (null != theDateTime)
{
TimeSpan AmountOfTime;
TimeSpan.TryParse(TimeZoneOffset, out AmountOfTime);
DateTime datetime = Convert.ToDateTime(theDateTime);
DateTime datetimeUTC = datetime.ToUniversalTime();
DtOffset = new DateTimeOffset(datetimeUTC.Ticks, AmountOfTime);
}
return DtOffset;
}
3.
When I and reading in the date from the SQL Server, I am doing this:
theContract.AwardDate = theRow.IsNull("AwardDate") ? new Nullable<DateTime>() : DateTimeOffset.Parse(Convert.ToString(theRow["AwardDate"])).DateTime;
In the controller, I modify the datetime to match the local time of the observer. (I am sure someone can do better with an extension or something):
theContract.AwardDate = Utilities.ConvertUTC2Local(theContract.AwardDate, CachedCurrentUser.TimeZoneOffset);
The method:
public static DateTime? ConvertUTC2Local(DateTime? theDateTime, string TimeZoneOffset)
{
if (null != theDateTime)
{
TimeSpan AmountOfTime;
TimeSpan.TryParse(TimeZoneOffset, out AmountOfTime);
DateTime datetime = Convert.ToDateTime(theDateTime);
datetime = datetime.Add(AmountOfTime);
theDateTime = new DateTime(datetime.Ticks, DateTimeKind.Utc);
}
return theDateTime;
}
In the View, I am just displaying/editing/validating a DateTime.
I hope this helps someone who has a similar need.