C# 带有 .Years 和 .Months 的实时时间跨度对象
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1916358/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
A Real Timespan Object With .Years & .Months
提问by Erx_VB.NExT.Coder
Consider the following 2 scenarios: Scenario 1). Today is May 1st 2012, and Scenario 2). Today is September 1st 2012.
考虑以下 2 个场景:场景 1)。今天是 2012 年 5 月 1 日,情景 2)。今天是 2012 年 9 月 1 日。
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
!
现在,考虑我们在我们的网页上写下关于某人留下的评论的以下内容:“此评论是在 3 个月零 12 天前写的”。即使语句完全相同,这两种情况下的天数也始终不同。在场景 1 中,“3 个月零 12 天”将等于102 days
. 但是,在场景 2 中,“3 个月零 12 天”将是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:
现在,为了说明我的观点,让我们举一个不同的例子,假设有人在 2013 年 1 月 30 日在我们的网站上发表了评论,今天是 2013 年 3 月 10 日。我们的真实 TimeSpan 对象需要知道这个相对日期,并且可以计算出以下内容:
- 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).
- 三月有10天,
- 一月有 1 天(从 30 日到 31 日)。
- 无论二月有多少天,二月都是一个月(即使是 28 天)。
So, it would mean 10 days + 1 day + 1 month total, translating to This comment was posted 1 Month and 11 Days ago
.
因此,这意味着总共 10 天 + 1 天 + 1 个月,转换为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.
现在,如果您使用 MS 样式的 TimeSpan 对象(或任何语言的任何 TimeSpan 对象),它将为您提供从 1 月 30 日到 3 月 10 日(39 天)的天数,并且因为 TimeSpan 对象不存储相对日期(我们减去基准/初始日期以获得时间跨度),如果你问它已经过了多少个月和多少天,它会假设一个月有 30 天,甚至最坏的情况是平均超过 30 天,并以天为单位返回其余部分,因此要达到 39 天,它会告诉您已经过了 1 个月零 9 天,您将获得 This comment was posted 1 Month and 9 Days ago
信息。请记住,这两个场景具有相同的开始日期和相同的当前/结束日期,是的 Microsoft TimeSpan 对象,不允许我们告诉它应该考虑 2013 年 2 月,给了我们一个完全不同的 TimeSpan,关闭整整2天。实际上,它对我们撒了谎。
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.
问题是,人们会相信这一点,谁知道他们可能有什么看法,他们对过去的看法可能会如何改变,以及他们试图在自己的脑海中重建过去的事件时可能做出的决定和生活选择,而从不注意到或理解今天无处不在的时间表现的缺陷和固有的失败。他们不会理解编程语言没有意识到(或关心)上个月有 31 天,而与 30、29 或 28 天相反,反之亦然,并且当您增加 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.
这是这篇文章的核心问题。我知道大多数人不会关心这种差异(但请确保我们中的一些人关心,并且不能背负这个),如果这不打扰您,那没关系。我希望它不会打扰我,我会为自己节省一些时间、压力和失望。如果这不是一个麻烦,您可以使用该函数来高效地文本显示相对时间(可自定义为 1 到 6 个节点,从秒到年),而不是将其用于它提供的通常可以忽略不计的精度。
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.
令我失望的是,我注意到没有真正的时间跨度对象,如果你得到一个时间跨度,然后做一个.years
或者.months
你什么也得不到,你只会得到.days
和降低,因为 timeSpan 对象没有任何东西告诉它哪个月或创建 timeSpan 的年份。因此,它永远不会真正知道它已经过了多少个月,因为每个月的天数在一年甚至闰年中都不同。
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...
作为回应,我将发布一个我开发的函数,以便获得准确的读数,并能够在我的 ASP.NET 网页上返回如下内容......
Posted 4 years, 3 months, 14 days, 15 hours, 18 minutes and 24 seconds ago
发表于 4 年 3 个月 14 天 15 小时 18 分 24 秒前
I figured there'd be a …
我以为会有一个……
timeSpan.GetActualNumberOf[Months/Days/Hours/etc]
(base date must be provided of course)
timeSpan.GetActualNumberOf[Months/Days/Hours/etc]
(当然必须提供基准日期)
… 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!
你真正需要做的就是在 timeSpan 对象上创建另一个属性,给它一个计算差异的基准日期,然后上面可爱的字符串将很容易计算,并且会存在.year
& .month
!
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.
更新:我在下面的答案中大大扩展并更新了我的官方答案和代码使用详细信息,100% 工作答案和代码(完整),准确和准确的相对时间/日期,没有近似值 - 谢谢。
采纳答案by Erx_VB.NExT.Coder
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.
这是代码的主要答案,请注意,您可以获得任意数量的日期/时间精度、秒和分,或秒、分和天,最多可达年(其中将包含 6 个部分/段)。如果您指定前两个并且它已经超过一年,它将返回“1 年零 3 个月前”并且不会返回其余部分,因为您请求了两个段。如果它只有几个小时,那么它只会返回“2 小时 1 分钟前”。当然,如果您指定 1、2、3、4、5 或 6 个段(最大为 6,因为秒、分钟、小时、天、月、年只有 6 种类型),则适用相同的规则。它还将纠正语法问题,例如“分钟”与“分钟”,具体取决于它是 1 分钟还是更长,所有类型都相同,以及“字符串”
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!
以下是一些使用示例: bAllowSegments 标识要显示多少个段... 即:如果 3,则返回字符串将是(作为示例)... "3 years, 2 months and 13 days"
(不包括小时、分钟和秒作为前 3 个时间返回类别),但是,如果日期是较新的日期,例如几天前,指定相同的段 (3) 将"4 days, 1 hour and 13 minutes ago"
改为返回,因此它会考虑所有因素!
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.
如果 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
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.
当然,您将需要一个“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 特定版本的字符串反转函数)将被编写为更有效:)。干杯。
<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
回答by Jeff Hornby
I would say that the current TimeSpan is a real timespan object, i.e., the amount of time between Jan 1 2008 1:31 a.m. and Feb. 3, 2008 at 6:45 a.m. is the same as the amount of time between Feb. 5, 2008 at 1:45 p.m. and March 9, 2008 at 6:59 p.m.. What you are looking for is in actuality the difference between two datetimes.
我会说当前的 TimeSpan 是一个真实的时间跨度对象,即 2008 年 1 月 1 日上午 1:31 和 2008 年 2 月 3 日上午 6:45 之间的时间量与 2 月 5 日之间的时间量相同,2008 年下午 1:45 和 2008 年 3 月 9 日下午 6:59。您正在寻找的实际上是两个日期时间之间的差异。
As for the .MakeMagicHappen.gimmeSomethingPretty.surelyMShasThoughtAboutThisDilema to fulfill the specific needs of your system, that's why people hire you as a programmer. If the framework you are using does absolutely everything, your company would just be able to presss a single button and their system would pop out fully formed and you'd be on the unemployment line along with the rest of us programmers.
至于 .MakeMagicHappen.gimmeSomethingPretty.surelyMShasThoughtAboutThisDilema 来满足您系统的特定需求,这就是人们雇用您作为程序员的原因。如果您使用的框架绝对可以解决所有问题,那么您的公司只需按一个按钮,他们的系统就会完全成型,您将与我们其他程序员一起处于失业线上。
回答by brianary
Here's how to add some extension methods for this with C# using mean values:
以下是如何使用 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);
}
}
回答by Jeffrey Hantin
What you are looking for is indeed not what TimeSpan
represents. TimeSpan
represents an interval as a count of ticks, without respect to a base DateTime
or Calendar
.
你要找的确实不是TimeSpan
代表什么。 TimeSpan
将间隔表示为滴答计数,与基数DateTime
或无关Calendar
。
A new DateDifference
type might make more sense here, with a constructor or factory method taking a base DateTime
, a target DateTime
, and optionally a Calendar
(defaulting to CultureInfo.CurrentCulture) with which to compute the various difference components (years, months, etc.)
新DateDifference
类型在这里可能更有意义,构造函数或工厂方法采用 base DateTime
、 targetDateTime
和可选的Calendar
(默认为 CultureInfo.CurrentCulture),用于计算各种差异组件(年、月等)
EDIT:It looks to me like Noda Timemay have the tools you need for this — the Period
class "[r]epresents a period of time expressed in human chronological terms: hours, days, weeks, months and so on", and in particular Period.Between(then, now, PeriodUnits.AllUnits)
seems to be the precise calculation you're asking for — but it's necessarily a much more complex class than TimeSpan
. The Key Concepts pageon the Noda Time wiki explains how "humans make time messy":
编辑:在我看来,Noda Time可能有你需要的工具——Period
该类“[r] 表示用人类时间顺序表示的一段时间:小时、天、周、月等”,特别是Period.Between(then, now, PeriodUnits.AllUnits)
似乎是您要求的精确计算 - 但它必然是一个比TimeSpan
. 的主要概念页上的野田佳彦时间维基解释如何“人类腾出时间凌乱”:
Leaving aside the tricky bits of astronomy and relativity, mankind has still made time hard to negotiate. If we all used ticks from the Unix epoch to talk about time, there wouldn't be a need for a library like Noda Time.
But no, we like to talk in years, months, days, weeks - and for some reason we like 12pm (which confusingly comes before 1pm) to be roughly the time at which the sun is highest... so we have time zones.
Not only that, but we don't all agree on how many months there are. Different civilizations have come up with different ways of splitting up the year, and different numbers for the years to start with. These are calendar systems.
撇开天文学和相对论的棘手问题不谈,人类仍然难以谈判。如果我们都使用 Unix 时代的刻度来谈论时间,那么就不需要像 Noda Time 这样的库了。
但是不,我们喜欢以年、月、日、周为单位进行讨论——出于某种原因,我们喜欢中午 12 点(令人困惑的是在下午 1 点之前)大致是太阳最高的时间……所以我们有时区。
不仅如此,但我们并不都同意有多少个月。不同的文明想出了不同的划分年份的方法,以及不同的年份数字。这些是日历系统。
回答by Tuco
I believe that the following method is pretty trusteable and straightforward, since it's based on the framework date calculation and returns a readable elapsed time strings like Facebook's ones. Sorry about the little portuguese words and plural treatment, in my case it was necessary.
我相信以下方法非常可靠且简单,因为它基于框架日期计算并返回一个可读的经过时间字符串,如 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());
}
}
回答by Zarepheth
Using .Net 4.5 and the CultureInfo
class, one can add months and years to a given date.
使用 .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);
Since that's a lot of typing, I prefer to create extension methods:
由于需要大量输入,我更喜欢创建扩展方法:
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);
回答by Marc Roussel
Well, better late then nothing I suppose ;)
好吧,我想晚一点就好了;)
And this is my modified version :
这是我的修改版本:
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 " : "");}
回答by user1367200
I took the accepted answer and converted it from VB.Net to C# and made a few modification/improvements as well. I got rid of the string reversals, which were being used to replace the last instance of a string, and used an extension method that more directly finds and replaces the last instance of a string.
我接受了接受的答案并将其从 VB.Net 转换为 C# 并进行了一些修改/改进。我摆脱了用于替换字符串的最后一个实例的字符串反转,并使用了一种更直接地查找和替换字符串的最后一个实例的扩展方法。
Example of how to call the method:
如何调用该方法的示例:
PeriodBetween(#2/28/2011#, DateTime.UtcNow, 6)
Main Method:
主要方法:
public static string PeriodBetween(DateTime then, DateTime now, byte numberOfPeriodUnits = 2)
{
// Translated from VB.Net to C# from: https://stackoverflow.com/a/1956265
// numberOfPeriodUnits identifies how many time period units to show.
// If numberOfPeriodUnits = 3, function would return:
// "3 years, 2 months and 13 days"
// If numberOfPeriodUnits = 2, function would return:
// "3 years and 2 months"
// If numberOfPeriodUnits = 6, (maximum value), function would return:
// "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds"
if (numberOfPeriodUnits > 6 || numberOfPeriodUnits < 1)
{
throw new ArgumentOutOfRangeException($"Parameter [{nameof(numberOfPeriodUnits)}] is out of bounds. Valid range is 1 to 6.");
}
short Years = 0;
short Months = 0;
short Days = 0;
short Hours = 0;
short Minutes = 0;
short Seconds = 0;
short DaysInBaseMonth = (short)(DateTime.DaysInMonth(then.Year, then.Month));
Years = (short)(now.Year - then.Year);
Months = (short)(now.Month - then.Month);
if (Months < 0)
{
Months += 12;
Years--; // add 1 year to months, and remove 1 year from years.
}
Days = (short)(now.Day - then.Day);
if (Days < 0)
{
Days += DaysInBaseMonth;
Months--;
}
Hours = (short)(now.Hour - then.Hour);
if (Hours < 0)
{
Hours += 24;
Days--;
}
Minutes = (short)(now.Minute - then.Minute);
if (Minutes < 0)
{
Minutes += 60;
Hours--;
}
Seconds = (short)(now.Second - then.Second);
if (Seconds < 0)
{
Seconds += 60;
Minutes--;
}
// This is the display functionality.
StringBuilder TimePeriod = new StringBuilder();
short NumberOfPeriodUnitsAdded = 0;
if (Years > 0)
{
TimePeriod.Append(Years);
TimePeriod.Append(" year" + (Years != 1 ? "s" : "") + ", ");
NumberOfPeriodUnitsAdded++;
}
if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
{
goto ParseAndReturn;
}
if (Months > 0)
{
TimePeriod.AppendFormat(Months.ToString());
TimePeriod.Append(" month" + (Months != 1 ? "s" : "") + ", ");
NumberOfPeriodUnitsAdded++;
}
if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
{
goto ParseAndReturn;
}
if (Days > 0)
{
TimePeriod.Append(Days);
TimePeriod.Append(" day" + (Days != 1 ? "s" : "") + ", ");
NumberOfPeriodUnitsAdded++;
}
if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
{
goto ParseAndReturn;
}
if (Hours > 0)
{
TimePeriod.Append(Hours);
TimePeriod.Append(" hour" + (Hours != 1 ? "s" : "") + ", ");
NumberOfPeriodUnitsAdded++;
}
if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
{
goto ParseAndReturn;
}
if (Minutes > 0)
{
TimePeriod.Append(Minutes);
TimePeriod.Append(" minute" + (Minutes != 1 ? "s" : "") + ", ");
NumberOfPeriodUnitsAdded++;
}
if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
{
goto ParseAndReturn;
}
if (Seconds > 0)
{
TimePeriod.Append(Seconds);
TimePeriod.Append(" second" + (Seconds != 1 ? "s" : "") + "");
NumberOfPeriodUnitsAdded++;
}
ParseAndReturn:
// If the string is empty, that means the datetime is less than a second in the past.
// An empty string being passed will cause an error, so we construct our own meaningful
// string which will still fit into the "Posted * ago " syntax.
if (TimePeriod.ToString() == "")
{
TimePeriod.Append("less than 1 second");
}
return TimePeriod.ToString().TrimEnd(' ', ',').ToString().ReplaceLast(",", " and");
}
ReplaceLast Extension Method:
ReplaceLast 扩展方法:
public static string ReplaceLast(this string source, string search, string replace)
{
int pos = source.LastIndexOf(search);
if (pos == -1)
{
return source;
}
return source.Remove(pos, search.Length).Insert(pos, replace);
}