My end goal is to get universal time from client with NO offset - just UTC time. I try to accomplish this like so:
Javascript: (new Date()).toUTCString(), the output of which logs: Thu, 17 Mar 2016 15:13:23 GMT, which is exactly what I need.
Then I take it to server and try to convert it to DateTimeOffset:
string dateTimeOffsetPattern = "ddd, dd MMM yyyy HH:mm:ss 'GMT'";
DateTimeOffset clientLoginTime = DateTimeOffset.ParseExact
(timeStringFromClient, dateTimeOffsetPattern, CultureInfo.InvariantCulture);
Which results in:
3/17/2016 3:13:23 PM -04:00
Somehow it adjusts the time for my local (Eastern) offset. I do NOT want this to happen, I want it to just return the UTC time, like so:
3/17/2016 3:13:23 PM +00:00
P.S. I did just ask another question about this, and I apologize, since I feel like it should be easy enough, but I don't get it. This should be really simple, but it looks like offset doesn't have a setter (unless I am completely missing some C# basics as usual):
public TimeSpan Offset { get; }
There's an overload of ParseExact where you can specify a DateTimeStyles. One of the values of DateTimeValues is AssumeUniversal, which says:
If format does not require that input contain an offset value, the returned DateTimeOffset object is given the UTC offset (+00:00).
which basically means "don't assume it's local, assume it's universal." Assuming local is the default, which is why you're seeing the result you are in that it's adjusting to local. Specifying AssumeUniversal should parse it the way you want.
DateTimeOffset clientLoginTime = DateTimeOffset.ParseExact
(timeStringFromClient, dateTimeOffsetPattern, CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal);
I would parse from JS normally, then do the following:
Strip OffSet from DateTimeOffset by returning DateTime
Set OffSetby instantiating another DateTimeOffset having TimeSpan set to ZERO.
In your case:
var clientLoginTime = new DateTimeOffset(clientLoginTime.DateTime, TimeSpan.FromHours(0));
This can be easily converted into an extension method
public static DateTimeOffset SetOffset(this DateTimeOffset dto, int timeZoneDiff)
{
return new DateTimeOffset(dto.DateTime, TimeSpan.FromHours(timeZoneDiff));
}
From JavaScript (or any other client) you should send DateTimes using ISO8601 format which would be yyyy-MM-ddTHH-mm-ss.sssz. The Z indicates that the datetime instance is GMT (UTC). You can also change this to add + or - from GMT. You can do this using the JavaScript Date object using myDate.toISOString()
When creating your WebAPI model(s) you can then use either a DateTime or DateTimeOffset types directly. The JSON.NET serializer for Web API will automatically deserialize the sent ISO8601 datetime string to the correct DateTime or DateTimeOffset type (depending on which one you are using). This means that you do not have to do any manuall parsing in your code which is good. (Imagine if you had to send everything as string and parse everything manually in all your methods?).
So you can now have a method
public async Task<IHttpActionResult> GetRecords(DateTimeOffset? startFrom)
And the startFrom will automatically be populated based on the sent ISO8601 formatted DateTime string.
Finally, the last and most import reason to do this is that your clients will probably not all use the same locale. You could have a user that has their browser set to Spanish so .toUTCString() will not yield an English string or possibly even a string with mm/dd but the reverse (like most countries other than the USA).
Long story short for WebAPI
Use ISO8601 from/to client.
Use DateTimeOffset or DateTime instances directly in your model (no parsing).
Related
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
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 try to parse date from string which contains time zone information. Input string is 2014-12-17T08:05:39+00:00.
I use DateTime.Parse() method which return me 2014-12-17 09:05:39 (one hour was added). I live in UTC+1:00 (Warsaw), so .NET adopt this date to my local time.
My question is how to use the parse method while skipping time zone, for example for 2014-12-17T08:05:39+00:00 I want to get 2014-12-17 08:05:39.
I would recommend parsing it as a DateTimeOffset instead of as a DateTime. You can then get the DateTime out of that, but it separates the "parsing the data you've been given" step from the "only using the bits I want from that" step.
It's possible that there are ways to make DateTime.Parse behave the way you want using DateTimeStyles - and I'm surprised it's converting to a "local" kind automatically anyway - but using DateTimeOffset will make it clearer.
(Of course I'd really recommend using Noda Time instead, parsing to an OffsetDateTime and then getting the LocalDateTime out of that, but that's a different matter...)
If you remove the part specifying time zone in input string then it parses directly without adjusting to local time. The date.Kind is then Unspecified.
var input = "2014-12-17T08:05:39";
var date = DateTime.Parse(fixedInput);
Although this works you might want to have a look on NodaTime as well.
You should try using DateTimeOffset instead of the DateTime
DateTimeOffset result = DateTimeOffset.Parse("2014-12-17T08:05:39+00:00", CultureInfo.InvariantCulture);
it gives you : 12/17/2014 8:05:39 AM +00:00
Simple question, I have this string:
string dateString = "7/12/2014 4:42:00 PM";
This is a date string and it's in the UTC timezone.
I need to convert it to a date, so I'm doing the following:
DateTimeOffset dateOffset;
DateTimeOffset.TryParse(dateString, out dateOffset);
DateTime date = dateOffset.UtcDateTime;
The problem:
When I'm parsing the string to date, the code is considering that the dateString is in the Local Timezone of the PC (+3 GMT), and not in the UTC timezone.
So I am getting the following the dateOffset = {7/12/2014 4:42:00 PM +03:00} and thus date = {7/12/2014 1:42:00 PM}
how can I tell him that the date string provided is in the UTC format and not in the local timezone format?
Thanks
how can I tell him that the date string provided is in the UTC format and not in the local timezone format?
Specify a DateTimeStyles value of AssumeUniversal in the call. That tells the parsing code what to do. For example:
// null here means the thread's current culture - adjust it accordingly.
if (DateTimeOffset.TryParse(dateString, null, DateTimeStyles.AssumeUniversal,
out dateOffset))
{
// Valid
}
You should always use the result of TryParse to tell whether or not it's successfully parsed.
If you know the format and the specific culture, I'd personally use DateTimeOffset.TryParseExact. (Well, to be honest I'd use my Noda Time project to start with, but that's a different matter.)
There is another overload of DateTimeOffset.TryParse
DateTimeOffset.TryParse Method (String, IFormatProvider, DateTimeStyles, DateTimeOffset)
which allows you specify DateTimeStyles. One of the DateTimeStyles is AssumeUniversal, which is what you're looking for:
If no time zone is specified in the parsed string, the string is
assumed to denote a UTC. This value cannot be used with AssumeLocal or
RoundtripKind.
Don't know how .Net API provides, but I guess you could probably use ISO8601 format to indicate a UTC timezone before parsing, i.e, first translate 7/12/2014 4:42:00 PM into something 2014-07-02T16:42:00Z, then use try parse using DateTimeOffset
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"