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

さまざまな形式で「不完全な」日付/時刻情報を取得することが予想される場合は、テキストを特定の異なる形式として、最も詳細でないものから最も詳細なものまで解析してみてください。例えば:

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を使用する方法(テキストにシリアル化されるなど)で保存される日付/時刻データを常にお勧めします。データがシリアル化されたときにどの文化が機能していたかわからないためです。ユニバーサルは、平等な競技場で日付/時刻データを取得する唯一の信頼できる方法です。その場合はを使用してDateTimeStyles.AssumeUnivesalください。

于 2012-07-26T14:45:53.957 に答える
2

私が考えることができる唯一のことは、日付の後処理です。その後に文字列があり、DateTime オブジェクトに年があります。文字列に年が含まれていない場合は、自分で年を設定してください。

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

私は非常によく似た問題を抱えていました。DateTime.Parse または DateTime.TryParse は、文字列に時刻情報が含まれていない場合、時刻が 00:00:00 であると想定します。年の仮定と同様に、別の時刻をデフォルトとして使用するように指定する方法はありません。これらのデフォルトを設定する時間は、解析メソッドがすべての詳細な手順を実行するであるため、これは実際の問題です。そうしないと、文字列にデフォルトを上書きする情報が含まれているかどうかを判断するために、非常に苦労して車輪を再発明する必要があります。

DateTime.TryParse のソース コードを調べたところ、予想どおり、Microsoft は DateTime クラスの拡張を困難にするためにあらゆる手を尽くしました。そこで、リフレクションを使用して、DateTime のソース コードからできることを活用するコードを作成しました。これにはいくつかの重大な欠点があります。

  • リフレクションを使用するコードは厄介です
  • リフレクションを使用するコードは、.Net フレームワークがアップグレードされた場合に変更される可能性がある内部メンバーを呼び出します
  • リフレクションを使用するコードは、リフレクションを使用する必要のない仮想的な代替手段よりも遅く実行されます

私の場合、DateTime.TryParse をゼロから作り直すことほど厄介なことはないと判断しました。内部メンバーが変更されたかどうかを示す単体テストがあります。そして、私の場合、パフォーマンスの低下は取るに足らないものだと思います。

私のコードは以下です。このコードは、デフォルトの時/分/秒をオーバーライドするために使用されますが、デフォルトの年などをオーバーライドするために簡単に変更または拡張できると思います。このコードは、内部 System.DateTimeParse.TryParse のオーバーロードの 1 つの内部コードを忠実に模倣しています (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 に答える