C# DateTimeOffset to DateTime Conversion - c#

Can somebody please explain to me why the following code returns DateTimeKind.Unspecified. I was expecting DateTimeKind.Utc:
DateTimeOffset dateTimeOffset = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc);
DateTimeKind dateTimeKind = dateTimeOffset.DateTime.Kind;

Implicit cast operator from DateTime to DateTimeOffset behaves as DateTimeOffset constructor. You can have a look at sources for details
// Constructs a DateTimeOffset from a DateTime. For Local and Unspecified kinds,
// extracts the local offset. For UTC, creates a UTC instance with a zero offset.
public DateTimeOffset(DateTime dateTime) {
TimeSpan offset;
if (dateTime.Kind != DateTimeKind.Utc) {
// Local and Unspecified are both treated as Local
offset = TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime);
}
else {
offset = new TimeSpan(0);
}
m_offsetMinutes = ValidateOffset(offset);
m_dateTime = ValidateDate(dateTime, offset);
}
And ValidateDate() returns Unspecified kind
...
// make sure the Kind is set to Unspecified
//
return new DateTime(utcTicks, DateTimeKind.Unspecified);
The important point here is also described in remarks section of DateTimeOffset constructor
This constructor's behavior depends on the value of the DateTime.Kind
property of the dateTime parameter:
If the value of DateTime.Kind is DateTimeKind.Utc, the DateTime property
of the new instance is set equal to dateTime, and the Offset property is set
equal to Zero.
If the value of DateTime.Kind is DateTimeKind.Local or
DateTimeKind.Unspecified, the DateTime property of the new instance is
set equal to dateTime, and the Offset property is set equal to the
offset of the local system's current time zone.
You are checking the input datetime Kind and extract Offset value based on it. The DateTime property of resulting DateTimeOffset is Unspecified, because Offset property already represented a date offset. DateTime property of DateTimeOffset is also saying that
The value of the DateTime.Kind property of the returned DateTime
object is DateTimeKind.Unspecified.

Related

Strange error with converting from UTC to TimeZone

I have a problem with converting from UTC time to the respective local time.
What I have is UTC Time and Location Time Zone in string (Like "Eastern Standard Time").
My code:
private static DateTime? LocalTimeConvert(string locationTimeZone, DateTime? dateTimeUtc)
{
var dateTimeUnspec = DateTime.SpecifyKind(dateTimeUtc.Value, DateTimeKind.Unspecified);
DateTime utcDateTime = TimeZoneInfo.ConvertTime(dateTimeUnspec, TimeZoneInfo.FindSystemTimeZoneById(locationTimeZone));
return utcDateTime;
}
public static T LocalTime<T>(T value, string locationTimeZone)
{
if (value.GetType().IsGenericType && value.GetType().GetGenericTypeDefinition() == typeof(List<>))
{
IList collection = (IList)value;
foreach (var element in collection)
{
VariableConvertion(element, locationTimeZone);
}
}
else
{
VariableConvertion(value, locationTimeZone);
}
return value;
}
private static T VariableConvertion<T>(T value, string locationTimeZone)
{
PropertyInfo[] props = value.GetType().GetProperties();
foreach (var property in props)
{
if (property.PropertyType == typeof(DateTime?) || property.PropertyType == typeof(DateTime))
{
if (property.GetValue(value) != null)
{
var localTime = LocalTimeConvert(locationTimeZone, DateTime.Parse(property.GetValue(value).ToString()));
property.SetValue(value, localTime);
}
}
}
return value;
}
The assumptions were that any value that enters the method is to search for variables that are date and change them with the appropriate local time.
Unfortunately, I have a strange bug where it converts correctly at one time and wrong at the other.
Data from LocalTimeConvert method:
Eastern Standard Time - correct
System.TimeZoneInfo.FindSystemTimeZoneById returned {(UTC-05:00) Stany Zjednoczone i Kanada (czas wschodni)} System.TimeZoneInfo
locationTimeZone "Eastern Standard Time" string
dateTimeUtc {19.03.2021 15:43:07} System.DateTime?
dateTimeUnspec {19.03.2021 15:43:07} System.DateTime
utcDateTime {19.03.2021 10:43:07} System.DateTime
Central European Standard Time - incorrect
System.TimeZoneInfo.FindSystemTimeZoneById returned {(UTC+01:00) Sarajewo, Skopie, Warszawa, Zagrzeb} System.TimeZoneInfo
locationTimeZone "Central European Standard Time" string
dateTimeUtc {22.03.2021 12:58:09} System.DateTime?
dateTimeUnspec {22.03.2021 12:58:09} System.DateTime
utcDateTime {22.03.2021 12:58:09} System.DateTime
I am also wondering why I get different time on the server in the USA and different time on the server in Poland. I use pipe in angular for date: 'short': 'UTC' so I supposed to get the exact date from the database without local convertion right? Or the DateTime in C# is also converting the date to server date?
Refer to the documentation of the TimeZoneInfo.ConvertTime(DateTime, TimeZoneInfo) method.
In the Remarks section, it says that if the Kind property of the source DateTime is DateTimeKind.Unspecified, that it is assumed to be local. Since you explicitly set the kind to Unspecified in your code, you are not converting from UTC to the named time zone, but you are instead converting from local time to the named time zone.
In this context, "local time" means local to the time zone setting of the computer where the code is executing. So if your server in Poland is set for Poland's time zone, then the conversion to Poland's time will be a no-op.
If your intention is to convert UTC to a specific time zone, then either the source value's Kind should be DateTimeKind.Utc, or you should use ConvertTimeFromUtc instead of ConvertTime (the difference being, Unspecified kind is assumed to be Utc rather than Local).
As an aside - you've got a potential bug with DateTime.Parse(property.GetValue(value).ToString(). Don't ever create a string from an object just to parse it back to an object again. Instead, cast the object to unbox it to the desired type. The tostring/parse approach often is slower and often introduces bugs related to date formats of the current culture.

