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


1514

Đưa ra một DateTimegiá trị cụ thể , 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

80
Nếu bạn muốn tính thời gian tương đối từ bây giờ đến Tương lai thì sao?
Jhonny D. Cano -Leftware-

2
Moment.js là một thư viện phân tích ngày rất đẹp .. Bạn có thể xem xét sử dụng (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 vì không ai nhắc đến nó ở đây
Matej

1
Có gói .net github.com/NickStrupat/TimeAgo , phần lớn thực hiện những gì đang được yêu cầu.
Rossco

Dự án này khá hấp dẫn để định dạng ngày github.com/Humanizr/Humanizer#humanize-datetime
Aaron Hudon

Câu trả lời:


988

Jeff, mã của bạn là tốt nhưng có thể rõ ràng hơn với các hằng số (như được đề xuất trong Hoàn thành mã).

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

220
Tôi ghét những hằng số như vậy với một niềm đam mê. Điều này có vẻ sai với bất cứ ai? Thread.Sleep(1 * MINUTE)? Bởi vì nó sai bởi hệ số 1000.
Roman Starkov

31
const int SECOND = 1;Thật kỳ lạ một giây là một giây.
Severdev

62
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 duy trì bằng 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 chính mình vì đã làm logic như thế này. Chỉ để bạn biết ...
Nik Reiman

73
Tôi nghĩ rằng nếu các hằng số được đổi tên để mô tả chính xác giá trị có trong chúng, thì sẽ dễ hiểu hơn. Vậy SecondsPerMinute = 60; MinutesPerHour = 60; SecondsPerHour = MinutesPerHour * SecondsPerHour; v.v. Chỉ cần gọi nó là MINUTE = 60 không cho phép người đọc xác định giá trị là gì.
slolife

14
Tại sao không ai (ngoại trừ Joe) quan tâm đến giá trị 'Hôm qua' hoặc 'ngày trước' sai ??? Hôm qua không phải là tính toán 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

363

plugin jquery.timeago

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

Những lợi ích:

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

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ả các abbryếu tố với một lớp thời gian và dấu thời gian ISO 8601 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>

mà năng suất: 4 tháng trước. 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ị.


39
Vì vậy, nếu bạn đã tắt Javascript, thì chuỗi ban đầu bạn đặt giữa các thẻ abbr sẽ được hiển thị. Thông thường, đây chỉ là một ngày hoặc thời gian trong 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

23
Ryan, tôi đã đề nghị SO sử dụng timeago một thời gian trước đây. Phản ứng của Jeff làm tôi khóc, tôi đề nghị bạn ngồi xuống: stackoverflow.uservoice.com/pages/1722-general/suggestions/ mẹo
Rob Fonseca-Ensor

7
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ó rất nhiều dấu thời gian. Tôi đã nghĩ rằng ít nhất anh ta sẽ đánh giá cao những lợi ích của việc lưu bộ đệm trang, ngay cả khi anh ta chọn tránh cập nhật tự động. Tôi chắc rằng Jeff cũng có thể cung cấp phản hồi để cải thiện plugin. Tôi an ủi khi biết các trang web như arstechnica.com sử dụng nó.
Ryan McGeary

19
@Rob Fonseca-Ensor - bây giờ nó cũng làm tôi khóc. Làm thế nào là một bản 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 nhấp nháy văn bản một lần một giây?
Daniel Earwicker

25
Câu hỏi là về C #, tôi không thấy plugin jQuery có liên quan như thế nào.
BartoszKP

331

Đâ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?


112
"<48 * 60 * 60s" 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ĩ 9:01 sáng thứ Hai là "ngày hôm qua" 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

139
Trình biên dịch thường khá giỏi trong việc tính toán trước các biểu thức hằng, 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 thành 86400 và đặt biểu thức ban đầu trong các nhận xét
zvolkov

11
@bzlm Tôi nghĩ rằng tôi đã làm cho một dự án mà tôi đang làm. Động lực của tôi ở đây là để cảnh báo cho những người khác rằng các tuần đã bị bỏ qua khỏi mẫu mã này. Làm thế nào để làm điều đó, nó dường như khá thẳng về phía tôi.
jray

9
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

5
@ Jeffy, bạn đã bỏ lỡ tính toán cho năm nhuận và kiểm tra liên quan
Saboor Awan

92
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 vì tính đơn giản và khả năng thêm điểm đánh dấu mới. Điều này có thể được gói gọn với một Latest()phần mở rộng cho Timespan thay vì 1 lớp lót dài đó, nhưng vì lợi ích của việc đăng tải, điều này sẽ làm được. Điều này sửa một giờ trước, 1 giờ trước, bằng cách cung cấp một giờ cho đến khi 2 giờ trôi qua


Tôi đang gặp đủ loại vấn đề khi sử dụng chức năng này, ví dụ nếu bạn giả định 'theDate = DateTime.Now.AddMinutes (-40);' Tôi đang nhận được '40 giờ trước ', nhưng với phản ứng tái cấu trúc của Michael, nó sẽ trả về đúng vào '40 phút trước'?
GONeale

Tôi nghĩ rằng bạn đang thiếu một số không, hãy thử: từ lâu = (DateTime.Now.Ticks - theDate.Ticks) / 10000000;
robnardo

8
Hmm, trong khi mã này có thể hoạt động, nó không chính xác và không hợp lệ khi cho 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 () không trả về lâu mà là int!. Nếu bạn muốn sắp xếp chúng thì bạn nên sử dụng SortedList <long, string>. Điều gì sai với các ngưỡng được đánh giá trong một tập hợp if / other if /.../ other? Bạn nhận được cùng một số so sánh. FYI băm cho lâu.MaxValue hóa ra giống như int.MinValue!
CodeMonkeyKing

OP quên t.Days> 30? t.Days / 30:
Lars Holm Jensen

Để khắc phục sự cố được đề cập bởi @CodeMonkeyKing, bạn có thể sử dụng SortedDictionarythay vì đơn giản Dictionary: Cách sử dụng là như nhau, nhưng nó đảm bảo rằng các khóa được sắp xếp. Nhưng ngay cả khi đó, thuật toán vẫn có lỗi, bởi vì RelativeDate(DateTime.Now.AddMonths(-3).AddDays(-3))trả về "95 tháng trước" , bất kể bạn đang sử dụng loại từ điển nào, không chính xác (nên trả về "3 tháng trước" hoặc "4 tháng trước" tùy thuộc vào ngưỡng nào bạn ' đang sử dụng) - ngay cả khi -3 không tạo ra một ngày trong năm qua (tôi đã thử nghiệm điều này vào tháng 12, vì vậy trong trường hợp này điều đó không nên xảy ra).
Matt

71

Đây là một bản 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";
    }
}    

