简体   繁体   中英

DateTime.TryParse -> “Ghost ticks”

i've the following unit test, which fails on the machine of one of our developers (He get some ticks in the result variable, while the datetime variable is at zero ticks), but runs well on every other machine.

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

Here's the used extension method:

    /// <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;
    }

I think this might be related to some windows date time settings he uses. In theory the ToNullableDateTime(string) should create a new culture info, which is user machine neutral. GetCultureInfo should call new CultureInfo(name, false) . The only thing I could come up with, is that there's a cached culture info, which contains some kind of user machine related modified datetime in the s_NameCachedCultures which is checked in the GetCultureInfoHelper ( http://referencesource.microsoft.com/#mscorlib/system/globalization/cultureinfo.cs,5fe58d4ecbba7689 ).

I know, that the CreateSpecificCulture method can return a user modified datetime, if you call it with the same culture than the windows machine. But I always thought, the GetDateTime would return a unmodified datetime in any case.

So there are two questions:

  • Could it be possible, that a modified CultureInfo be stored in the internal cache?
  • If so, is the only way to get a unmodified CultureInfo by manually calling new CultrueInfo("xy", false) ?

When you do

string value = dateTime.ToString();

this will use CultureInfo.CurrentCulture. You then try and parse this string using...

CultureInfo.GetCultureInfo(CultureInfo.CurrentCulture.Name);

So you are specifically using a culture to parse the string that is different to the one you've created the string with. Of course there will be instances where this doesn't pass.

I'd suggest the issue is on most peoples machines

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

would pass but on the machine in question it doesn't and neither do your strings.

I'd suggest you probably want to use CultureInfo.InvariantCulture . So...

    [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;
    }

I've overseen a little detail. The problem wasn't related to the GetCultureInfo returning a modified datetime, the problem already startet with the dateTime.ToString(); which uses the Thread.CurrentThread.CultureInfo (which equals to the windows culture, which can be modified). The developers machine translates the DateTime(1, 1, 1) to 01.01.01 00:00:00 . On any other machine the output is 01.01.0001 00:00:00 . So an abbreviated version of the year is used, which seems to be interpreted as the "year 1 of the current century" in the TryParse method (quick sidenode: So it's not possible for users older than 100 years to tell their birthdate with the abbreviated year version). It's actually an interesting behaviour..

        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"));
        }

leads to:

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

So, the abbreviated birth date for anyone older 86 would lead to a date in the feature.. but this moves away from the questions context ..

I think there's no real solution the actual problem (beside telling developers to not use local CultureInfos outside of UI strings and never use abbreviated dates for inputs).

We don't have such problems in our code itself, because we use CultureInfo.InvariantCulture for all internal stuff. I just think about the unit test .. I think the test itself is correct. It shows, that the function actually doesn't work correct with abbreviated dates. I will change the behaviour of the ToNullableDateTime() to throw an exception, if a datetime string with a abbreviated year is recognized.

I guess, most probably the CultureInfo is in a cache. This would be very easy to check in a unit test ( AreSame ).

Instead of DateTime(1, 1, 1) use DateTime(1, 2, 3) and check the ghost ticks. Are they found in the string somewhere? Did you try the same culture (hard-coded culture name) on two machines which had different results?

Also check if the difference is the offset to UTC in your culture.

You probably can use TryParseExact .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM