1,409


536

C#で相対時間を計算する

特定の `DateTime`値が与えられたとき、どうやって相対時間を表示するのですか:

  • 2時間前

  • 3日前

  • 一ヶ月前

36 Answer


923


Jeff、 あなたのコードは素晴らしいですが、定数を使うとより明確になるかもしれません(コード補完で提案されるように)。

const int SECOND = 1;
const int MINUTE = 60 * SECOND;
const int HOUR = 60 * MINUTE;
const int DAY = 24 * HOUR;
const int MONTH = 30 * DAY;

var ts = new TimeSpan(DateTime.UtcNow.Ticks - yourDate.Ticks);
double delta = Math.Abs(ts.TotalSeconds);

if (delta < 1 * MINUTE)
  return ts.Seconds == 1 ? "one second ago" : ts.Seconds + " seconds ago";

if (delta < 2 * MINUTE)
  return "a minute ago";

if (delta < 45 * MINUTE)
  return ts.Minutes + " minutes ago";

if (delta < 90 * MINUTE)
  return "an hour ago";

if (delta < 24 * HOUR)
  return ts.Hours + " hours ago";

if (delta < 48 * HOUR)
  return "yesterday";

if (delta < 30 * DAY)
  return ts.Days + " days ago";

if (delta < 12 * MONTH)
{
  int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
  return months <= 1 ? "one month ago" : months + " months ago";
}
else
{
  int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
  return years <= 1 ? "one year ago" : years + " years ago";
}


358


jquery.timeagoプラグイン

Jeff、Stack OverflowはjQueryを多用しているので、https://timeago.yarp.com/ [jquery.timeagoプラグイン]をお勧めします。

利点:

  • ページが開かれた場合でも、「1分前」のタイムスタンプを避けます 10分前; timeagoは自動的に更新されます。

  • ページやフラグメントのキャッシュを最大限に活用できます タイムスタンプはサーバー上で計算されないため、Webアプリケーション。

  • あなたはクールな子供たちのようにマイクロフォーマットを使うようになる。

DOMのタイムスタンプに添付するだけです。

jQuery(document).ready(function() {
    jQuery('abbr.timeago').timeago();
});

これはすべての `abbr`要素をtimeagoクラスとhttps://en.wikipedia.org/wiki/ISO_8601 [ISO 8601]タイムスタンプをタイトルに含むようにします。

July 17, 2008

このようなものに:

4 months ago

4ヶ月前。 時間が経過すると、タイムスタンプは自動的に更新されます。

_免責事項:私はこのプラグインを書いたので、偏っています。


326


これが私のやり方です

var ts = new TimeSpan(DateTime.UtcNow.Ticks - dt.Ticks);
double delta = Math.Abs(ts.TotalSeconds);

if (delta < 60)
{
  return ts.Seconds == 1 ? "one second ago" : ts.Seconds + " seconds ago";
}
if (delta < 120)
{
  return "a minute ago";
}
if (delta < 2700) // 45 * 60
{
  return ts.Minutes + " minutes ago";
}
if (delta < 5400) // 90 * 60
{
  return "an hour ago";
}
if (delta < 86400) // 24 * 60 * 60
{
  return ts.Hours + " hours ago";
}
if (delta < 172800) // 48 * 60 * 60
{
  return "yesterday";
}
if (delta < 2592000) // 30 * 24 * 60 * 60
{
  return ts.Days + " days ago";
}
if (delta < 31104000) // 12 * 30 * 24 * 60 * 60
{
  int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
  return months <= 1 ? "one month ago" : months + " months ago";
}
int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
return years <= 1 ? "one year ago" : years + " years ago";

提案? コメント? このアルゴリズムを改良する方法は?


88


public static string RelativeDate(DateTime theDate)
{
    Dictionary thresholds = new Dictionary();
    int minute = 60;
    int hour = 60 * minute;
    int day = 24 * hour;
    thresholds.Add(60, "{0} seconds ago");
    thresholds.Add(minute * 2, "a minute ago");
    thresholds.Add(45 * minute, "{0} minutes ago");
    thresholds.Add(120 * minute, "an hour ago");
    thresholds.Add(day, "{0} hours ago");
    thresholds.Add(day * 2, "yesterday");
    thresholds.Add(day * 30, "{0} days ago");
    thresholds.Add(day * 365, "{0} months ago");
    thresholds.Add(long.MaxValue, "{0} years ago");
    long since = (DateTime.Now.Ticks - theDate.Ticks) / 10000000;
    foreach (long threshold in thresholds.Keys)
    {
        if (since < threshold)
        {
            TimeSpan t = new TimeSpan((DateTime.Now.Ticks - theDate.Ticks));
            return string.Format(thresholds[threshold], (t.Days > 365 ? t.Days / 365 : (t.Days > 0 ? t.Days : (t.Hours > 0 ? t.Hours : (t.Minutes > 0 ? t.Minutes : (t.Seconds > 0 ? t.Seconds : 0))))).ToString());
        }
    }
    return "";
}

私はその簡潔さと新しい目盛り点を追加する能力のためにこのバージョンを好みます。 これは、その長い1ライナーの代わりにTimespanの `Latest()`拡張子でカプセル化することができますが、投稿を簡潔にするために、これを行います。 *これは1時間前、1時間前、2時間が経過するまで1時間を提供することによって修正します。


70


これは、Jeffs Script for PHPの書き直しです。

define("SECOND", 1);
define("MINUTE", 60 * SECOND);
define("HOUR", 60 * MINUTE);
define("DAY", 24 * HOUR);
define("MONTH", 30 * DAY);
function relativeTime($time)
{
    $delta = time() - $time;

    if ($delta < 1 * MINUTE)
    {
        return $delta == 1 ? "one second ago" : $delta . " seconds ago";
    }
    if ($delta < 2 * MINUTE)
    {
      return "a minute ago";
    }
    if ($delta < 45 * MINUTE)
    {
        return floor($delta / MINUTE) . " minutes ago";
    }
    if ($delta < 90 * MINUTE)
    {
      return "an hour ago";
    }
    if ($delta < 24 * HOUR)
    {
      return floor($delta / HOUR) . " hours ago";
    }
    if ($delta < 48 * HOUR)
    {
      return "yesterday";
    }
    if ($delta < 30 * DAY)
    {
        return floor($delta / DAY) . " days ago";
    }
    if ($delta < 12 * MONTH)
    {
      $months = floor($delta / DAY / 30);
      return $months <= 1 ? "one month ago" : $months . " months ago";
    }
    else
    {
        $years = floor($delta / DAY / 365);
        return $years <= 1 ? "one year ago" : $years . " years ago";
    }
}


64


public static string ToRelativeDate(DateTime input)
{
    TimeSpan oSpan = DateTime.Now.Subtract(input);
    double TotalMinutes = oSpan.TotalMinutes;
    string Suffix = " ago";

    if (TotalMinutes < 0.0)
    {
        TotalMinutes = Math.Abs(TotalMinutes);
        Suffix = " from now";
    }

    var aValue = new SortedList>();
    aValue.Add(0.75, () => "less than a minute");
    aValue.Add(1.5, () => "about a minute");
    aValue.Add(45, () => string.Format("{0} minutes", Math.Round(TotalMinutes)));
    aValue.Add(90, () => "about an hour");
    aValue.Add(1440, () => string.Format("about {0} hours", Math.Round(Math.Abs(oSpan.TotalHours)))); // 60 * 24
    aValue.Add(2880, () => "a day"); // 60 * 48
    aValue.Add(43200, () => string.Format("{0} days", Math.Floor(Math.Abs(oSpan.TotalDays)))); // 60 * 24 * 30
    aValue.Add(86400, () => "about a month"); // 60 * 24 * 60
    aValue.Add(525600, () => string.Format("{0} months", Math.Floor(Math.Abs(oSpan.TotalDays / 30)))); // 60 * 24 * 365
    aValue.Add(1051200, () => "about a year"); // 60 * 24 * 365 * 2
    aValue.Add(double.MaxValue, () => string.Format("{0} years", Math.Floor(Math.Abs(oSpan.TotalDays / 365))));

    return aValue.First(n => TotalMinutes < n.Key).Value.Invoke() + Suffix;
}

C#6バージョン

静的読み取り専用SortedList> offset = new SortedList> {{0.75、_ => "1分未満"}、{1.5、_ => "約1分"}、{45、x => $ "{x.TotalMinutes: F0}分 "}、{90、x =>"約1時間 "}、{1440、x => $"約{x.TotalHours:F0}時間 "}、{2880、x =>" 1日 "} 、{43200、x => $ "{x.TotalDays:F0}日"}、{86400、x => "約1ヶ月"}、{525600、x => $ "{x.TotalDays / 30:F0}月 "}、{1051200、x =>"約1年 "}、{double.MaxValue、x => $" {x.TotalDays / 365:F0}年 "}};

public static string ToRelativeDate(このDateTime入力){TimeSpan x = DateTime.Now  - 入力;文字列Suffix = x.TotalMinutes> 0? "前": "今から"; x = new TimeSpan(Math.Abs​​(x.Ticks)); first(n => x.TotalMinutes <n.Key).Value(x)サフィックス。 }


49


これは、DateTimeクラスに拡張メソッドとして追加した実装で、将来と過去の両方の日付を処理し、探している詳細レベルを指定できる近似オプションを提供します( "3時間前"と "3時間"。 23分12秒前 "):

using System.Text;

///
/// Compares a supplied date to the current date and generates a friendly English
/// comparison ("5 days ago", "5 days from now")
///
/// The date to convert
/// When off, calculate timespan down to the second.
/// When on, approximate to the largest round unit of time.
///
public static string ToRelativeDateString(this DateTime value, bool approximate)
{
    StringBuilder sb = new StringBuilder();

    string suffix = (value > DateTime.Now) ? " from now" : " ago";

    TimeSpan timeSpan = new TimeSpan(Math.Abs(DateTime.Now.Subtract(value).Ticks));

    if (timeSpan.Days > 0)
    {
        sb.AppendFormat("{0} {1}", timeSpan.Days,
          (timeSpan.Days > 1) ? "days" : "day");
        if (approximate) return sb.ToString() + suffix;
    }
    if (timeSpan.Hours > 0)
    {
        sb.AppendFormat("{0}{1} {2}", (sb.Length > 0) ? ", " : string.Empty,
          timeSpan.Hours, (timeSpan.Hours > 1) ? "hours" : "hour");
        if (approximate) return sb.ToString() + suffix;
    }
    if (timeSpan.Minutes > 0)
    {
        sb.AppendFormat("{0}{1} {2}", (sb.Length > 0) ? ", " : string.Empty,
          timeSpan.Minutes, (timeSpan.Minutes > 1) ? "minutes" : "minute");
        if (approximate) return sb.ToString() + suffix;
    }
    if (timeSpan.Seconds > 0)
    {
        sb.AppendFormat("{0}{1} {2}", (sb.Length > 0) ? ", " : string.Empty,
          timeSpan.Seconds, (timeSpan.Seconds > 1) ? "seconds" : "second");
        if (approximate) return sb.ToString() + suffix;
    }
    if (sb.Length == 0) return "right now";

    sb.Append(suffix);
    return sb.ToString();
}


38


クライアント側でもこれを計算することをお勧めします。 サーバーの仕事量が少なくなります。

以下は私が使っているバージョンです(Zach Leathermanより)

/*
 *
Javascript Humane Dates
 *
Copyright (c) 2008 Dean Landolt (deanlandolt.com)
 *
Re-write by Zach Leatherman (zachleat.com)
 *
 *
Adopted from the John Resig's pretty.js
 *
at http://ejohn.org/blog/javascript-pretty-date
 *
and henrah's proposed modification
 *
at http://ejohn.org/blog/javascript-pretty-date/#comment-297458
 *
 *
Licensed under the MIT license.
 */

function humane_date(date_str){
        var time_formats = [
                [60, 'just now'],
                [90, '1 minute'], // 60*1.5
                [3600, 'minutes', 60], // 60*60, 60
                [5400, '1 hour'], // 60*60*1.5
                [86400, 'hours', 3600], // 60*60*24, 60*60
                [129600, '1 day'], // 60*60*24*1.5
                [604800, 'days', 86400], // 60*60*24*7, 60*60*24
                [907200, '1 week'], // 60*60*24*7*1.5
                [2628000, 'weeks', 604800], // 60*60*24*(365/12), 60*60*24*7
                [3942000, '1 month'], // 60*60*24*(365/12)*1.5
                [31536000, 'months', 2628000], // 60*60*24*365, 60*60*24*(365/12)
                [47304000, '1 year'], // 60*60*24*365*1.5
                [3153600000, 'years', 31536000], // 60*60*24*365*100, 60*60*24*365
                [4730400000, '1 century'] // 60*60*24*365*100*1.5
        ];

        var time = ('' + date_str).replace(/-/g,"/").replace(/[TZ]/g," "),
                dt = new Date,
                seconds = ((dt - new Date(time) + (dt.getTimezoneOffset() * 60000)) / 1000),
                token = ' ago',
                i = 0,
                format;

        if (seconds < 0) {
                seconds = Math.abs(seconds);
                token = '';
        }

        while (format = time_formats[i++]) {
                if (seconds < format[0]) {
                        if (format.length == 2) {
                                return format[1] + (i > 1 ? token : ''); // Conditional so we don't return Just Now Ago
                        } else {
                                return Math.round(seconds / format[2]) + ' ' + format[1] + (i > 1 ? token : '');
                        }
                }
        }

        // overflow for centuries
        if(seconds > 4730400000)
                return Math.round(seconds / 4730400000) + ' centuries' + token;

        return date_str;
};

if(typeof jQuery != 'undefined') {
        jQuery.fn.humane_dates = function(){
                return this.each(function(){
                        var date = humane_date(this.title);
                        if(date && jQuery(this).text() != date) // don't modify the dom if we don't have to
                                jQuery(this).text(date);
                });
        };
}


32


NugetのHumanizerというパッケージもあり、実際にはとてもうまく機能します。

DateTime.UtcNow.AddHours(-30).Humanize()=> "昨日" DateTime.UtcNow.AddHours(-2).Humanize()=> "2時間前"

DateTime.UtcNow.AddHours(30).Humanize()=> "明日" DateTime.UtcNow.AddHours(2).Humanize()=> "2時間後"

TimeSpan.FromMilliseconds(1299630020).Humanize()=> "2週間" TimeSpan.FromMilliseconds(1299630020).Humanize(3)=> "2週間、1日、1時間"

Scott Hanselmanが彼の blogに書いています。


30


@jeff

私見あなたのものは少し長いようです。 しかし、それは "昨日"と "年"のサポートでもう少し堅牢に見えます。 しかし、これが使用されるときの私の経験では、人は最初の30日以内にコンテンツを見る可能性が最も高いです。 それ以降に来るのは本当にハードコアな人々だけです。 だから私は通常これを短く単純にすることを選びます。

これは私が現在私のウェブサイトの一つで使っている方法です。 これは相対的な日、時、時間のみを返します。 そして、ユーザーは出力の中の「前」を平手打ちする必要があります。

public static string ToLongString(このTimeSpanの時刻){string output = String.Empty;

if(time.Days> 0)出力= time.Days "days";

if((time.Days == 0 || time.Days == 1)

if(time.Days == 0

if(output.Length == 0)output = time秒「sec」;

出力を返します。Trim(); }