#----------------------------------------------------------------------
#
# calendar.test --
#
#		Tests for [calendar::CommonCalendar] and
#	[calendar::GregorianCalendar]
#
# RCS: @(#) $Id: gregorian.test,v 1.6 2006/10/09 21:41:40 andreas_kupries Exp $
#
#----------------------------------------------------------------------

source [file join \
	[file dirname [file dirname [file join [pwd] [info script]]]] \
	devtools testutilities.tcl]

testsNeedTcl     8.2
testsNeedTcltest 1.0

testing {
    useLocal calendar.tcl calendar
}

#----------------------------------------------------------------------
#
# TEST CASES
#
#----------------------------------------------------------------------
# Unix epoch

array set gregChange {
    ERA          CE
    YEAR         1752
    MONTH        9
    DAY_OF_MONTH 14
}

array set gregUnixEpoch {
    ERA          CE
    YEAR         1970
    MONTH        1
    DAY_OF_MONTH 1
}

set gregChangeJ [calendar::GregorianCalendar::EYMDToJulianDay gregChange]
set unixEpoch   [calendar::GregorianCalendar::EYMDToJulianDay gregUnixEpoch]

if 0 {
    puts "Gregorian calendar was adopted in England on Julian Day $gregChangeJ"
    puts "Posix epoch is Julian day $unixEpoch"
}

#----------------------------------------------------------------------
# Procedure that tests EYMDToJulianDay, EYDToJulianDay, JulianDayToEYD,
# and JulianDayToEYMD

proc testCal { month day year } {

    global unixEpoch

    # Convert the requested date to seconds from the Posix epoch

    set seconds [clock scan $month/$day/$year -gmt true]

    # Convert to days from the Posix epoch

    set days [ expr { $seconds / 86400 }]

    # Test EYMDToJulianDay

    set dateIn(ERA) CE
    set dateIn(YEAR) $year
    set dateIn(MONTH) $month
    set dateIn(DAY_OF_MONTH) $day
    set dateIn(DAY_OF_YEAR) \
	[string trimleft [clock format $seconds -gmt true -format %j] 0]
    set jcdOut [calendar::GregorianCalendar::EYMDToJulianDay dateIn]
    if { $jcdOut - $unixEpoch != $days } {
	error "date $month/$day/$year julian day is $jcdout\
               should be [expr $days + $unixEpoch]"
    }

    # Test JulianDayToEYMD and its internal call to JulianDayToEYD

    calendar::GregorianCalendar::JulianDayToEYMD $jcdOut dateOut
    foreach f {ERA YEAR DAY_OF_YEAR MONTH DAY_OF_MONTH} {
	if { [string compare $dateIn($f) $dateOut($f)] } {
	    error "date $month/$day/$year field $f\
                   is $dateOut($f) should be $dateIn($f)"
	}
    }

    # Test EYDToJulianDay (possible because JulianDayToEYMD leaves
    # DAY_OF_YEAR

    set jcdOut2 [calendar::GregorianCalendar::EYDToJulianDay dateOut]
    if { $jcdOut2 - $unixEpoch != $days } {
	error "date $month/$day/$year julian day is $jcdout2\
               should be [expr $days + $unixEpoch]"
    }


}

# Procedure that tests EFYWDToJulianDay and JulianDayToEFYWD.  Inputs are
# fiscal year, week, day, calendar year, month, and day of month.  Conversion
# in both directions is tested.

proc testISO { fy w d cy m dm } {
    set date(ERA) CE
    set date(FISCAL_YEAR) $fy
    set date(WEEK_OF_YEAR) $w
    set date(DAY_OF_WEEK) $d
    set dayNo [calendar::GregorianCalendar::EFYWDToJulianDay date]
    calendar::GregorianCalendar::JulianDayToEYMD $dayNo date2
    if { $date2(YEAR) != $cy
	 || $date2(MONTH) != $m
	 || $date2(DAY_OF_MONTH) != $dm } {
	error "[info level 0]: bad date should be $cy-$m-$dm:\
               year $date2(YEAR) month $date2(MONTH) day $date2(DAY_OF_MONTH)"
    }

    set date3(ERA) CE
    set date3(YEAR) $cy
    set date3(MONTH) $m
    set date3(DAY_OF_MONTH) $dm
    set dayNo [calendar::GregorianCalendar::EYMDToJulianDay date3]
    calendar::GregorianCalendar::JulianDayToEFYWD $dayNo date4
    if { $date4(FISCAL_YEAR) != $fy
	 || $date4(WEEK_OF_YEAR) != $w
	 || $date4(DAY_OF_WEEK) != $d } {
	error "[info level 0]: bad date should be $fy-W$w-$d:
               year $date4(FISCAL_YEAR) week $date4(WEEK_OF_YEAR) day $date4(DAY_OF_WEEK)"
    }

}

