带有 .Years & 的实时时间跨度对象.月 [英] A Real Timespan Object With .Years & .Months

查看:35
本文介绍了带有 .Years & 的实时时间跨度对象.月的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

现在,假设我们在我们的网页上写了以下关于某人留下的评论的内容:此评论是在 3 个月零 12 天前写的".即使语句完全相同,这两种情况下的天数也始终不同.在场景 1 中,3 个月零 12 天"等于 102 天.但是,在场景 2 中,3 个月零 12 天"将是 104 天

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

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

所以,这意味着总共 10 天 + 1 天 + 1 个月,转换为 此评论发表于 1 个月零 11 天前.

现在,如果您使用 MS 样式的 TimeSpan 对象(或任何语言的任何 TimeSpan 对象),它将为您提供从 1 月 30 日到 3 月 10 日(39 天)的天数,并且因为 TimeSpan 对象没有存储相对日期(我们减去的基准/初始日期以获得时间跨度),如果你问它已经过了多少个月和多少天,它会假设一个月有 30 天,甚至最坏的情况是,平均值更大超过 30 天,并在几天内返回其余的,所以到 39 天,它会告诉你已经 1 个月零 9 天,你会得到 此评论发表于 1 个月零 9 天前 消息.请记住,这两个场景具有相同的开始日期和相同的当前/结束日期,是的 Microsoft TimeSpan 对象,不允许我们告诉它应该考虑 2013 年 2 月,给了我们一个完全不同的 TimeSpan,关闭整整2天.实际上,它对我们撒了谎.

问题是,人们会相信这一点,谁知道他们可能有什么看法,他们对过去的看法可能会如何改变以及决策和决策当他们试图在自己的脑海中重建过去的事件时,他们可能做出的生活选择,却从未注意到或理解当今无处不在的表现时间的缺陷和固有的失败.他们不会理解编程语言没有意识到(或关心)上个月有 31 天,而与 30、29 或 28 天相反,反之亦然,并且当您增加 TimeSpan 时,这会加起来.

这是这篇文章的核心问题.我知道大多数人不会关心这种差异(但请确保我们中的一些人关心,并且不能背负这个),如果这不打扰您,那没关系.我希望它不会打扰我,我会为自己节省一些时间、压力和失望.如果这不麻烦,您可以使用该函数来高效地以文本显示相对时间(可自定义为 1 到 6 个节点,从秒到年),而不是将其用于通常可以忽略不计的精度.

令我失望的是,我注意到没有真正的时间跨度对象,如果你得到一个时间跨度,然后执行 .years.months 你将一无所获,你只会得到 .days 和更低,因为 timeSpan 对象不携带任何东西来告诉它 timeSpan 是在哪一个月或哪一年创建的.因此,它永远不会真正知道它过去了多少个月,因为每个月的天数在一年甚至更远的闰年变化.

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

<块引用>

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

我想会有一个…

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

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

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

更新:我在下面的答案中显着扩展并更新了我的官方答案和代码使用详细信息,100% 有效答案和代码(完整),准确且准确的相对时间/日期,没有近似值 - 谢谢.

解决方案

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

以下是一些使用示例:bAllowSegments 标识要显示多少段...即:如果 3,则返回字符串将是(例如)... 3 年 2 个月零 13 天"(不包括返回前 3 个时间类别的小时、分钟和秒),但是,如果日期是较新的日期,例如几天前,指定相同的段 (3) 将返回 "4 days, 1小时 13 分钟前",所以它会考虑所有因素!

