Перевести строку даты в определенный часовой пояс (с поддержкой перехода на летнее время)

#c# #timezone #datetimeoffset

#c# #Часовой пояс #datetimeoffset

Вопрос:

Хорошо, я очень усердно работал последние несколько недель и столкнулся с небольшой проблемой. Я не думаю, что мой разум сейчас вполне справляется с этой задачей 🙂 поэтому мне нужны несколько советов / помощи! это, вероятно, очень просто, но у меня в голове пока не щелкает.

Пользователи будут вводить дату и время в AEST. Также для приложения установлен часовой пояс «по умолчанию» (поскольку его, возможно, потребуется изменить), в настоящее время для него установлено значение «AUS Eastern Standard Time»

Итак, у нас есть пользовательская строка без часового пояса и с определенным системным часовым поясом на сервере в США (поэтому local не соответствует, и его нельзя изменить или использовать)

Теперь, что мне нужно, так это способ сказать «проанализируйте эту введенную пользователем строку, используя часовой пояс X». я не могу просто ввести 10 или 11 в качестве смещения, поскольку даты могут быть в переходе на летнее время или за его пределами; что да, изменяет его между 10 и 11 даже для того же часового пояса!

Текущее время AEST также может находиться в DST или вне его, поэтому я не могу просто преобразовать дату UTC в текущее время AEST и получить строку «zzz» и прикрепить ее, поскольку даты будут отклонены на час для всего, что введено вне текущей настройки DST.

На данный момент код фактически делает именно это:

 TimeZoneInfo ConvTo = TimeZoneInfo.FindSystemTimeZoneById(ConfigurationManager.AppSettings["DefaultTimeZone"]);
DateTimeOffset getDate = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, ConvTo);
string TimeZoneId = " "   getDate.ToString("zzz");
DateTimeOffset cvStartDate = DateTimeOffset.MinValue; DateTimeOffset.TryParse(StartDate   TimeZoneId, out cvStartDate);
  

Затем я проверяю, не является ли дата недействительной, проверяя, по-прежнему ли она == DateTimeOffset .Уменьшите значение или преобразуйте его в UTC и добавьте в базу данных, оно будет преобразовано обратно в AEST при отображении. Однако некоторые даты отклонены на час, а другие идеальны (как и ожидалось) 🙂

Какой самый элегантный способ решить эту проблему?

Редактировать:

Чтобы помочь объяснить проблему, я написал некоторый тестовый код в качестве тестового приложения Windows:

 // User entered date
string EnteredDate = "2011/01/01 10:00:00 AM";

// Get the timezone we want to use
TimeZoneInfo myTimeZone = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");

// Find the timezone string of the selected timezone to parse the user string
// This is the part that is incorrect and what i need help with.
DateTimeOffset getDate = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, myTimeZone);
string TimeZoneId = " "   getDate.ToString("zzz");

// Parse the string into the date object
DateTimeOffset cvEnteredDate = DateTimeOffset.MinValue; DateTimeOffset.TryParse(EnteredDate   TimeZoneId, out cvEnteredDate);

// Display
textBox1.Text  = "Parsed: "   cvEnteredDate.ToString("yyyy/MM/dd HH:mm:ss zzz")   Environment.NewLine;

// Convert to UTC and display
cvEnteredDate = cvEnteredDate.ToUniversalTime();
textBox1.Text  = "UTC: "   cvEnteredDate.ToString("yyyy/MM/dd HH:mm:ss zzz")   Environment.NewLine;

// Convert back to AEST and display
cvEnteredDate = TimeZoneInfo.ConvertTime(cvEnteredDate, myTimeZone);
textBox1.Text  = "Changed Back: "   cvEnteredDate.ToString("yyyy/MM/dd HH:mm:ss zzz")   Environment.NewLine;
  

Каков результат этого?

