28

次の 2 つのシナリオを検討してください: シナリオ 1)。今日は 2012 年 5 月 1 日で、シナリオ 2)。今日は 2012 年 9 月 1 日です。

ここで、誰かが残したコメントについて、「このコメントは 3 か月と 12 日前に書かれました」と Web ページに書いたとします。ステートメントがまったく同じであっても、これら両方のシナリオの日数は常に異なります。シナリオ 1 では、「3 か月と 12 日」は102 days. ただし、シナリオ 2 では、「3 か月と 12 日」は104 days!

さて、私の主張に追いつくために、別の例を使用して、誰かが 2013 年 1 月 30 日に私たちのサイトにコメントを残したとしましょう。今日は 2013 年 3 月 10 日です。私たちの実際の TimeSpan オブジェクトは、この相対的な日付を知る必要があり、以下を出します:

  • 3月は10日あるということで、
  • 1月は1日(30日から31日まで数えます)。
  • 2 月が何日あっても (28 日であっても) 1 か月であること。

つまり、合計で 10 日 + 1 日 + 1 か月、つまりThis comment was posted 1 Month and 11 Days ago.

ここで、MS スタイルの TimeSpan オブジェクト (または任意の言語の TimeSpan オブジェクト) を使用すると、1 月 30 日から 3 月 10 日までの日数 (39 日) が得られます。これは、TimeSpan オブジェクトが相対日付を格納しないためです。 (TimeSpan を取得するために差し引いた基本/初期日付)、それが何月何日であるかを尋ねると、1 か月に 30 日、または最悪の場合、30 日を超える平均があると仮定します。 、残りを日単位で返すので、39 日になると、1 か月と 9 日であることがわかり、次のようになります。This comment was posted 1 Month and 9 Days agoメッセージ。これらのシナリオは両方とも同じ開始日と同じ現在/終了日を持っていることを思い出してください。はい、Microsoft TimeSpan オブジェクトは、2013 年 2 月を考慮する必要があることを伝えることを許可しないことで、完全に異なる TimeSpan を与えました。丸2日。それは事実上、私たちに嘘をつきました。

問題は、人々がこれを信じるだろうということであり、彼らがどのような認識を持っているか、過去の認識がどのように変化するか、過去の出来事を自分の心の中で再構築しようとするときに下す決定と人生の選択を誰が知っているかということです。今日どこにでも蔓延している時間を表現することの欠点と固有の失敗に気づき、理解する. 彼らは、先月が 30 日、29 日、28 日ではなく 31 日だったこと、またはその逆であることをプログラミング言語が認識しない (または気にしない) こと、および TimeSpan を増やすとこれが加算されることを理解できません。

これが、この投稿の中心にある問題です。私は、ほとんどの人がこの違いを気にしないことを理解しています (しかし、私たちの何人かは気にしていて、これを背負うことができないことを確認してください)。気にしなければよかったのに、時間、ストレス、失望を節約できたでしょう。これが問題にならない場合は、この関数を使用して、相対時間を効率的にテキスト表示することができます (秒から年まで 1 ~ 6 ノードにカスタマイズ可能)。

がっかりしたことに、実際の timespan オブジェクトがないことに気付きました。timespan を取得し、何も取得し.yearsない場合は、timeSpan オブジェクトには何月かを伝えるものがないため、.months取得して下げるだけです。.daysまたは timeSpan が作成された年。したがって、各月の日は 1 年にわたって変化し、閏年ではさらに変化するため、実際には何月経過したかを知ることはできません。

これに対応して、正確な読み取り値を取得し、ASP.NET Web ページで次のようなものを返すことができるようにするために開発した関数を投稿します...

4 年 3 か月 14 日 15 時間 18 分 24 秒前の投稿

…があるだろうと思った

timeSpan.GetActualNumberOf[Months/Days/Hours/etc](もちろん、基準日を指定する必要があります)

… このデータ型の type メソッドですが、ありませんでした。

実際に必要なのは、timeSpan オブジェクトに別のプロパティを作成して、差が計算された基準日を与えることだけです。そうすれば、上記の素敵な文字列は非常に簡単に計算でき、.year&.monthが存在します!

更新:以下の回答で、公式の回答とコードの使用方法の詳細を大幅に拡張および更新しました。

4

8 に答える 8

29

平均値を使用して C# でこれにいくつかの拡張メソッドを追加する方法を次に示します。

public static class TimeSpanExtensions
{
    public static int GetYears(this TimeSpan timespan)
    {
        return (int)(timespan.Days/365.2425);
    }
    public static int GetMonths(this TimeSpan timespan)
    {
        return (int)(timespan.Days/30.436875);
    }
}
于 2009-12-18T00:32:35.750 に答える
11

あなたが探しているのは、実際には何TimeSpanを表すものではありません。 ベースまたはTimeSpanに関係なく、間隔をティック数として表します。DateTimeCalendar

ここでは、 base 、 target 、および必要に応じて(デフォルトで CultureInfo.CurrentCulture) を使用して、さまざまな差分コンポーネント (年、月など) を計算するDateDifferenceコンストラクターまたはファクトリ メソッドを使用して、新しい型を使用する方が理にかなっています。DateTimeDateTimeCalendar

編集:野田時間にはこれに必要なツールがあるように見えます—Periodクラス「[r]時間、日、週、月などの人間の年代順で表現された期間を表します」、特にPeriod.Between(then, now, PeriodUnits.AllUnits)あなたが求めている正確な計算のようですが、それは必然的に よりもはるかに複雑なクラスですTimeSpan。Noda Time wikiのKey Concepts ページでは、「人間が時間を乱雑にする」方法について説明しています。

天文学と相対性理論のトリッキーな部分は別として、人類は依然として時間を交渉するのに苦労してきました. Unix エポックのティックを使用して時間について話す場合、Noda Time のようなライブラリは必要ありません。

しかし、いいえ、私たちは年、月、日、週で話すのが好きです - そして何らかの理由で、正午 (紛らわしいことに午後 1 時より前に来る) を太陽が最も高い時間とほぼ同じにするのが好きです... そのため、タイムゾーンがあります。

それだけでなく、何ヶ月あるのかについて全員が同意しているわけではありません。文明が異なれば、年を分割する方法も異なり、開始する年の数も異なります。これらは暦体系です

于 2009-12-18T20:06:47.157 に答える
5

これがコードによる主な答えです。日付/時刻の精度、秒と分、または秒、分、日、最大数年までの任意の数を取得できることに注意してください(6つの部分/セグメントが含まれます)。上位 2 つを指定して 1 年以上経過している場合、「1 年 3 か月前」が返され、残りは 2 つのセグメントを要求したため返されません。数時間前の場合は、「2 時間 1 分前」のみが返されます。もちろん、1、2、3、4、5、または 6 セグメントを指定した場合も同じ規則が適用されます (秒、分、時間、日、月、年では 6 つのタイプしか作成できないため、最大 6 までです)。また、「分」と「分」のような文法の問題も修正します。これは、1 分以上であるかどうかに応じて、すべてのタイプで同じであり、「文字列」

使用例を次に示します: bAllowSegments は、表示するセグメントの数を識別します... 例: 3 の場合、返される文字列は (例として)... "3 years, 2 months and 13 days"(上位 3 時間として時間、分、秒は含まれません)カテゴリが返されます)、ただし、日付が数日前などの新しい日付である場合は、"4 days, 1 hour and 13 minutes ago"代わりに同じセグメント (3) を指定すると返されるため、すべてが考慮されます!

