Уловка: большинство решений, которые я нашел, уже берут объект DateTime и преобразуют его, но я хочу СОЗДАТЬ его В КОНКРЕТНОМ часовом поясе, зная часовой пояс.
Так что это не DateTimeKind.Utc и не DateTimeKind.Local, это будет DateTime в часовом поясе.
Базовые данные:
1. string From = "09:00" //local time because summertime/wintertime
2. string Till = "17:00" //local time because summertime/wintertime
3. string TimeZoneResolved = "Europe/Vienna"
Неявно у меня есть:
TimeZoneInfo timeZoneInfo = TZConvert.GetTimeZoneInfo(TimeZoneResolved);
TimeSpan workHoursStart = TimeSpan.Parse(From);
TimeSpan workHoursEnd = TimeSpan.Parse(Till);
Чего я хочу достичь:
//reconstruct today 9am in that country of timeZoneInfo
var now = DateTime.UtcNow;
var startTime = new DateTime(now.Year, now.Month, now.Day, workHoursStart.Hour, workHoursStart.Minute, 0, 0, timeZoneInfo);
-> недопустимо, поскольку параметр TimeZoneInfo недействителен. Ожидает DateTimeKind
Потому что перегрузка конструктора может быть сложной, может быть, так
var now = DateTime.UtcNow;
var officeHoursStart = new DateTime().BasedOnTz(timeZoneInfo, now.Year, now.Month, now.Day, workHoursStart.Hour, workHoursStart.Minute, 0, 0);
var officeHoursStartUtc = officeHoursStart.ToUtc();
Чтобы переформулировать проблему: у вас есть входные данные о времени и часовом поясе, и вам нужно на выходе указать момент времени, который соответствует этому времени текущего дня в данном часовом поясе.
Поскольку вывод представляет собой момент времени, вам следует использовать DateTimeOffset
. (Если вы должны использовать DateTime
, вы можете взять из него свойство .DateTime
.)
Поскольку вводом является время суток, лучше использовать TimeOnly
, доступный в .NET 6 и более поздних версиях. (Если вы используете более раннюю версию .NET, используйте TimeSpan
.)
Вот общий подход:
using System;
using System.Globalization;
// Get the time zone as a TimeZoneInfo object, either directly or via TimeZoneConverter.
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById("Eurpope/Vienna");
// Parse the time string. Prefer using TimeOnly (.NET 6+).
string timeString = "17:00";
TimeOnly time = TimeOnly.ParseExact(timeString, "HH:mm", CultureInfo.InvariantCulture);
// Or use TimeSpan on older .NET
// TimeSpan timeSpan = TimeSpan.ParseExact(timeString, "hh\\:mm", CultureInfo.InvariantCulture);
// Get the time on "today" with respect to the given time zone
DateTimeOffset timeTodayInTimeZone = time.OnTodayInTimeZone(timeZone);
Вам понадобятся некоторые методы расширения. Выберите один из них, в зависимости от того, какой ввод вы используете.
public static DateTimeOffset OnTodayInTimeZone(this TimeOnly time, TimeZoneInfo tz) =>
DateOnly.FromDateTime(TimeZoneInfo.ConvertTime(DateTime.UtcNow, tz))
.ToDateTime(time)
.ToDateTimeOffset(tz);
public static DateTimeOffset OnTodayInTimeZone(this TimeSpan time, TimeZoneInfo tz) =>
TimeZoneInfo.ConvertTime(DateTime.UtcNow, tz).Date
.Add(time)
.ToDateTimeOffset(tz);
И, наконец, вам нужен этот метод расширения, в котором содержится основная часть логики. (Я использовал это на нескольких других ответов сейчас.)
public static DateTimeOffset ToDateTimeOffset(this DateTime dt, TimeZoneInfo tz)
{
if (dt.Kind != DateTimeKind.Unspecified)
{
// Handle UTC or Local kinds (regular and hidden 4th kind)
DateTimeOffset dto = new DateTimeOffset(dt.ToUniversalTime(), TimeSpan.Zero);
return TimeZoneInfo.ConvertTime(dto, tz);
}
if (tz.IsAmbiguousTime(dt))
{
// Prefer the daylight offset, because it comes first sequentially (1:30 ET becomes 1:30 EDT)
TimeSpan[] offsets = tz.GetAmbiguousTimeOffsets(dt);
TimeSpan offset = offsets[0] > offsets[1] ? offsets[0] : offsets[1];
return new DateTimeOffset(dt, offset);
}
if (tz.IsInvalidTime(dt))
{
// Advance by the gap, and return with the daylight offset (2:30 ET becomes 3:30 EDT)
TimeSpan[] offsets = { tz.GetUtcOffset(dt.AddDays(-1)), tz.GetUtcOffset(dt.AddDays(1)) };
TimeSpan gap = offsets[1] - offsets[0];
return new DateTimeOffset(dt.Add(gap), offsets[1]);
}
// Simple case
return new DateTimeOffset(dt, tz.GetUtcOffset(dt));
}
Извините, мне потребовалось некоторое время, чтобы вернуться к этой теме. Спас мою задницу. Большое спасибо!
Обратите внимание, что ваш псевдокод неверен, потому что дата UTC может не обязательно совпадать с датой, действующей для данного часового пояса.