# Procedure that tests day-of-week-in-month for a given year-month-day.
# Assumes that days of month are presented in order.

proc testWeekInMonth { y m d } {
    global count lastYM
    if { ![info exists lastYM]
	 || [string compare $lastYM [list $y $m]] } {
	set lastYM [list $y $m]
	for { set dw 0 } { $dw < 7 } { incr dw } {
	    set count($dw) 0
	}
    }
    set date(ERA) CE
    set date(YEAR) $y
    set date(MONTH) $m
    set date(DAY_OF_MONTH) $d
    set jd [calendar::GregorianCalendar::EYMDToJulianDay date]
    calendar::GregorianCalendar::JulianDayToEYMWD $jd date2
    set s [clock scan "$m/$d/$y" -gmt true]
    set dw [clock format $s -format "%w" -gmt true]
    if { $dw != $date2(DAY_OF_WEEK) } {
	error "JulianDayToEYMWD computed wrong day\
                       $date2(DAY_OF_WEEK) for $y-$m-$d should be $dw"
    }
    incr count($dw)
    if { $count($dw) != $date2(DAY_OF_WEEK_IN_MONTH) } {
	error "JulianDateToEYMD computed wrong week\
                       $date2(DAY_OF_WEEK_IN_MONTH) for $y-$m-$d\
                       should be $count($dw)"
    }
    foreach field {ERA YEAR MONTH DAY_OF_WEEK_IN_MONTH DAY_OF_WEEK} {
	set date3($field) $date2($field)
    }
    set jd2 [calendar::GregorianCalendar::EYMWDToJulianDay date3]
    unset date2 date3
    if { $jd2 != $jd } {
	error "EYMDToJulianDate computed wrong day $jd2\
                       for $y-$m-$d should be $jd"
    }
    return
}

# Procedure that tests day-of-week-from-end-ofmonth for a given year-month-day.
# Assumes that days of month are presented in reverse order.

proc testWeekFromEndOfMonth { y m d } {
    global count lastYM
    if { ![info exists lastYM]
	 || [string compare $lastYM [list $y $m]] } {
	set lastYM [list $y $m]
	for { set dw 0 } { $dw < 7 } { incr dw } {
	    set count($dw) 0
	}
    }
    set date(ERA) CE
    set date(YEAR) $y
    set date(MONTH) $m
    set date(DAY_OF_MONTH) $d
    set jd [calendar::GregorianCalendar::EYMDToJulianDay date]

    set s [clock scan "$m/$d/$y" -gmt true]
    set dw [clock format $s -format "%w" -gmt true]
    incr count($dw) -1

    foreach field {ERA YEAR MONTH} {
	set date2($field) $date($field)
    }
    set date2(DAY_OF_WEEK_IN_MONTH) $count($dw)
    set date2(DAY_OF_WEEK) $dw
    set jd2 [calendar::GregorianCalendar::EYMWDToJulianDay date2]
    if { $jd2 != $jd } {
	error "EYMWDToJulianDate computed wrong day $jd2\
                       for $y-$m-$d (week $count($dw), day $dw) should be $jd"
    }
    return
}

#----------------------------------------------------------------------

test calendar-1.1 {Julian Day converting to/from Gregorian year-month-day} {

    set n 0
    for { set year 1902 } { $year < 2038 } { incr year } {

	# Test the first and last day of each month.  Test 28 February
	# always, 29 February of leap years.

	testCal 1 1 $year
	testCal 1 31 $year
	testCal 2 28 $year
	if { $year % 4  == 0} {
	    testCal 2 29 $year
	    incr n
	}
	testCal 3 1 $year
	testCal 3 31 $year
	testCal 4 1 $year
	testCal 4 30 $year
	testCal 5 1 $year
	testCal 5 31 $year
	testCal 6 1 $year
	testCal 6 30 $year
	testCal 7 1 $year
	testCal 7 31 $year
	testCal 8 1 $year
	testCal 8 31 $year
	testCal 9 1 $year
	testCal 9 30 $year
	testCal 10 1 $year
	testCal 10 31 $year
	testCal 11 1 $year
	testCal 11 30 $year
	testCal 12 1 $year
	testCal 12 31 $year
	incr n 24
    }

    set n
} 3298

