Câu hỏi Tính thời gian tương đối trong C #


Cho một cụ thể DateTime giá trị, làm cách nào để hiển thị thời gian tương đối, như:

  • 2 giờ trước
  • 3 ngày trước
  • một tháng trước

1344
2017-09-08 14:02


gốc


Điều gì sẽ xảy ra nếu bạn muốn tính thời gian tương đối từ nay sang tương lai? - Jhonny D. Cano -Leftware-
moment.js là một thư viện phân tích cú pháp ngày rất đẹp. Bạn có thể xem xét sử dụng thư viện đó (phía máy chủ hoặc phía máy khách), tùy thuộc vào nhu cầu của bạn. chỉ fyi bởi vì không ai đề cập đến nó ở đây - code ninja
Có gói .net github.com/NickStrupat/TimeAgo mà khá nhiều những gì đang được hỏi. - Rossco


Các câu trả lời:


Jeff, ma cua ban là tốt đẹp nhưng có thể rõ ràng hơn với các hằng số (như được đề xuất trong Mã Hoàn thành).

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";
}

891
2017-09-08 14:02



Tôi ghét những hằng số như thế với niềm đam mê. Cái này có sai với ai không? Thread.Sleep(1 * MINUTE)? Bởi vì nó sai bởi hệ số 1000. - Roman Starkov
const int SECOND = 1; Lạ thật một giây là một giây. - seriousdev
Loại mã này gần như không thể bản địa hóa. Nếu ứng dụng của bạn chỉ cần giữ nguyên tiếng Anh, thì tốt thôi. Nhưng nếu bạn nhảy sang các ngôn ngữ khác, bạn sẽ ghét bản thân mình vì làm logic như thế này. Chỉ cần y'all biết ... - Nik Reiman
Tôi nghĩ rằng nếu các hằng số được đổi tên để mô tả chính xác giá trị trong đó, nó sẽ dễ hiểu hơn. Vì vậy, SecondsPerMinute = 60; MinutesPerHour = 60; SecondsPerHour = MinutesPerHour * SecondsPerHour; vv Chỉ cần gọi nó MINUTE = 60 không cho phép người đọc xác định giá trị là gì. - slolife
Tại sao không ai (trừ Joe) quan tâm đến giá trị 'Ngày hôm qua' hoặc 'ngày trước' sai? Hôm qua không phải là một phép tính giờ, mà là tính toán hàng ngày. Vì vậy, có, đây là một mã sai ít nhất trong hai trường hợp thường xuyên. - CtrlX


jquery.timeago plugin

Jeff, vì Stack Overflow sử dụng jQuery rộng rãi, tôi khuyên bạn nên jquery.timeago plugin.

Lợi ích:

  • Tránh dấu thời gian ngày "1 phút trước" mặc dù trang đã được mở cách đây 10 phút; timeago tự động làm mới.
  • Bạn có thể tận dụng tối đa bộ nhớ đệm trang và / hoặc đoạn trong các ứng dụng web của bạn, vì các dấu thời gian không được tính toán trên máy chủ.
  • Bạn có thể sử dụng microformats như những đứa trẻ tuyệt vời.

Chỉ cần đính kèm nó vào dấu thời gian của bạn trên DOM đã sẵn sàng:

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

Điều này sẽ biến tất cả abbr các yếu tố với một lớp thời gian và một ISO 8601 dấu thời gian trong tiêu đề:

<abbr class="timeago" title="2008-07-17T09:24:17Z">July 17, 2008</abbr>

vào một cái gì đó như thế này:

<abbr class="timeago" title="July 17, 2008">4 months ago</abbr>

sản lượng: 4 tháng trước. Khi thời gian trôi qua, dấu thời gian sẽ tự động cập nhật.

Disclaimer: Tôi đã viết plugin này, vì vậy tôi thiên vị.


345



