I am storing datetime in a stringified JSON object in Redis cache like so:
"{\"email\":\"myemail#testdomain.com\", \"expiry\": \"2018-03-19T23:00:03.0658822+00:00\"}"
In C#, when I query this data from Redis and convert it to string, it loses its timezone value and gets automatically stripped off of its timezone information.
RedisValue cookie = GetRedisDatabase().StringGet("sessionhash");
JObject cookieValue = JObject.Parse(cookie.ToString());
var email = JObject.Parse(cookie.ToString())["email"];
var expiry = JObject.Parse(cookie.ToString())["expiry"].ToString();
The "expiry" string above only contains "2018/03/19 23:00:03". It seems like C# is automatically detecting the string to be of datetime format, and is stripping off timezone information from it.
How can I ensure the "expiry" string is "2018-03-19T23:00:03.0658822+00:00"?
DateTime does not know about timezones. Instead it has a DateTimeKind property which tells you if the time is machine local, UTC, or unknown. Methods ToLocalTime will convert a known UTC or unknown time to local time, and do nothing of already local.
You'll need to use something else that keeps the timezone information, i believe DateTimeOffset can track a time with a variable offset, but not the timezone.
NodaTime is a library which understands timezones.
internal class Program
{
private static void Main()
{
string expiry = "2018-03-19T23:00:03.0658822+00:00";
DateTime parsedExpiry = DateTime.Parse(expiry);
Console.WriteLine(parsedExpiry.ToString());
Console.ReadKey();
}
}
This code converts 19/3/2018 23:00 into 20/3/2018 7:00.
The reason it does this is because, as per above answers, DateTime doesn't hold on to any TimeZone information. The only information you have is DateTime.Kind, which in the case of my code, outputs Local. I can use parsedExpirey.ToUniversalTime() to get UTC.
You could do some extra parsing on the string representation and use the TimeZoneInfo class to maintain the timezone, but you'll likely need an extra column / storage space to store that info. You can use the Convert option, but then you'll be storing DateTimes in all different timezones, you'd be better off using ToUniversalTime and storing it all in UTC (best practice), then converting it to Local time for presentation to the user (or leave it UTC, depending on the application).
Your final ToString asked for the time without TZ info. Do this
RedisValue cookie = GetRedisDatabase().StringGet("sessionhash");
JObject cookieValue = JObject.Parse(cookie.ToString());
var email = JObject.Parse(cookie.ToString())["email"];
var expiry = JObject.Parse(cookie.ToString())["expiry"].ToString("O");
I have a few general rules regarding handling DateTimes:
Always store, retrieve and transmit the value in UTC. Windows is pretty good at translating any UTC value to whatever the current user picked as his favourite timezone. You do not want to deal with Timezones if you can avoid it at all.
Never store, retrieve and transmit the value as string.
In case 3 can not work, at least pick a fixed culture and string encoding at all endpoints. You do not want to add those to your worries.
In rare cases (Callendar Apps) it might be beneficial to store the "originally saved timezone".
Unfortunately you cannot determine the time zone from an ISO date/time string. You can only determine the offset. The time zone names and codes are not unique-- for example, "Arabia Standard Time" has an offset of UTC+03, but has the code "AST," which collides with "Atlantic Standard Time" (offset UTC-04). So while you can map in one direction, you can't reliably map in the other.
That being said, getting the offset isn't so bad if you use a DateTimeOffset instead of DateTime. If the field isn't a DateTimeOffset in your object model, you can create a temporary anonymous type as a template and get it that way. Example:
public static void Main()
{
var input = "{\"email\":\"myemail#testdomain.com\", \"expiry\": \"2018-03-19T23:00:03.0658822+01:00\"}";
var template = new { email = "", expiry = DateTimeOffset.Now };
var result = JsonConvert.DeserializeAnonymousType(input, template);
Console.WriteLine("DateTime (GMT): {0:R}\r\nOffset from GMT: {1}", result.expiry, result.expiry.Offset);
}
Output:
DateTime (GMT): Mon, 19 Mar 2018 22:00:03 GMT
Offset from GMT: 01:00:00
Code on DotNetFiddle
Related
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.
We have timestamps from CSV files that look like this:
06-02-2018 15:04:21
We do not control the delivery of them, nor the timezone.
What we do know so far is that we have seen so far is this:
Standard Romance Time
Standard Romance Time, but without DST compensation.
UTC
From this, we gather that we need an engine that can take any timestamp (written in patterns that DateTime.ParseExact understands), belong to any timezone, optionally ignore DST, and then convert it to UTC (the format we internally use).
I was hoping this could do it:
public DateTime ConvertToUtc(string fromTimestamp, DataReaderConfiguration dataReaderConfiguration)
{
DateTime readTime = DateTime.ParseExact(fromTimestamp, dataReaderConfiguration.TimestampFormat,
CultureInfo.InvariantCulture, DateTimeStyles.None);
DateTime utcTime = TimeZoneInfo.ConvertTimeToUtc(readTime,
TimeZoneInfo.FindSystemTimeZoneById(dataReaderConfiguration.TimeZone));
return utcTime;
}
but C# does not have timezones defined without DST (except for UTC).
So we need to expand on the method to allow for a timezone conversion without DST.
You can create this function yourself, by leveraging the TimeZoneInfo.BaseUtcOffset property and a DateTimeOffset.
public static DateTime ConvertTimeToUtc(DateTime dt, TimeZoneInfo tz, bool useDST)
{
if (useDST)
{
// the normal way (converts using the time in effect - standard or daylight)
return TimeZoneInfo.ConvertTimeToUtc(dt, tz);
}
// the way to convert with just the standard time (even when DST is in effect)
var dto = new DateTimeOffset(dt, tz.BaseUtcOffset);
return dto.UtcDateTime;
}
Then just pass whatever property from your DataReaderConfiguration object that indicates whether you want to use DST (or negate it if necessary).
Also note that this gives the standard time based on the current set of rules. If you are dealing with historical dates where the time zone's standard time has changed, things get a bit more complex. You'd have to figure out which adjustment rules were in place at the time, etc. You might even find edge cases where the Windows time zone data is insufficient.
I am trying to figure out the best way of letting the users set the internal DateTime value of the Portable Class Library based on some string parameter they provide. The string parameter has to be a simple format.
So, now I have some considerations.
Is specifying UTC Offset enough for getting the right DateTime
public static DateTime FromUtcOffset(string value)
{
var utcDateTime = DateTime.UtcNow;
var offSet = TimeSpan.Parse(value);
return utcDateTime + offSet;
}
Or is specifying the TimeZone has some advantage over UTC Offset
TimeZoneInfo someTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
DateTime convertTimeFromUtc = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, someTimeZone);
My question is: What would be the right string parameter that can be taken from the user to let him decide what the value of DateTime would be?
Utc Offset
TimeZone
Or any other alternative that's less verbose.
Actually, it depends:
Do you work with network hosts, located in different time zones
Do you store time values for using them in future
Does your library work locally (hence, knows user's timezone)
1+2 basically mean if your time offset might change. If it not (the library is intended for local use only), get local time and don't care about the time offset. However, if the offset might change, usually storing "absolute" time in UTC format should be enough. To do this, you can:
Ask user for UTC time, not their local time
or
Ask for local time + offset (or get the offset from the local time zone, if possible)
Convert it to UTC time and store/process in UTC time
Provide output using local time (using the offset from 1. if it didn't change)
In 1 and 3 you will need a timezone to figure out the time offset. You don't need to know timezone if you already know the offset. Moreover, DateTime itself can store time offset information. It also can tell you if it stores local or UTC time (see DateTime.Kind Property).
I have a datetime in database which I read using SqlDataReader and then cast it to (DateTime). After the cast its Kind property is DateTimeKind.Unspecified.
Then I have another string which I read from some other source. Its format is like this 2016-01-20T22:20:29.055Z. I do DateTime.Parse("2016-01-20T22:20:29.055Z") and its Kind property is DateTimeKind.Local.
How do I properly parse the both date times for comparison? Do I need to use DateTimeOffsets? How should I parse them?
Thanks
Because SQLReader cannot reasonably infer a DateTimeKind, it leaves it as unspecified. You'll want to use DateTime.SpecifyKind to change the DateTimeKind on your output from the SQLReader to the appropriate value. This works ok if you are only dealing with UTC and one consistent local time zone; otherwise, you really should be using DateTimeOffset in both your code and the SQL Database.
The string "2016-01-20T22:20:29.055Z" is ISO 8601 compliant and is a UTC date; however, DateTime.Parse with only 1 argument can end up performing a conversion to local time. Per the documentation:
Generally, the Parse method returns a DateTime object whose Kind
property is DateTimeKind.Unspecified. However, the Parse method may
also perform time zone conversion and set the value of the Kind
property differently, depending on the values of the s and styles
parameters:
If s contains time zone information, the date and time is converted
to the time in the local time zone and the Kind is DateTimeKind.Local.
If s contains time zone information, and styles includes the
AdjustToUniversalflag, the date and time is converted to Coordinated
Universal Time (UTC) and the Kind is DateTimeKind.Utc.
If s contains the Z or GMT time zone designator, and styles includes
the RoundtripKind flag, the date and time are interpreted as UTC and
the Kind is DateTimeKind.Utc.
Also see UTC gotchas in .NET and SQL Server in Derek Fowler's blog for additional coverage on the topic.
In your second example, 2016-01-20T22:20:29.055Z has timezone information provided with it; the 'Z' at the end indicates that the timestamp is intended for Coordinated Universal Time (UTC). However, DateTime.Parse() will default its conversion using DateTimeKind.Local unless a specific timezone is specified. You can use DateTime.ParseExact to be more specific.
As to why the datetime values in your database are coming out as Unspecified, that's likely because they contain no timezone indication at all. Check to see if your database values specify timezone information, either by using 'Z' at the end or specifying an exact timezone, such as 2016-01-20T22:20:29.055-07:00 (UTC-7).
You can use something like this:
string format = "ddd dd MMM h:mm tt yyyy";
DateTime dateTime = DateTime.ParseExact(dateString, format,
CultureInfo.InvariantCulture);
In format variable, you can put the format you want, and pass it to ParseExact function.
Hope it helps.
You are missing the datetime context (offset) in your database. You should persist it either in a datetimeoffset column or in a datetime column but persisting utc datetimes.
And always better compare two utc datetimes.
I coded a quick C# console app that I pasted in below. This converts a UTC date and time to a string (format similar to the ISO 8601 format described in another post with some extra digits of precision), writes it to a file, reads it from the file (as a string) and then converts it back to a UTC date and time.
It then compares the two UTC Date Time objects, which are both of UTC kind, and they match.
class Program
{
// "2016-01-20T22:20:29.055Z" is ISO 8601 compliant and is a UTC date
const string dtf = "yyyy-MM-ddTHH:mm:ss.fffffffZ";
static void Main(string[] args)
{
string file = #"c:\temp\file.txt";
DateTime dt = DateTime.UtcNow;
using (var sw = new System.IO.StreamWriter(file))
{
sw.WriteLine(dt.ToString(dtf, System.Globalization.CultureInfo.InvariantCulture));
}
DateTime dtin;
using (var sr = new System.IO.StreamReader(file))
{
dtin = DateTime.ParseExact(sr.ReadLine(), dtf, System.Globalization.CultureInfo.InvariantCulture);
}
Console.WriteLine(dt.ToString(dtf) + "\r\n" + dtin.ToString(dtf) + "\r\nEquality:" + (dt == dtin));
Console.ReadLine();
}
}
I want to get client timezone so i'm using the code below
function filltime()
{
document.getElementById("hdnTime").value = new Date();
}
conversion
Dim time As Date = DateTime.ParseExact(hdnTime.Value,
"ddd MMM d HH:mm:ss UTCzzzzz yyyy",InvariantCulture)
I'm not getting exact value. Its showing server time only.
But hdnTime.Value contains the correct value("Mon Feb 18 14:46:49 UTC+0530 2013"). I think the problem is with conversion.
What is the problem? How can I solve?
Dates and times are a pain in 1 language, let alone when passing a value between 2.
I'd recommend serializing the JavaScript Date() object into JSON before posting it back to the server. Then de-serializing it into a C# DateTime object using a library such as JSON.NET. There's comprehensive documentation (Serializing Dates in JSON) with regards to what settings can be applied when serializing and de-serializing.
JavaScript
function filltime()
{
document.getElementById("hdnTime").value = JSON.stringify(new Date());
}
JSON isn't native to every browser, so you mean need to manually load it in, for more information you can refer to: Browser-native JSON support (window.JSON)
C# using JSON.NET
DateTime dateTime = JsonConvert.DeserializeObject<DateTime>(hdnTime.Value);
You are confusing the DateTime object with its displaying
it is normal that you see the server time because you are seeing the datetime rappresentation with your current timezone.
What you don't get is how DateTime works...
If you pass a datetime with timezone info then it will be converted to your timezone with the right offset.
If you want to pass a datetime and get it as it is than you have to remove the timezone part.
In you situation, anyway, if you need only to know the client timezone just pass it!
var d = new Date()
var n = d.getTimezoneOffset();
The getTimezoneOffset() method returns the time difference between UTC time and local time, in minutes.
For example, If your time zone is GMT+2, -120 will be returned.
For general discussion:
in my experience the best way to deal with datetime converted as string and passed between different systems, is to use the ISODATE format:
DateTime.Now.ToString("s"); //"2013-02-18T11:17:24"