Need a formula: Extracting Years from Seconds Since January 1, 0001 12:00 AM
输入:自0001年1月1日起的秒数
产量:本期满年产量
我开发了一种我认为不是最优解的算法。我认为应该有一个不涉及循环的解决方案。有关a)确定天数的算法,请参见代码块1;b)根据闰年从天数总和中迭代减去366或365,同时递增年份总和。
这不像将daycount除以365.2425并截断那么简单,因为我们在2000年1月1日遇到了一个故障点(31536000秒/(365.2425*24*60*60))=0.99934。
对于从1月1日1月1日12:00开始的秒数中提取年份的非循环方法有什么想法吗?
我需要弄清楚这一点,因为我需要一个嵌入在一个长(存储秒)中的日期,这样我就可以用1秒的精度追踪年数到1200多万。
代码块1-从秒(包括闰年)中获取年的低效算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | Dim Days, Years As Integer 'get Days Days = Ticks * (1 / 24) * (1 / 60) * (1 / 60) 'Ticks = Seconds from Year 1, January 1 'get years by counting up from the beginning Years = 0 While True 'if leap year If (Year Mod 4 = 0) AndAlso (Year Mod 100 <> 0) OrElse (Year Mod 400 = 0) Then If Days >= 366 Then 'if we have enough days left to increment the year Years += 1 Days -= 366 Else Exit While End If 'if not leap year Else If Days >= 365 Then 'if we have enough days left to increment the year Years += 1 Days -= 365 Else Exit While End If End If End While Return Years |
编辑:我的解决方案是跳过在8位内嵌入日期所节省的内存,并将每个值(秒到年)存储在单独的整数中。这会导致以牺牲内存为代价的即时检索。
edit2:第一次编辑中的拼写错误(8位)
如果您需要精确到第二秒,那么您可能需要一个商业级的日期时间包;它太复杂了,无法用简单的算法精确地完成。例如:
- 很多人都注意到我们每四年有一次闰年,但是你知道每一年被100整除而不是被400整除不是闰年吗?这甚至在大型商业产品中也引起了问题。
- 有些国家不遵守夏令时,而那些遵守夏令时的国家则在一年中的不同时间遵守夏令时。这一点每年都会发生变化。
- 1582年前使用稍有不同的日历
- 1582年只有355天(1752年是354天,视国家而定)。
- 各国在切换时区时存在重大问题。
- 然后是跳跃秒。一些管理机构任意决定我们是否应该每年在时钟上增加一(有时,两)秒。没有办法提前知道下一个闰秒是什么时候,也没有办法知道跳过闰秒的模式。
由于这些和更多的复杂性,您最好不要自己编写代码,除非您可以放松限制,您需要的准确性在1200万年内达到第二位。
"October 4, 1582 – Saint Teresa of ávila dies. She is buried the next day, October 15."
wikipeda有一篇关于朱利安日期的文章,里面有一个算法,你可以适应你的需要。
您不需要一个循环,计算从0001年1月1日到Unix Epoch Start(1970年1月1日00:00:00)的秒数,然后保存到某个地方。然后从输入中减去它,然后使用任何可用的工具将unix时间戳(从1970年1月1日开始的秒数)转换为年份,然后添加1970。我不太懂用VB编程来发布详细的指南。
1 2 3 4 | Const TICKS_PER_YEAR As Long = 315360000000000 Function YearsSinceBeginningOfTimeUntil(ByVal d As DateTime) As Integer Return Math.Floor(d.Ticks / TICKS_PER_YEAR) End Function |
我知道这个问题现在很老了,但我经常看到这样的问题,这里没有任何简单的答案。
我的解决方案使用了一个古老的技巧,即将两个日期写成数字(如"2013年12月12日"即20131212),然后从另一个日期中减去一个日期,然后丢弃最后四个数字。我在f_中找到了我的实现,您可以将它粘贴到linqpad中以检查答案。也要考虑闰年等因素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | let dateFrom = new DateTime(1,1,1) let dateTo = dateFrom.AddSeconds(100000000.0) let yearsSince dateFrom dateTo = let intRepresentation (date: DateTime) = date.ToString"yyyy.MMdd" |> Convert.ToDouble let dateToNum = intRepresentation dateTo let dateFromNum = intRepresentation dateFrom int (dateToNum - dateFromNum) yearsSince dateFrom dateTo |> Dump let dob = DateTime(1985, 4, 16) let calculateAge = yearsSince dob calculateAge DateTime.Today |> Dump |
请注意,这是非常幼稚的:它不考虑时区或历史时区更改,而考虑那些已经由.NET的datetime类处理的更改。实际的咕噜声工作是由datetime.addseconds方法完成的。希望这有帮助。
以下假设公历将在未来的五百八十四年和五十万年内保持有效。不过,要做好失望的准备;当我们的太阳开始膨胀,改变地球的轨道和一年中的持续时间时,日历可能会被废弃,当地球从现在开始坠入太阳75亿年后,它很可能会被采用。
顺便提一句,我甚至不想在采用公历之前处理日期。我只返回日期在1582年10月15日之前发生的天数,并且需要能够表示这种返回值是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | Sub GetDateFromSerial(ByVal dateSerial As ULong, ByRef year As Long, ByRef month As Integer, ByRef dayOfMonth As Integer, ByRef secondsIntoDay As Integer, ByRef asString As String) Const SecondsInOneDay As ULong = 86400 ' 24 hours * 60 minutes per hour * 60 seconds per minute 'Dim startOfGregorianCalendar As DateTime = New DateTime(1582, 10, 15) 'Dim startOfGregorianCalendarInSeconds As ULong = (startOfGregorianCalendar - New DateTime(1, 1, 1)).TotalSeconds Const StartOfGregorianCalendarInSeconds As ULong = 49916304000 secondsIntoDay = dateSerial Mod SecondsInOneDay If dateSerial < StartOfGregorianCalendarInSeconds Then year = -1 month = -1 dayOfMonth = -1 Dim days As Integer = (StartOfGregorianCalendarInSeconds - dateSerial) \ SecondsInOneDay asString = days & IIf(days = 1," day"," days") &" before the adoption of the Gregorian calendar on October 15, 1582" Else 'Dim maximumDateValueInSeconds As ULong = (DateTime.MaxValue - New DateTime(1, 1, 1)).TotalSeconds Const MaximumDateValueInSeconds As ULong = 315537897600 If dateSerial <= MaximumDateValueInSeconds Then Dim parsedDate As DateTime = DateTime.MinValue.AddSeconds(dateSerial) year = parsedDate.Year month = parsedDate.Month dayOfMonth = parsedDate.Day Else ' Move the date back into the range that DateTime can parse, by stripping away blocks of ' 400 years. Aim to put the date within the range of years 2001 to 2400. Dim dateSerialInDays As ULong = dateSerial \ SecondsInOneDay Const DaysInFourHundredYears As Integer = 365 * 400 + 97 ' Three multiple-of-4 years in each 400 are not leap years. Dim fourHundredYearBlocks As Integer = dateSerialInDays \ DaysInFourHundredYears Dim blocksToFactorInLater As Integer = fourHundredYearBlocks - 5 Dim translatedDateSerialInDays As ULong = dateSerialInDays - blocksToFactorInLater * CLng(DaysInFourHundredYears) ' Parse the date as normal now. Dim parsedDate As DateTime = DateTime.MinValue.AddDays(translatedDateSerialInDays) year = parsedDate.Year month = parsedDate.Month dayOfMonth = parsedDate.Day ' Factor back in the years we took out earlier. year += blocksToFactorInLater * 400L End If asString = New DateTime(2000, month, dayOfMonth).ToString("dd MMM") &"," & year End If End Sub Function GetSerialFromDate(ByVal year As Long, ByVal month As Integer, ByVal dayOfMonth As Integer, ByVal secondsIntoDay As Integer) As ULong Const SecondsInOneDay As Integer = 86400 ' 24 hours * 60 minutes per hour * 60 seconds per minute If (year < 1582) Or _ ((year = 1582) And (month < 10)) Or _ ((year = 1582) And (month = 10) And (dayOfMonth < 15)) Then Throw New Exception("The specified date value has no meaning because it falls before the point at which the Gregorian calendar was adopted.") End If ' Use DateTime for what we can -- which is years prior to 9999 -- and then factor the remaining years ' in. We do this by translating the date back by blocks of 400 years (which are always the same length, ' even factoring in leap years), and then factoring them back in after the fact. Dim fourHundredYearBlocks As Integer = year \ 400 Dim blocksToFactorInLater As Integer = fourHundredYearBlocks - 5 If blocksToFactorInLater < 0 Then blocksToFactorInLater = 0 year = year - blocksToFactorInLater * 400L Dim dateValue As DateTime = New DateTime(year, month, dayOfMonth) Dim translatedDateSerialInDays As ULong = (dateValue - New DateTime(1, 1, 1)).TotalDays Const DaysInFourHundredYears As ULong = 365 * 400 + 97 ' Three multiple-of-4 years in each 400 are not leap years. Dim dateSerialInDays As ULong = translatedDateSerialInDays + blocksToFactorInLater * DaysInFourHundredYears Dim dateSerial As ULong = dateSerialInDays * SecondsInOneDay + secondsIntoDay Return dateSerial End Function |
我想这个对你有用
1 2 3 4 5 6 7 8 9 10 | function foo(seconds): count = seconds year = 0 while (count > 0): if leap_year(year) count = count - 366 else count = count - 365 year ++ return year |