Проанализировано: 2011/01/01 10:00:00  10:00
UTC: 2011/01/01 00:00:00  00:00
Изменено обратно: 2011/01/01 11:00:00  11:00

Пожалуйста, обратите внимание, что час отклонен на единицу, а смещение отличается. Кроме того, что, если мы ПРОСТО изменим введенную дату на:

 string EnteredDate = "2011/04/20 10:00:00 AM";
  

we get:

Parsed: 2011/04/20 10:00:00  10:00
UTC: 2011/04/20 00:00:00  00:00
Changed Back: 2011/04/20 10:00:00  10:00

Which is perfectly good and fine, using the same code just a different entered date.

This happens because the current DST setting and the DST setting of the entered date are different, this is what i want a solution for 🙂

Think of it like the chicken and egg problem. I need the correct timezone data for the entered string before i parse it which i can only get after i’ve parsed the string (so will be an elaborate solution)

Or i need .NET to parse the string using the myTimeZone object so it knows what to set it to itself, but i can’t see any functions that do this, they all take a already parsed and set datetime or datetimeoffset object

So i’m looking for elegant solutions others might have done? I certainly can’t be the only one who has noticed this?

ПРАВКА2:

Хорошо, я создал «рабочую» функцию, которая, как мне кажется, решает проблему, вот пример (добавьте текстовое поле в приложение c # для Windows и используйте приведенный ниже код, чтобы проверить себя):

 private void Form1_Load(object sender, EventArgs e)
{
    TimeZoneInfo myTimeZone = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");

    DateTimeOffset get1Date = ReadStringWithTimeZone("2011/01/01 10:00:00 AM", myTimeZone);
    textBox1.Text  = "Read1: "   get1Date.ToString("yyyy/MM/dd HH:mm:ss zzz")   Environment.NewLine;
    get1Date = get1Date.ToUniversalTime();
    textBox1.Text  = "Read1 - UTC: "   get1Date.ToString("yyyy/MM/dd HH:mm:ss zzz")   Environment.NewLine;
    get1Date = TimeZoneInfo.ConvertTime(get1Date, myTimeZone);
    textBox1.Text  = "Changed Back: "   get1Date.ToString("yyyy/MM/dd HH:mm:ss zzz")   Environment.NewLine   Environment.NewLine;

    DateTimeOffset get2Date = ReadStringWithTimeZone("2011/04/20 10:00:00 AM", myTimeZone);
    textBox1.Text  = "Read2: "   get2Date.ToString("yyyy/MM/dd HH:mm:ss zzz")   Environment.NewLine;
    get2Date = get2Date.ToUniversalTime();
    textBox1.Text  = "Read2 - UTC: "   get2Date.ToString("yyyy/MM/dd HH:mm:ss zzz")   Environment.NewLine;
    get2Date = TimeZoneInfo.ConvertTime(get2Date, myTimeZone);
    textBox1.Text  = "Changed Back: "   get2Date.ToString("yyyy/MM/dd HH:mm:ss zzz")   Environment.NewLine   Environment.NewLine;
}

public DateTimeOffset ReadStringWithTimeZone(string EnteredDate, TimeZoneInfo tzi)
{
    DateTimeOffset cvUTCToTZI = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, tzi);
    DateTimeOffset cvParsedDate = DateTimeOffset.MinValue; DateTimeOffset.TryParse(EnteredDate   " "   cvUTCToTZI.ToString("zzz"), out cvParsedDate);
    if (tzi.SupportsDaylightSavingTime)
    {
        TimeSpan getDiff = tzi.GetUtcOffset(cvParsedDate);
        string MakeFinalOffset = (getDiff.Hours < 0 ? "-" : " ")   (getDiff.Hours > 9 ? "" : "0")   getDiff.Hours   ":"   (getDiff.Minutes > 9 ? "" : "0")   getDiff.Minutes;
        textBox1.Text  = "Diff: "   MakeFinalOffset   Environment.NewLine;
        DateTimeOffset.TryParse(EnteredDate   " "   MakeFinalOffset, out cvParsedDate);
        return cvParsedDate;
    }
    else
    {
        return cvParsedDate;
    }
}
  

