2010-01-27 19 views
7

de entrada: Número de segundos desde el 1 de enero del año 0001fórmula: Años extraer de segundos desde el 1 de enero de 0001 12:00a.m.

de salida: Número de años completos durante este período de tiempo

Desarrollé un algoritmo que no creo que sea la solución óptima. Creo que debería haber una solución que no implique un ciclo. Ver Código Bloque 1 para el algoritmo que A) Determina la cantidad de días y B) Resta iterativamente 366 o 365 según los Años Leap del Total diario al incrementar el Total del año

No es tan simple como Dividir DayCount por 365.2425 y truncando, porque llegamos a un punto de falla el 1 de enero de 0002 (31536000 Seconds/(365.2425 * 24 * 60 * 60)) = 0.99934.

¿Alguna idea sobre un método sin bucle para extraer años de una cantidad de segundos desde el 1 de enero de 0001 12:00 AM?

Necesito resolver esto porque necesito una fecha incrustada en un largo (que almacena segundos) para que pueda rastrear años a más de 12 millones con una precisión de 1 segundo.

bloque de código 1 - ineficiente Algoritmo para obtener Años de segundos (incluyendo los años bisiestos)

 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 

Editar: Mi solución fue saltarse el ahorro de memoria de incrustar una fecha dentro de 8 bits y almacenar cada valor (segundos a años) en enteros separados. Esto causa recuperaciones instantáneas a expensas de la memoria.

Edit2: error tipográfico en la primera edición (8 bits)

+4

¿Está al tanto de las reformas del calendario que resultaron en unos pocos días que se saltaron? – Thilo

+1

¡No te olvides de los segundos interminables! : D – Miles

+0

Estoy confundido, ¿por qué no estás usando la época? – JSchlather

Respuesta

20

Si necesita una precisión hasta el segundo, es probable que desee un paquete de fecha y hora de calidad comercial; es demasiado complejo para hacer con precisión con un algoritmo simple. Por ejemplo:

  • Muchas personas han notado que tenemos años bisiestos cada 4 años, pero ¿sabía que todos los años divisibles por 100 pero no por 400 es no un año bisiesto? Esto ha causado problemas incluso en large commercial products.
  • Algunos países no observan ahorro de luz diurna, y los que sí lo observan do so at different times of the year. Esto changes arbitrarily de un año a otro.
  • años antes de 1582 utilizar un slightly different calendar
  • Había sólo 355 días en the year 1582 (o 354 en the year 1752, dependiendo del país).
  • Hay major issues cuando los países cambian de zona horaria.
  • Luego está leap-seconds. Algunos órganos rectores deciden arbitrariamente si debemos agregar (o algunas veces, dos) segundos al reloj cada año.No hay manera de saber de antemano cuándo tendremos el siguiente segundo bisiesto, y ningún patrón en los segundos intercalares pasados.

Debido a estas y más complicaciones, es mejor no escribir el código usted mismo, a menos que se puede relajar la restricción de que se necesita una precisión de las segundas muy más de 12 millones de años.

"4 de octubre, 1582 - Muere Santa Teresa de Ávila. Está enterrada al día siguiente, 15 de octubre".

5

Wikipeda tiene un artículo sobre Julian Date con un algoritmo que se podía adaptar a sus necesidades.

+0

Esto podría funcionar, lo analizaré un poco más.Si puedo obtener un algoritmo O (1) para ir a Segundos desde 1900 -> Julian -> Gregorian, entonces seremos configurados (suponiendo que es más rápido que las iteraciones de 2000-10,000,000 a través del ciclo anterior) –

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 
+1

Las marcas por año están más cerca de 31556952 que es un poco más que las marcas por leapyear, por lo que nos quedamos cortos en lograr el siguiente número entero, por lo que Floor nos da el número del año anterior. –

+0

¿Has probado la función? – Jay

0

creo que esto va a funcionar para usted

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

Tenía la esperanza de evitar un bucle –

+0

Aunque este es el más limpio –

+1

Creo que hay más de 366 segundos en un año –

0

Lo siguiente asume que el calendario gregoriano permanecerá vigente durante los próximos quinientos ochenta y cuatro mil quinientos millones de años. Sin embargo, prepárate para la decepción; el calendario puede terminar siendo eliminado cuando nuestro sol comienza a expandirse, cambiando la órbita de la Tierra y la duración del año, y es muy probable que se adopte algo más cuando la Tierra caiga en el sol siete mil quinientos millones de años desde ahora.

Como un lado, ni siquiera trato de manejar las fechas antes de la adopción del calendario gregoriano. Simplemente devuelvo el número de días que la fecha ocurrió antes del 15 de octubre de 1582, y la necesidad de poder expresar ese tipo de valor de retorno es la única razón por la cual la función GetDateFromSerial tiene un parámetro 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 
1

No es necesario un bucle, el cálculo de segundos desde enero 1ro 0001 a Unix Epoch inicio (1 de enero 1970 00:00:00), y guardar en algún lugar. Luego reste de su entrada, luego use cualquier herramienta disponible para convertir la marca de tiempo de Unix (segundos del 1 de enero de 1970) en años, luego agregue 1970. No sé mucha programación de VB para publicar una guía detallada.

0

Sé que esta pregunta ya es antigua, pero veo las que a menudo y no hubo respuestas fáciles aquí.

Mi solución usa un viejo truco para escribir dos fechas como si fueran números (por ejemplo, '12 de diciembre de 2013' como 20131212) y luego sustraer una de la otra y descartar los últimos cuatro dígitos. Desenterré mi implementación en F #, puedes pegarla en LinqPad para verificar las respuestas. Toma años bisiestos etc en cuenta también:

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 

Tenga en cuenta que esto es bastante ingenua: No se necesita husos horarios o cambios de zona horaria históricos en cuenta más allá de los que ya están a cargo de la clase DateTime de .NET. El trabajo actual se está haciendo con el método DateTime.AddSeconds. Espero que esto ayude.

Cuestiones relacionadas