Seb, Nếu bạn đã tắt Javascript, thì chuỗi bạn đặt ban đầu giữa các thẻ abbr sẽ được hiển thị. Thông thường, đây chỉ là ngày hoặc giờ ở bất kỳ định dạng nào bạn muốn. Timeago xuống cấp một cách duyên dáng. Nó không đơn giản hơn nhiều. - Ryan McGeary
Ryan, tôi đề nghị rằng SO sử dụng timeago một thời gian trước đây. Câu trả lời của Jeff khiến tôi khóc, tôi đề nghị bạn ngồi xuống: stackoverflow.uservoice.com/pages/1722-general/suggestions/… - Rob Fonseca-Ensor
Heh, Cảm ơn Rob. Không sao đâu. Nó hầu như không đáng chú ý, đặc biệt là khi chỉ có một số thay đổi trong quá trình chuyển đổi, mặc dù các trang SO có nhiều dấu thời gian. Tôi đã có thể nghĩ rằng ông sẽ có ít nhất đánh giá cao những lợi ích của bộ nhớ đệm trang mặc dù, ngay cả khi ông chọn để tránh tự động cập nhật. Tôi chắc rằng Jeff cũng đã cung cấp phản hồi để cải thiện plugin. Tôi nhận được sự an ủi khi biết các trang web như arstechnica.com sử dụng nó. - Ryan McGeary
@Rob Fonseca-Ensor - bây giờ nó làm cho tôi khóc quá. Cách cập nhật một lần mỗi phút, để hiển thị thông tin chính xác, theo bất kỳ cách nào liên quan đến văn bản nhấp nháy mỗi giây một lần? - Daniel Earwicker
Câu hỏi là về C #, tôi không thấy làm thế nào một plugin jQuery có liên quan. - BartoszKP


Đây là cách tôi làm điều đó

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";

Gợi ý? Bình luận? Cách để cải thiện thuật toán này?


320



"<48 * 60 * 60" là một định nghĩa khá độc đáo cho "ngày hôm qua". Nếu là 9 giờ sáng Thứ Tư, bạn có thực sự nghĩ đến 9:01 sáng thứ Hai là "ngày hôm qua" hay không. Tôi đã nghĩ rằng một thuật toán cho ngày hôm qua hoặc "n ngày trước" nên xem xét trước / sau nửa đêm. - Joe
Các trình biên dịch thường khá tốt khi tính toán các biểu thức hằng số, như 24 * 60 * 60, vì vậy bạn có thể trực tiếp sử dụng chúng thay vì tự tính toán nó thành 86400 và đặt biểu thức gốc trong các chú thích - zvolkov
nhận thấy rằng chức năng này không bao gồm tuần - jray
@ bzlm Tôi nghĩ rằng tôi đã làm cho một dự án tôi đã làm việc trên. Động lực của tôi ở đây là để cảnh báo người khác rằng tuần bị bỏ qua từ mẫu mã này. Làm thế nào để làm điều đó, nó có vẻ khá thẳng về phía tôi. - jray
Tôi nghĩ rằng cách tốt để cải thiện thuật toán là hiển thị 2 đơn vị như "2 tháng 21 ngày trước", "1 giờ 40 phút trước" để tăng độ chính xác. - Evgeny Levin


public static string RelativeDate(DateTime theDate)
{
    Dictionary<long, string> thresholds = new Dictionary<long, string>();
    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 "";
}

Tôi thích phiên bản này cho sự phù hợp của nó, và khả năng thêm vào các điểm đánh dấu mới. Điều này có thể được đóng gói với một Latest() mở rộng cho Timespan thay vì đó 1 lớp lót dài, nhưng vì lợi ích của ngắn gọn trong việc đăng bài, điều này sẽ làm. Điều này khắc phục cách đây một giờ, cách đây 1 giờ, bằng cách cung cấp một giờ cho đến khi 2 giờ trôi qua


84