7
Câu hỏi được gắn thẻ C # Tại sao mã PHP ?
Kiquenet

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

IMO này rất hay :) Điều này cũng có thể được tái cấu trúc như một phương thức mở rộng? từ điển có thể trở thành tĩnh để nó chỉ được tạo một lần và được tham chiếu từ đó sau đó không?
Pure.Krom


5
Bạn có thể muốn kéo từ điển đó ra một trường để bạn giảm thời gian khởi động và khuấy đảo. Bạn sẽ phải thay đổi Func<string>thành Func<double>.
vẽ Noakes

49

Đây là một 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à ngày trước và cung cấp tùy chọn gần đúng 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();
}

38

Tôi cũng khuyên bạn nên tính toán điều này ở phía khách hàng. Ít làm việc cho máy chủ.

Sau đây là phiên bản mà tôi sử dụng (từ 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);
                });
        };
}

4
Câu hỏi được gắn thẻ C # Tại sao mã Javascript ?
Kiquenet

36

Ngoài ra còn có một gói có tên Humanizr trên Nuget và nó thực sự hoạt động rất tốt và nằm trong .NET Foundation.

DateTime.UtcNow.AddHours(-30).Humanize() => "yesterday"
DateTime.UtcNow.AddHours(-2).Humanize() => "2 hours ago"

DateTime.UtcNow.AddHours(30).Humanize() => "tomorrow"
DateTime.UtcNow.AddHours(2).Humanize() => "2 hours from now"

TimeSpan.FromMilliseconds(1299630020).Humanize() => "2 weeks"
TimeSpan.FromMilliseconds(1299630020).Humanize(3) => "2 weeks, 1 day, 1 hour"

Scott Hanselman có một bài viết trên blog của mình


3
lưu ý thân thiện: Trên .net 4.5 trở lên không cài đặt đầy đủ Humanizer ... chỉ cài đặt Humanizer. Thêm một phần của nó .. vì các gói ngôn ngữ khác không được hỗ trợ trên phiên bản này
Ahmad

Rất hữu ích! Câu trả lời này phải cao hơn nhiều trong danh sách này. Nếu tôi có 100 phiếu, tôi sẽ cho nó. Rõ ràng (đến từ vùng đất JS), việc tìm kiếm gói này không hề đơn giản.
kumarharsh

29

@jeff

IMHO của bạn có vẻ hơi dài. Tuy nhiên, nó có vẻ mạnh mẽ hơn một chút với sự hỗ trợ cho "ngày hôm qua" và "năm". Nhưng theo kinh nghiệm của tôi khi sử dụng, người này có khả năng xem nội dung trong 30 ngày đầu tiên. Nó chỉ là những người thực sự khó khăn sau đó. Vì vậy, đó là lý do tại sao tôi thường chọn để giữ cho ngắn và đơn giản này.

Đây là phương pháp tôi hiện đang sử dụng trên một trong những trang web của mình. Điều này chỉ trả về một ngày, giờ, thời gian tương đối. Và sau đó người dùng phải tát vào "trước" trong đầu ra.

public static string ToLongString(this TimeSpan time)
{
    string output = String.Empty;

    if (time.Days > 0)
        output += time.Days + " days ";

    if ((time.Days == 0 || time.Days == 1) && time.Hours > 0)
        output += time.Hours + " hr ";

    if (time.Days == 0 && time.Minutes > 0)
        output += time.Minutes + " min ";

    if (output.Length == 0)
        output += time.Seconds + " sec";

    return output.Trim();
}

24

Một vài năm muộn của bữa tiệc, nhưng tôi có một yêu cầu phải làm điều này cho cả những ngày trong quá khứ và tương lai, vì vậy tôi đã kết hợp của JeffVincent vào việc này. Đó là một ngoại truyện ternarytastic! :)

