Нужна формула: Извлечение лет из секунд с 1 января 0001 00:00 - PullRequest
8 голосов
/ 27 января 2010

Ввод: количество секунд с 1 января года 0001

Вывод: количество полных лет за этот период времени

Я разработал алгоритм, который я не считаю оптимальным решением. Я думаю, что должно быть решение, которое не включает цикл. См. Кодовый блок 1 для алгоритма, который A) определяет количество дней и B) итеративно вычитает 366 или 365 в зависимости от високосных годов из общей суммы дня, увеличивая итоговое значение года

Это не так просто, как делить DayCount на 365,2425 и усекать, потому что мы достигли точки сбоя 1 января 2000 года (31536000 секунд / (365,2425 * 24 * 60 * 60)) = 0,99934.

Есть идеи о цикличном методе извлечения лет из количества секунд с 1 января 0001 00:00?

Мне нужно это выяснить, потому что мне нужна дата, встроенная в long (которая хранит секунды), чтобы я мог отследить годы до 12+ миллионов с точностью до 1 секунды.

Блок кода 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 бит)

Ответы [ 7 ]

20 голосов
/ 27 января 2010

Если вам нужна точность с точностью до секунды , вам, вероятно, понадобится коммерческий пакет datetime; это просто слишком сложно сделать точно с простым алгоритмом. Например:

Из-за этих и других сложностей вам лучше не писать код самостоятельно, если только вы не сможете ослабить ограничение, в котором вам нужна точность, до с точностью до секунды за 12 миллионов лет.

"4 октября 1582 года - святая Тереза ​​из Авилы умирает. Она похоронена на следующий день, 15 октября".

5 голосов
/ 27 января 2010

В Wikipeda есть статья на Юлианская дата с алгоритмом, который вы можете адаптировать к вашим потребностям.

1 голос
/ 08 мая 2012

Вам не нужен цикл, подсчитайте секунды с 1 января 0001 до начала эпохи Unix (1 января 1970 00:00:00) и сохраните где-нибудь. Затем вычтите его из своего ввода, затем используйте любой доступный инструмент для преобразования метки времени Unix (секунды с 1 января 1970 года) в годы, а затем добавьте 1970. Я не знаю много VB-программирования, чтобы опубликовать подробное руководство.

1 голос
/ 27 января 2010
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
0 голосов
/ 10 декабря 2013

Я знаю, что этот вопрос сейчас старый, но я часто вижу такие, и здесь не было простых ответов.

Мое решение использует старый прием написания двух дат, как если бы они были числами (например, «12 декабря 2013», как 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

Обратите внимание, что это довольно наивно: он не учитывает часовые пояса или исторические изменения часового пояса, кроме тех, которые уже обрабатываются классом DateTime .NET. Фактическая работа выполняется методом DateTime.AddSeconds. Надеюсь, это поможет.

0 голосов
/ 02 ноября 2011

Ниже предполагается, что григорианский календарь будет действовать в течение следующих пятисот восьмидесяти четырех с половиной миллиардов лет. Будьте готовы к разочарованию, хотя; календарь может закончиться, поскольку наше Солнце начинает расширяться, изменяя орбиту Земли и продолжительность года, и очень вероятно, что что-то еще будет принято, когда Земля упадет на Солнце семь с половиной миллиардов лет с этого момента.

Кроме того, я даже не пытаюсь обрабатывать даты до принятия григорианского календаря. Я просто возвращаю количество дней, в течение которых дата произошла до 15 октября 1582 года, и необходимость в выражении такого возвращаемого значения является единственной причиной, по которой функция 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
0 голосов
/ 27 января 2010

Я думаю, что это будет работать для вас

function foo(seconds):
  count = seconds
  year = 0
  while (count > 0):
    if leap_year(year)
      count = count - 366
    else
      count = count - 365
    year ++
  return year
...