bAllowSegments が 2 の場合は が返さ"3 years and 2 months"れ、6 (最大値)の場合は が返されますが、6 つのセグメントを指定しても上位 3 つのセグメントに日付データがないことを認識して無視するため、このよう"3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds"になることに注意してください。 、心配しないでください:)。もちろん、0 のセグメントがある場合は、それを考慮して文字列を作成し、「0 時間」の部分を無視してと表示します。楽しんで、よろしかったらコメントください。NEVER RETURN"0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago""3 days and 4 seconds ago"

 Public Function RealTimeUntilNow(ByVal dt As DateTime, Optional ByVal bAllowSegments As Byte = 2) As String
  ' bAllowSegments identifies how many segments to show... ie: if 3, then return string would be (as an example)...
  ' "3 years, 2 months and 13 days" the top 3 time categories are returned, if bAllowSegments is 2 it would return
  ' "3 years and 2 months" and if 6 (maximum value) would return "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds"
  Dim rYears, rMonths, rDays, rHours, rMinutes, rSeconds As Int16
  Dim dtNow = DateTime.Now
  Dim daysInBaseMonth = Date.DaysInMonth(dt.Year, dt.Month)

  rYears = dtNow.Year - dt.Year
  rMonths = dtNow.Month - dt.Month
  If rMonths < 0 Then rMonths += 12 : rYears -= 1 ' add 1 year to months, and remove 1 year from years.
  rDays = dtNow.Day - dt.Day
  If rDays < 0 Then rDays += daysInBaseMonth : rMonths -= 1
  rHours = dtNow.Hour - dt.Hour
  If rHours < 0 Then rHours += 24 : rDays -= 1
  rMinutes = dtNow.Minute - dt.Minute
  If rMinutes < 0 Then rMinutes += 60 : rHours -= 1
  rSeconds = dtNow.Second - dt.Second
  If rSeconds < 0 Then rSeconds += 60 : rMinutes -= 1

  ' this is the display functionality
  Dim sb As StringBuilder = New StringBuilder()
  Dim iSegmentsAdded As Int16 = 0

  If rYears > 0 Then sb.Append(rYears) : sb.Append(" year" & If(rYears <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMonths > 0 Then sb.AppendFormat(rMonths) : sb.Append(" month" & If(rMonths <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rDays > 0 Then sb.Append(rDays) : sb.Append(" day" & If(rDays <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rHours > 0 Then sb.Append(rHours) : sb.Append(" hour" & If(rHours <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMinutes > 0 Then sb.Append(rMinutes) : sb.Append(" minute" & If(rMinutes <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rSeconds > 0 Then sb.Append(rSeconds) : sb.Append(" second" & If(rSeconds <> 1, "s", "") & "") : iSegmentsAdded += 1

parseAndReturn:

  ' if the string is entirely empty, that means it was just posted so its less than a second ago, and an empty string getting passed will cause an error
  ' so we construct our own meaningful string which will still fit into the "Posted * ago " syntax...

  If sb.ToString = "" Then sb.Append("less than 1 second")

  Return ReplaceLast(sb.ToString.TrimEnd(" ", ",").ToString, ",", " and")

 End Function

もちろん、ソース文字列、置換する必要があるものを指定する引数、および置換するものを指定する別の引数を取る「ReplaceLast」関数が必要になります。これは、その文字列の最後の出現のみを置換します...持っていない、または実装したくない場合は、私のものを含めました。これは、変更を必要とせずに「そのまま」機能します。私はreverseit機能がもはや必要でないことを知っています(.netに存在します)が、ReplaceLastとReverseIt機能は.net以前の日から引き継がれているので、それがどのように古く見えるかを許してください(まだ100%動作しますが、使用していますem は 10 年以上、バグがないことを保証できます)... :)。また、VB6 を使用している場合は、StrReverse (.ReverseIt 拡張メソッドで拡張された文字列をラップする) を使用できます。ReverseIt() 関数 (拡張メソッドとして提供) を使用する代わりに。したがって、sReplacable.ReverseIt を実行する代わりに、StrReverse(sReplacable) を実行します。StrReverse() は組み込みの VB6 関数です (まったく同じことを行い、指定された文字列を反転し、それ以上のことはしません)。汎用の ReverseIt 関数の代わりに StrReverse() を使用する場合は、ReverseIt 関数/拡張機能を自由に削除してください。従来の ms-visualbasic-dll ライブラリをインポートしている限り、StrReverse() 関数は .NET で使用できるはずです。どちらの方法でも違いはありません.StrReverse()関数が存在することを知る前に私はReverseIt()を書いていました. StrReverse) - 実際、StrReverse (または同様の新しい . 文字列反転関数の NET 固有のバージョン) は、より効率的に記述されるでしょう :)。乾杯。

<Extension()> _ 
Public Function ReplaceLast(ByVal sReplacable As String, ByVal sReplaceWhat As String, ByVal sReplaceWith As String) As String 
    ' let empty string arguments run, incase we dont know if we are sending and empty string or not. 
    sReplacable = sReplacable.ReverseIt 
    sReplacable = Replace(sReplacable, sReplaceWhat.ReverseIt, sReplaceWith.ReverseIt, , 1) ' only does first item on reversed version! 
    Return sReplacable.ReverseIt.ToString 
End Function 

<Extension()> _ 
Public Function ReverseIt(ByVal strS As String, Optional ByVal n As Integer = -1) As String 
    Dim strTempX As String = "", intI As Integer 

    If n > strS.Length Or n = -1 Then n = strS.Length 

    For intI = n To 1 Step -1 
        strTempX = strTempX + Mid(strS, intI, 1) 
    Next intI 

    ReverseIt = strTempX + Right(strS, Len(strS) - n) 

End Function 
于 2009-12-24T01:32:59.517 に答える
1

つまり、2008 年 1 月 1 日午前 1 時 31 分から 2008 年 2 月 3 日午前 6 時 45 分までの時間は、2 月 5 日から 2 月 5 日までの時間と同じです。 、2008 年午後 1 時 45 分、2008 年 3 月 9 日午後 6 時 59 分。あなたが探しているのは、実際には2つの日時の違いです。

.MakeMagicHappen.gimmeSomethingPretty.surelyMShasThoughtAboutThisDilema を使用してシステムの特定のニーズを満たすため、人々はあなたをプログラマーとして雇います。使用しているフレームワークが完全にすべてを実行する場合、会社はボタンを 1 つ押すだけでシステムが完全に形成され、他のプログラマーと同様に失業の危機に瀕することになります。

于 2009-12-16T18:32:20.893 に答える
1

次のメソッドは、フレームワークの日付計算に基づいており、Facebook のもののような読み取り可能な経過時間文字列を返すため、かなり信頼できて簡単だと思います。ポルトガル語の単語と複数形の扱いについては申し訳ありませんが、私の場合は必要でした。

public static string ElapsedTime(DateTime dtEvent)
{
    TimeSpan TS = DateTime.Now - dtEvent;

    int intYears = TS.Days / 365;
    int intMonths = TS.Days / 30;
    int intDays = TS.Days;
    int intHours = TS.Hours;
    int intMinutes = TS.Minutes;
    int intSeconds = TS.Seconds;

    if (intYears > 0) return String.Format("há {0} {1}", intYears, (intYears == 1) ? "ano" : "anos");
    else if (intMonths > 0) return String.Format("há {0} {1}", intMonths, (intMonths == 1) ? "mês" : "meses");
    else if (intDays > 0) return String.Format("há {0} {1}", intDays, (intDays == 1) ? "dia" : "dias");
    else if (intHours > 0) return String.Format("há ± {0} {1}", intHours, (intHours == 1) ? "hora" : "horas");
    else if (intMinutes > 0) return String.Format("há ± {0} {1}", intMinutes, (intMinutes == 1) ? "minuto" : "minutos");
    else if (intSeconds > 0) return String.Format("há ± {0} {1}", intSeconds, (intSeconds == 1) ? "segundo" : "segundos");
    else
    {
        return String.Format("em {0} às {1}", dtEvent.ToShortDateString(), dtEvent.ToShortTimeString());
    }
}
于 2011-05-23T19:01:40.387 に答える