public static class DateTimeHelper
    {
        private const int SECOND = 1;
        private const int MINUTE = 60 * SECOND;
        private const int HOUR = 60 * MINUTE;
        private const int DAY = 24 * HOUR;
        private const int MONTH = 30 * DAY;

        /// <summary>
        /// Returns a friendly version of the provided DateTime, relative to now. E.g.: "2 days ago", or "in 6 months".
        /// </summary>
        /// <param name="dateTime">The DateTime to compare to Now</param>
        /// <returns>A friendly string</returns>
        public static string GetFriendlyRelativeTime(DateTime dateTime)
        {
            if (DateTime.UtcNow.Ticks == dateTime.Ticks)
            {
                return "Right now!";
            }

            bool isFuture = (DateTime.UtcNow.Ticks < dateTime.Ticks);
            var ts = DateTime.UtcNow.Ticks < dateTime.Ticks ? new TimeSpan(dateTime.Ticks - DateTime.UtcNow.Ticks) : new TimeSpan(DateTime.UtcNow.Ticks - dateTime.Ticks);

            double delta = ts.TotalSeconds;

            if (delta < 1 * MINUTE)
            {
                return isFuture ? "in " + (ts.Seconds == 1 ? "one second" : ts.Seconds + " seconds") : ts.Seconds == 1 ? "one second ago" : ts.Seconds + " seconds ago";
            }
            if (delta < 2 * MINUTE)
            {
                return isFuture ? "in a minute" : "a minute ago";
            }
            if (delta < 45 * MINUTE)
            {
                return isFuture ? "in " + ts.Minutes + " minutes" : ts.Minutes + " minutes ago";
            }
            if (delta < 90 * MINUTE)
            {
                return isFuture ? "in an hour" : "an hour ago";
            }
            if (delta < 24 * HOUR)
            {
                return isFuture ? "in " + ts.Hours + " hours" : ts.Hours + " hours ago";
            }
            if (delta < 48 * HOUR)
            {
                return isFuture ? "tomorrow" : "yesterday";
            }
            if (delta < 30 * DAY)
            {
                return isFuture ? "in " + ts.Days + " days" : ts.Days + " days ago";
            }
            if (delta < 12 * MONTH)
            {
                int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
                return isFuture ? "in " + (months <= 1 ? "one month" : months + " months") : months <= 1 ? "one month ago" : months + " months ago";
            }
            else
            {
                int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
                return isFuture ? "in " + (years <= 1 ? "one year" : years + " years") : years <= 1 ? "one year ago" : years + " years ago";
            }
        }
    }

21

Có một cách dễ dàng để làm điều này trong Java? Các java.util.Datelớp học dường như khá hạn chế.

Đây là giải pháp Java nhanh và bẩn của tôi:

import java.util.Date;
import javax.management.timer.Timer;

String getRelativeDate(Date date) {     
  long delta = new Date().getTime() - date.getTime();
  if (delta < 1L * Timer.ONE_MINUTE) {
    return toSeconds(delta) == 1 ? "one second ago" : toSeconds(delta) + " seconds ago";
  }
  if (delta < 2L * Timer.ONE_MINUTE) {
    return "a minute ago";
  }
  if (delta < 45L * Timer.ONE_MINUTE) {
    return toMinutes(delta) + " minutes ago";
  }
  if (delta < 90L * Timer.ONE_MINUTE) {
    return "an hour ago";
  }
  if (delta < 24L * Timer.ONE_HOUR) {
    return toHours(delta) + " hours ago";
  }
  if (delta < 48L * Timer.ONE_HOUR) {
    return "yesterday";
  }
  if (delta < 30L * Timer.ONE_DAY) {
    return toDays(delta) + " days ago";
  }
  if (delta < 12L * 4L * Timer.ONE_WEEK) { // a month
    long months = toMonths(delta); 
    return months <= 1 ? "one month ago" : months + " months ago";
  }
  else {
    long years = toYears(delta);
    return years <= 1 ? "one year ago" : years + " years ago";
  }
}

private long toSeconds(long date) {
  return date / 1000L;
}

private long toMinutes(long date) {
  return toSeconds(date) / 60L;
}

private long toHours(long date) {
  return toMinutes(date) / 60L;
}

private long toDays(long date) {
  return toHours(date) / 24L;
}

private long toMonths(long date) {
  return toDays(date) / 30L;
}

private long toYears(long date) {
  return toMonths(date) / 365L;
}

1
Câu hỏi được gắn thẻ C # Tại sao mã Java ?
Kiquenet

20

Phiên bản iPhone Objective-C

+ (NSString *)timeAgoString:(NSDate *)date {
    int delta = -(int)[date timeIntervalSinceNow];

    if (delta < 60)
    {
        return delta == 1 ? @"one second ago" : [NSString stringWithFormat:@"%i seconds ago", delta];
    }
    if (delta < 120)
    {
        return @"a minute ago";
    }
    if (delta < 2700)
    {
        return [NSString stringWithFormat:@"%i minutes ago", delta/60];
    }
    if (delta < 5400)
    {
        return @"an hour ago";
    }
    if (delta < 24 * 3600)
    {
        return [NSString stringWithFormat:@"%i hours ago", delta/3600];
    }
    if (delta < 48 * 3600)
    {
        return @"yesterday";
    }
    if (delta < 30 * 24 * 3600)
    {
        return [NSString stringWithFormat:@"%i days ago", delta/(24*3600)];
    }
    if (delta < 12 * 30 * 24 * 3600)
    {
        int months = delta/(30*24*3600);
        return months <= 1 ? @"one month ago" : [NSString stringWithFormat:@"%i months ago", months];
    }
    else
    {
        int years = delta/(12*30*24*3600);
        return years <= 1 ? @"one year ago" : [NSString stringWithFormat:@"%i years ago", years];
    }
}

19

Với thế giới và chồng của cô ấy dường như đang đăng các mẫu mã, đây là những gì tôi đã viết cách đây một thời gian, dựa trên một vài câu trả lời.

Tôi có một nhu cầu cụ thể để mã này có thể bản địa hóa. Vì vậy, tôi có hai lớp - Grammar, trong đó chỉ định các thuật ngữ có thể bản địa hóa, và FuzzyDateExtensions, chứa một loạt các phương thức mở rộng. Tôi không có nhu cầu xử lý các mốc thời gian trong tương lai, vì vậy không có nỗ lực nào được thực hiện để xử lý chúng với mã này.

Tôi đã để lại một số XMLdoc trong nguồn, nhưng đã loại bỏ hầu hết (nơi chúng rõ ràng) vì lý do ngắn gọn. Tôi cũng không bao gồm mọi thành viên trong lớp ở đây:

public class Grammar
{
    /// <summary> Gets or sets the term for "just now". </summary>
    public string JustNow { get; set; }
    /// <summary> Gets or sets the term for "X minutes ago". </summary>
    /// <remarks>
    ///     This is a <see cref="String.Format"/> pattern, where <c>{0}</c>
    ///     is the number of minutes.
    /// </remarks>
    public string MinutesAgo { get; set; }
    public string OneHourAgo { get; set; }
    public string HoursAgo { get; set; }
    public string Yesterday { get; set; }
    public string DaysAgo { get; set; }
    public string LastMonth { get; set; }
    public string MonthsAgo { get; set; }
    public string LastYear { get; set; }
    public string YearsAgo { get; set; }
    /// <summary> Gets or sets the term for "ages ago". </summary>
    public string AgesAgo { get; set; }

    /// <summary>
    ///     Gets or sets the threshold beyond which the fuzzy date should be
    ///     considered "ages ago".
    /// </summary>
    public TimeSpan AgesAgoThreshold { get; set; }

    /// <summary>
    ///     Initialises a new <see cref="Grammar"/> instance with the
    ///     specified properties.
    /// </summary>
    private void Initialise(string justNow, string minutesAgo,
        string oneHourAgo, string hoursAgo, string yesterday, string daysAgo,
        string lastMonth, string monthsAgo, string lastYear, string yearsAgo,
        string agesAgo, TimeSpan agesAgoThreshold)
    { ... }
}

Các FuzzyDateStringlớp học bao gồm:

public static class FuzzyDateExtensions
{
    public static string ToFuzzyDateString(this TimeSpan timespan)
    {
        return timespan.ToFuzzyDateString(new Grammar());
    }

    public static string ToFuzzyDateString(this TimeSpan timespan,
        Grammar grammar)
    {
        return GetFuzzyDateString(timespan, grammar);
    }

    public static string ToFuzzyDateString(this DateTime datetime)
    {
        return (DateTime.Now - datetime).ToFuzzyDateString();
    }

    public static string ToFuzzyDateString(this DateTime datetime,
       Grammar grammar)
    {
        return (DateTime.Now - datetime).ToFuzzyDateString(grammar);
    }


    private static string GetFuzzyDateString(TimeSpan timespan,
       Grammar grammar)
    {
        timespan = timespan.Duration();

        if (timespan >= grammar.AgesAgoThreshold)
        {
            return grammar.AgesAgo;
        }

        if (timespan < new TimeSpan(0, 2, 0))    // 2 minutes
        {
            return grammar.JustNow;
        }

        if (timespan < new TimeSpan(1, 0, 0))    // 1 hour
        {
            return String.Format(grammar.MinutesAgo, timespan.Minutes);
        }

        if (timespan < new TimeSpan(1, 55, 0))    // 1 hour 55 minutes
        {
            return grammar.OneHourAgo;
        }

        if (timespan < new TimeSpan(12, 0, 0)    // 12 hours
            && (DateTime.Now - timespan).IsToday())
        {
            return String.Format(grammar.HoursAgo, timespan.RoundedHours());
        }

        if ((DateTime.Now.AddDays(1) - timespan).IsToday())
        {
            return grammar.Yesterday;
        }

        if (timespan < new TimeSpan(32, 0, 0, 0)    // 32 days
            && (DateTime.Now - timespan).IsThisMonth())
        {
            return String.Format(grammar.DaysAgo, timespan.RoundedDays());
        }

        if ((DateTime.Now.AddMonths(1) - timespan).IsThisMonth())
        {
            return grammar.LastMonth;
        }

        if (timespan < new TimeSpan(365, 0, 0, 0, 0)    // 365 days
            && (DateTime.Now - timespan).IsThisYear())
        {
            return String.Format(grammar.MonthsAgo, timespan.RoundedMonths());
        }

        if ((DateTime.Now - timespan).AddYears(1).IsThisYear())
        {
            return grammar.LastYear;
        }

        return String.Format(grammar.YearsAgo, timespan.RoundedYears());
    }
}

Một trong những điều quan trọng tôi muốn đạt được, cũng như nội địa hóa, là "hôm nay" sẽ chỉ có nghĩa là "ngày lịch này", vì vậy IsToday, IsThisMonth, IsThisYearphương pháp giống như thế này:

public static bool IsToday(this DateTime date)
{
    return date.DayOfYear == DateTime.Now.DayOfYear && date.IsThisYear();
}

