VBScriptさんま屋.

VBScriptの個人的なメモです (´・ω・`)

カレンダー計算(3)万年カレンダー

万年カレンダー

MJDか日付を入力すると、その月のカレンダーの形状を表示する。
入力した日は★で表す。曜日と日干支も表示する。
日付は1582年10月15日以降はグレゴリオ暦、それより前はユリウス暦で表示する。

'MJDや日付を指定すると、カレンダーを表示する

Dim cal '自作のオブジェクト
Dim ret
Dim mjd '修正ユリウス日
Dim dat '日付
Dim wk  '曜日
Dim eto '日干支
Dim tbl 'カレンダーを表す文字列

Set cal = New C_Calender
cal.Mode = 0 'グレゴリオ暦かユリウス暦かを自動で判断

ret = Date() 'デフォルト値として本日の日付

Do  
    ret = InputBox("MJDまたは日付を入力してください。", "万年カレンダー", ret)
    If ret = "" Then Exit Do 'キャンセルボタンが押された場合は終了
    If IsNumeric(ret) Then 'MJDが入力された場合
        mjd = CDbl(ret)
        dat = cal.GetDate(mjd)
        wk = cal.GetJWeek(mjd)
        eto = cal.GetEto(mjd)
        tbl = cal.GetCalender(dat)
        Call ShowCalender(dat, mjd, wk, eto, tbl)
    
    ElseIf UBound(Split(ret, "/")) = 2 Then '日付が入力された場合
        dat = ret '年月日の配列
        mjd = cal.GetMJD(dat)
        wk = cal.GetJWeek(mjd)
        eto = cal.GetEto(mjd)
        tbl = cal.GetCalender(dat)
        Call ShowCalender(dat, mjd, wk, eto, tbl)

    Else
        ret = Date()

    End If

Loop


Function ShowCalender(dat, mjd, wk, eto, tbl)
    Dim buf
    Dim title
    Dim msg

    buf = Split(dat, "/")
    title = buf(1) & "月" & buf(2) & "日(" & wk & ")"

    msg = "MJD=" & mjd & vbCr & eto & vbCr & vbCr & tbl 
    Msgbox msg, , title
End Function


Class C_Calender
    Private m_mode                

    Public Property Let Mode(val)
        m_mode = val
    End Property


    Public Function GetMJD(dt)
    '引数の日付のMJDを返す
        Dim buf, y, m, d, reki

        buf = Split(dt, "/") '年,月,日の配列

        y = CDbl(buf(0)) '年, 数値へ変換
        m = CDbl(buf(1)) '月, 数値へ変換
        d = CDbl(buf(2)) '日, 数値へ変換

        Select Case m_mode
        Case 1, 2
            reki = m_mode

        Case 0 'グレゴリオ暦かユリウス暦かを自動判断
            If 10000*y + 100*m + d >= 15821015 Then
                reki = 1
            Else
                reki = 2
            End If
        End Select
        
        '1,2月を前年の13,14月にする
        If m < 3 Then
            y = y - 1
            m = m + 12
        End If

        Select Case reki
        Case 1
            'グレゴリオ暦の日付から修正ユリウス日を算出
            GetMJD = - 678882 + 365 * y + MonthDay(m - 1) + d + Int(y / 4) _
                - Int(y / 100) + Int(y / 400)
        Case 2
            'ユリウス暦の日付から修正ユリウス日を算出
            GetMJD = - 678884 + 365 * y + MonthDay(m - 1) + d + Int(y / 4)
        End Select
    End Function


    Public Function GetDate(mjd)
    'MJDに対応する日付を返す
        Dim y, m, d, a, reki
        Dim arrYears, arrDays, zeroDay

        'グレゴリオ暦かユリウス暦かを自動判断
        If mjd >= -100840 Then
            reki = 1
        Else
            reki = 2
        End If

        arrYears = Array(0, 1, 4, 100, 400)

        If reki = 1 Then 'グレゴリオ暦の場合
            arrDays  = Array(1,365,1461,36524,146097)
            zeroDay = -678882 '西暦0年(紀元前1年)2月29日のMJD

        ElseIf reki = 2 Then 'ユリウス暦の場合
            arrDays  = Array(1,365,1461)
            zeroDay = -678884 '西暦0年(紀元前1年)2月29日のMJD
        End If

        'MJDをそれぞれの暦の西暦0年(紀元前1年)2月29日を第0日とした日数へ変換
        d = mjd - zeroDay
        y = 0
        m = 3

        '年の計算
        a = Div(d, arrDays)
        y = y + Mul(a, arrYears)
        d = a(0)

        '月の計算
        If d < 1 Then 'dは小数点以下で時刻を表していることに注意
        '2月末日の場合
            m = 2 '2月
            If LeapYear(a) Then
                d = 29 + d
            Else
                d = 28 + d
            End If
        
        Else '2月末日以外の場合
            Do While MonthDay(m) < d
                m = m + 1
            Loop
            d = d - MonthDay(m - 1)
            If m > 12 Then
                y = y + 1
                m = m - 12
            End If

        End If

        GetDate =  Join(Array(y,m,Round(d, 5)), "/") '日は小数点以下5桁まで
    End Function


    Private Function LeapYear(a)
    'うるう年ならTrue、そうでなければFalseを返す
    'aは配列
        LeapYear = False

        If UBound(a) = 4 Then 'グレゴリオ暦の場合
            If a(2) = 0 And a(3) <> 0 Then
                Exit Function
            End If
        End If

        If a(1) = 0 Then
            LeapYear = True
        End If

    End Function


    Private Function MonthDay(M)
    '2月末日を第0日としてM月末日までの日数を返す(M=2~13)
        MonthDay = Int(30.59 * (M - 1)) - 30
    End Function


    Public Function GetCalender(dt)
    'その月のカレンダーを返す
        Dim buf, y, m, d

        buf = Split(dt, "/") '年,月,日の配列
        y = CDbl(buf(0))     '年, 数値へ変換
        m = CDbl(buf(1))     '月, 数値へ変換
        d = CInt(buf(2))   '日, 数値へ変換

        Dim mjd1, mjd2, mjd, days

        mjd1 = GetMJD(y & "/" & m & "/" & 1)     '当月1日の修正ユリウス日
        mjd2 = GetMJD(y & "/" & m + 1 & "/" & 1) '翌月1日の修正ユリウス日
        mjd  = GetMJD(y & "/" & m & "/" & d)
        days = mjd2 - mjd1
        
        Dim cale(41) 'カレンダーの6週×7日を表す配列
        Dim i, j

        j = GetWeek(mjd1) '当月1日の曜日

        For i = 0 To j - 1
            cale(i) = "□"
        Next

        For i = j To j - 1 + days
            cale(i) = "■"
        Next

        cale(j + mjd -mjd1) = "★"

        'カレンダーの6週目を表示するかどうか
        If j + days - 1 > 34 Then
            For i = j + days To 41
                cale(i) = "□"
            Next
        Else
            For i = j + days To 34
                cale(i) = "□"
            Next
        End If

        Dim buf2

        buf2 = y & "年" & m & "月"
        For i = 0 To  41
            If i Mod 7 = 0 Then
                buf2 = buf2 & vbCr
            End If
            buf2 = buf2 + cale(i)
        Next

        '結果を返す
        GetCalender = buf2

    End Function


    Public Function GetWeek(mjd)
    '曜日を返す(0~6の数値)
        GetWeek = (mjd + 3) - Int((mjd + 3) / 7) * 7
    End Function


    Public Function GetJWeek(mjd)
    '日本語の曜日を返す
        GetJWeek = Mid("日月火水木金土", GetWeek(mjd) + 1, 1)
    End Function


    Public Function GetEto(mjd)
        Dim p1, p2

        p1 = mjd Mod 10
        If p1 < 0 Then p1 = p1 + 10
        p2 = (mjd + 2) Mod 12
        If p2 < 0 Then p2 = p2 + 12
        GetEto = Mid("甲乙丙丁戊己庚辛壬癸", p1+1, 1) _
        & Mid("子丑寅卯辰巳午未申酉戌亥", p2+1, 1)
    End Function

End Class


Function Div(val, arr)
'例えばarr=[1,365,1461]の場合、a(0)+365*a(1)+1461*a(2)=valとなる配列aを返す
    Dim i, iMax
    Dim a '戻り値となる配列

    iMax = UBound(arr)
    ReDim a(iMax)
    a(0) = val
    For i = iMax To 1 Step -1
        a(i) = Int(a(0) / arr(i))   '商の整数部
        a(0) = a(0) - arr(i) * a(i) '余り(>0)
    Next
    Div = a
End Function


Function Mul(arr1, arr2)
'2つの配列の「内積」を返す
    Dim i, iMax
    Dim a '戻り値となる配列

    If UBound(arr1) < UBound(arr2) Then
        iMax = UBound(arr1)
    Else
        iMax = UBound(arr2)
    End If

    For i = 0 To iMax
        a = a + arr1(i) * arr2(i)
    Next
    Mul = a
End Function

表示例

1月1日(水)

MJD=60676
庚午

2025年1月
□□□★■■■
■■■■■■■
■■■■■■■
■■■■■■■
■■■■■■□