Convert timestamp from any timezone to UTC, and optionally without DST - c#

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.

Related

How to calculate time zone UTC offset for specified date and time with NodaTime (given daylight)?

I have Iana time zone Id and need to know the time zone offset for some DateTime.
As I understand, the following function returns the offset for DateTime.Now (or no daylight offset?).
TimeSpan GetTimeZoneOffset(string timeZoneId) =>
new DateTimeZoneCache(TzdbDateTimeZoneSource.Default)
.GetZoneOrNull(timeZoneId)
.GetUtcOffset(SystemClock.Instance.GetCurrentInstant())
.ToTimeSpan();
But I need
TimeSpan GetTimeZoneOffset(string timeZoneId, DateTime utcInstant)
How to implement it?
Fundamentally, you need DateTimeZone.GetUtcOffset, which accepts an Instant.
If the DateTime value always has a Kind of Utc, you can use Instant.FromDateTimeUtc. If it might have a different Kind, you'll need to work out more detailed requirements.
Next you need an IDateTimeZoneProvider to map the time zone ID into a DateTimeZone. That might be DateTimeZoneProviders.Tzdb, or it might be one you've injected somewhere for testability.
Once you've got the Instant and the DateTimeZone, you can call GetUtcOffset to get an Offset. You can convert that back to a TimeSpan, but I'd actually encourage you to avoid using DateTime and TimeSpan as far as possible in your app - if you can use the Noda Time types everywhere within your codebase and only convert between those and the BCL types at boundaries (e.g. database access) you'll find you need to do a lot less of this work.
But if you really need a TimeSpan, the method would look like this:
TimeSpan GetTimeZoneOffset(string timeZoneId, DateTime dateTimeUtc)
{
Instant instant = Instant.FromDateTimeUtc(dateTimeUtc);
DateTimeZone zone = DateTimeZoneProviders.Tzdb[timeZoneId];
Offset offset = zone.GetUtcOffset(instant);
return offset.ToTimeSpan();
}
Note that the IDateTimeZoneProvider[string] indexer will throw an exception if the time zone isn't found in that provider. If you want to handle this a different way, use IDateTimeZoneProvider.GetZoneOrNull() and check whether the result is null or not.
TimeSpan GetTimeZoneOffset(string timeZoneId, DateTime date) =>
new DateTimeZoneCache(TzdbDateTimeZoneSource.Default)
.GetZoneOrNull(timeZoneId)
.GetUtcOffset(Instant.FromDateTimeUtc(utcDay))
.ToTimeSpan();

Correct way to convert datetime timezone for display

We are developing a website and currently, the timezone of the website and database is in German time zone (European standard time zone). But the application is being accessed from the US also. There is a screen in the application which contains a DateTime field called ValidFrom and the time we are storing is UTC time. currently, users are not selecting the time so we are using .NET built-in DateTime.UTCNow to store DateTime value in the database. But the problem is while displaying, we need to display it according to User timezone. So after googling for many hours, we found two solutions one using moment and another approach is using DateTime.SpecifyKind. We tried using moment.js but it converted the date time to local time once again. So we ended up using DateTime.SpecifyKind as below.
[DataMember]
private DateTime _validFrom;
public DateTime ValidFrom
{
get { return _validFrom; }
set { _validFrom = DateTime.SpecifyKind(value, DateTimeKind.Utc); }
}
And now the values are displayed according to timezone. But my doubt is, is it the correct approach to handle timezone display or any other better solution exist?
I'd use DateTimeOffset instead, something like:
var utc = DateTimeOffset.UtcNow;
var tz = TimeZoneInfo.FindSystemTimeZoneById("Your Specific Time Zone Id");
var zonedDateTime = TimeZoneInfo.ConvertTime(utc, tz);
Save the UTC and user's time zone in the database, and convert UTC to specific time zone any time you want to show it to your users. I also suggest you take a look at NodaTime if you want to do anything serious with date and time. The built-in DateTime in .Net is misleading.
I wrote an extension method for this:
public static DateTime ConvertFromUTC(this DateTime date, TimeZoneInfo destZone)
{
var utcZone = TimeZoneInfo.FindSystemTimeZoneById("UTC");
return TimeZoneInfo.ConvertTime(date, utcZone, destZone);
}
However, if you plan to use something like this you need to be aware of daylight saving time. The result may be off if the conversion crosses a DST change in either timezone. So it isn't really suitable if you need absolute precision, which depends on the website, e.g.: is it a blog or a stock trading app?

How to combine date time and Timezone offset in an ASP.net application?