И вывод:

Разница:  11:00
Прочитано1: 2011/01/01 10:00:00  11:00
Прочитано1 - UTC: 2010/12/31 23:00:00  00:00
Изменено обратно: 2011/01/01 10:00:00  11:00

Разница:  10:00
Прочитано2: 2011/04/20 10:00:00  10:00
Прочитано2 - UTC: 2011/04/20 00:00:00  00:00
Изменено обратно: 2011/04/20 10:00:00  10:00

Единственное, может возникнуть проблема, если введенная пользователем дата соответствует часу перехода на летнее время, она все еще может быть отключена на час, поскольку она просто считывает текущее смещение и использует это, затем проверяет, должно ли оно быть переходом на летнее время или нет, и если оно там, оно будет прочитано неправильно. Однако это намного лучше, чем то, что у меня есть сейчас.

Может ли кто-нибудь помочь мне с очисткой этой функции? Это лучший маршрут для того, что мне нужно? идеи?

Комментарии:

1. Это один из наиболее впечатляюще написанных вопросов, которые я когда-либо видел… Отличная работа.

2. Смещение AEST летом не меняется. Области, соблюдающие переход на летнее время, переключаются на AEDT.

Ответ №1:

Вот простое решение для предопределенного формата, оно также может быть динамическим. Я лично использую это для работы с javascript:

 public DateTimeOffset ParseDateExactForTimeZone(string dateTime, TimeZoneInfo timezone)
{
    var parsedDateLocal = DateTimeOffset.ParseExact(dateTime, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
    var tzOffset = timezone.GetUtcOffset(parsedDateLocal.DateTime);
    var parsedDateTimeZone = new DateTimeOffset(parsedDateLocal.DateTime, tzOffset);
    return parsedDateTimeZone;
}
  

Комментарии:

1. Спасибо! те же результаты, но в более сжатой версии (моя переработанная версия по-прежнему является дополнительной строкой по сравнению с вашей). По-прежнему происходит нечетное изменение часа при изменении часа: Read1: 2012/10/07 02:00:00 10:00 Прочитано1 — UTC: 2012/10/06 16:00:00 00:00 Изменено обратно: 2012/10/07 03:00:00 11:00 Но в остальном у меня все работает нормально.

2. @WhiteDragon Если вам нужно это сделать, то я бы взял время UTC, вернулся на нужное вам время, а затем снова преобразовал его в часовой пояс.

3. Похоже, это просто применяет смещение к дате-времени. Не возникло бы проблемы, если бы дата была во время перехода на летнее время?

4. @Fidel Нет, GetUtcOffset функция принимает datetime и ищет, какое смещение будет применяться в этом часовом поясе в это время и дату. А затем создает DateTimeOffset. GetUtcOffset Используется, чтобы избежать проблемы, о которой вы говорите. Но это очень серьезная проблема.

Ответ №2:

Кажется, это работает для меня, когда мне нужно проанализировать строку даты и времени из известного часового пояса в мой локальный. Не тестировал это во многих случаях, но до сих пор это отлично работало для разбора времени на сервере где-нибудь в ЕС.

TimeZoneInfo.ConvertTime (дата-время.Разбор("2012-05-25 23:17:15", CultureInfo.Создайте specificculture("en-EU")),TimeZoneInfo.Найдите systemtimezonebyid("W. Европейское стандартное время"), TimeZoneInfo.Local)

Комментарии:

1. Я совершенно забыл об этом маленьком вопросе! Ваше решение на самом деле очень близко к тому, что я использую сейчас. Однако мне нужно использовать объекты DateTimeOffset, и я никогда не смогу использовать локальное время сервера — Но в качестве примечания для других вы можете перевести текст непосредственно в часовой пояс с помощью объекта DateTimeOffset, используя тот же метод! 🙂

2. Я хотел бы добавить к моему комментарию выше (чтобы было более понятно), что это работает, только если сервер использует тот же часовой пояс, из которого вы хотите прочитать проанализированные пользователем данные. Использование приведенного выше на моем локальном компьютере работает (и на нашем текущем сервере), но перемещение кода в GoGrid (что-то вроде -9) завершается неудачей, если я не использую свою функцию, как показано ниже

Ответ №3:

Поскольку никто не предоставил никаких лучших решений (что очень удивительно!) я принимаю свою собственную функцию в качестве ответа, хотя я мог бы сделать функцию более краткой и переработать ее немного позже:

 public DateTimeOffset ReadStringWithTimeZone(string EnteredDate, TimeZoneInfo tzi)
{
    DateTimeOffset cvUTCToTZI = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, tzi);
    DateTimeOffset cvParsedDate = DateTimeOffset.MinValue;
    DateTimeOffset.TryParse(EnteredDate   " "   cvUTCToTZI.ToString("zzz"), out cvParsedDate);
    if (tzi.SupportsDaylightSavingTime)
    {
        TimeSpan getDiff = tzi.GetUtcOffset(cvParsedDate);
        string MakeFinalOffset = (getDiff.Hours < 0 ? "-" : " ")   (getDiff.Hours > 9 ? "" : "0")   getDiff.Hours   ":"   (getDiff.Minutes > 9 ? "" : "0")   getDiff.Minutes;
        DateTimeOffset.TryParse(EnteredDate   " "   MakeFinalOffset, out cvParsedDate);
        return cvParsedDate;
    }
    else
    {
        return cvParsedDate;
    }
}
  

