DateTime.TryParse -> "Ghost ticks"

本文关键字:quot Ghost ticks gt TryParse DateTime | 更新日期: 2023-09-27 18:03:43

我有下面的单元测试,它在我们的一个开发人员的机器上失败了(他在result变量中得到一些刻度,而datetime变量是零刻度),但在其他机器上运行良好。

    [TestMethod]
    public void DateTimeStringDateTimeMinCurrentCultureToNullableDateTimeSuccessTest()
    {
        var dateTime = new DateTime(1, 1, 1);
        string value = dateTime.ToString();
        var result = value.ToNullableDateTime();
        Assert.AreEqual(dateTime, result);
    }

下面是使用的扩展方法:

    /// <summary>
    /// Converts a string to a nullable DateTime. If the string is a invalid dateTime returns null.
    /// Uses the current culture.
    /// </summary>
    public static DateTime? ToNullableDateTime(this string s)
    {
        //Don't use CultureInfo.CurrentCulture to override user changes of the cultureinfo.
        return s.ToNullableDateTime(CultureInfo.GetCultureInfo(CultureInfo.CurrentCulture.Name));
    }
    /// <summary>
    /// Converts a string to a nullable DateTime. If the string is a invalid dateTime returns null.
    /// </summary>
    public static DateTime? ToNullableDateTime(this string s, CultureInfo cultureInfo)
    {
        if (String.IsNullOrEmpty(s)) return null;
        DateTime i;
        if (DateTime.TryParse(s, cultureInfo, DateTimeStyles.None, out i)) return i;
        return null;
    }

我认为这可能与他使用的一些windows日期时间设置有关。理论上,ToNullableDateTime(string)应该创建一个新的文化信息,这是用户机器中立的。GetCultureInfo应该呼叫new CultureInfo(name, false)。我唯一能想到的是,有一个缓存的文化信息,它包含GetCultureInfoHelper中检查的s_NameCachedCultures中与用户机器相关的修改日期时间(http://referencesource.microsoft.com/#mscorlib/system/globalization/cultureinfo.cs,5fe58d4ecbba7689)。

我知道,CreateSpecificCulture方法可以返回一个用户修改的datetime,如果你用与windows机器相同的区域性调用它。但我一直认为,GetDateTime在任何情况下都会返回一个未修改的日期时间。

所以有两个问题:

  • 是否有可能,修改后的CultureInfo存储在内部缓存中?
  • 如果是这样,只有通过手动调用new CultrueInfo("xy", false)才能获得未修改的CultureInfo吗?

DateTime.TryParse -> "Ghost ticks"

当你这么做的时候

string value = dateTime.ToString();

this将使用CultureInfo.CurrentCulture。然后尝试使用…

解析该字符串。

CultureInfo.GetCultureInfo (CultureInfo.CurrentCulture.Name);

所以你特别使用区域性来解析与你创建字符串时不同的字符串。当然,也会有不及格的情况。

我认为大多数人的机器都有这个问题

Assert.AreEqual(CultureInfo.CurrentCulture, CultureInfo.GetCultureInfo(CultureInfo.CurrentCulture.Name));

可以通过,但是在所讨论的机器上不能通过,字符串也不能通过。

我建议你可能想使用CultureInfo.InvariantCulture。所以…

    [TestMethod]
    public void DateTimeStringDateTimeMinCurrentCultureToNullableDateTimeSuccessTest()
    {
        DateTime dateTime = new DateTime(1, 1, 1);
        string value = dateTime.ToStringInvariant();
        var result = value.ToNullableDateTime();
        Assert.AreEqual(dateTime, result);
    }

    public static string ToStringInvariant(this DateTime? date)
    {
        if (date.HasValue)
            return date.Value.ToStringInvariant();
        return null;
    }
    public static string ToStringInvariant(this DateTime date)
    {
        return date.ToString(CultureInfo.InvariantCulture);
    }
    /// <summary>
    /// Converts a string to a nullable DateTime. If the string is a invalid dateTime returns null.
    /// Uses the current culture.
    /// </summary>
    public static DateTime? ToNullableDateTime(this string s)
    {
        //Don't use CultureInfo.CurrentCulture to override user changes of the cultureinfo.
        return s.ToNullableDateTime(CultureInfo.InvariantCulture);
    }
    /// <summary>
    /// Converts a string to a nullable DateTime. If the string is a invalid dateTime returns null.
    /// </summary>
    public static DateTime? ToNullableDateTime(this string s, CultureInfo cultureInfo)
    {
        if (String.IsNullOrEmpty(s)) return null;
        DateTime i;
        if (DateTime.TryParse(s, cultureInfo, DateTimeStyles.None, out i)) return i;
        return null;
    }

我疏忽了一点细节。这个问题与GetCultureInfo返回一个修改的日期时间无关,这个问题已经开始于使用Thread.CurrentThread.CultureInfodateTime.ToString();(它等于windows区域性,可以修改)。开发人员机器将DateTime(1, 1, 1)转换为01.01.01 00:00:00。在任何其他机器上,输出都是01.01.0001 00:00:00。因此使用了年份的缩写版本,在TryParse方法中,这似乎被解释为"当前世纪的第1年"(快速补充:所以100岁以上的用户不可能用缩写版本来告诉他们的出生日期)。这实际上是一个有趣的行为…

        for (int i = 0; i < 100; i++)
        {
            var year = 1900 + i;
            DateTime date = new DateTime(year, 1, 1);
            var parsedDate = DateTime.ParseExact(date.ToString("yy"), "yy", CultureInfo.InvariantCulture);
            Console.WriteLine("{0}: {1}", year, parsedDate.ToString("yyyy"));
        }

导致:

[...]
1928: 2028
1929: 2029
1930: 1930
1931: 1931
[...]

因此,任何年龄在86岁以上的人的缩写出生日期都会导致功能中的日期。但这偏离了问题的上下文…

我认为没有真正的解决方案(除了告诉开发人员不要在UI字符串之外使用本地cultureinfo和永远不要在输入中使用缩写日期)。

我们的代码本身没有这样的问题,因为我们使用CultureInfo.InvariantCulture来处理所有内部的东西。我只是想到了单元测试…我认为测试本身是正确的。它表明,这个函数实际上不能正确地处理缩写日期。我将更改ToNullableDateTime()的行为,如果识别出具有缩写年份的日期时间字符串,则抛出异常。

我猜,最有可能的CultureInfo是在缓存中。这在单元测试(AreSame)中是非常容易检查的。

DateTime(1, 2, 3)代替DateTime(1, 1, 1)并检查幽灵刻度。它们在字符串的某个地方找到了吗?您是否在两台产生不同结果的机器上尝试了相同的区域性(硬编码的区域性名称)?

还要检查差异是否为您的区域性中与UTC的偏移量。

您可以使用TryParseExact