Конвертировать DateTime в base36 - PullRequest
       43

Конвертировать DateTime в base36

1 голос
/ 01 октября 2019

Я работаю со старой базой данных, и мне нужно начать генерировать идентификатор с помощью c #. Мне нужно создать идентификаторы, которые соответствуют старым идентификаторам.

Я хотел бы преобразовать DateTime в 7-значную \ base36 конфигурацию. Я подумал, что будет достаточно легко провести обратный инжиниринг, когда у меня будет код для преобразования из кода base36 в DateTime (еще раз спасибо, Джошуа), однако у меня все еще возникают трудности.

Я провел день, пытаясьвыясните, как преобразовать DateTime в base36.

Ниже приведен код для преобразования из base36 в DateTime. Этот код, кажется, работает нормально. Идентификатор добавляется в sRecid, а затем преобразуется в DateTime.

    id          Date Time
    A7LXZMM     2004-02-02 09:34:47.000
    KWZKXEX     2018-11-09 11:15:46.000
    LIZTMR9     2019-09-13 11:49:46.000

    using System;
    using System.Globalization;         
    using System.Text;
    using System.Numerics;

    public class Program
    {
      public static void Main()
      {

        string sRecid = "KWZKXEX";
        char c0 = sRecid[0];
        char c1 = sRecid[1];
        char c2 = sRecid[2];
        char c3 = sRecid[3];
        char c4 = sRecid[4];
        char c5 = sRecid[5];
        char c6 = sRecid[6];

        double d6, d5, d4, d3, d2, d1, d0, dsecs;

        Console.WriteLine("c0 = " + c0.ToString());
        Console.WriteLine();

        d6 = Math.Pow(36, 6) * ((Char.IsNumber(c0)) ? (byte)c0 - 48 : (byte)c0 - 55);
        d5 = Math.Pow(36, 5) * ((Char.IsNumber(c1)) ? (byte)c1 - 48 : (byte)c1 - 55);
        d4 = Math.Pow(36, 4) * ((Char.IsNumber(c2)) ? (byte)c2 - 48 : (byte)c2 - 55);
        d3 = Math.Pow(36, 3) * ((Char.IsNumber(c3)) ? (byte)c3 - 48 : (byte)c3 - 55);
        d2 = Math.Pow(36, 2) * ((Char.IsNumber(c4)) ? (byte)c4 - 48 : (byte)c4 - 55);
        d1 = Math.Pow(36, 1) * ((Char.IsNumber(c5)) ? (byte)c5 - 48 : (byte)c5 - 55);
        d0 = Math.Pow(36, 0) * ((Char.IsNumber(c6)) ? (byte)c6 - 48 : (byte)c6 - 55);

        dsecs = (d6 + d5 + d4 + d3 + d2 + d1 + d0) / 50;

        DateTime dt = new DateTime(1990, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
        dt = dt.AddSeconds(dsecs).ToLocalTime();

        Console.WriteLine("d6 = " + d6.ToString());
        Console.WriteLine("d5 = " + d5.ToString());
        Console.WriteLine("d4 = " + d4.ToString());
        Console.WriteLine("d3 = " + d3.ToString());
        Console.WriteLine("d2 = " + d2.ToString());
        Console.WriteLine("d1 = " + d1.ToString());
        Console.WriteLine("d0 = " + d0.ToString());
        Console.WriteLine("dsecs = " + dsecs.ToString());
        Console.WriteLine("dt = " + dt.ToString());
      }
    }

Это код, с которым у меня проблемы.

    using System;
    using System.Globalization;         
    using System.Text;
    using System.Numerics;

    public class Program
    {
      /*
        A7LXZMM     2004-02-02 09:34:47.000
        KWZKXEX     2018-11-09 11:15:46.000
        LIZTMR9     2019-09-13 11:49:46.000 
      */

      public static void Main()
      {

        DateTime dt = new DateTime(2004, 02, 02, 09, 34, 47);  // Convert this datetime to A7LXZMM

        DateTime dtBase = new DateTime(1990, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
        double offsetseconds = (DateTime.Now - DateTime.UtcNow).TotalSeconds;
        double seconds = ((dt - dtBase).TotalSeconds) * 50;

        double d6 = seconds / (Math.Pow(36, 6));
        var q6 = d6.ToString().Split('.');
        double dQuotient = double.Parse(q6[0]);
        double dRemainder = double.Parse(q6[1]);
        char c0 = ((dQuotient <= 9) ? (char)(dQuotient + 48) : (char)(dQuotient + 55));

        Console.WriteLine("d6 = " + d6.ToString());
        Console.WriteLine("dQuotient = " + dQuotient.ToString());
        Console.WriteLine("c0 = " + c0.ToString());
        Console.WriteLine("");

        double d5 = dQuotient / (Math.Pow(36, 5));
        var q5 = d5.ToString().Split('.');
        dQuotient = double.Parse(q5[0]);
        dRemainder = double.Parse(q5[1]);
        char c1 = ((dQuotient <= 9) ? (char)(dQuotient + 48) : (char)(dQuotient + 55));

        Console.WriteLine("d5 = " + d5.ToString());
        Console.WriteLine("dQuotient = " + dQuotient.ToString());
        Console.WriteLine("c1 = " + c1.ToString());
        Console.WriteLine("");

      }
    }

Код запускается нормально, яЯ могу получить первый символ (c0), но у меня проблемы со следующими символами (c1 и далее).

В моем примере я передаю дату 2004-02-02 09: 34: 47 и собираюсь вернуться A7LXZMM. Куда я иду не так?

Спасибо.

Ответы [ 2 ]

2 голосов
/ 02 октября 2019

Корень вашей проблемы заключается в том, что вы не включили в расчет смещение местного часового пояса. Вы пытаетесь определить смещение 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() выполняются только тогда, когда вычисления программы не соответствуют ожидаемым).

0 голосов
/ 02 октября 2019

Не проходите через строковое представление даты. Используйте Tick Represantation, это длинное (64 бита), более простое в обращении.

Затем вы делите его на 36, пока оно не равно нулю,

string result = string.Empty;
long ticks = DateTime.Now.Ticks;
while (ticks>0)
{
   int n = ticks % 36;
   ticks /= 36;
   char c = n<26 ? ('A'+n) : ('0'+n-26);
   result = c + result;
}

Этотак же, как и для любой другой базы.

Чтобы повернуть ее, вы берете наоборот:

int n = c <= '9' ? (c-'0'+26) : (c-'A')

умножьте на показатель степени 36

Если это требование Base36 не внешнеопределено, вы получаете более короткие строки, когда берете больший алфавит. С 63 символами вам, скорее всего, понадобится всего 6 символов для одного диапазона. Просто добавьте строчные буквы и одну дополнительную пунктуацию, и вы получите это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...