如果 bAllowSegments 是 2 它将返回 "3 年零 2 个月" 如果 6(最大值)将返回 "3 年、2 个月、13 天、13 小时、29分 9 秒",但是,请注意它会 NEVER RETURN 类似这样的 0 年 0 月 0 天 3 小时 2 分 13 秒ago" 因为它知道前 3 个段中没有日期数据并忽略它们,即使您指定了 6 个段,所以不要担心:).当然,如果里面有一个带0的段,它在形成字符串时会考虑到这一点,并显示为3 days and 4 seconds ago"并忽略0 hours"部分!喜欢,如果喜欢,请评论.

 公共函数 RealTimeUntilNow(ByVal dt As DateTime, Optional ByVal bAllowSegments As Byte = 2) As String' bAllowSegments 标识要显示多少段... 即:如果 3,则返回字符串将是(作为示例)...' "3 years, 2 Month and 13 days" 返回前 3 个时间类别,如果 bAllowSegments 为 2 则返回' 3 年零 2 个月",如果 6(最大值)将返回3 年零 2 个月,13 天,13 小时,29 分 9 秒"Dim rYears、rMonths、rDays、rHours、rMinutes、rSeconds 作为 Int16Dim dtNow = DateTime.NowDim daysInBaseMonth = Date.DaysInMonth(dt.Year, dt.Month)rYears = dtNow.Year - dt.YearrMonths = dtNow.Month - dt.Month如果 rMonths <0 然后 rMonths += 12 : rYears -= 1 ' 将 1 年添加到月中,并从年中删除 1 年.rDays = dtNow.Day - dt.Day如果 rDays <0 然后 rDays += daysInBaseMonth : rMonths -= 1rHours = dtNow.Hour - dt.Hour如果 rHours <0 然后 rHours += 24 : rDays -= 1rMinutes = dtNow.Minute - dt.Minute如果 rMinutes <0 然后 rMinutes += 60 : rHours -= 1rSeconds = dtNow.Second - dt.Second如果 rSeconds <0 然后 rSeconds += 60 : rMinutes -= 1'这是显示功能Dim sb As StringBuilder = New StringBuilder()Dim iSegmentsAdded As Int16 = 0如果 rYears >0 然后 sb.Append(rYears) : sb.Append(" year" & If(rYears <> 1, "s", "") & ", ") : iSegmentsAdded += 1如果 bAllowSegments = iSegmentsAdded 然后转到 parseAndReturn如果 rMonths >0 然后 sb.AppendFormat(rMonths) : sb.Append("month" & If(rMonths <> 1, "s", "") & ", ") : iSegmentsAdded += 1如果 bAllowSegments = iSegmentsAdded 然后转到 parseAndReturn如果 rDays >0 然后 sb.Append(rDays) : sb.Append(" day" & If(rDays <> 1, "s", "") & ", ") : iSegmentsAdded += 1如果 bAllowSegments = iSegmentsAdded 然后转到 parseAndReturn如果 rHours >0 然后 sb.Append(rHours) : sb.Append("hour" & If(rHours <> 1, "s", "") & ", ") : iSegmentsAdded += 1如果 bAllowSegments = iSegmentsAdded 然后转到 parseAndReturn如果 rMinutes >0 然后 sb.Append(rMinutes) : sb.Append("minute" & If(rMinutes <> 1, "s", "") & ", ") : iSegmentsAdded += 1如果 bAllowSegments = iSegmentsAdded 然后转到 parseAndReturn如果 rSeconds >0 然后 sb.Append(rSeconds) : sb.Append(" second" & If(rSeconds <> 1, "s", "") & "") : iSegmentsAdded += 1解析并返回:' 如果字符串完全为空,则意味着它刚刚发布,所以它不到一秒前,并且传递空字符串将导致错误' 所以我们构造了我们自己的有意义的字符串,它仍然适合Posted * ago"语法......If sb.ToString = "" Then sb.Append("小于1秒")Return ReplaceLast(sb.ToString.TrimEnd(" ", ",").ToString, ",", " and")结束函数

当然,您将需要一个ReplaceLast"函数,它接受一个源字符串和一个指定需要替换的内容的参数,以及另一个指定要替换的内容的参数,它只替换最后一次出现的内容那个字符串......如果你没有或不想实现它,我已经包含了我的一个,所以在这里,它可以按原样"工作,无需修改.我知道不再需要 reverseit 函数(存在于 .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 特定版本的字符串反转函数)将被编写为更有效 :).干杯.

_公共函数 ReplaceLast(ByVal sReplacable As String, ByVal sReplaceWhat As String, ByVal sReplaceWith As String) As String' 让空字符串参数运行,以防我们不知道我们是否正在发送空字符串.sReplacable = sReplacable.ReverseItsReplacable = Replace(sReplacable, sReplaceWhat.ReverseIt, sReplaceWith.ReverseIt, , 1) ' 仅在反向版本中执行第一项!返回 sReplacable.ReverseIt.ToString结束函数<扩展()>_公共函数 ReverseIt(ByVal strS As String, Optional ByVal n As Integer = -1) As StringDim strTempX As String = "", intI As Integer如果 n >strS.Length 或 n = -1 然后 n = strS.Length对于 intI = n 到 1 步 -1strTempX = strTempX + Mid(strS, intI, 1)下一个intIReverseIt = strTempX + Right(strS, Len(strS) - n)结束函数

Consider the following 2 scenarios: Scenario 1). Today is May 1st 2012, and Scenario 2). Today is September 1st 2012.

Now, consider that we write on our webpage the following about a comment someone has left: "This comment was written 3 months and 12 days ago". The amount of days in both these scenarios will ALWAYS be different even though the statement is exactly the same. In Scenario 1, "3 months and 12 days" would equal 102 days. However, in Scenario 2, "3 months and 12 days" would be 104 days!