Tôi đang nhận được tất cả các loại vấn đề bằng cách sử dụng chức năng này, ví dụ nếu bạn giả 'theDate = DateTime.Now.AddMinutes (-40);' Tôi nhận được '40 giờ trước ', nhưng với phản ứng tái cấu trúc của Michael, nó trả về chính xác ở '40 phút trước'? - GONeale
tôi nghĩ rằng bạn đang thiếu một số không, hãy thử: dài kể từ = (DateTime.Now.Ticks - theDate.Ticks) / 10000000; - robnardo
Hmm, trong khi mã này có thể hoạt động không chính xác và không hợp lệ để giả định rằng thứ tự của các khóa trong Từ điển sẽ theo một thứ tự cụ thể. Từ điển sử dụng Object.GetHashCode () mà không trả về một thời gian dài nhưng một int !. Nếu bạn muốn chúng được sắp xếp thì bạn nên sử dụng SortedList <long, string>. Điều gì là sai với các ngưỡng được đánh giá trong một tập hợp nếu / else nếu /.../ khác? Bạn nhận được cùng một số lượng so sánh. FYI băm cho long.MaxValue hóa ra là giống như int.MinValue! - CodeMonkeyKing
OP quên t.Days> 30? t.Thời gian / 30: - Lars Holm Jensen
Để khắc phục vấn đề được đề cập bởi @CodeMonkeyKing, bạn có thể sử dụng SortedDictionary thay vì một đồng bằng Dictionary: Việc sử dụng là như nhau, nhưng nó đảm bảo rằng các phím được sắp xếp. Nhưng ngay cả sau đó, thuật toán có sai sót, bởi vì RelativeDate(DateTime.Now.AddMonths(-3).AddDays(-3)) trả về "95 tháng trước", bất kể loại từ điển bạn đang sử dụng, không chính xác (nó sẽ trả về "3 tháng trước" hoặc "4 tháng trước" tùy thuộc vào ngưỡng bạn đang sử dụng) - ngay cả khi -3 không tạo ngày trong quá khứ năm (Tôi đã thử nghiệm này trong tháng mười hai, vì vậy trong trường hợp này nó không nên xảy ra). - Matt


Ở đây viết lại từ Jeffs Script cho 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";
    }
}    

68



Câu hỏi là Đã gắn thẻ C # Tại sao Mã PHP ? - Kiquenet


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<double, Func<string>>();
    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;
}

http://refactormycode.com/codes/493-twitter-esque-relative-dates

Phiên bản C # 6:

static readonly SortedList<double, Func<TimeSpan, string>> offsets = 
   new SortedList<double, Func<TimeSpan, string>>
{
    { 0.75, _ => "less than a minute"},
    { 1.5, _ => "about a minute"},
    { 45, x => $"{x.TotalMinutes:F0} minutes"},
    { 90, x => "about an hour"},
    { 1440, x => $"about {x.TotalHours:F0} hours"},
    { 2880, x => "a day"},
    { 43200, x => $"{x.TotalDays:F0} days"},
    { 86400, x => "about a month"},
    { 525600, x => $"{x.TotalDays / 30:F0} months"},
    { 1051200, x => "about a year"},
    { double.MaxValue, x => $"{x.TotalDays / 365:F0} years"}
};

public static string ToRelativeDate(this DateTime input)
{
    TimeSpan x = DateTime.Now - input;
    string Suffix = x.TotalMinutes > 0 ? " ago" : " from now";
    x = new TimeSpan(Math.Abs(x.Ticks));
    return offsets.First(n => x.TotalMinutes < n.Key).Value(x) + Suffix;
}

61



điều này là rất tốt đẹp IMO :) Điều này cũng có thể được refactored như là một phương pháp mở rộng? từ điển có thể trở thành tĩnh vì vậy nó chỉ được tạo một lần và được tham chiếu từ sau đó? - Pure.Krome
Pure.Krome: stackoverflow.com/questions/11/how-do-i-calculate-relative-time/… - Chris Charabaruk
Bạn có thể muốn kéo từ điển đó ra ngoài một trường để bạn giảm bớt sự khởi tạo và GC. Bạn sẽ phải thay đổi Func<string> đến Func<double>. - Drew Noakes
trong javascript - jsfiddle.net/drzaus/eMUzF - drzaus


Dưới đây là triển khai tôi đã thêm làm phương thức mở rộng cho lớp DateTime xử lý cả ngày trong tương lai và quá khứ và cung cấp tùy chọn xấp xỉ cho phép bạn chỉ định mức độ chi tiết bạn đang tìm kiếm ("3 giờ trước" so với "3 giờ, 23 phút, 12 giây trước "):

using System.Text;

/// <summary>
/// Compares a supplied date to the current date and generates a friendly English 
/// comparison ("5 days ago", "5 days from now")
/// </summary>
/// <param name="date">The date to convert</param>
/// <param name="approximate">When off, calculate timespan down to the second.
/// When on, approximate to the largest round unit of time.</param>
/// <returns></returns>
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();
}

48