Ответ №4:

TimeZoneInfo имеет статический метод ConvertTimeToUtc , который позволяет указать дату, время и часовой пояс. Это позволит выполнить корректировку времени за вас и вернуть дату UTC.

Документация MSDN является http://msdn.microsoft.com/en-us/library/bb495915.aspx , в комплекте с примером.

Комментарии:

1. Я думаю, вы пропустили то, что я говорю, эта функция принимает уже установленный объект datetime, я знаю, КАК конвертировать между часовыми поясами, но я спрашиваю, как решить проблему, которую я описал выше. строка -> datetimeoffset, компенсирующий часовой пояс

2. Могу ли я предложить тогда другой подход. Убедитесь, что вы сохраняете все даты как значения DateTime (даже в базе данных, а не строки), тогда преобразования datetime будут для вас полуавтоматическими. Или альтернативно преобразовать в UTC и сохранить все значения времени в UTC, затем, когда вам нужно их отобразить, преобразуйте их из UTC в соответствующий часовой пояс.

3. Я думаю, что вы все еще здесь, все даты являются DateTimeOffsets в базе данных и коде, которые уже сохранены как UTC. Синтаксический анализ выполняется на основе введенных ПОЛЬЗОВАТЕЛЕМ данных 🙂 и в этой части проблема. Проверьте 2 правки, новую функцию и код, чтобы точно понять, о чем я говорю (код говорит громче слов)

4. пользователь вводит начальную и конечную даты в администраторе, чтобы элемент заработал и завершился, эти даты должны быть введены как AEST, но сервер находится в США, поэтому localtime — это не то, что я мог бы использовать, даже если бы захотел. У нас действует строгое эмбарго с точностью до часа, поэтому при смене летнего времени введенные даты и время должны быть правильными и могут быть введены в будущем, что может быть совершенно другим смещением (но в том же часовом поясе!) Из-за перехода на летнее время

Ответ №5:

Самое простое решение: сначала преобразовать строку в локальное DateTime, использовать любой метод синтаксического анализа, затем вызвать

 new DateTimeOffset(dateTimeLocal.Ticks, timezone.BaseUtcOffset)