8

输入:自 0001 年 1 月 1 日以来的秒数

输出:此时间段内的整年数

我开发了一种我认为不是最佳解决方案的算法。我认为应该有一个不涉及循环的解决方案。有关 A) 确定天数和 B) 的算法,请参见代码块 1,根据闰年从总天数中迭代地减去 366 或 365,同时增加总年数

它不像将 DayCount 除以 365.2425 并截断那么简单,因为我们在 0002 年 1 月 1 日遇到了故障点(31536000 秒/(365.2425 * 24 * 60 * 60))= 0.99934。

关于从 0001 年 1 月 1 日 12:00 AM 以来的秒数中提取年份的非循环方法的任何想法?

我需要弄清楚这一点,因为我需要一个嵌入 long (存储秒)的日期,以便我可以以 1 秒的精度跟踪 12+ 百万年。

代码块 1 - 从秒中获取年份的低效算法(包括闰年)

        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 位)

4

7 回答 7

23

如果您需要精确到,您将需要一个商业级的日期时间包;用简单的算法准确地完成它太复杂了。例如:

由于这些以及更多的复杂性,最好不要自己编写代码,除非您可以将需要精确度的约束放宽到1200 万年的那一秒。

“1582 年 10 月 4 日——阿维拉的圣特蕾莎去世。她于次日 10 月 15 日被埋葬。”

于 2010-01-27T14:47:33.757 回答
5

Wikipeda 有一篇关于Julian Date的文章,其中包含可以适应您需要的算法。

于 2010-01-27T06:37:49.123 回答
1
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
于 2010-01-27T06:44:59.327 回答
1

您不需要循环,计算从 0001 年 1 月 1 日到 unix 纪元开始(1970 年 1 月 1 日 00:00:00)的秒数,然后保存在某处。然后从您的输入中减去它,然后使用任何可用的工具将 unix 时间戳(从 1970 年 1 月 1 日开始的秒数)转换为年,然后添加 1970。我不太了解 VB 编程来发布详细指南。

于 2012-05-08T13:35:05.747 回答
0

以下假设公历将在接下来的五百八十四亿年保持有效。不过,要做好失望的准备;当我们的太阳开始膨胀,改变地球的轨道和一年的持续时间时,日历可能最终会被废弃,当地球落入太阳七十五亿年时,很可能会采用其他东西现在起。

顺便说一句,我什至不尝试在采用公历之前处理日期。我只是返回日期在 1582 年 10 月 15 日之前发生的天数,并且需要能够表达这种返回值是GetDateFromSerial函数具有asString参数的唯一原因。

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
于 2011-11-01T20:09:18.477 回答
0

我知道这个问题现在很老了,但我经常看到类似的问题,而且这里没有任何简单的答案。

我的解决方案使用了一个旧技巧,将两个日期写成数字(例如 'Dec 12th 2013' as 20131212),然后从另一个中减去一个并丢弃最后四位数字。我在 F# 中挖掘了我的实现,您可以将其粘贴到 LinqPad 中以检查答案。也考虑闰年等:

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 方法完成。希望这可以帮助。

于 2013-12-10T12:00:11.543 回答
-1

我认为这对你有用:

function foo(days):
  count = days
  year = 0
  while (count > 0):
    if leap_year(year)
      count = count - 366
    else
      count = count - 365
    year ++
  return year
于 2010-01-27T06:52:56.657 回答