và các phương pháp làm tròn là như thế này (tôi đã bao gồm RoundedMonths, vì nó hơi khác một chút):

public static int RoundedDays(this TimeSpan timespan)
{
    return (timespan.Hours > 12) ? timespan.Days + 1 : timespan.Days;
}

public static int RoundedMonths(this TimeSpan timespan)
{
    DateTime then = DateTime.Now - timespan;

    // Number of partial months elapsed since 1 Jan, AD 1 (DateTime.MinValue)
    int nowMonthYears = DateTime.Now.Year * 12 + DateTime.Now.Month;
    int thenMonthYears = then.Year * 12 + then.Month;                    

    return nowMonthYears - thenMonthYears;
}

Tôi hy vọng mọi người thấy điều này hữu ích và / hoặc thú vị: o)


17

Trong PHP, tôi làm theo cách này:

<?php
function timesince($original) {
    // array of time period chunks
    $chunks = array(
        array(60 * 60 * 24 * 365 , 'year'),
        array(60 * 60 * 24 * 30 , 'month'),
        array(60 * 60 * 24 * 7, 'week'),
        array(60 * 60 * 24 , 'day'),
        array(60 * 60 , 'hour'),
        array(60 , 'minute'),
    );

    $today = time(); /* Current unix time  */
    $since = $today - $original;

    if($since > 604800) {
    $print = date("M jS", $original);

    if($since > 31536000) {
        $print .= ", " . date("Y", $original);
    }

    return $print;
}

// $j saves performing the count function each time around the loop
for ($i = 0, $j = count($chunks); $i < $j; $i++) {

    $seconds = $chunks[$i][0];
    $name = $chunks[$i][1];

    // finding the biggest chunk (if the chunk fits, break)
    if (($count = floor($since / $seconds)) != 0) {
        break;
    }
}

$print = ($count == 1) ? '1 '.$name : "$count {$name}s";

return $print . " ago";

} ?>

5
Câu hỏi được gắn thẻ C # . Tại sao mã PHP này ? IMHO, chỉ áp dụng mã C #
Kiquenet

17

sử dụng Fluent DateTime

var dateTime1 = 2.Hours().Ago();
var dateTime2 = 3.Days().Ago();
var dateTime3 = 1.Months().Ago();
var dateTime4 = 5.Hours().FromNow();
var dateTime5 = 2.Weeks().FromNow();
var dateTime6 = 40.Seconds().FromNow();

14

Tôi nghĩ rằng tôi sẽ cho nó một shot bằng cách sử dụng các lớp và đa hình. Tôi đã có một lần lặp trước đó sử dụng phân lớp phụ mà cuối cùng lại có quá nhiều chi phí. Tôi đã chuyển sang mô hình đối tượng tài sản công / đại biểu linh hoạt hơn, tốt hơn đáng kể. Mã của tôi chính xác hơn một chút, tôi ước tôi có thể tạo ra một cách tốt hơn để tạo ra "những tháng trước" mà dường như không quá kỹ thuật.

Tôi nghĩ rằng tôi vẫn gắn bó với tầng của if-then bởi vì nó ít mã hơn và nó đơn giản hơn (chắc chắn sẽ dễ dàng hơn để đảm bảo nó sẽ hoạt động như mong đợi).

Đối với mã bên dưới PrintRelativeTime.GetRelativeTimeMessage (TimeSpan trước) trả về thông báo thời gian tương đối (ví dụ: "ngày hôm qua").

public class RelativeTimeRange : IComparable
{
    public TimeSpan UpperBound { get; set; }

    public delegate string RelativeTimeTextDelegate(TimeSpan timeDelta);

    public RelativeTimeTextDelegate MessageCreator { get; set; }

    public int CompareTo(object obj)
    {
        if (!(obj is RelativeTimeRange))
        {
            return 1;
        }
        // note that this sorts in reverse order to the way you'd expect, 
        // this saves having to reverse a list later
        return (obj as RelativeTimeRange).UpperBound.CompareTo(UpperBound);
    }
}

public class PrintRelativeTime
{
    private static List<RelativeTimeRange> timeRanges;

    static PrintRelativeTime()
    {
        timeRanges = new List<RelativeTimeRange>{
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromSeconds(1),
                MessageCreator = (delta) => 
                { return "one second ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromSeconds(60),
                MessageCreator = (delta) => 
                { return delta.Seconds + " seconds ago"; }

            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromMinutes(2),
                MessageCreator = (delta) => 
                { return "one minute ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromMinutes(60),
                MessageCreator = (delta) => 
                { return delta.Minutes + " minutes ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromHours(2),
                MessageCreator = (delta) => 
                { return "one hour ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromHours(24),
                MessageCreator = (delta) => 
                { return delta.Hours + " hours ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.FromDays(2),
                MessageCreator = (delta) => 
                { return "yesterday"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = DateTime.Now.Subtract(DateTime.Now.AddMonths(-1)),
                MessageCreator = (delta) => 
                { return delta.Days + " days ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = DateTime.Now.Subtract(DateTime.Now.AddMonths(-2)),
                MessageCreator = (delta) => 
                { return "one month ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = DateTime.Now.Subtract(DateTime.Now.AddYears(-1)),
                MessageCreator = (delta) => 
                { return (int)Math.Floor(delta.TotalDays / 30) + " months ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = DateTime.Now.Subtract(DateTime.Now.AddYears(-2)),
                MessageCreator = (delta) => 
                { return "one year ago"; }
            }, 
            new RelativeTimeRange
            {
                UpperBound = TimeSpan.MaxValue,
                MessageCreator = (delta) => 
                { return (int)Math.Floor(delta.TotalDays / 365.24D) + " years ago"; }
            }
        };

        timeRanges.Sort();
    }

    public static string GetRelativeTimeMessage(TimeSpan ago)
    {
        RelativeTimeRange postRelativeDateRange = timeRanges[0];

        foreach (var timeRange in timeRanges)
        {
            if (ago.CompareTo(timeRange.UpperBound) <= 0)
            {
                postRelativeDateRange = timeRange;
            }
        }

        return postRelativeDateRange.MessageCreator(ago);
    }
}

13
using System;
using System.Collections.Generic;
using System.Linq;

public static class RelativeDateHelper
{
    private static Dictionary<double, Func<double, string>> sm_Dict = null;

    private static Dictionary<double, Func<double, string>> DictionarySetup()
    {
        var dict = new Dictionary<double, Func<double, string>>();
        dict.Add(0.75, (mins) => "less than a minute");
        dict.Add(1.5, (mins) => "about a minute");
        dict.Add(45, (mins) => string.Format("{0} minutes", Math.Round(mins)));
        dict.Add(90, (mins) => "about an hour");
        dict.Add(1440, (mins) => string.Format("about {0} hours", Math.Round(Math.Abs(mins / 60)))); // 60 * 24
        dict.Add(2880, (mins) => "a day"); // 60 * 48
        dict.Add(43200, (mins) => string.Format("{0} days", Math.Floor(Math.Abs(mins / 1440)))); // 60 * 24 * 30
        dict.Add(86400, (mins) => "about a month"); // 60 * 24 * 60
        dict.Add(525600, (mins) => string.Format("{0} months", Math.Floor(Math.Abs(mins / 43200)))); // 60 * 24 * 365 
        dict.Add(1051200, (mins) => "about a year"); // 60 * 24 * 365 * 2
        dict.Add(double.MaxValue, (mins) => string.Format("{0} years", Math.Floor(Math.Abs(mins / 525600))));

        return dict;
    }

    public static string ToRelativeDate(this 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";
        }

        if (null == sm_Dict)
            sm_Dict = DictionarySetup();

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

Giống như một câu trả lời khác cho câu hỏi này nhưng là một phương pháp mở rộng với một từ điển tĩnh.


Từ điển mua gì ở đây?
StriplingWar Warrior

StriplingWar Warrior: Dễ đọc và sửa đổi so với câu lệnh chuyển đổi hoặc một chồng các câu lệnh if / other. Từ điển tĩnh có nghĩa là nó và các đối tượng Func <,> không phải được tạo mỗi khi chúng ta muốn sử dụng ToRelativeDate; nó chỉ được tạo một lần, so với câu tôi liên kết trong câu trả lời của mình.
Chris Charabaruk

Tôi hiểu rồi. Tôi chỉ nghĩ rằng, vì tài liệu về Dictionarycác trạng thái rằng "Thứ tự trả lại các mục không được xác định", ( msdn.microsoft.com/en-us/l Library / xfhwa508.aspx ) có lẽ đó không phải là cấu trúc dữ liệu tốt nhất để sử dụng khi bạn không quan tâm đến thời gian tra cứu nhiều như việc sắp xếp mọi thứ theo trật tự.
StriplingWar Warrior

StriplingWar Warrior: Tôi tin rằng LINQ tính đến điều đó khi được sử dụng với Dictionarys. Nếu bạn vẫn không thoải mái với nó, bạn có thể sử dụng SortedDictionary, nhưng kinh nghiệm của riêng tôi cho thấy điều đó là không cần thiết.
Chris Charabaruk

12

Khi bạn biết múi giờ của người xem, có thể rõ ràng hơn khi sử dụng ngày theo lịch theo thang ngày. Tôi không quen thuộc với các thư viện .NET vì vậy tôi không biết làm thế nào bạn làm điều đó trong C #, thật không may.

Trên các trang web của người tiêu dùng, bạn cũng có thể trở nên khéo léo hơn một phút. "Chưa đầy một phút trước" hoặc "chỉ bây giờ" có thể đủ tốt.


11

bạn có thể thử cái này. Tôi nghĩ nó sẽ hoạt động chính xác.

long delta = new Date().getTime() - date.getTime();
const int SECOND = 1;
const int MINUTE = 60 * SECOND;
const int HOUR = 60 * MINUTE;
const int DAY = 24 * HOUR;
const int MONTH = 30 * DAY;

if (delta < 0L)
{
  return "not yet";
}
if (delta < 1L * MINUTE)
{
  return ts.Seconds == 1 ? "one second ago" : ts.Seconds + " seconds ago";
}
if (delta < 2L * MINUTE)
{
  return "a minute ago";
}
if (delta < 45L * MINUTE)
{
  return ts.Minutes + " minutes ago";
}
if (delta < 90L * MINUTE)
{
  return "an hour ago";
}
if (delta < 24L * HOUR)
{
  return ts.Hours + " hours ago";
}
if (delta < 48L * HOUR)
{
  return "yesterday";
}
if (delta < 30L * DAY)
{
  return ts.Days + " days ago";
}
if (delta < 12L * 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";
}

9

Java để sử dụng gwt phía máy khách:

import java.util.Date;

public class RelativeDateFormat {

 private static final long ONE_MINUTE = 60000L;
 private static final long ONE_HOUR = 3600000L;
 private static final long ONE_DAY = 86400000L;
 private static final long ONE_WEEK = 604800000L;

 public static String format(Date date) {

  long delta = new Date().getTime() - date.getTime();
  if (delta < 1L * ONE_MINUTE) {
   return toSeconds(delta) == 1 ? "one second ago" : toSeconds(delta)
     + " seconds ago";
  }
  if (delta < 2L * ONE_MINUTE) {
   return "one minute ago";
  }
  if (delta < 45L * ONE_MINUTE) {
   return toMinutes(delta) + " minutes ago";
  }
  if (delta < 90L * ONE_MINUTE) {
   return "one hour ago";
  }
  if (delta < 24L * ONE_HOUR) {
   return toHours(delta) + " hours ago";
  }
  if (delta < 48L * ONE_HOUR) {
   return "yesterday";
  }
  if (delta < 30L * ONE_DAY) {
   return toDays(delta) + " days ago";
  }
  if (delta < 12L * 4L * ONE_WEEK) {
   long months = toMonths(delta);
   return months <= 1 ? "one month ago" : months + " months ago";
  } else {
   long years = toYears(delta);
   return years <= 1 ? "one year ago" : years + " years ago";
  }
 }

 private static long toSeconds(long date) {
  return date / 1000L;
 }

 private static long toMinutes(long date) {
  return toSeconds(date) / 60L;
 }

 private static long toHours(long date) {
  return toMinutes(date) / 60L;
 }

 private static long toDays(long date) {
  return toHours(date) / 24L;
 }

 private static long toMonths(long date) {
  return toDays(date) / 30L;
 }

 private static long toYears(long date) {
  return toMonths(date) / 365L;
 }

}

Câu hỏi được gắn thẻ C # . Tại sao mã Java này ? IMHO, chỉ áp dụng mã C #
Kiquenet

9

@Jeff

var ts = new TimeSpan(DateTime.UtcNow.Ticks - dt.Ticks);

Làm một phép trừ trên DateTimetrả về một TimeSpananyway.

Vì vậy, bạn chỉ có thể làm

(DateTime.UtcNow - dt).TotalSeconds

Tôi cũng ngạc nhiên khi thấy các hằng số được nhân ra bằng tay và sau đó các bình luận được thêm vào với các phép nhân. Có phải đó là một số tối ưu hóa sai lầm?


8

Đây là stackoverflow thuật toán sử dụng nhưng viết lại chính xác hơn bằng mã giả hoàn chỉnh với một sửa lỗi (không có "một giờ trước"). Hàm này lấy số (dương) của giây trước và trả về chuỗi thân thiện với con người như "3 giờ trước" hoặc "ngày hôm qua".

agoify($delta)
  local($y, $mo, $d, $h, $m, $s);
  $s = floor($delta);
  if($s<=1)            return "a second ago";
  if($s<60)            return "$s seconds ago";
  $m = floor($s/60);
  if($m==1)            return "a minute ago";
  if($m<45)            return "$m minutes ago";
  $h = floor($m/60);
  if($h==1)            return "an hour ago";
  if($h<24)            return "$h hours ago";
  $d = floor($h/24);
  if($d<2)             return "yesterday";
  if($d<30)            return "$d days ago";
  $mo = floor($d/30);
  if($mo<=1)           return "a month ago";
  $y = floor($mo/12);
  if($y<1)             return "$mo months ago";
  if($y==1)            return "a year ago";
  return "$y years ago";

8

Bạn có thể sử dụng tiện ích mở rộng TimeAgo từ đó trông giống như sau:

public static string TimeAgo(this DateTime dateTime)
{
    string result = string.Empty;
    var timeSpan = DateTime.Now.Subtract(dateTime);

    if (timeSpan <= TimeSpan.FromSeconds(60))
    {
        result = string.Format("{0} seconds ago", timeSpan.Seconds);
    }
    else if (timeSpan <= TimeSpan.FromMinutes(60))
    {
        result = timeSpan.Minutes > 1 ? 
            String.Format("about {0} minutes ago", timeSpan.Minutes) :
            "about a minute ago";
    }
    else if (timeSpan <= TimeSpan.FromHours(24))
    {
        result = timeSpan.Hours > 1 ? 
            String.Format("about {0} hours ago", timeSpan.Hours) : 
            "about an hour ago";
    }
    else if (timeSpan <= TimeSpan.FromDays(30))
    {
        result = timeSpan.Days > 1 ? 
            String.Format("about {0} days ago", timeSpan.Days) : 
            "yesterday";
    }
    else if (timeSpan <= TimeSpan.FromDays(365))
    {
        result = timeSpan.Days > 30 ? 
            String.Format("about {0} months ago", timeSpan.Days / 30) : 
            "about a month ago";
    }
    else
    {
        result = timeSpan.Days > 365 ? 
            String.Format("about {0} years ago", timeSpan.Days / 365) : 
            "about a year ago";
    }

    return result;
}

Hoặc sử dụng plugin jQuery với phần mở rộng Dao cạo từ Timeago.


8

Bạn có thể giảm tải phía máy chủ bằng cách thực hiện phía máy khách logic này. Xem nguồn trên một số trang Digg để tham khảo. Họ có máy chủ phát ra giá trị thời gian kỷ nguyên được xử lý bằng Javascript. Bằng cách này, bạn không cần phải quản lý múi giờ của người dùng cuối. Mã phía máy chủ mới sẽ là một cái gì đó như:

public string GetRelativeTime(DateTime timeStamp)
{
    return string.Format("<script>printdate({0});</script>", timeStamp.ToFileTimeUtc());
}

Bạn thậm chí có thể thêm một khối NOSCRIPT ở đó và chỉ cần thực hiện ToString ().


8

Cái này, tôi lấy từ một trong những blog của Bill Gates. Tôi cần tìm nó trong lịch sử trình duyệt của mình và tôi sẽ cung cấp cho bạn liên kết.

Mã Javascript để làm điều tương tự (theo yêu cầu):

function posted(t) {
    var now = new Date();
    var diff = parseInt((now.getTime() - Date.parse(t)) / 1000);
    if (diff < 60) { return 'less than a minute ago'; }
    else if (diff < 120) { return 'about a minute ago'; }
    else if (diff < (2700)) { return (parseInt(diff / 60)).toString() + ' minutes ago'; }
    else if (diff < (5400)) { return 'about an hour ago'; }
    else if (diff < (86400)) { return 'about ' + (parseInt(diff / 3600)).toString() + ' hours ago'; }
    else if (diff < (172800)) { return '1 day ago'; } 
    else {return (parseInt(diff / 86400)).toString() + ' days ago'; }
}

Về cơ bản, bạn làm việc trong vài giây ...


6

Tôi nghĩ rằng đã có một số câu trả lời liên quan đến bài đăng này, nhưng người ta có thể sử dụng nó rất dễ sử dụng giống như plugin và cũng dễ đọc cho các lập trình viên. Gửi ngày cụ thể của bạn và nhận giá trị của nó ở dạng chuỗi:

public string RelativeDateTimeCount(DateTime inputDateTime)
{
    string outputDateTime = string.Empty;
    TimeSpan ts = DateTime.Now - inputDateTime;

    if (ts.Days > 7)
    { outputDateTime = inputDateTime.ToString("MMMM d, yyyy"); }

    else if (ts.Days > 0)
    {
        outputDateTime = ts.Days == 1 ? ("about 1 Day ago") : ("about " + ts.Days.ToString() + " Days ago");
    }
    else if (ts.Hours > 0)
    {
        outputDateTime = ts.Hours == 1 ? ("an hour ago") : (ts.Hours.ToString() + " hours ago");
    }
    else if (ts.Minutes > 0)
    {
        outputDateTime = ts.Minutes == 1 ? ("1 minute ago") : (ts.Minutes.ToString() + " minutes ago");
    }
    else outputDateTime = "few seconds ago";

    return outputDateTime;
}

5
/** 
 * {@code date1} has to be earlier than {@code date2}.
 */
public static String relativize(Date date1, Date date2) {
    assert date2.getTime() >= date1.getTime();

    long duration = date2.getTime() - date1.getTime();
    long converted;

    if ((converted = TimeUnit.MILLISECONDS.toDays(duration)) > 0) {
        return String.format("%d %s ago", converted, converted == 1 ? "day" : "days");
    } else if ((converted = TimeUnit.MILLISECONDS.toHours(duration)) > 0) {
        return String.format("%d %s ago", converted, converted == 1 ? "hour" : "hours");
    } else if ((converted = TimeUnit.MILLISECONDS.toMinutes(duration)) > 0) {
        return String.format("%d %s ago", converted, converted == 1 ? "minute" : "minutes");
    } else if ((converted = TimeUnit.MILLISECONDS.toSeconds(duration)) > 0) {
        return String.format("%d %s ago", converted, converted == 1 ? "second" : "seconds");
    } else {
        return "just now";
    }
}

5

Nếu bạn muốn có một đầu ra như thế nào "2 days, 4 hours and 12 minutes ago", bạn cần một khoảng thời gian:

TimeSpan timeDiff = DateTime.Now-CreatedDate;

Sau đó, bạn có thể truy cập các giá trị bạn thích:

timeDiff.Days
timeDiff.Hours

Vân vân...


4

Tôi sẽ cung cấp một số phương thức tiện ích mở rộng tiện dụng cho việc này và làm cho mã dễ đọc hơn. Đầu tiên, vài phương thức mở rộng cho Int32.

public static class TimeSpanExtensions {

    public static TimeSpan Days(this int value) {

        return new TimeSpan(value, 0, 0, 0);
    }

    public static TimeSpan Hours(this int value) {

        return new TimeSpan(0, value, 0, 0);
    }

    public static TimeSpan Minutes(this int value) {

        return new TimeSpan(0, 0, value, 0);
    }

    public static TimeSpan Seconds(this int value) {

        return new TimeSpan(0, 0, 0, value);
    }

    public static TimeSpan Milliseconds(this int value) {

        return new TimeSpan(0, 0, 0, 0, value);
    }

    public static DateTime Ago(this TimeSpan value) {

        return DateTime.Now - value;
    }
}

Sau đó, một cho DateTime.

public static class DateTimeExtensions {

    public static DateTime Ago(this DateTime dateTime, TimeSpan delta) {

        return dateTime - delta;
    }
}

Bây giờ, bạn có thể làm một cái gì đó như dưới đây:

var date = DateTime.Now;
date.Ago(2.Days()); // 2 days ago
date.Ago(7.Hours()); // 7 hours ago
date.Ago(567.Milliseconds()); // 567 milliseconds ago
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.