8

入力:0001年の1月1日からの秒数

出力:この期間中の通年の数

私は、最適な解決策ではないと思うアルゴリズムを開発しました。ループを伴わない解決策があるべきだと思います。A)日数を決定し、B)うるう年に応じて、うるう年に応じて366または365を繰り返し減算し、年の合計をインクリメントするアルゴリズムについては、コードブロック1を参照してください。

DayCountを365.2425で除算して切り捨てるほど単純ではありません。これは、0002年1月1日に障害点に達したためです(31536000秒/(365.2425 * 24 * 60 * 60))=0.99934。

0001年1月1日午前12:00以降の秒数から年を抽出するための非ループ方式に関するアイデアはありますか?

1秒の精度で1200万年以上の年数を追跡できるように、長い(秒を格納する)に日付を埋め込む必要があるため、これを理解する必要があります。

コードブロック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には、ニーズに適応できるアルゴリズムを使用したユリウス日に関する記事があります。

于 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

以下は、グレゴリオ暦が今後の584億年の間有効であり続けることを前提としています。ただし、失望に備えてください。私たちの太陽が拡大し始め、地球の軌道と一年の期間が変わると、カレンダーが廃棄される可能性があります。地球が太陽に沈むと、75億年後に何か他のものが採用される可能性が非常に高くなります。今から。

余談ですが、私はグレゴリオ暦が採用される前の日付を処理しようとさえしていません。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

この質問は今では古くなっていることは知っていますが、よく似た質問があり、ここには簡単な答えはありませんでした。

私のソリューションでは、2つの日付を数字のように記述し(たとえば、「2013年12月12日」として20131212)、一方を他方から減算して最後の4桁を破棄するという古いトリックを使用しています。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 に答える