!********o*********o*********o*********o*********o*********o*********o**
! This program accepts your birthdate (or any other date) and tells
! you how many days you have been alive, as well as whe you should
! celebrate your next 100th day anniversary, etc.
!---------------------
! Method:
!   We represent dates internally as days since Jan/1/1901.  This
!   allows for easy data arithmetic within the program.  We convert
!   to and from mmm/dd/yyyy only for input and output.
!
!   Our valid operating range is Jan/1/1901 thru Dec/31/2099.  This
!   range was chosen because there are no exceptions to the leap
!   year rule over this range-- every 4th year is a leap year.
!
! WARNING! WARNING!
!   This program has not undergone rigorous testing.
!---------------------
! Inputs
!   month, day, year (of birth)
!
! Outputs
!   ageInDays
!   month, day, year (of 100th day cellebration)
!---------------------
! Author:   Bob Harris
! Class:    CMPSC 201F
! Section:  1234
! Date Due: 4 Nov 2003
!********o*********o*********o*********o*********o*********o*********o**

PROGRAM DaysOfAge
implicit none

integer      :: month, day, year        ! a date (month is 1..12, day is
                                        ! ... 1..31, year is 1901..2099)
character    :: separator1, separator2  ! date field separating characters
character(8) :: dateString              ! date, in CCYYMMDD format, as per
                                        ! ... instrinsic routine DATE_AND_TIME
integer      :: birthDays               ! number of days from jan/1/1901
                                        ! ... to our user's birth date
integer      :: todayDays               ! number of days from jan/1/1901
                                        ! ... to today
integer      :: ageInDays               ! our user's age, counted in days
integer      :: daysSinceCellebration   ! number of days from today back to
                                        ! ... last 100th day cellebration
integer      :: cellebrationDays        ! number of days from jan/1/1901
                                        ! ... to next 100th day cellebration
logical      :: debug = .false.         ! control to enable extra print stuff

!----------
! fetch and validate the birth date
!----------

! fetch and birth date

print *,"Please enter your birthbirth date, in mm/dd/yyyy format:"
read  '(I2,A1,I2,A1,I4)', month,separator1, day,separator2, year

! valibirth date birth date

if ((separator1 /= '/') .or. (separator2 /= '/')) then
    print *,"illegal input, missing slashes in birth dates"
    stop
end if

if ((year < 1901) .or. (year > 2099)) then
    print *,"sorry, but that year is beyond our range of accuracy"
    stop
end if

if ((month < 1) .or. (month > 12)) then
    print *,"illegal input, that month does not exist"
    stop
end if

if ((day < 1) .or. (day > DaysInMonth(month,year))) then
    print *,"illegal input, that birth date does not exist in that month"
    stop
end if

! convert the birth date into days since Jan/1/1901

birthDays = DaysSinceDawnOfTime (month, day, year)
if (debug) then
    print *, "birthdays =", birthDays
end if

!----------
! get today's date
!----------

! get the date

call DATE_AND_TIME (date=dateString)
if (debug) then
    print *, "dateString =", dateString
end if

! unpack it (DATE_AND_TIME returns the date as a string of characters,
! in the format CCYYMMDD, where CC is the century, YY is the year, and
! so on)

year  = 100*TwoDigits(dateString(1:1),dateString(2:2)) &
      +     TwoDigits(dateString(3:3),dateString(4:4))

month =     TwoDigits(dateString(5:5),dateString(6:6))

day   =     TwoDigits(dateString(7:7),dateString(8:8))

! convert it to days since Jan/1/1901

todayDays = DaysSinceDawnOfTime (month, day, year)
if (debug) then
    print *, "todayDays =", todayDays
end if

! make sure she hasn't given us a date in the future

if (birthDays > todayDays) then
    print *,"sorry, you haven't been born yet"
    stop
else if (birthDays == todayDays) then
    print *,"happy birth day!"
    stop
end if

!----------
! calculate and display results
!----------

! show her her current age in days

ageInDays = todayDays - birthDays

print '(A,I,A)', "You are now ", ageInDays, " days old"

! show her when her next 100 day cellebration is

daysSinceCellebration = MOD(ageInDays,100)

if (daysSinceCellebration == 0) then
    print *, "Congratulations, you entitled to a 100 day cellebration today!"
else
    cellebrationDays = todayDays + (100 - daysSinceCellebration)
    if (debug) then
        print *, "cellebrationDays =", cellebrationDays
    end if
    call ToMonthDayYear (cellebrationDays, month, day, year)
    print '(A,2(I2,A),I4,A)', "Your next 100 day cellebration is on ", &
                              month, "/", day, "/", year, "!"
end if

CONTAINS

