我正在处理一个旧数据库,需要开始使用c#生成id。我需要生成与旧身份证相对应的身份证。
我想把一个DateTime转换成一个7位数的\ base36配置。我认为,一旦我将代码从base36代码转换为DateTime代码(再次感谢约书亚),就可以很容易地进行逆向工程,但是我仍然有困难。
我花了一天的时间试图弄清楚如何从DateTime转换到base36。
下面是要从base36代码转换为DateTime的代码。这段代码似乎很好用。id被添加到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。我哪里出问题了?
谢谢。
发布于 2019-10-02 00:33:11
问题的根源在于未能在计算中包含本地时区偏移量。您尝试确定TZ偏移量,尽管方式不正确,但实际上从未使用过。
尝试不正确的原因是您检索了当前时间两次,而当前时间实际上可能在这两个调用之间发生更改。在.NET环境中,由于DateTime.Now和.UtcNow属性的分辨率相对较低,这是不太可能的,但理论上,如果您在适当的时候捕捉到调用,就会发生这种情况。
当然,这个bug并不重要,因为计算值从未被使用过。
IMHO,这两个代码示例的另一个大问题是它们不够一般化,也没有足够的抽象:
通用代码:代码是完全不可重用的,它假设了value.
代码的最后一个大错误是,它将编码时间调整为本地时间。当地时间仅供人类使用。这在某种程度上与抽象方面相联系。但主要的一点是,在处理时间值时,代码应该只在内部使用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()调用只有在程序的计算与预期的不匹配时才会执行)。
发布于 2019-10-01 21:52:14
不要通过日期的字符串表示。使用滴答表示,这是一个长(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个字符为同一范围。只要加上小写字符和一个额外的标点符号,你就会得到它。
https://stackoverflow.com/questions/58190914
复制相似问题