I have code that converts long numbers to dates.
DateTimeOffset value =
DateTimeOffset.FromUnixTimeSeconds(1597325462);
DateTime showTime = value.DateTime;
string easternZoneId = "America/New_York";
TimeZoneInfo easternZone =
TimeZoneInfo.FindSystemTimeZoneById(easternZoneId);
DateTime targetTime =
TimeZoneInfo.ConvertTime(showTime, easternZone);
Console.WriteLine("DateTime is {0}", targetTime);
On my Mac, the output is "DateTime is 8/13/2020 6:31:02 AM"
On my server the output is "DateTime is 8/13/2020 9:31:02 AM"
Identical code on both.
The Linux box value is accurate. How can I get the same result on my Mac?
The overload of TimeZone.ConvertTime you're using takes a DateTime value and a destination TimeZoneInfo. There's no mention of the source time zone, so it is infered from the .Kind property of the DateTime being passed in.
In your case, that is DateTimeKind.Unspecified, because the .DateTime property of a DateTimeOffset always returns unspecified, regardless of what the offset is.
In the ConvertTime call, if the kind is DateTimeKind.Unspecified, it is assumed to be local time (as if it were DateTimeKind.Local). (Scroll down to the Remarks section in the docs here.) Thus, you are converting as if the Unix timestamp were local-time based, rather than the actuality that it is UTC based. You get different results between your workstation and server because they have different system-local time zones - not because they are running different operating systems.
There are a number of different ways to rewrite this to address the problem. I will give you a few to choose from, in order of my preference:
You could leave everything as a DateTimeOffset throughout the conversion process:
DateTimeOffset value = DateTimeOffset.FromUnixTimeSeconds(1597325462);
string easternZoneId = "America/New_York";
TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById(easternZoneId);
DateTimeOffset targetTime = TimeZoneInfo.ConvertTime(value, easternZone);
You could use the .UtcDateTime property instead of the .DateTime property:
DateTimeOffset value = DateTimeOffset.FromUnixTimeSeconds(1597325462);
DateTime showTime = value.UtcDateTime;
string easternZoneId = "America/New_York";
TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById(easternZoneId);
DateTime targetTime = TimeZoneInfo.ConvertTime(showTime, easternZone);
You could use ConvertTimeFromUtc instead of ConvertTime:
DateTimeOffset value = DateTimeOffset.FromUnixTimeSeconds(1597325462);
DateTime showTime = value.DateTime;
string easternZoneId = "America/New_York";
TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById(easternZoneId);
DateTime targetTime = TimeZoneInfo.ConvertTimeFromUtc(showTime, easternZone);
You could specify UTC as the source time zone in the ConvertTime call:
DateTimeOffset value = DateTimeOffset.FromUnixTimeSeconds(1597325462);
DateTime showTime = value.DateTime;
string easternZoneId = "America/New_York";
TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById(easternZoneId);
DateTime targetTime = TimeZoneInfo.ConvertTime(showTime, TimeZoneInfo.Utc, easternZone);
There are a few other options, such as explicitly setting the kind, but I think the above gives you enough to go on.
If in doubt, pick the first option. DateTimeOffset is much easier to rationalize than DateTime in most cases.
The issue seems to be that there is not such a timezone I'd in the ICU library that both OS look into.
Check the example in the Microsoft docs:
https://learn.microsoft.com/en-us/dotnet/api/system.timezoneinfo.converttimebysystemtimezoneid?view=netcore-3.1
DateTime currentTime = DateTime.Now;
Console.WriteLine("New York: {0}",
TimeZoneInfo.ConvertTimeBySystemTimeZoneId(currentTime, TimeZoneInfo.Local.Id, "Eastern Standard Time"));
So it looks like the id you should look for is ""Eastern Standard Time"
If you can't find it then run the following code to check the available timezones on your pc
foreach (TimeZoneInfo z in TimeZoneInfo.GetSystemTimeZones())
Console.WriteLine(z.Id);
The destinationTimeZoneId parameter must correspond exactly to the time zone's identifier in length, but not in case, for a successful match to occur; that is, the comparison of destinationTimeZoneId with time zone identifiers is case-insensitive.
So check the timezones and copy the correct string to use based on what exists on the machine.
Related
I have a bunch of data with a DateTime that's in UTC, I want to group each data by date by using .GroupBy, but I want to group them according to CST or EST. Is there a way to do this using EF core? I haven't been successful at finding if this is possible by using DateTimeOffset or TimeSpan.
You can create a TimeZoneInfo object representing e.g. Eastern Standard Time with the TimeZoneInfo.FindSystemTimeZoneById method. (Note: this depends on the OS timezone rules, which e.g. in the case of Windows do not cover all DST history.)
var tzest = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
Then you can convert any given DateTime test that has DateTime.Kind of DateTimeKind.Utc to the new timezone:
var testest = TimeZoneInfo.ConvertTime(test, tzest);
If your DateTime has Kind as Unspecified you can use TimeZoneInfo.ConvertTimeFromUtc():
var testest = TimeZoneInfo.ConvertTimeFromUtc(test, tzest);
If test has TimeZoneKind.Local, this will throw an exception and you will need to override the TimeZoneKind:
var testest = TimeZoneInfo.ConvertTimeFromUtc(DateTime.SpecifyKind(test, DateTimeKind.Utc), tzest);
I am sending from webbrowser this string "2019-01-25T00:00:00+01:00"
I undestand this as: this is local time and in utc should be "2019-01-24T23:00:00"
but on the server :
myDate.Kind is local
myDate "2019-01-24T23:00:00"
myDate.ToLocalTime() is the same "2019-01-24T23:00:00"
myDate.ToUniversalTime() is the same "2019-01-24T23:00:00"
what I need is if I sent this string "2019-01-25T00:00:00+01:00" I need to know on the server that there is 1h difference between local and utc
and parsing this string is done automatically by dot net core api (DateTime is method parameter)
The DateTime Type does not have any concept of time zones: if you need this, use DateTimeOffset instead.
I suspect your server is in the UTC timezone, Since ToLocalTime and ToUniversalTime give the same result.
You can try AdjustToUniversal option, e.g.
string source = "2019-01-25T00:00:00+01:00";
DateTime myDate = DateTime.ParseExact(
source,
"yyyy-MM-dd'T'HH:mm:sszzz",
CultureInfo.InvariantCulture,
DateTimeStyles.AdjustToUniversal);
Console.Write(string.Join(Environment.NewLine,
$"Value = {myDate:HH:mm:ss}",
$"Kind = {myDate.Kind}"));
Outcome:
Value = 23:00:00
Kind = Utc
Edit: If you can't change server's code and thus you have to provide a string (source) such that DateTime.Parse(source)
will return a correct date you can try to convert existing time-zone (+01:00) into Zulu:
string source = "2019-01-25T00:00:00+01:00";
// 2019-01-24T23:00:00Z
source = DateTime
.ParseExact(source,
"yyyy-MM-dd'T'HH:mm:sszzz",
CultureInfo.InvariantCulture,
DateTimeStyles.AdjustToUniversal)
.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'");
Then on the server you'll have
// source is treated as UTC-time;
// However, by default (when no options provided) myDate will have Kind = Local
DateTime myDate = DateTime.Parse(source);
Console.Write(string.Join(Environment.NewLine,
$"Value = {myDate:HH:mm:ss}",
$"Kind = {myDate.Kind}"));
Outcome:
Value = 02:00:00 // May vary; adjusted to server's time zone (In my case MSK: +03:00)
Kind = Local // DateTime.Parse returns Local when no options specified
Assume Instant variable contains "4/8/2014 11:09:24 AM" when i pass this value to this method it gives me an output "4/8/2014 12:09:24 PM" i checked in some time zone calcluator plus in oracle i should be getting "4/8/2014 04:09:24 PM" or "4/8/2014 16:09:24 PM" depending on hour format. why is it not converting into proper time format?
public static string ConvertDateTimeToUserTimeZone()
{
DateTime dt = DateTime.Parse("4/8/2014 11:09:24 AM");
Instant now = Instant.FromDateTimeUtc(DateTime.SpecifyKind(dt,DateTimeKind.Utc));
DateTimeZone dtZone = DateTimeZoneProviders.Tzdb["Europe/London"];
ZonedDateTime zdt = now.InZone(dtZone);
return zdt.ToDateTimeOffset().ToString("G");
}
Reading through your comments, it would appear that you'd like to do the following:
Parse the input string
Assign it to the the US Eastern time zone ("America/New_York")
Convert it to the time zone for the UK ("Europe/London")
Format it as a string in the general format
I will show using Noda Time for all of these steps, rather than mixing in non-Noda mechanisms.
var generalPattern = LocalDateTimePattern.CreateWithCurrentCulture("G");
string inputString = "4/8/2014 11:09:24 AM";
string sourceTimeZone = "America/New_York";
string targetTimeZone = "Europe/London";
LocalDateTime ldt1 = generalPattern.Parse(inputString).Value;
DateTimeZone tz1 = DateTimeZoneProviders.Tzdb[sourceTimeZone];
ZonedDateTime zdt1 = ldt1.InZoneLeniently(tz1);
DateTimeZone tz2 = DateTimeZoneProviders.Tzdb[targetTimeZone];
ZonedDateTime zdt2 = zdt1.WithZone(tz2);
LocalDateTime ldt2 = zdt2.LocalDateTime;
string output = generalPattern.Format(ldt2);
Notice that I used CreateWithCurrentCulture when setting up the pattern. This assumes you mean to use whatever the current culture is for the machine the code is running on. If that's not the case, then you should set a specific culture instead. This is important when you realize that users in the US will use M/D/Y formatting, while users in the UK will use D/M/Y formatting. This applies to both dates regardless of time zone. (In other words, 4/8/2014 could be April 8th, or August 4th).
Also notice that I used InZoneLeniently when applying the source time zone. This has lenient behavior when it comes to ambiguous and invalid input values due to DST transitions. If you want different behavior, then you might instead use InZoneStrictly, or use InZone and provide your own algorithm.
And finally, it should be noted that I inferred from your comments that you were sourcing these from the US Eastern time zone, which would be either EDT or EST depending on what time of year it is. If you actually meant that the values were always EDT, even when EST is the norm, then you would do the following:
DateTimeZone tz1 = DateTimeZone.ForOffset(Offset.FromHours(-4));
The value you got back from the method is in fact correct.
Let's break it down using LINQPad:
void Main()
{
var t = new DateTime(2014, 8, 4, 11, 09, 24, DateTimeKind.Utc);
var i = Instant.FromDateTimeUtc(t);
var s = ConvertDateTimeToUserTimeZone(i);
s.Dump("User time zone value");
}
public static string ConvertDateTimeToUserTimeZone(Instant now)
{
DateTimeZone dtZone = DateTimeZoneProviders.Tzdb["Europe/London"];
dtZone.GetUtcOffset(now).Dump("TZ at instant");
ZonedDateTime zdt = now.InZone(dtZone);
return zdt.ToDateTimeOffset().ToString("G");
}
Output:
TZ at instant
+01
User time zone value
04.08.2014 12:09:24
The time zone at the point of that instant is thus UTC+1, which means that the value you gave the code gives you the correct value back, 12:09.
To get 16:09, you would need UTC+5, and Europe/London has never been that value.
So 12:09:24 is correct, your assumptions that it should be 16:09:24 is wrong.
You need to go back to the code and/or sites you used to get the "correct" value, the source of your confusion is there, not in this code.
If I run:
// 7:10 am at a location which has a +2 offset from UTC
string timeString = "2011-06-15T07:10:25.894+02:00";
DateTime time = DateTime.Parse(timeString);
It gives me time = 6/14/2011 10:10:25 PM. This is the local time where I am at (Pacific time i.e. UTC -7).
Is there an elegant way of getting the local time at the origin i.e. 6/15/2011 07:10:25 AM?
You can use TimeZoneInfo:
DateTime localTime = DateTime.Now;
TimeZoneInfo targetTimeZone =
TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime targetTime = TimeZoneInfo.ConvertTime(localTime, targetTimeZone);
Actually, the ConvertTimeBySystemTimeZoneId method would be even more succinct:
DateTime targetTime =
TimeZoneInfo.ConvertTimeBySystemTimeZoneId(localTime, "Eastern Standard Time");
You can get information for time zones available using TimeZoneInfo.GetSystemTimeZones().
The DateTimeOffset structure seems to be built to specifically handle timezones. It includes most of the functionality of the DateTime type.
string timeString = "2011-06-15T07:10:25.894+02:00";
DateTimeOffset time = DateTimeOffset.Parse(timeString);
As this article illustrates, you should DateTimeOffset instead of DateTime whenever you need to unambiguously identify a single point in time.
Lock into using TimeZoneInfo - http://msdn.microsoft.com/en-us/library/system.timezoneinfo.aspx to do conversions. FindSystemTimeZoneById and ConvertTimeFromUtc should be enough. You may need to convert your local DateTime to UTC first with DateTime.ToUniversalTime.
You can format the way DateTime is Parse.
For example, if I want the DateTime to be format in french Canadian format :
IFormatProvider culture = new CultureInfo("fr-CA", true);
DateTime dt = DateTime.ParseExact(dateString, "dd-MM-yyyy", culture);
You can do it the same way for a en-US culture and add the time format to specify the format you want ...
I'm facing an issue while converting dates between my server and client where both is running in Germany. The Regional settings on the client machines could be set to both UK or Germany.I recieve a date from the server which is CET format, and I need to represent this time on UI as UK time. For example a time recieved from server like say, 01/07/2010 01:00:00 should be represented on the UI as 01/07/2010 00:00:00. I have written a converter for this purpose, however while running it 'am getting a time difference of 2 hours.Below is the code, please can you help?
public class LocalToGmtConverter : IDateConverter
{
private readonly TimeZoneInfo timeZoneInfo;
public LocalToGmtConverter()
: this(TimeZoneInfo.Local)
{
}
public LocalToGmtConverter(TimeZoneInfo timeZoneInfo)
{
this.timeZoneInfo = timeZoneInfo;
}
public DateTime Convert(DateTime localDate)
{
var utcKind = DateTime.SpecifyKind(localDate, DateTimeKind.Utc);
return utcKind;
}
public DateTime ConvertBack(object fromServer)
{
DateTime serverDate = (DateTime)fromServer;
var utcOffset = timeZoneInfo.GetUtcOffset(serverDate);
var uiTime = serverDate- utcOffset;
return uiTime;
}
}
I think you're converting to UTC (instead of UK) time. Since there is still summer time in Central Europe (event if the temperatures say otherwise), the difference is +2 hours until October, 31st.
If you know that you're converting from Germany to UK (i.e. CEST to BST in summer and CET to GMT in winter), why you don't just subtract 1 hour?
If you want the time zone information for UK, you can construct it using
var britishZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
Then you could convert the date using
var newDate = TimeZoneInfo.ConvertTime(serverDate, TimeZoneInfo.Local, britishZone);
This is what I do:
var BritishZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
DateTime dt = DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Unspecified);
DateTime DateTimeInBritishLocal = TimeZoneInfo.ConvertTime(dt, TimeZoneInfo.Utc, BritishZone);
I needed to add the SpecifyKind part or the ConvertTime throws an exception
Use TimeZoneInfo.ConvertTime to convert original input timezone (CET) to target timezone (UK).
public static DateTime ConvertTime(
DateTime dateTime,
TimeZoneInfo sourceTimeZone,
TimeZoneInfo destinationTimeZone
)
Full guidance on MSDN here:
Converting Times Between Time Zones
Modified code sample from MSDN:
DateTime ceTime = new DateTime(2007, 02, 01, 08, 00, 00);
try
{
TimeZoneInfo ceZone = TimeZoneInfo.FindSystemTimeZoneById("Central Europe Standard Time");
TimeZoneInfo gmtZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
Console.WriteLine("{0} {1} is {2} GMT time.",
ceTime,
ceZone.IsDaylightSavingTime(ceTime) ? ceZone.DaylightName : ceZone.StandardName,
TimeZoneInfo.ConvertTime(ceTime, ceZone, gmtZone));
}
catch (TimeZoneNotFoundException)
{
Console.WriteLine("The registry does not define the required timezones.");
}
catch (InvalidTimeZoneException)
{
Console.WriteLine("Registry data on the required timezones has been corrupted.");
}
The better approach to deal with local times is store them in unified representation such as UTC.
So you can convert all input times to UTC (via .ToUniversalTime()), and store (or transmit) its value. When you need to show it just convert back by using .ToLocalTime().
So you avoid rquirements to know which time zone was original value and can easily show stored value in different timezones.
Also you can avoid incoming troubles where you have to write specific logic for processing time in next timezone trying to figure out how to convert them amongs all available.
I realize the question is for C#, but if all you want to do is a single conversion you can do this from the PowerShell command line:
$d = [DateTime]::Parse("04/02/2014 17:00:00")
$gmt = [TimeZoneInfo]::FindSystemTimeZoneById("GMT Standard Time");
[TimeZoneInfo]::ConvertTime($d, $gmt, [TimeZoneInfo]::Local)
This script would convert 17:00 UK time into your local time zone.
For me, that would be CST. It's interesting to note that if I had set the date to 03/27/2014, the result would be different because the UK daylight saving time kicks in on different dates that the US.