这是这篇文章的核心问题.我知道大多数人不会关心这种差异(但请确保我们中的一些人关心,并且不能背负这个),如果这不打扰您,那没关系.我希望它不会打扰我,我会为自己节省一些时间、压力和失望.如果这不麻烦,您可以使用该函数来高效地以文本显示相对时间(可自定义为 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 然后 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 当然,您将需要一个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 agoI 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 += 1parseAndReturn: ' 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 FunctionOf 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.ToStringEnd 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 & 的实时时间跨度对象.月的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云! 08-01 22:42