下面实际上是您可以做到的最准确的方法,因为“1 个月”的定义会根据月份的不同而变化,并且其他答案都没有考虑到这一点!如果您想了解有关框架中未内置的问题的更多信息,您可以阅读这篇文章:A Real Timespan Object With .Years & .Months(但是,阅读该文章对于理解和使用以下功能不是必需的,它可以 100% 工作,没有其他人喜欢使用的近似值的固有不准确性 - 并且可以随意将 .ReverseIt 函数替换为您的框架上可能具有的内置 .Reverse 函数(这里只是为了完整性)。
请注意,您可以获得任意数量的日期/时间精度、秒和分钟,或秒、分钟和天,最长可达数年(其中包含 6 个部分/段)。如果您指定前两个并且它已超过一年,它将返回“1 年零 3 个月前”并且不会返回其余部分,因为您请求了两个片段。如果它只有几个小时,那么它只会返回“2 小时 1 分钟前”。当然,如果您指定 1、2、3、4、5 或 6 个段(最大为 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"
,因为它知道前 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”函数,它接受一个源字符串和一个指定需要替换的参数,以及另一个指定要替换它的参数,它只替换该字符串的最后一次出现...如果您没有或不想实现它,我已经包含了我的一个,所以在这里,它可以“按原样”工作,无需修改。我知道 reverseit 函数不再需要(存在于 .net 中),但 ReplaceLast 和 ReverseIt 函数是从 pre-.net 时代遗留下来的,所以请原谅它看起来多么过时(仍然 100% 有效,一直在使用em 使用了十多年,可以保证它们没有错误)... :)。干杯。
<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