【问题标题】:DateTime.TryParse -> "Ghost ticks"DateTime.TryParse -> "幽灵滴答声"
【发布时间】:2015-07-20 06:27:12
【问题描述】:

我有以下单元测试,它在我们的一位开发人员的机器上失败(他在结果变量中得到了一些滴答声,而 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)。我唯一能想到的是,有一个缓存的文化信息,它在s_NameCachedCultures 中包含某种与用户机器相关的修改日期时间,在GetCultureInfoHelper (http://referencesource.microsoft.com/#mscorlib/system/globalization/cultureinfo.cs,5fe58d4ecbba7689) 中检查。

我知道,CreateSpecificCulture 方法可以返回用户修改的日期时间,如果您使用与 Windows 机器相同的文化来调用它。但我一直认为,GetDateTime 无论如何都会返回未修改的日期时间。

所以有两个问题:

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

【问题讨论】:

  • 为什么在扩展方法的短重载中选择使用CultureInfo.GetCultureInfo? documentation 表示比普通实例构造函数 (new CultureInfo(...)) 更喜欢它的原因是缓存速度。
  • 知道s 和CultureInfo.CurrentCulture.Name 在未通过测​​试的机器上以及在测试通过的机器上会很有帮助。

标签: c# .net datetime


【解决方案1】:

当你这样做时

字符串值 = dateTime.ToString();

这将使用 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;
    }

【讨论】:

    【解决方案2】:

    我已经监督了一些细节。该问题与返回修改后的日期时间的 GetCultureInfo 无关,该问题已经从使用 Thread.CurrentThread.CultureInfo 的 dateTime.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 字符串之外使用本地 CultureInfos 并且永远不要使用缩写日期作为输入)。

    我们的代码本身没有这样的问题,因为我们使用CultureInfo.InvariantCulture 处理所有内部内容。我只考虑单元测试..我认为测试本身是正确的。它表明,该函数实际上不适用于缩写日期。如果识别到带有缩写年份的日期时间字符串,我将更改 ToNullableDateTime() 的行为以引发异常。

    【讨论】:

      【解决方案3】:

      我猜,CultureInfo 很可能在缓存中。这将很容易在单元测试中检查 (AreSame)。

      使用DateTime(1, 2, 3) 代替DateTime(1, 1, 1) 并检查幽灵滴答声。他们是在某个地方的字符串中找到的吗?您是否在两台机器上尝试过相同的文化(硬编码的文化名称)却得到不同的结果?

      还要检查差异是否是您文化中与 UTC 的偏移量。

      您可能可以使用TryParseExact。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-03-05
        • 1970-01-01
        • 1970-01-01
        • 2014-05-24
        • 2013-03-03
        • 1970-01-01
        • 1970-01-01
        • 2010-10-31
        相关资源
        最近更新 更多