带有.Years和.Months的Real Timespan对象

Erx*_*der 24 c# vb.net asp.net datetime timespan

考虑以下两种情况:场景1).今天是2012年5月1日,场景2).今天是2012年9月1日.

现在,请考虑我们在我们的网页上写下以下关于某人留下的评论:"此评论写于3个月和12天前".即使声明完全相同,这两种情况下的天数也总是不同.在情景1中,"3个月和12天"将相等102 days.但是,在场景2中,"3个月和12天"将是104 days!

现在,谈谈我的观点,让我们使用一个不同的例子,并说有人在2013年1月30日在我们的网站上发表评论,今天是2013年3月10日.我们真实的TimeSpan对象需要知道这个相对日期,并且可以计算出来以下内容:

  • 三月有10天,
  • 1月1日(从30日到31日).
  • 2月是一个月,无论其中有多少天(即使它是28天).

因此,这意味着10天+ 1天+ 1个月总计,转换为This comment was posted 1 Month and 11 Days ago.

现在,如果您使用MS样式TimeSpan对象(或任何语言的任何TimeSpan对象),它将为您提供从1月30日到3月10日(39天)的天数,并且因为TimeSpan对象不存储相对日期(我们减去获得TimeSpan的基数/初始日期),如果你问它有多少个月和几天,它会假设一个月内有30天,甚至最差,大于30天的平均值,并在几天内返回其余的,所以要到39天,它会告诉你它是1个月和9天,你会收到This comment was posted 1 Month and 9 Days ago消息.请记住,这两种情况都具有相同的开始日期和相同的当前/结束日期,是的Microsoft TimeSpan对象,不允许我们告诉它应该考虑2013年2月,给了我们一个完全不同的TimeSpan,关闭整整2天.它实际上对我们撒了谎.

问题是,人们会相信这一点,谁知道他们可能有什么看法,他们对过去的看法如何改变,以及他们在尝试重建过去内部事件时可能做出的决定和生活选择,而从不注意或理解代表时间的缺点和固有的失败,这在当今无处不在.他们不会理解编程语言没有实现(或关心)上个月有31天,反对30,29或28 - 反之亦然,并且当你增加TimeSpan时这会增加.

这是这篇文章的核心问题.我知道大多数人都不会关心这种差异(但要确保我们有些人这样做,并且不能背负这种差异),如果这不打扰你,那没关系.我希望它不会打扰我,我会节省一些时间,压力和失望.如果这不是一个麻烦,您可以使用该功能有效地文本显示相对时间(可定制为从几秒到几年的1到6个节点),而不是使用它提供的通常可忽略的精度.

令我失望的是,我注意到没有真正的时间跨度对象,如果你得到一个时间跨度,做一个.years.months你什么也得不到,你只会得到.days和降低,因为timeSpan对象没有任何东西告诉它哪个月或者年份是什么时候创建的.因此,它永远不会真正知道自每个月的日子在一年内变化多少个月甚至是闰年的变化.

为此,我将发布一个我开发的函数,以获得准确的读数,并能够在我的ASP.NET网页上返回如下内容......

发表于4年,3个月,14天,15小时,18分钟和24秒之前

我想有一个......

timeSpan.GetActualNumberOf[Months/Days/Hours/etc] (当然必须提供基准日期)

...此数据类型的类型方法,但没有.

所有你真的需要做的是在时间跨度对象上创建另一个属性给它上计算出的差异,那么上面可爱的字符串将是可计算很容易地和一个基准日.year.month会存在!

更新:我已经大大扩展并更新了我在下面的答案中的官方答案和代码使用细节,100%工作答案和代码(完整),准确和准确的相对时间/日期,没有近似值 - 谢谢.

bri*_*ary 25

以下是如何使用平均值为C#添加一些扩展方法:

public static class TimeSpanExtensions
{
    public static int GetYears(this TimeSpan timespan)
    {
        return (int)(timespan.Days/365.2425);
    }
    public static int GetMonths(this TimeSpan timespan)
    {
        return (int)(timespan.Days/30.436875);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我只是想补充一点,这是一个近似值,违背了讨论准确时间跨度方法的目的,尽管这是节省 CPU 周期的好主意,特别是在流量大的网站上。如果您想要 100% 准确的方法,请阅读我的答案(和代码),因为我已经更新了它。 (2认同)
  • 实际上,这是表示时间跨度的非常准确的方式。@Erx_VB.NExT.Coder 想要的不是业界其他人所说的时间跨度,而是 ISO 8601 所说的时间间隔。 (2认同)

Jef*_*tin 11

你在寻找什么并不TimeSpan代表什么. TimeSpan表示间隔作为刻度的计数,而不考虑基数DateTimeCalendar.

这里的新DateDifference类型可能更有意义,构造函数或工厂方法采用基础DateTime,目标DateTime和可选的Calendar(默认为CultureInfo.CurrentCulture)来计算各种差异组件(年,月等)

编辑:在我看来,Noda Time可能拥有您需要的工具 - Period课程"[r]表示以人类时间顺序表示的一段时间:小时,天,周,月等等",特别是Period.Between(then, now, PeriodUnits.AllUnits)似乎是你要求的精确计算 - 但它必然比一个更复杂的类TimeSpan.的主要概念页上的野田佳彦时间维基解释如何"人类腾出时间凌乱":

抛开天文学和相对论的棘手问题,人类仍然花时间谈判.如果我们都使用Unix时代的刻度来谈论时间,就不需要像Noda Time那样的库.

但不,我们喜欢在几年,几个月,几天,几周内谈话 - 由于某种原因,我们喜欢下午12点(在下午1点之前容易混淆)大致是太阳最高的时间 ......所以我们有时区.

不仅如此,我们并不都同意有多少个月.不同的文明已经提出了不同的分裂方式,以及多年来不同的数字.这些是日历系统.

  • @John:这可能是因为他们总是使用近似值,所有facebook和gmail帖子总是显示相对于特定帖子或事件的时间,这样做而不承认提出的问题肯定会是一个近似值,叫我痴迷但是我_cannot_为我的用户提供不准确的数据,这就是为什么我感到惊讶,并且需要开发一个解决方案_always_根据提供的事件/发布日期给出正确的相对时间.相对时间几乎用于所有地方. (3认同)
  • 编辑:问题在于 Erx_VB.NExT 认为 msft 应该将特定于日历的功能(公历)添加到一个类 TimeSpan 中,该类基本上与日历无关。我可以理解他的沮丧,但他提出的解决方案 - 向 TimeSpan 本身添加方法 - 被误导了。在亚洲工作了 8 年多,我很了解其他日历(在泰国,现在是 2555 年)。也许他的函数应该添加到 Globalization.Calendar,因为计算是*日历特定*(例如,格里高利闰年与其他日历不同)。 (2认同)

Mar*_*sel 6

好吧,我想晚一点就好了;)

C# 函数给出了一切

这是我的修改版本:

private string GetElapsedTime(DateTime from_date, DateTime to_date) {
int years;
int months;
int days;
int hours;
int minutes;
int seconds;
int milliseconds;

//------------------
// Handle the years.
//------------------
years = to_date.Year - from_date.Year;

//------------------------
// See if we went too far.
//------------------------
DateTime test_date = from_date.AddMonths(12 * years);

if (test_date > to_date)
{
    years--;
    test_date = from_date.AddMonths(12 * years);
}

//--------------------------------
// Add months until we go too far.
//--------------------------------
months = 0;

while (test_date <= to_date)
{
    months++;
    test_date = from_date.AddMonths(12 * years + months);
}

months--;

//------------------------------------------------------------------
// Subtract to see how many more days, hours, minutes, etc. we need.
//------------------------------------------------------------------
from_date = from_date.AddMonths(12 * years + months);
TimeSpan remainder = to_date - from_date;
days = remainder.Days;
hours = remainder.Hours;
minutes = remainder.Minutes;
seconds = remainder.Seconds;
milliseconds = remainder.Milliseconds;

return (years > 0 ? years.ToString() + " years " : "") +
       (months > 0 ? months.ToString() + " months " : "") +
       (days > 0 ? days.ToString() + " days " : "") +
       (hours > 0 ? hours.ToString() + " hours " : "") +
       (minutes > 0 ? minutes.ToString() + " minutes " : "");}
Run Code Online (Sandbox Code Playgroud)


Erx*_*der 5

这是代码的主要答案,请注意,您可以获得任意数量的日期/时间精度,秒和分钟或秒,分钟和天,最多可达几年(可能包含6个部分/段)。如果您指定前两个且已超过一年,则它将返回“ 1年零3个月前”,而不会返回其余部分,因为您已请求了两个细分。如果只使用了几个小时,则只会返回“ 2小时零一分钟前”。当然,如果您指定1、2、3、4、5或6个segmet,则同样的规则也适用(由于秒,分钟,小时,天,月,年,年仅产生6种类型,因此最大为6)。它还将纠正语法问题,例如“分钟”与“分钟”,具体取决于是否为1分钟或更长(所有类型均相同)以及“字符串”

以下是一些使用示例:bAllowSegments标识要显示的段数...即:如果为3,则返回字符串为(作为示例)... "3 years, 2 months and 13 days"(不包括小时,分钟和秒作为前3个时间)类别)),但是,如果该日期是较新的日期(例如几天前的某天),则将指定返回相同的细分(3)"4 days, 1 hour and 13 minutes ago",因此将所有内容都考虑在内!