Preserve DateTimeOffset in Date String after UTC to TimeZoneInfo Date Conversion

Given the following UTC date string: "2020-10-10T12:00:00"
When converting to string, why do I loose the datetime offset, despite specifying it in the format?
string parseString = "2020-10-10T12:00:00";
if (DateTime.TryParseExact(parseString, "s", null, DateTimeStyles.None, out DateTime parsedDate))
{
TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific/Honolulu");
DateTime atUtc = DateTime.SpecifyKind(parsedDate, DateTimeKind.Utc);
DateTimeOffset dateAtTimeZone = TimeZoneInfo.ConvertTime(atUtc, timeZoneInfo);
Console.WriteLine(dateAtTimeZone.ToString("O"));
Console.WriteLine(dateAtTimeZone.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK"));
}
Why do I get back the following: 2020-10-10T02:00:00.0000000+13:00
Instead of: 2020-10-10T02:00:00.0000000-10:00
There are two TimeZoneInfo.ConvertTime that takes 2 parameters - one that takes a DateTime and returns a DateTime, and one that takes an DateTimeOffset and returns a DateTimeOffset. Right now, you are calling the one that takes a DateTime:
DateTime atUtc = DateTime.SpecifyKind(parsedDate, DateTimeKind.Utc);
DateTimeOffset dateAtTimeZone = TimeZoneInfo.ConvertTime(atUtc, timeZoneInfo); // atUtc is a DateTime here!
The fact that you are assigning the result to DateTimeOffset doesn't matter, because the return value type does not participate in overload resolution.
The overload that takes a DateTime just changes the date and time parts of the DateTime passed in, since timezone/offset information is not a part of DateTimes. When you convert it to DateTimeOffset, your local offset is added to it during the conversion, and that is what gets printed.
What you should be calling is the overload that takes an DateTimeOffset. Because it takes a DateTimeOffset, it is able to change the offset part of the DateTimeOffset as well as the date and time parts, to the desired values. You need to declare atUtc as DateTimeOffset too:
DateTimeOffset atUtc = DateTime.SpecifyKind(parsedDate, DateTimeKind.Utc);
DateTimeOffset dateAtTimeZone = TimeZoneInfo.ConvertTime(atUtc, timeZoneInfo);
Or better, use DateTimeOffset.TryParseExact in the first place!

Why UtcDateTime function is not adding the offset to the UTC date?

For instantaneous DateTime tracking, I am using a DateTimeOffset datatype. The following function adds the user corresponding TimeZone ID offset to the UTC DateTime property of DateTimeOffset
According to the documentation, UtcDateTime will perform both a time zone conversion and a type conversion on a DateTimeOffset. The following code does not though. Why is the conversion not taking place?
Function to add TimeSpan offset,
public static DateTimeOffset GetUtcDateTime (DateTime sourceDateTime, string timeZoneId) {
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById (timeZoneId);
TimeSpan offset = timeZone.GetUtcOffset (sourceDateTime);
DateTimeOffset utcTime = new DateTimeOffset (sourceDateTime, offset);
return utcTime;
}
and here where I am trying to convert,
DateTimeOffset utcDate = (DateTime.UtcNow);
DateTime fromUtc = utcDate.DateTime;
DateTimeOffset UtcDate = StaticHandlers.GetUtcDateTime (fromUtc, "America/Los_Angeles");
Console.WriteLine ("UTC now is {0} and UTC Date LA is {1} and UtcDateTime LA is {2}", utcDate, UtcDate, utcDate.UtcDateTime);
the output is,
UTC now is 5/8/18 6:43:37 AM +00:00 and and UTC Date LA is 5/8/18
6:43:37 AM -07:00 UtcDateTime LA is 5/8/18 6:43:37 AM
update,
I want to preserve both UTC and the user offset for tracking purposes. DST matters in this context. The example below shows what I am talking about.
DateTime currentDateTime = DateTime.Now;
DateTime beforeDST_LA = new DateTime (2018, 3, 11, 0, 0, 0);
DateTime afterDST_LA = new DateTime (2018, 3, 12, 0, 0, 0);
TimeSpan offsetCurrent = tzi.GetUtcOffset (currentDateTime);
TimeSpan offsetBeforeDST = tzi.GetUtcOffset (beforeDST_LA);
TimeSpan offsetAfterDST = tzi.GetUtcOffset (afterDST_LA);
Console.WriteLine ("Current offset is {0} before DST is {1} and After DST is {2}", offsetCurrent, offsetBeforeDST, offsetAfterDST);
Current offset is -07:00:00 before DST is -08:00:00 and After DST is
-07:00:00
First, I would not call your function GetUtcDateTime, because that's not what it does. It is trying to get a DateTimeOffset for a specific time zone for a specific time, so call it something like GetDateTimeOffset.
The main concept you're missing in your code is that DateTime has .Kind property, which sets a DateTimeKind value. The kind is taken into consideration by several places in your code:
GetUtcOffset will convert Utc or Local kinds to the zone provided before determining the offset.
new DateTimeOffset (the constructor) will error if the kind and the offset conflict, if you provide an offset.
When you assign a DateTime to a DateTimeOffset, the implicit conversion is evaluating the kind.
When you call .DateTime from the DateTimeOffset, the kind will always be Unspecified - regardless of the offset.
If you take all of this into account, you'll realize you need to check the kind yourself before calling GetUtcOffset. If it's not Unspecified then you'll need to convert it to the specified time zone before getting the offset.
public static DateTimeOffset GetDateTimeOffset(DateTime sourceDateTime, string timeZoneId)
{
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
// here, is where you need to convert
if (sourceDateTime.Kind != DateTimeKind.Unspecified)
sourceDateTime = TimeZoneInfo.ConvertTime(sourceDateTime, timeZone);
TimeSpan offset = timeZone.GetUtcOffset(sourceDateTime);
return new DateTimeOffset(sourceDateTime, offset);
}
Now that this is handled, turn to the next set of problems, which is where you call it.
DateTimeOffset utcDate = (DateTime.UtcNow);
DateTime fromUtc = utcDate.DateTime;
In line 1, the implicit cast from DateTime to DateTimeOffset sets the offset to 00:00 - because DateTime.UtcNow has .Kind == DateTimeKind.Utc.
In line 2, the call to the .DateTime property sets fromUtc.Kind == DateTimeKind.Unspecified. Essentially, you've stripped away the kind.
So instead of this, just pass DateTime.UtcNow directly into the function. The kind will persist, and it will all work - now that the Kind is recognized and the conversion is happening inside the function.
All that said, if your original values are all DateTimeOffset (example, DateTimeOffset.UtcNow) then you don't need that function at all. Just call TimeZoneInfo.ConvertTime with the DateTimeOffset directly.

Why is the DateTimeOffset.AddMonths(1).Date not the same as ((dto) => dto.AddMonths(1).Date)

I have a helper function in domain logic:
private static DateTimeOffset CalculateNextDate(DateTimeOffset previousDate, TimeUnit timeUnit, int quantity)
{
Func<int, DateTimeOffset, DateTimeOffset> calcMethod;
switch (timeUnit)
{
case TimeUnit.Month:
calcMethod = (i, offset) => offset.AddMonths(i);
break;
// ... [irrelevant] ...
default:
// ... [irrelevant] ...
}
return calcMethod(quantity, previousDate).Date;
}
When my unittest tried to validate a month for example:
// Arrange
int monthsToAdd = 1;
var timeUnit = TimeUnit.Month;
var sut = new TimeInterval(timeUnit, monthsToAdd);
var now = DateTimeOffset.Now;
// Act
var result = sut.NextDate(now);
// Assert
var expectedDate = now.AddMonths(1).Date;
Assert.AreEqual(expectedDate, result); // FAILS
The assert fails because the expectedDate is {26/04/2017 0:00:00} while the result is {26/04/2017 0:00:00 +02:00}
I don't understand why one can keep the offset while the other ditches it. Is it because I cast the .Date to a DateTimeOffset? Can't find the explanation on msdn...
Take a look at the DateTimeOffset.Date page on MSDN:
This property removes any significant part of the time component from a DateTimeOffset object and returns only its significant date component. For example, if the DateTimeOffset object has a date and time value of "1/12/07 4:01pm +7:30", the property returns a DateTime value of "1/12/07 12:00:00 AM". The DateTime value can then be displayed by using any of the standard or custom format specifiers that display dates only. (See the Example section for an illustration.)
The value of the DateTime.Kind property of the returned DateTime object is always DateTimeKind.Unspecified. It is not affected by the value of the Offset property.
See also the DateTimeOffset Constructor (DateTime) page on MSDN:
This constructor's behavior depends on the value of the DateTime.Kind property of the dateTime parameter:
If the value of DateTime.Kind is DateTimeKind.Utc, the DateTime property of the new instance is set equal to dateTime, and the Offset property is set equal to Zero.
If the value of DateTime.Kind is DateTimeKind.Local or DateTimeKind.Unspecified, the DateTime property of the new instance is set equal to dateTime, and the Offset property is set equal to the offset of the local system's current time zone.
Thus, by converting to DateTime using the Date property, you remove the time component, but you also remove the offset and Kind.
When you converted from DateTimeOffset to DateTime then implicitly cast back to DateTimeOffset, it assumes that this is a time at the current timezone.

Converting UTC DateTime to EST DateTime with correct offset

My app gets a DateTime object in UTC and needs to output as a string it in EST format. I've tried the following code, but when I get the output the offset still shows as +00:00 instead of -05:00
static void Main(string[] args)
{
var currentDate = DateTime.Now.ToUniversalTime();
var convertedTime = ConvertUtcToEasternStandard(currentDate);
Console.WriteLine(currentDate.ToString("yyyy-MM-ddTHH:mm:sszzz"));
Console.WriteLine(convertedTime.ToString("yyyy-MM-ddTHH:mm:sszzz"));
}
private static DateTime ConvertUtcToEasternStandard(DateTime dateTime)
{
var easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
return TimeZoneInfo.ConvertTimeFromUtc(dateTime, easternZone);
}
This outputs:
2016-11-18T06:56:14+00:00
2016-11-18T01:56:14+00:00
So the time is being shifted correctly, but the offset stays at +00:00 when I want it to be -05:00. Any idea how I can get a DateTime object with the correct offset when output with the above format string?
I read this in the API not long ago, basically, with DateTime values, it's very much impossible to get the zzz format offset to be something useful.
With DateTime values, the "zzz" custom format specifier represents the signed offset of the local operating system's time zone from UTC, measured in hours and minutes. It does not reflect the value of an instance's DateTime.Kind property. For this reason, the "zzz" format specifier is not recommended for use with DateTime values.
With DateTimeOffset values, this format specifier represents the DateTimeOffset value's offset from UTC in hours and minutes.
The offset is always displayed with a leading sign. A plus sign (+) indicates hours ahead of UTC, and a minus sign (-) indicates hours behind UTC. A single-digit offset is formatted with a leading zero.
For example, I'm in Eastern Standard Time, and this is my result:
2016-11-18T07:9:38-05:00
2016-11-18T02:9:38-05:00
Obviously UTC time shouldn't have an offset of -05:00.
Modify your code just a bit and we have a solution:
void Main()
{
var currentDate = DateTimeOffset.Now.ToUniversalTime();
var convertedTime = ConvertUtcToEasternStandard(currentDate);
var format = "yyyy-MM-ddTHH:m:sszzz";
Console.WriteLine(currentDate.ToString(format));
Console.WriteLine(convertedTime.ToString(format));
}
// Define other methods and classes here
private static DateTimeOffset ConvertUtcToEasternStandard(DateTimeOffset dateTime)
{
var easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
return TimeZoneInfo.ConvertTime(dateTime, easternZone);
}
Result:
2016-11-18T07:17:46+00:00
2016-11-18T02:17:46-05:00
That's more like it.
Note: replaced previous code, ignorance got the best of me and I failed to realize that it wasn't working right. TimeZoneInfo.ConvertTime takes a DateTimeOffset which does what we want.
And if we add another case for Pacific Standard Time:
private static DateTimeOffset ConvertUtcToPacificStandard(DateTimeOffset dateTime)
{
var pacificZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
return TimeZoneInfo.ConvertTime(dateTime, pacificZone);
}
We get the correct result:
2016-11-17T23:21:21-08:00

Categories

Resources