关于日期时间:需要一个公式:自0001年1月1日上午12:00起提取秒数

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日之前发生的天数,并且需要能够表示这种返回值是GetDateFromSerial函数具有asString参数的唯一原因。

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