如果bAllowSegments是2,它将返回"3 years and 2 months",如果6(最大值)将返回"3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds",但是要提醒您,它将NEVER RETURN这样进行,"0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago"因为即使您指定了6个段,它也可以理解前3个段中没有日期数据并忽略它们。 ,所以请放心:)。当然,如果其中有一个段为0,则在形成字符串时会考虑到这一点,并显示为"3 days and 4 seconds ago"并忽略“ 0 hours”部分!享受,如果您愿意,请发表评论。

 Public Function RealTimeUntilNow(ByVal dt As DateTime, Optional ByVal bAllowSegments As Byte = 2) As String
  ' bAllowSegments identifies how many segments to show... ie: if 3, then return string would be (as an example)...
  ' "3 years, 2 months and 13 days" the top 3 time categories are returned, if bAllowSegments is 2 it would return
  ' "3 years and 2 months" and if 6 (maximum value) would return "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds"
  Dim rYears, rMonths, rDays, rHours, rMinutes, rSeconds As Int16
  Dim dtNow = DateTime.Now
  Dim daysInBaseMonth = Date.DaysInMonth(dt.Year, dt.Month)

  rYears = dtNow.Year - dt.Year
  rMonths = dtNow.Month - dt.Month
  If rMonths < 0 Then rMonths += 12 : rYears -= 1 ' add 1 year to months, and remove 1 year from years.
  rDays = dtNow.Day - dt.Day
  If rDays < 0 Then rDays += daysInBaseMonth : rMonths -= 1
  rHours = dtNow.Hour - dt.Hour
  If rHours < 0 Then rHours += 24 : rDays -= 1
  rMinutes = dtNow.Minute - dt.Minute
  If rMinutes < 0 Then rMinutes += 60 : rHours -= 1
  rSeconds = dtNow.Second - dt.Second
  If rSeconds < 0 Then rSeconds += 60 : rMinutes -= 1

  ' this is the display functionality
  Dim sb As StringBuilder = New StringBuilder()
  Dim iSegmentsAdded As Int16 = 0

  If rYears > 0 Then sb.Append(rYears) : sb.Append(" year" & If(rYears <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMonths > 0 Then sb.AppendFormat(rMonths) : sb.Append(" month" & If(rMonths <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rDays > 0 Then sb.Append(rDays) : sb.Append(" day" & If(rDays <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rHours > 0 Then sb.Append(rHours) : sb.Append(" hour" & If(rHours <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rMinutes > 0 Then sb.Append(rMinutes) : sb.Append(" minute" & If(rMinutes <> 1, "s", "") & ", ") : iSegmentsAdded += 1
  If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn

  If rSeconds > 0 Then sb.Append(rSeconds) : sb.Append(" second" & If(rSeconds <> 1, "s", "") & "") : iSegmentsAdded += 1

parseAndReturn:

  ' if the string is entirely empty, that means it was just posted so its less than a second ago, and an empty string getting passed will cause an error
  ' so we construct our own meaningful string which will still fit into the "Posted * ago " syntax...

  If sb.ToString = "" Then sb.Append("less than 1 second")

  Return ReplaceLast(sb.ToString.TrimEnd(" ", ",").ToString, ",", " and")

 End Function
Run Code Online (Sandbox Code Playgroud)

当然,您将需要一个“ ReplaceLast”函数,该函数需要一个源字符串,一个参数指定需要替换的内容,另一个arg指定要替换的内容,并且它仅替换该字符串的最后一次出现...如果您没有一个或不想实现它,我已经包含了我的一个,因此在这里,它可以“按原样”运行而无需修改。我知道不再需要反向功能(存在于.net中),但是ReplaceLast和ReverseIt函数已从.net之前的日期开始使用,因此请原谅它的日期看起来(仍然可以100%使用, em超过十年,可以保证它们没有错误)... :)。另外,如果您使用的是VB6,则可以使用StrReverse(将其环绕在使用.ReverseIt扩展方法扩展的字符串周围),而不是使用ReverseIt()函数(作为扩展方法提供)。因此,您不必执行sReplacable.ReverseIt,而是执行StrReverse(sReplacable),因为StrReverse()是VB6内置函数(并且做完全相同的事情,反转给定的字符串,并且不执行任何操作)。如果您使用StrReverse()而不是我的通用ReverseIt函数,请随时删除ReverseIt函数/扩展名。只要您导入旧版ms-visualbasic-dll库,StrReverse()函数就应该在.NET中可用。两种方式都没有区别,在我还不知道StrReverse()函数存在之前就已经编写了ReverseIt(),并且从那以后就一直在使用它(出于习惯(没有真正的理由使用我的而不是内置的泛型函数)) StrReverse)-实际上,我确定StrReverse(或类似的更新版本)。NET特定版本的字符串反向功能)将被编写为效率更高的:)。干杯。

<Extension()> _ 
Public Function ReplaceLast(ByVal sReplacable As String, ByVal sReplaceWhat As String, ByVal sReplaceWith As String) As String 
    ' let empty string arguments run, incase we dont know if we are sending and empty string or not. 
    sReplacable = sReplacable.ReverseIt 
    sReplacable = Replace(sReplacable, sReplaceWhat.ReverseIt, sReplaceWith.ReverseIt, , 1) ' only does first item on reversed version! 
    Return sReplacable.ReverseIt.ToString 
End Function 

<Extension()> _ 
Public Function ReverseIt(ByVal strS As String, Optional ByVal n As Integer = -1) As String 
    Dim strTempX As String = "", intI As Integer 

    If n > strS.Length Or n = -1 Then n = strS.Length 

    For intI = n To 1 Step -1 
        strTempX = strTempX + Mid(strS, intI, 1) 
    Next intI 

    ReverseIt = strTempX + Right(strS, Len(strS) - n) 

End Function 
Run Code Online (Sandbox Code Playgroud)

  • 我认为,如果您要寻找的是绝对精度,那么人们会期望2/28 + 1年= 2/28,与年份无关。 (3认同)