带有.Years和.Months的Real 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时这会增加。

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

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

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

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

我想有一个……

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

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

你真正需要做的就是在timeSpan对象上创建另一个属性,为它提供计算差异的基准日期,然后上面可爱的字符串可以很容易地计算出来,并且会存在.year.month

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

以下是使用平均值为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); } } 

您所寻找的确实不是TimeSpan代表的。 TimeSpan将间隔表示为刻度计数,而不考虑基本DateTimeCalendar

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

编辑:在我看来, Noda Time可能拥有您需要的工具 – Period类“[r]表示以人类时间顺序表示的一段时间:小时,天,周,月等等”,以及特别是Period.Between(then, now, PeriodUnits.AllUnits)似乎是你要求的精确计算 – 但它必然是一个比TimeSpan更复杂的类。 Noda Time wiki上的Key Concepts页面解释了“人类如何让时间变得混乱”:

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

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

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

以下是代码的主要答案,请注意您可以获得任意数量的日期/时间精度,秒和分钟,或秒,分钟和天,任何地方长达数年(包含6个部分/段)。 如果您指定前两名且超过一年,它将返回“1年零3个月前”并且不会返回其余部分,因为您已请求两个段。 如果它只有几个小时,那么它只会返回“2小时1分钟前”。 当然,如果您指定1,2,3,4,5或6个分段,则适用相同的规则(最大值为6,因为秒,分钟,小时,天,月,年仅生成6种类型)。 它还将纠正语法问题,如“分钟”与“分钟”,具体取决于它是否为1分钟或更长时间,对所有类型都相同,并且生成的“字符串”将始终在语法上正确。

以下是一些使用示例:bAllowSegments标识要显示的段数…即:如果为3,则返回字符串将(例如)… "3 years, 2 months and 13 days" (不包括但是,返回前三个时间类别的小时,分​​钟和秒数,如果日期是较新的日期,例如几天前的某些日期,则指定相同的段(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"因为它知道前3个段中没有日期数据并忽略它们,即使您指定了6个段,所以别担心:) 当然,如果其中有一个段为0,则在形成字符串时会考虑到这一点,并将显示为"3 days and 4 seconds ago"并忽略“0小时”部分! 如果你愿意,可以享受并发表评论。

  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 

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

  _ 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  _ 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 

好吧,晚了好,我想没什么;)

C#function提供一切

这是我的修改版本:

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

我想说当前TimeSpan是一个真正的时间跨度对象,即2008年1月1日上午1:31到2008年2月3日上午6:45之间的时间量与2月5日之间的时间量相同。 ,2008年下午1:45和2008年3月9日下午6:59。 您正在寻找的是两个日期时间之间的差异。

至于.MakeMagicHappen.gimmeSomethingPretty.surelyMShasThoughtAboutThisDilema,以满足您系统的特定需求,这就是人们雇用您作为程序员的原因。 如果你使用的框架绝对是一切,你的公司只能按一个按钮,他们的系统将完全形成,你将与我们其他程序员一起处于失业状态。

使用.Net 4.5和CultureInfo类,可以在给定日期添加月份和年份。

 DateTime datetime = DateTime.UtcNow; int years = 15; int months = 7; DateTime yearsAgo = CultureInfo.InvariantCulture.Calendar.AddYears(datetime, -years); DateTime monthsInFuture = CultureInfo.InvariantCulture.Calendar.AddMonths(datetime, months); 

由于这是很多打字,我更喜欢创建扩展方法:

 public static DateTime AddYears(this DateTime datetime, int years) { return CultureInfo.InvariantCulture.Calendar.AddYears(datetime, years); } public static DateTime AddMonths(this DateTime datetime, int months) { return CultureInfo.InvariantCulture.Calendar.AddMonths(datetime, months); } DateTime yearsAgo = datetime.AddYears(-years); DateTime monthsInFuture = datetime.AddMonths(months); 

我相信以下方法是非常易于理解和简单的,因为它基于框架日期计算并返回可读的经过时间的字符串,如Facebook的那些。 对于小葡萄牙语单词和复数处理感到抱歉,在我的情况下,这是必要的。

 public static string ElapsedTime(DateTime dtEvent) { TimeSpan TS = DateTime.Now - dtEvent; int intYears = TS.Days / 365; int intMonths = TS.Days / 30; int intDays = TS.Days; int intHours = TS.Hours; int intMinutes = TS.Minutes; int intSeconds = TS.Seconds; if (intYears > 0) return String.Format("há {0} {1}", intYears, (intYears == 1) ? "ano" : "anos"); else if (intMonths > 0) return String.Format("há {0} {1}", intMonths, (intMonths == 1) ? "mês" : "meses"); else if (intDays > 0) return String.Format("há {0} {1}", intDays, (intDays == 1) ? "dia" : "dias"); else if (intHours > 0) return String.Format("há ± {0} {1}", intHours, (intHours == 1) ? "hora" : "horas"); else if (intMinutes > 0) return String.Format("há ± {0} {1}", intMinutes, (intMinutes == 1) ? "minuto" : "minutos"); else if (intSeconds > 0) return String.Format("há ± {0} {1}", intSeconds, (intSeconds == 1) ? "segundo" : "segundos"); else { return String.Format("em {0} às {1}", dtEvent.ToShortDateString(), dtEvent.ToShortTimeString()); } }