My web service call to a third party applications returns the date,time zone and timezone_offset values. I need to add this to a calendar in Asp.net application. What is the best way to combine this together so that my date object understands that its from Eastern time zone?
<start_date>2014-11-17 19:00:00</start_date>
<timezone>America/New_York</timezone>
<timezone_offset>GMT-0500</timezone_offset>
Since you have the offset too, you can use DateTimeOffset.Parse() to get the DateTimeOffset. From there, you can read the DateTime property. The output dt variable will have 2014-11-17 7:00:00 PM with DateTimeKind property set to "Unspecified"
var dtOffset = DateTimeOffset.Parse("2014-11-17 19:00:00-0500", CultureInfo.InvariantCulture);
var dt = dtOffset.DateTime;
A DateTimeOffset represents a point in time. Usually, its relative to UTC. So, it is a natural structure to initially parse the fields that you have.
If you want a reference to the same datetime in UTC, you can use this. Here the output dt variable will have 2014-11-18 12:00:00 AM with DateTimeKind property set to "Utc"
var dt = DateTime.Parse("2014-11-17 19:00:00-0500", CultureInfo.InvariantCulture).ToUniversalTime();
If you don't have the offset but just have the timeZoneId, you can still do it but you need NodaTime for that.
I'll focus on this part of the question:
What is the best way to combine this together so that my date object understands that its from Eastern time zone?
There is no data type built in to .NET that can sufficiently do that. The DateTimeOffset type associates with a particular offset, such as the -05:00 or -04:00 that might be in use by the Eastern time zone (depending on the date). But those same offsets might also be sourced from some other time zone entirely. A time zone has one or more offsets, but an offset isn't a time zone itself.
Fortunately there are solutions. There are two options to consider:
You could pair a DateTimeOffset with a TimeZoneInfo object. When storing or transmitting these, you would only send the full DateTimeOffset along with the Id of the time zone.
The Noda Time library is a much more effective way to work with date and time, especially when it comes to time zone concerns. It contains a type called ZonedDateTime that already combines a date, time, offset, and time zone. It can also be used to work with IANA time zone identifiers, such as the "America/New_York" time zone you specified in the question.

Does ConvertTimeFromUtc() and ToUniversalTime() handle DST?

If daylight saving time is in effect, and a date object has been saved into the database (UTC format) which you retrieve to show it in the view (for example the view in asp.net-mvc).
And you do that by using this method:
public static DateTime ConvertToLocalTimeFromUtcTime(DateTime utcDate, string timeZoneId)
{
TimeZoneInfo localZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
DateTime localTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, localZone);
if (localZone.IsDaylightSavingTime(localTime))
localTime = localTime.AddHours(1); // is this needed !?
return localTime;
}
The question is, does TimeZoneInfo.ConvertTimeFromUtc() handle DST's or do you have to check that yourself and either add or subtract X hour(s) to the date object?
Same question for when persisting a date object to the database by converting it to UTC format with ToUniversalTime().
Yes. ConvertTimeFromUtc will automatically handle daylight saving time adjustments, as long as the time zone that you are targeting uses daylight saving time.
From the MSDN documentation:
When performing the conversion, the ConvertTimeFromUtc method applies any adjustment rules in effect in the destinationTimeZone time zone.
You should not try to add an additional hour in your conversion. That will give you an incorrect translation.
Regarding DateTime.ToUniversalTime, it does take DST into account, but be careful with this method. It assumes that the input value is in the computer's local time zone. If you just need to mark it with DateTimeKind.Utc, then use DateTime.SpecifyKind instead.

Converting UK times (both BST and GMT) represented as strings to UTC (in C#)

I have to use some dates and times from a legacy database. They are represented as strings. Dates are dd/MM/yy. Times are HH:mm.
I'd like to convert these to UTC as soon as I pull them from the database. I'm working on US systems, so need a common time.
The problem I'm facing is how to convert them to UTC DateTime values. I can do the parsing, etc. The real problem I have concerns the timezone.
I'm trying to use the following approach:
DateTime ukTime = // Parse the strings in a DateTime value.
TimeZoneInfo timeZoneInformation = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
DateTimeOffset utcTime = new DateTimeOffset(ukTime, timeZoneInformation.BaseUtcOffset);
However, this gives incorrect values if the date is in the British Summer Time period.
I can use "GMT Daylight Time" on those dates, but that requires me to know when the switchover is. I'm sure there must be a less laborious way.
As I'm not using a machine with UK time settings I can't rely on local time.
Basically, I need something like:
// Works for both GMT (UTC+0) and BST (UTC+1) regardless of the regional settings of the system it runs on.
DateTime ParseUkTimeAsUtcTime(string date, string time)
{
...
}
I've scoured the posts, but couldn't find anything that addressed this directly. Surely this is also an issue with EST, EDT, etc?
Try using the GetUtcOffset() method on your TimeZoneInfo instance, which takes "adjustment rules" into consideration.
Using this should work basically the same as your original example, but you'll use that method instead of the BaseUtcOffset property.
DateTime ukTime = // Parse the strings in a DateTime value.
TimeZoneInfo timeZoneInformation = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
DateTimeOffset utcTime = new DateTimeOffset(ukTime, timeZoneInformation.GetUtcOffset(ukTime));
How about:
DateTime.Parse(dateTimeString).ToUniversalTime();
Assuming that the database server stores its datetimes in the same timezone as your application server.

Categories

Resources