Now, to corner in on my point, lets use a different example and say that someone left a comment on our site on Jan 30th 2013, and today is March 10th 2013. Our real TimeSpan object needs to know this relative date, and can figure out the following:

  • That there is 10 days in March,
  • That there is 1 day in Jan (counting from 30th to 31st).
  • That the month Feb is one month regardless of how many days there are in it (even though it's 28 days).

So, it would mean 10 days + 1 day + 1 month total, translating to This comment was posted 1 Month and 11 Days ago.

Now, if you used the MS style TimeSpan object (or any TimeSpan object in any language), it would give you the number of days from 30th Jan to 10 March (39 days), and because the TimeSpan object doesn't store relative date (the base/initial date we subtracted to get the TimeSpan), if you asked it how many months and days it has been, it will assume there is 30 days in one month, or even worst, the average which is greater than 30 days, and return the rest in days, so to get to 39 days, it will tell you it's been 1 Month and 9 Days and you will get the This comment was posted 1 Month and 9 Days ago message. Remember, both these scenarios have the same start date and same current/end date, yes the Microsoft TimeSpan object, by not allowing us to tell it the month of Feb 2013 should be considered, has given us a completely different TimeSpan, off by a whole 2 days. It has, in effect, lied to us.

The problem is, people will believe this, and who knows what perceptions they may have, how their perceptions of the past may change and the decisions & life choices they may make when trying to reconstruct events within the past inside their own minds, while never noticing or understanding the drawback and inherent failure of representing time that is so pervasive everywhere today. They will not understand that programming languages don't realize (or care) that last month had 31 days in it, as oppposed to 30, 29 or 28 - or visa versa, and that this adds up when you increase the TimeSpan.

This is the problem at the heart of this post. I understand that most people will not care about this difference (but be sure that some of us do, and cannot have this on our backs), and if this doesn't bother you, thats ok. I wish it didn't bother me, I would have saved myself some time, stress and disappointment. If this is not a bother, you can use the function for the efficient textual display of relative time (customizable to 1 to 6 nodes from seconds to years), instead of using it for the usually negligible accuracy it provides.

To my disappointment I noticed that there is no real timespan object, if you get a timespan, and do a .years or .months you'll get nothing, you'll only get .days and lower because a timeSpan object doesn't carry anything to tell it which month or year the timeSpan was created on. Therefore it'll never really know how many months it's been since days in each month vary over a year and even further over a leap year.

In response to this, I'll post a function I developed in order to get ACCURATE readings and be able to return things like the following on my ASP.NET web page...

Posted 4 years, 3 months, 14 days, 15 hours, 18 minutes and 24 seconds ago

I figured there'd be a …

timeSpan.GetActualNumberOf[Months/Days/Hours/etc] (base date must be provided of course)

… type method on this datatype, but there wasn't.

All you'd really have to do is create another property on the timeSpan object to give it a base date on which the difference was calculated, then the above lovely string would be calculable pretty easily, and a .year & .month would exist!

UPDATE: I have significantly expanded upon and updated my official answer and code usage details in my answer below, 100% working answer and code (in full), accurate and exact relative time/dates, no approximations - thanks.

解决方案

Here is the main answer with code, please note that you can get any number of dates/times accuracy, seconds & minutes, or seconds, minutes and days, anywhere up to years (which would contain 6 parts/segments). If you specify top two and it's over a year old, it will return "1 year and 3 months ago" and won't return the rest because you've requested two segments. if it's only a few hours old, then it will only return "2 hours and 1 minute ago". Of course, same rules apply if you specify 1, 2, 3, 4, 5 or 6 segmets (maxes out at 6 because seconds, minutes, hours, days, months, years only make 6 types). It will also correct grammer issues like "minutes" vs "minute" depending on if it's 1 minute or more, same for all types, and the "string" generated will always be grammatically correct.

Here are some examples for use: 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" (won't include hours, minutes and seconds as the top 3 time categories are returned), if however, the date was a newer date, such as something a few days ago, specifying the same segments (3) will return "4 days, 1 hour and 13 minutes ago" instead, so it takes everything into account!

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", but, be reminded that it will NEVER RETURN something like this "0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago" as it understands there is no date data in the top 3 segments and ignores them, even if you specify 6 segments, so don't worry :). Of course, if there is a segment with 0 in it, it will take that into account when forming the string, and will display as "3 days and 4 seconds ago" and ignoring the "0 hours" part! Enjoy and please comment if you like.

 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

Of course, you will need a "ReplaceLast" function, which takes a source string, and an argument specifying what needs to be replaced, and another arg specifying what you want to replace it with, and it only replaces the last occurance of that string... i've included my one if you don't have one or dont want to implement it, so here it is, it will work "as is" with no modification needed. I know the reverseit function is no longer needed (exists in .net) but the ReplaceLast and the ReverseIt func are carried over from the pre-.net days, so please excuse how dated it may look (still works 100% tho, been using em for over ten years, can guarante they are bug free)... :). Also, if you are using VB6, you can use StrReverse (wrapping it around the string extended with the .ReverseIt extension method), instead of using the ReverseIt() function (provided as an extension method). So, instead of doing sReplacable.ReverseIt, you'd do StrReverse(sReplacable) as StrReverse() is a built in VB6 function (and does the exact same thing, reverses a given string, and does nothing more). If you use StrReverse() instead of my generic ReverseIt function, feel free to delete the ReverseIt function/extension. StrReverse() function should be available in .NET as long as you are importing the legacy ms-visualbasic-dll library. Makes no difference either way, I had written ReverseIt() before I even know a StrReverse() function had existed, and had been using it ever since out of habit (no real reason to use mine as opposed to the in-built generic function StrReverse) - in fact, I'm sure StrReverse (or a similar, newer .NET specific version of a string reversing function) would be written to be more efficient :). cheers.

<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 

这篇关于带有 .Years &amp; 的实时时间跨度对象.月的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