test calendar-2.1 {ISO date conversions} {

    # Test the first and last week of a 52- and 53-week year beginning on each
    # possible day of week

    testISO 2000 52 1 2000 12 25
    testISO 2000 52 7 2000 12 31
    testISO 2001 1 1 2001 1 1
    testISO 2001 1 7 2001 1 7
    testISO 2001 2 1 2001 1 8

    testISO 2001 52 1 2001 12 24
    testISO 2001 52 7 2001 12 30
    testISO 2002 1 1 2001 12 31
    testISO 2002 1 2 2002 1 1
    testISO 2002 1 7 2002 1 6
    testISO 2002 2 1 2002 1 7

    testISO 2002 52 1 2002 12 23
    testISO 2002 52 7 2002 12 29
    testISO 2003 1 1 2002 12 30
    testISO 2003 1 2 2002 12 31
    testISO 2003 1 3 2003 1 1
    testISO 2003 1 7 2003 1 5
    testISO 2003 2 1 2003 1 6

    testISO 2003 52 1 2003 12 22
    testISO 2003 52 7 2003 12 28
    testISO 2004 1 1 2003 12 29
    testISO 2004 1 3 2003 12 31
    testISO 2004 1 4 2004 1 1
    testISO 2004 1 7 2004 1 4
    testISO 2004 2 1 2004 1 5

    testISO 2004 52 1 2004 12 20
    testISO 2004 52 7 2004 12 26
    testISO 2004 53 1 2004 12 27
    testISO 2004 53 5 2004 12 31
    testISO 2004 53 6 2005 1 1
    testISO 2004 53 7 2005 1 2
    testISO 2005 1 1 2005 1 3
    testISO 2005 1 7 2005 1 9
    testISO 2005 2 1 2005 1 10

    testISO 2005 52 1 2005 12 26
    testISO 2005 52 6 2005 12 31
    testISO 2005 52 7 2006 1 1
    testISO 2006 1 1 2006 1 2
    testISO 2006 1 7 2006 1 8
    testISO 2006 2 1 2006 1 9

    testISO 2009 52 1 2009 12 21
    testISO 2009 52 7 2009 12 27
    testISO 2009 53 1 2009 12 28
    testISO 2009 53 4 2009 12 31
    testISO 2009 53 5 2010 1 1
    testISO 2009 53 7 2010 1 3
    testISO 2010 1 1 2010 1 4
    testISO 2010 1 7 2010 1 10
    testISO 2010 2 1 2010 1 11

} {}

test calendar-3.1 {Day-of-week-in-month} {
    # Test each day of month for one month of each possible length
    # starting on each day of the week.

    foreach { y m l } {
	2001 1 31
	2001 11 30
	2001 2 28
	2001 3 31
	2001 4 30
	2001 5 31
	2001 6 30
	2001 7 31
	2001 8 31
	2001 9 30
	2002 2 28
	2002 3 31
	2002 4 30
	2003 2 28
	2003 3 31
	2003 4 30
	2004 2 29
	2004 9 30
	2005 2 28
	2006 2 28
	2008 2 29
	2009 2 28
	2010 2 28
	2012 2 29
	2016 2 29
	2020 2 29
	2024 2 29
	2028 2 29
    } {
	for { set d 1 } { $d <= $l } { incr d } {
	    testWeekInMonth $y $m $d
	}
    }
    concat
} {}

test calendar-3.2 {Day-of-week from end of month} {
    # Test each day of month for one month of each possible length
    # starting on each day of the week.

    foreach { y m l } {
	2001 1 31
	2001 11 30
	2001 2 28
	2001 3 31
	2001 4 30
	2001 5 31
	2001 6 30
	2001 7 31
	2001 8 31
	2001 9 30
	2002 2 28
	2002 3 31
	2002 4 30
	2003 2 28
	2003 3 31
	2003 4 30
	2004 2 29
	2004 9 30
	2005 2 28
	2006 2 28
	2008 2 29
	2009 2 28
	2010 2 28
	2012 2 29
	2016 2 29
	2020 2 29
	2024 2 29
	2028 2 29
    } {
	for { set d $l } { $d >= 1 } { incr d -1 } {
	    testWeekFromEndOfMonth $y $m $d
	}
    }
    concat
} {}

#----------------------------------------------------------------------
testsuiteCleanup

# Local Variables:
# mode:tcl
# End:
