Корень вашей проблемы заключается в том, что вы не включили в расчет смещение местного часового пояса. Вы пытаетесь определить смещение TZ, хотя и некорректно, но оно фактически никогда не используется.
Причина, по которой эта попытка неверна, заключается в том, что вы извлекаете текущее время дважды, и текущее время может фактическиизменить между этими двумя вызовами. В среде .NET это маловероятно из-за относительно низкого разрешения свойств DateTime.Now
и .UtcNow
, но теоретически это может произойти, если вы перехватываете вызовы в нужный момент.
Конечноэта ошибка не имеет значения, потому что вычисленное значение никогда не используется.
ИМХО, другая большая проблема в обоих примерах кода состоит в том, что они недостаточно обобщены и недостаточно абстрагированы:
- Обобщенный: код вообще не может использоваться повторно, делая предположения о формате Base36, а также об эпохе для значения времени.
- Резюме: код объединяет всю реализацию в единую массупрограммные операторы, что делает очень трудным рассуждать об отдельных компонентах вычислений.
Еще одна серьезная ошибка в коде состоит в том, что он корректирует кодированное время по местному времени. Местное время предназначено только для потребления человеком . Это в некоторой степени связано с аспектом абстракции. Но главное в том, что при работе со значениями времени ваш код должен использовать UTC только для внутреннего использования. Местное время полезно только при взаимодействии с пользователем и даже тогда только для поддержки пользовательских сценариев, в которых вы хотите, чтобы пользователь специально работал в своем местном часовом поясе (иногда у вас есть пользователи по всему миру, и они координируют друг с другомваши значения времени, и имеет смысл заставить пользователей использовать UTC).
Этот последний момент также мешает остальным из нас задействовать ваш код, потому что вы находитесь в другом часовом поясеот остальных из нас (кажется, на час впереди UTC).
Вот пример того, как я мог бы подойти к проблеме. Главное, что я сломал обработку Base36 как отдельную от обработки значений базы данных. Я также ввел явный параметр смещения часового пояса и переключился на использование DateTimeOffset
вместо DateTime
, потому что это позволяет мне работать в вашем часовом поясе. :) В действительности, я бы использовал значения DateTimeOffset
, но использовал бы только UTC, то есть со смещением 0. Ненулевое смещение используется только в этом примере, а параметры смещения можно было бы опустить в производственном коде, есливы придерживаетесь UTC внутри.
Во-первых, класс кодера Base36:
static class Base36
{
public static string EncodeAsFixedWidth(long value, int totalWidth)
{
string base36Text = Encode(value);
return base36Text.PadLeft(totalWidth, '0');
}
public static string Encode(long value)
{
StringBuilder sb = new StringBuilder();
while (value >= 36)
{
int digit = (int)(value % 36);
char digitCharacter = _GetDigitCharacter(digit);
sb.Append(digitCharacter);
value = value / 36;
}
sb.Append(_GetDigitCharacter((int)value));
_Reverse(sb);
return sb.ToString();
}
public static long Decode(string base36Text)
{
long value = 0;
foreach (char ch in base36Text)
{
value = value * 36 + _GetBase36DigitValue(ch);
}
return value;
}
private static void _Reverse(StringBuilder sb)
{
for (int i = 0; i < sb.Length / 2; i++)
{
char ch = sb[i];
sb[i] = sb[sb.Length - i - 1];
sb[sb.Length - i - 1] = ch;
}
}
private static int _GetBase36DigitValue(char ch)
{
return ch < 'A' ? ch - '0' : ch - 'A' + 10;
}
private static char _GetDigitCharacter(int digit)
{
return (char)(digit < 10 ? '0' + digit : 'A' + digit - 10);
}
}
Хорошо, теперь, когда у нас это есть, легко написать класс для обработки кодировки для значения базы данных. сам по себе:
static class DatabaseDateTime
{
private static readonly DateTimeOffset _epoch = new DateTimeOffset(1990, 1, 1, 0, 0, 0, 0, TimeSpan.Zero);
public static DateTimeOffset Decode(string databaseText, int timeZoneOffsetHours)
{
double secondsSinceEpoch = Base36.Decode(databaseText) / 50d;
DateTimeOffset result = _epoch.AddSeconds(secondsSinceEpoch);
return new DateTimeOffset(result.AddHours(timeZoneOffsetHours).Ticks, TimeSpan.FromHours(timeZoneOffsetHours));
}
internal static string Encode(DateTimeOffset testResult)
{
double secondsSinceEpoch = testResult.Subtract(_epoch).TotalSeconds;
return Base36.EncodeAsFixedWidth((long)(secondsSinceEpoch * 50), 7);
}
}
Наконец, для целей этого упражнения я написал небольшую программу-пример, которая использует ваши примеры значений, чтобы убедиться, что приведенный выше код работает должным образом и ожидаемым образом:
static void Main(string[] args)
{
(string DatabaseText, DateTimeOffset EncodedDateTime)[] testCases =
{
("A7LXZMM", DateTimeOffset.Parse("2004-02-02 09:34:47.000 +01:00")),
("KWZKXEX", DateTimeOffset.Parse("2018-11-09 11:15:46.000 +01:00")),
("LIZTMR9", DateTimeOffset.Parse("2019-09-13 11:49:46.000 +01:00"))
};
List<DateTimeOffset> testResults = new List<DateTimeOffset>(testCases.Length);
foreach (var testCase in testCases)
{
DateTimeOffset decodedDateTime = DatabaseDateTime.Decode(testCase.DatabaseText, 1);
// Compare as string, because reference data was provided as string and is missing
// some of the precision in the actual database text provided.
if (decodedDateTime.ToString() != testCase.EncodedDateTime.ToString())
{
WriteLine($"ERROR: {testCase.DatabaseText} -- expected: {testCase.EncodedDateTime}, actual: {decodedDateTime}");
}
testResults.Add(decodedDateTime);
}
foreach (var testCase in testResults.Zip(testCases, (r, c) => (Result: r, DatabaseText: c.DatabaseText)))
{
string base36Text = DatabaseDateTime.Encode(testCase.Result);
if (base36Text != testCase.DatabaseText)
{
WriteLine($"ERROR: {testCase.Result} -- expected: {testCase.DatabaseText}, actual: {base36Text}");
}
}
}
Когда я запускаю приведенный выше код, я не получаю никакого вывода, как хотелось бы (т. Е. Только вышеприведенные вызовы WriteLine()
выполняются только тогда, когда вычисления программы не соответствуют ожидаемым).