10

我需要解析一个可以采用任何合理格式的日期字符串。例如:

  • 2012-12-25
  • 25 december 2012
  • 25 dec
  • 17:35

其中一些字符串包含不明确的日期,这可能会导致几个可能的DateTime值(例如25 dec,可以解释为2012-12-252011-12-251066-12-25等)。

当前处理这些模糊值的方式DateTime.Parse是使用当前系统日期来确定上下文。因此,如果当前日期是2012 年 7 月 26 日,则假定字符串25 dec在当年并被解析为2012-12-25

是否有可能以某种方式更改此行为并自己设置当前日期上下文?

4

4 回答 4

2

我唯一能想到的就是后处理日期。之后你有字符串,你在 DateTime 对象中有年份。如果字符串不包含年份,则自己设置年份。

if(! string.contains(DateTime.Year.toString() ) {
    // Set the year yourself
}
于 2012-07-26T14:31:59.703 回答
2

如果您希望获得各种格式的“不完整”日期/时间信息,您可以尝试将文本解析为从最不详细到最详细的特定不同格式。例如:

var text = "June 15";
DateTime datetime;
if(DateTime.TryParseExact(text, "m", CultureInfo.CurrentCulture, DateTimeStyles.AssumeLocal, out datetime))
{
    // was just month, day, replace year with specific value:
    datetime = new DateTime(1966, datetime.Month, datetime.Day);
}
else
{
    // wasn't just month, day, try parsing a "whole" date/time:
    datetime = DateTime.Parse(text);
}

...此代码尝试解析当前文化中的月/日格式(如果您有一个特定的,独立于当前文化,您可以将“CultureInfo.CurrentCulture”替换为具有您想要的格式的文化)。如果失败,它会假定文本更详细,然后像往常一样继续解析它。

如果您的日期/时间不是本地的,请不要使用DateTimeStyles.AssumeLocal. 我始终建议您始终使用 Universal 以任何方式(如序列化为文本)存储的日期/时间数据;因为当数据被序列化时,你不知道什么文化在起作用。Universal 是在公平竞争环境中获取日期/时间数据的唯一可靠方法。在这种情况下使用DateTimeStyles.AssumeUnivesal.

于 2012-07-26T14:45:53.957 回答
1

你可以尝试处理一些IFormatProvider事情,但这可能需要一段时间。作为一个快速的解决方案,我可以提出一个扩展方法:

public static class MyDateTimeStringExtensions
{
    public static DateTime ToDateTimeWithYear(this string source, int year)
    {
        var dateTime = DateTime.Parse(source);

        return dateTime.AddYears(year - dateTime.Year);
    }
}
 ....
"2/2".ToDateTimeWithYear(2001) // returns 2/2/2001 12:00:00 AM
于 2012-07-26T14:35:33.403 回答
1

我有一个非常相似的问题。当字符串不包含时间信息时,DateTime.Parse 或 DateTime.TryParse 将假定时间为 00:00:00。与年份假设一样,无法指定不同的时间作为默认时间。这是一个真正的问题,因为设置这些默认值的时间是解析方法完成所有详细步骤之前。否则,您必须非常痛苦地重新发明轮子来确定字符串是否包含会覆盖默认值的信息。

我查看了 DateTime.TryParse 的源代码,不出所料,Microsoft 已经竭尽全力使 DateTime 类难以扩展。因此,我编写了一些使用反射来利用 DateTime 源代码的代码。这有一些明显的缺点:

  • 使用反射的代码很尴尬
  • 使用反射的代码调用内部成员,如果升级 .Net 框架,这些成员可能会发生变化
  • 使用反射的代码将比一些不需要使用反射的假设替代方案运行得更慢

就我而言,我认为没有什么比从头开始重新发明 DateTime.TryParse 更尴尬的了。我有单元测试来表明内部成员是否发生了变化。而且我相信在我的情况下,性能损失是微不足道的。

我的代码如下。此代码用于覆盖默认的小时/分钟/秒,但我认为可以轻松修改或扩展它以覆盖默认年份或其他内容。该代码忠实地模仿了内部 System.DateTimeParse.TryParse 的重载之一的内部代码(它完成了 DateTime.TryParse 的实际工作),尽管我不得不使用笨拙的反射来这样做。与 System.DateTimeParse.TryParse 唯一有效不同的是,它分配了一个默认的小时/分钟/秒,而不是让它们全部为零。

作为参考,这是我正在模仿的类 DateTimeParse 的方法

        internal static bool TryParse(String s, DateTimeFormatInfo dtfi, DateTimeStyles styles, out DateTime result) { 
        result = DateTime.MinValue;
        DateTimeResult resultData = new DateTimeResult();  // The buffer to store the parsing result.
        resultData.Init();
        if (TryParse(s, dtfi, styles, ref resultData)) { 
            result = resultData.parsedDate;
            return true; 
        } 
        return false;
    } 

这是我的代码

public static class TimeExtensions
{
    private static Assembly _sysAssembly;
    private static Type _dateTimeParseType, _dateTimeResultType;
    private static MethodInfo _tryParseMethod, _dateTimeResultInitMethod;
    private static FieldInfo _dateTimeResultParsedDateField,
                _dateTimeResultHourField, _dateTimeResultMinuteField, _dateTimeResultSecondField;
    /// <summary>
    /// This private method initializes the private fields that store reflection information
    /// that is used in this class.  The method is designed so that it only needs to be called
    /// one time.
    /// </summary>
    private static void InitializeReflection()
    {
        // Get a reference to the Assembly containing the 'System' namespace
        _sysAssembly = typeof(DateTime).Assembly;
        // Get non-public types of 'System' namespace
        _dateTimeParseType = _sysAssembly.GetType("System.DateTimeParse");
        _dateTimeResultType = _sysAssembly.GetType("System.DateTimeResult");
        // Array of types for matching the proper overload of method System.DateTimeParse.TryParse
        Type[] argTypes = new Type[] 
        {
            typeof(String), 
            typeof(DateTimeFormatInfo), 
            typeof(DateTimeStyles), 
            _dateTimeResultType.MakeByRefType()
        };
        _tryParseMethod = _dateTimeParseType.GetMethod("TryParse",
                BindingFlags.Static | BindingFlags.NonPublic, null, argTypes, null);
        _dateTimeResultInitMethod = _dateTimeResultType.GetMethod("Init",
                BindingFlags.Instance | BindingFlags.NonPublic);
        _dateTimeResultParsedDateField = _dateTimeResultType.GetField("parsedDate",
                BindingFlags.Instance | BindingFlags.NonPublic);
        _dateTimeResultHourField = _dateTimeResultType.GetField("Hour",
                BindingFlags.Instance | BindingFlags.NonPublic);
        _dateTimeResultMinuteField = _dateTimeResultType.GetField("Minute",
                BindingFlags.Instance | BindingFlags.NonPublic);
        _dateTimeResultSecondField = _dateTimeResultType.GetField("Second",
                BindingFlags.Instance | BindingFlags.NonPublic);
    } 
    /// <summary>
    /// This method converts the given string representation of a date and time to its DateTime
    /// equivalent and returns true if the conversion succeeded or false if no conversion could be
    /// done.  The method is a close imitation of the System.DateTime.TryParse method, with the
    /// exception that this method takes a parameter that allows the caller to specify what the time
    /// value should be when the given string contains no time-of-day information.  In contrast,
    /// the method System.DateTime.TryParse will always apply a value of midnight (beginning of day)
    /// when the given string contains no time-of-day information.
    /// </summary>
    /// <param name="s">the string that is to be converted to a DateTime</param>
    /// <param name="result">the DateTime equivalent of the given string</param>
    /// <param name="defaultTime">a DateTime object whose Hour, Minute, and Second values are used
    /// as the default in the 'result' parameter.  If the 's' parameter contains time-of-day 
    /// information, then it overrides the value of 'defaultTime'</param>
    public static Boolean TryParse(String s, out DateTime result, DateTime defaultTime)
    {
        // Value of the result if no conversion can be done
        result = DateTime.MinValue;
        // Create the buffer that stores the parsed result
        if (_sysAssembly == null) InitializeReflection();
        dynamic resultData = Activator.CreateInstance(_dateTimeResultType);
        _dateTimeResultInitMethod.Invoke(resultData, new Object[] { });
        // Override the default time values of the buffer, using this method's parameter
        _dateTimeResultHourField.SetValue(resultData, defaultTime.Hour);
        _dateTimeResultMinuteField.SetValue(resultData, defaultTime.Minute);
        _dateTimeResultSecondField.SetValue(resultData, defaultTime.Second);
        // Create array parameters that can be passed (using reflection) to 
        // the non-public method DateTimeParse.TryParse, which does the real work
        Object[] tryParseParams = new Object[]
        {
            s, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, resultData
        };
        // Call non-public method DateTimeParse.TryParse
        Boolean success = (Boolean)_tryParseMethod.Invoke(null, tryParseParams);
        if (success)
        {
            // Because the DateTimeResult object was passed as a 'ref' parameter, we need to
            // pull its new value out of the array of method parameters
            result = _dateTimeResultParsedDateField.GetValue((dynamic)tryParseParams[3]);
            return true;
        }
        return false;
    }
}

--编辑-- 后来我意识到我需要对方法 DateTime.TryParseExact 做同样的事情。但是,上述方法不适用于 TryParseExact,这让我担心这种方法比我想象的还要脆弱。那好吧。令人高兴的是,我能够为 TryParseExact 想到一种非常不同的方法,它不使用任何反射

        public static Boolean TryParseExact(String s, String format, IFormatProvider provider,
                            DateTimeStyles style, out DateTime result, DateTime defaultTime)
    {
        // Determine whether the format requires that the time-of-day is in the string to be converted.
        // We do this by creating two strings from the format, which have the same date but different
        // time of day.  If the two strings are equal, then clearly the format contains no time-of-day
        // information.
        Boolean willApplyDefaultTime = false;
        DateTime testDate1 = new DateTime(2000, 1, 1, 2, 15, 15);
        DateTime testDate2 = new DateTime(2000, 1, 1, 17, 47, 29);
        String testString1 = testDate1.ToString(format);
        String testString2 = testDate2.ToString(format);
        if (testString1 == testString2)
            willApplyDefaultTime = true;

        // Let method DateTime.TryParseExact do all the hard work
        Boolean success = DateTime.TryParseExact(s, format, provider, style, out result);

        if (success && willApplyDefaultTime)
        {
            DateTime rawResult = result;
            // If the format contains no time-of-day information, then apply the default from
            // this method's parameter value.
            result = new DateTime(rawResult.Year, rawResult.Month, rawResult.Day,
                             defaultTime.Hour, defaultTime.Minute, defaultTime.Second);
        }
        return success;
    }
于 2014-01-28T18:55:42.720 回答