!-----------------------------------------------------------------------
! DaysSinceDawnOfTime--
!   Determine the number of days from jan/1/1901 until a given date.
!---------------------
! Arguments
!   m:  The month        (1..12)
!   d:  The day of month (1..31)
!   y:  The year         (1901..2099)
!
! Returns
!   The number of days from jan/1/1901 thru the given date.  Note that
!   if the given date is outside of the range jan/1/1901..dec/31/2099,
!   the results will be incorrect!
!-----------------------------------------------------------------------

INTEGER FUNCTION DaysSinceDawnOfTime (m,d,y)
    integer, intent(in) :: m, d, y
    integer             :: leapYears
    integer             :: numberOfDays
    integer             :: monthScan

    ! determine how many leap years we've had, not counting the current
    ! year

    leapYears = (y-1901) / 4

    ! account for all the days in full years since jan/1/1901;  this
    ! will be the number of days from jan/1/1901 to jan/1/yyyy, where
    ! yyyy is the current year

    numberOfDays = 365 * (y-1901) + leapYears

    ! account for days up to the start of the current month

    monthScan = 1
    do while (monthScan < m)
        numberOfDays = numberOfDays + DaysInMonth(monthScan,y)
        monthScan = monthScan + 1
    end do

    ! account for days up within the current month

    numberOfDays = numberOfDays + (d-1)

    DaysSinceDawnOfTime = numberOfDays

END FUNCTION DaysSinceDawnOfTime

!-----------------------------------------------------------------------
! ToMonthDayYear--
!   Determine the month, day, and year for a given day-count.
!---------------------
! Arguments
!   days:  The number of days since jan/1/1901.  Note that if this
!          ... day count represents a date outside of the range
!          ... jan/1/1901..dec/31/2099, the results will be incorrect!
!
! Returns
!   m: The month that contains that day-count.
!   d: The day of month for that day-count.
!   y: The year that contains that day-count.
!
!-----------------------------------------------------------------------

SUBROUTINE ToMonthDayYear (days, m, d, y)
    integer, intent(in)  :: days
    integer, intent(out) :: m, d, y
    integer              :: leapYears, years, daysLeft

    !----------
    ! determine the year
    !----------

    ! determine how many complete four year periods we've had

    leapYears = days / (4*365 + 1)

    ! add that many four year periods, subtract the corresponding days

    y        = 1901 + 4*leapYears
    daysLeft = days - leapYears * (4*365 + 1)

    ! determine how many remaining full years we've had

    years = daysLeft / 365

    ! add that many years, subtract the corresponding days

    y        = y + years
    daysLeft = daysLeft - years * 365

    !----------
    ! determine the month
    !----------

    m = 1
    do while (daysLeft > DaysInMonth(m,y))
        m = m + 1
        daysLeft = daysLeft - DaysInMonth(m,y)
    end do

    !----------
    ! determine the days
    !----------

    d = daysLeft

END SUBROUTINE ToMonthDayYear

!-----------------------------------------------------------------------
! DaysInMonth--
!   Determine the number of days in a month (in a given year).
!---------------------
! Arguments
!   m:  The month (1..12)
!   y:  The year  (1901..2099)
!
! Returns
!   The number of days in that month (28, 29, 30, or 31), provided the
!   month and year are valid.  Otherwise, we return 0.
!-----------------------------------------------------------------------

INTEGER FUNCTION DaysInMonth (m,y)
    integer, intent(in) :: m, y

    if ((y < 1901) .or. (y > 2099)) then
        DaysInMonth = 0               ! invalid year
    else
        select case (m)
            case default
                DaysInMonth = 0       ! invalid month

            case (1,3,5,7,8,10,12)    ! jan,mar,may,jul,aug,oct,dec
                DaysInMonth = 31

            case (4,6,9,11)           ! apr,jun,sep,nov
                DaysInMonth = 30

            case (2)                  ! feb
                if (MOD(y,4) == 0) then
                    DaysInMonth = 29  ! leap year
                else
                    DaysInMonth = 28
                end if

        end select
    end if

END FUNCTION DaysInMonth

!-----------------------------------------------------------------------
! TwoDigits--
!   Convert two ascii digits into an integer with the corresponding value.
!---------------------
! Arguments
!   tens:  The tens character ('0'..'9')
!   ones:  The ones character ('0'..'9')
!
! Returns
!   The integer corresponding to those two characters.  For example, if
!   tens is '3' and ones is '7', then we return 37.
!-----------------------------------------------------------------------

INTEGER FUNCTION TwoDigits (tens,ones)
    character, intent(in) :: tens, ones

    TwoDigits = 10 * (IACHAR (tens) - IACHAR ('0')) &
              +      (IACHAR (ones) - IACHAR ('0'))

END FUNCTION TwoDigits

END PROGRAM DaysOfAge
