Учитывая конкретную DateTime
значение, как отобразить относительное время, например:
- 2 часа назад
- 3 дня назад
- месяц назад
Учитывая конкретную DateTime
значение, как отобразить относительное время, например:
Джефф, ваш код это хорошо, но может быть яснее с константами (как указано в Code Complete).
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";
}
Джефф, потому что Stack Overflow широко использует jQuery, я рекомендую Плагин jquery.timeago ,
Выгоды:
Просто прикрепите его к отметкам времени на DOM:
jQuery(document).ready(function() {
jQuery('abbr.timeago').timeago();
});
Это превратит все abbr
элементы с классом timeago и ISO 8601 отметка времени в названии:
<abbr class="timeago" title="2008-07-17T09:24:17Z">July 17, 2008</abbr>
в нечто подобное:
<abbr class="timeago" title="July 17, 2008">4 months ago</abbr>
который дает: 4 месяца назад. По прошествии времени метки времени будут автоматически обновляться.
Отказ от ответственности: я написал этот плагин, поэтому я предвзятый.
Вот как я это делаю
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";
Предложения? Комментарии? Способы улучшения этого алгоритма?
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 "";
}
Я предпочитаю эту версию за ее краткость и способность добавлять новые отметки.
Это может быть инкапсулировано Latest()
расширение для Timespan вместо этого длинного 1 лайнера, но для краткости в публикации это будет сделано. Это фиксирует час назад, 1 час назад, предоставляя час до истечения 2 часов
Здесь переписывается с Jeffs Script для 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";
}
}
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
Версия 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;
}
Вот реализация, которую я добавил как метод расширения для класса DateTime, который обрабатывает как будущие, так и прошлые даты, и предоставляет параметр аппроксимации, который позволяет указать уровень детализации, который вы ищете («3 часа назад» против «3 часа», 23 мин, 12 секунд назад "):
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();
}
I would recommend computing this on the client side too. Less work for the server.
The following is the version that I use (from 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);
});
};
}
There are also a package called Humanizer on Nuget and it actually works really well
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 has a writeup on it on his blog