Dado um DateTime
valor específico , como faço para exibir o tempo relativo, como:
- 2 horas atrás
- 3 dias atrás
- um mês atrás
Dado um DateTime
valor específico , como faço para exibir o tempo relativo, como:
Respostas:
Jeff, seu código é bom, mas pode ser mais claro com constantes (como sugerido no Código completo).
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";
}
Thread.Sleep(1 * MINUTE)
? Porque está errado por um fator de 1000.
const int SECOND = 1;
Um segundo tão estranho é um segundo.
Jeff, como o Stack Overflow usa o jQuery extensivamente, eu recomendo o plugin jquery.timeago .
Benefícios:
Basta anexá-lo aos seus registros de data e hora no DOM pronto:
jQuery(document).ready(function() {
jQuery('abbr.timeago').timeago();
});
Isso transformará todos os abbr
elementos com uma classe de timeago e um carimbo de data / hora ISO 8601 no título:
<abbr class="timeago" title="2008-07-17T09:24:17Z">July 17, 2008</abbr>
em algo como isto:
<abbr class="timeago" title="July 17, 2008">4 months ago</abbr>
que rende: há 4 meses. Com o passar do tempo, os carimbos de hora serão atualizados automaticamente.
Disclaimer: Eu escrevi este plugin, por isso sou tendencioso.
Aqui está como eu faço
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";
Sugestões? Comentários? Maneiras de melhorar esse algoritmo?
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 "";
}
Prefiro esta versão por sua concisão e capacidade de adicionar novos pontos de verificação. Isso pode ser encapsulado com uma Latest()
extensão do Timespan em vez do longo forro, mas por uma questão de brevidade na postagem, isso funcionará.
Isso corrige uma hora atrás, uma hora atrás, fornecendo uma hora até duas horas decorridas
SortedDictionary
vez de um simples Dictionary
: o uso é o mesmo, mas garante que as chaves sejam classificadas. Mas, mesmo assim, o algoritmo tem falhas, porque RelativeDate(DateTime.Now.AddMonths(-3).AddDays(-3))
retorna "95 meses atrás" , independentemente do tipo de dicionário que você está usando, o que está incorreto (ele deve retornar "há 3 meses" ou "4 meses atrás", dependendo de qual limite você ' reutilizando) - mesmo que -3 não crie uma data no ano passado (testei isso em dezembro, portanto, nesse caso, isso não deve acontecer).
Aqui, uma reescrita do 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";
}
}
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
Versão 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;
}
Func<string>
para Func<double>
.
Aqui está uma implementação que eu adicionei como método de extensão à classe DateTime que lida com datas futuras e passadas e fornece uma opção de aproximação que permite especificar o nível de detalhe que você está procurando ("3 horas atrás" vs "3 horas, 23 minutos, 12 segundos atrás "):
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();
}
Eu recomendaria computar isso no lado do cliente também. Menos trabalho para o servidor.
A seguir, a versão que eu uso (de 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);
});
};
}
Há também um pacote chamado Humanizr no Nuget, que realmente funciona muito bem e está no .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 escreveu um artigo em seu blog
@jeff
IMHO o seu parece um pouco longo. No entanto, parece um pouco mais robusto com suporte para "ontem" e "anos". Mas, na minha experiência, quando isso é usado, é mais provável que a pessoa visualize o conteúdo nos primeiros 30 dias. São apenas as pessoas realmente hardcore que vêm depois disso. É por isso que geralmente escolho manter isso curto e simples.
Esse é o método que estou usando atualmente em um dos meus sites. Isso retorna apenas um dia, hora e hora relativos. E então o usuário tem que dar um tapa em "atrás" na saída.
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();
}
Alguns anos atrasado para a festa, mas eu tinha um requisito para fazer isso em datas passadas e futuras, então eu combinei Jeff e Vincent nisso. É uma extravagância ternitártica! :)
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";
}
}
}
Existe uma maneira fácil de fazer isso em Java? A java.util.Date
turma parece bastante limitada.
Aqui está minha solução Java rápida e suja:
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;
}
+ (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];
}
}
Dado que o mundo e seu marido parecem postar amostras de código, eis o que escrevi há pouco, com base em algumas dessas respostas.
Eu tinha uma necessidade específica de esse código ser localizável. Então, eu tenho duas classes - Grammar
, que especifica os termos localizáveis e FuzzyDateExtensions
, que contém vários métodos de extensão. Como não precisava lidar com datas futuras, não é feita nenhuma tentativa de lidar com eles com esse código.
Deixei alguns dos XMLdoc na fonte, mas removi a maioria (onde eles seriam óbvios) por uma questão de brevidade. Também não incluí todos os alunos aqui:
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)
{ ... }
}
A FuzzyDateString
classe conté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());
}
}
Uma das principais coisas que eu queria alcançar, assim como a localização, era que "hoje" seria apenas média "neste dia de calendário", de modo a IsToday
, IsThisMonth
, IsThisYear
métodos parecido com este:
public static bool IsToday(this DateTime date)
{
return date.DayOfYear == DateTime.Now.DayOfYear && date.IsThisYear();
}
e os métodos de arredondamento são assim (eu incluí RoundedMonths
, pois isso é um pouco diferente):
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;
}
Espero que as pessoas achem isso útil e / ou interessante: o)
No PHP, faço da seguinte maneira:
<?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";
} ?>
usando DateTime fluente
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();
Eu pensei em dar uma chance a isso usando classes e polimorfismo. Eu tinha uma iteração anterior que usava subclassificação que acabava sobrecarregando demais. Mudei para um modelo de objeto delegado / propriedade pública mais flexível, que é significativamente melhor. Meu código é um pouco mais preciso, eu gostaria de poder encontrar uma maneira melhor de gerar "meses atrás" que não parecesse exagerada demais.
Eu acho que ainda continuaria com a cascata if-then de Jeff, porque é menos código e mais simples (é definitivamente mais fácil garantir que funcione conforme o esperado).
Para o código abaixo, PrintRelativeTime.GetRelativeTimeMessage (TimeSpan ago) retorna a mensagem de tempo relativo (por exemplo, "ontem").
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);
}
}
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;
}
}
O mesmo que outra resposta a esta pergunta, mas como um método de extensão com um dicionário estático.
Dictionary
afirma que "A ordem na qual os itens são retornados é indefinida" ( msdn.microsoft.com/en-us/library/xfhwa508.aspx ) talvez essa não seja a melhor estrutura de dados a ser usada quando você não se importa com os tempos de pesquisa, mas com as coisas em ordem.
Dictionary
s. Se você ainda não se sente à vontade com isso, pode usar SortedDictionary
, mas minha própria experiência mostra que isso é desnecessário.
Quando você conhece o fuso horário do visualizador, pode ser mais claro usar os dias do calendário na escala de dias. Como não conheço as bibliotecas .NET, não sei como você faria isso em C #, infelizmente.
Em sites de consumidores, você também pode ter menos agitação manual em menos de um minuto. "Menos de um minuto atrás" ou "agora" poderia ser bom o suficiente.
Você pode tentar isso. Acho que funcionará corretamente.
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";
}
Java para uso do GWT do lado do cliente:
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;
}
}
@Jeff
var ts = new TimeSpan(DateTime.UtcNow.Ticks - dt.Ticks);
Fazer uma subtração em DateTime
retorna um de TimeSpan
qualquer maneira.
Então você pode fazer
(DateTime.UtcNow - dt).TotalSeconds
Também fico surpreso ao ver as constantes multiplicadas à mão e depois comentários adicionados com as multiplicações. Isso foi uma otimização equivocada?
Aqui está o algoritmo que o stackoverflow usa, mas reescrito de forma mais concisa no pseudocódigo perlish com uma correção de bug (não há "uma hora atrás"). A função leva um número (positivo) de segundos atrás e retorna uma cadeia de caracteres humana como "3 horas atrás" ou "ontem".
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";
Você pode usar a extensão TimeAgo da qual se parece com o seguinte:
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;
}
Ou use o plugin jQuery com extensão Razor do Timeago.
Você pode reduzir a carga do lado do servidor executando essa lógica do lado do cliente. Veja a fonte em algumas páginas do Digg para referência. Eles fazem o servidor emitir um valor de época que é processado por Javascript. Dessa forma, você não precisa gerenciar o fuso horário do usuário final. O novo código do lado do servidor seria algo como:
public string GetRelativeTime(DateTime timeStamp)
{
return string.Format("<script>printdate({0});</script>", timeStamp.ToFileTimeUtc());
}
Você pode até adicionar um bloco NOSCRIPT lá e apenas executar um ToString ().
Eu recebi isso de um dos blogs de Bill Gates. Preciso encontrá-lo no histórico do navegador e fornecerei o link.
O código Javascript para fazer a mesma coisa (conforme solicitado):
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'; }
}
Basicamente, você trabalha em segundos ...
Eu acho que já existem várias respostas relacionadas a este post, mas pode-se usar isso que é fácil de usar, como o plug-in e também facilmente legível para os programadores. Envie sua data específica e obtenha seu valor na forma de sequência:
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;
}
/**
* {@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";
}
}
Eu forneceria alguns métodos úteis de extensões para isso e tornaria o código mais legível. Primeiro, alguns métodos de extensão para 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;
}
}
Então, um para DateTime
.
public static class DateTimeExtensions {
public static DateTime Ago(this DateTime dateTime, TimeSpan delta) {
return dateTime - delta;
}
}
Agora, você pode fazer algo como abaixo:
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