【问题标题】:Set the year that is assumed when parsing partial date values in .NET设置在 .NET 中解析部分日期值时假定的年份
【发布时间】:2012-07-26 14:21:46
【问题描述】:

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

  • 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

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

【问题讨论】:

标签: c# parsing date


【解决方案1】:

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

if(! string.contains(DateTime.Year.toString() ) {
    // Set the year yourself
}

【讨论】:

  • 如果字符串是“25 dec 12”怎么办?那么年份将设置为2012,但字符串不包含“2012”。
  • DateTime.Year 将当前年份返回为 4 位数字,因此对于任何具有 2 位数字年份的输入日期,这将失败。在某些情况下,仅检查两位数会导致月份/日期不明确。
  • 这不是一个糟糕的解决方法。我担心的是,当前年份可能合法地存在于日期字符串的其他地方。例如。 “2008-11-01T19:35:00.2012000Z”
  • 是的,我想这取决于输入的实际变化程度。
【解决方案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

【讨论】:

    【解决方案3】:

    您可以尝试处理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
    

    【讨论】:

      【解决方案4】:

      我有一个非常相似的问题。当字符串不包含时间信息时,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;
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多