!
! Date_Utility
!
! Module containing date conversion routines
!
!
! CREATION HISTORY:
!       Written by:     Paul van Delst, 03-Apr-2000
!                       paul.vandelst@noaa.gov

MODULE Date_Utility

  ! -----------------
  ! Environment setup
  ! -----------------
  IMPLICIT NONE



  ! ------------
  ! Visibilities
  ! ------------
  PRIVATE
  ! Parameters
  PUBLIC :: N_MONTHS
  PUBLIC :: DAYS_PER_MONTH_IN_NONLEAP
  PUBLIC :: MONTH_NAME
  PUBLIC :: N_DAYS
  PUBLIC :: DAY_NAME
  ! Procedures
  PUBLIC :: IsLeapYear
  PUBLIC :: DayOfYear
  PUBLIC :: DaysInMonth
  PUBLIC :: NameOfMonth
  PUBLIC :: DayOfWeek


  ! ----------
  ! Parameters
  ! ----------
  CHARACTER(*), PARAMETER :: MODULE_VERSION_ID = &
  '$Id: Date_Utility.f90 99117 2017-11-27 18:37:14Z tong.zhu@noaa.gov $'
  ! String length for character functions
  INTEGER, PARAMETER :: NL = 20
  ! Number of Months in a Year
  INTEGER, PARAMETER :: N_MONTHS = 12
  ! Days per Month in a non leap Year
  INTEGER, PARAMETER :: DAYS_PER_MONTH_IN_NONLEAP(N_MONTHS) = &
  (/ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 /)
  ! Month names
  CHARACTER(*), PARAMETER :: MONTH_NAME(N_MONTHS) = &
  (/'January  ','February ','March    ','April    ','May      ','June     ', &
    'July     ','August   ','September','October  ','November ','December ' /)
  ! Number of Days in a Week
  INTEGER, PARAMETER :: N_DAYS = 7
  ! Day names
  CHARACTER(*), PARAMETER :: DAY_NAME(N_DAYS) = &
  (/'Sunday   ','Monday   ','Tuesday  ','Wednesday','Thursday ','Friday   ','Saturday '/)


CONTAINS


!##############################################################################
!##############################################################################
!##                                                                          ##
!##                       ## PUBLIC MODULE ROUTINES ##                       ##
!##                                                                          ##
!##############################################################################
!##############################################################################

!------------------------------------------------------------------------------
!:sdoc+:
!
! NAME:
!       IsLeapYear
!
! PURPOSE:
!       Elemental function to determine if a specified year is a leap year.
!
! CALLING SEQUENCE:
!       Result = IsLeapYear( Year )
!
! INPUT ARGUMENTS:
!       Year:     The year in 4-digit format, e.g. 1997.
!                 UNITS:      N/A
!                 TYPE:       INTEGER
!                 DIMENSION:  Scalar
!                 ATTRIBUTES: INTENT(IN)
!
! FUNCTION RESULT:
!       Result:   The return value is a logical value indicating whether
!                 the specified year is a leap year.
!                 If .TRUE.  the specified year IS a leap year.
!                    .FALSE. the specified year is NOT a leap year.
!                 UNITS:      N/A
!                 TYPE:       LOGICAL
!                 DIMENSION:  Scalar
!:sdoc-:
!------------------------------------------------------------------------------

  ELEMENTAL FUNCTION IsLeapYear( Year )
    INTEGER, INTENT(IN) :: Year
    LOGICAL :: IsLeapYear

    IsLeapYear = ( (MOD(Year,4)   == 0) .AND. (MOD(Year,100) /= 0) ) .OR. &
                 (MOD(Year,400) == 0)

  END FUNCTION IsLeapYear


!------------------------------------------------------------------------------
!:sdoc+:
!
! NAME:
!       DayOfYear
!
! PURPOSE:
!       Elemental function to convert input numeric (e.g. DD,MM,YYYY) date
!       information to a sequential day of year.
!
! CALLING SEQUENCE:
!       DoY = DayOfYear ( Day, Month, Year )
!
! INPUTS:
!       Day:       The day-of-month.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Scalar or any rank
!                  ATTRIBUTES: INTENT(IN)
!
!       Month:     The month-of-year.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Same as Day input
!                  ATTRIBUTES: INTENT(IN)
!
!       Year:      The year in 4-digit format, e.g. 1997.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Same as Day input
!                  ATTRIBUTES: INTENT(IN)
!
! FUNCTION RESULT:
!       DoY:       Integer defining the day-of-year.
!                  Return value is 0 for invalid input.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Same as Day input
!:sdoc-:
!------------------------------------------------------------------------------

  ELEMENTAL FUNCTION DayOfYear( &
    Day  , &  ! Input
    Month, &  ! Input
    Year ) &  ! Input
  RESULT( DoY )
    ! Arguments
    INTEGER, INTENT(IN) :: Day
    INTEGER, INTENT(IN) :: Month
    INTEGER, INTENT(IN) :: Year
    ! Function result
    INTEGER :: DoY
    ! Local variables
    INTEGER :: Days_per_Month( N_MONTHS )

    ! Set up
    DoY = 0
    ! ...Check year and month input
    IF ( Year  < 1 .OR. &
         Month < 1 .OR. &
         Month > N_MONTHS ) RETURN
    ! ...Check the day of month
    Days_per_Month = DAYS_PER_MONTH_IN_NONLEAP
    IF ( IsLeapYear(Year) ) Days_per_Month(2) = 29
    IF ( Day > Days_per_Month(Month) ) RETURN

    ! Compute the day of year
    DoY = SUM(Days_per_Month(1:Month-1)) + Day

  END FUNCTION DayOfYear


!------------------------------------------------------------------------------
!:sdoc+:
! NAME:
!       DaysInMonth
!
! PURPOSE:
!       Elemental function to return the number of days in a given
!       month and year.
!
! CALLING SEQUENCE:
!       n_Days = DaysInMonth( Month, Year )
!
! INPUTS:
!       Month:     The month of the year (1-12).
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Scalar or any rank
!                  ATTRIBUTES: INTENT(IN)
!
!       Year:      The year in 4-digit format, e.g. 1997.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Same as Month input
!                  ATTRIBUTES: INTENT(IN)
!
! FUNCTION RESULT:
!       n_Days:    The number of days in the month.
!                  Return value is 0 for invalid input.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Same as input
!:sdoc-:
!------------------------------------------------------------------------------

  ELEMENTAL FUNCTION DaysInMonth(Month, Year) RESULT(n_Days)
    ! Arguments
    INTEGER, INTENT(IN) :: Month
    INTEGER, INTENT(IN) :: Year
    ! Function result
    INTEGER :: n_Days
    ! Local variables
    INTEGER :: Days_per_Month(N_MONTHS)

    ! Set up
    n_Days = 0
    ! ...Check year and month input
    IF ( Year  < 1 .OR. &
         Month < 1 .OR. &
         Month > N_MONTHS ) RETURN

    ! Assemble the days of month
    Days_per_Month = DAYS_PER_MONTH_IN_NONLEAP
    IF ( IsLeapYear(Year=Year) ) Days_per_Month(2) = 29

    ! Set the number of days
    n_Days = Days_per_Month(Month)

  END FUNCTION DaysInMonth


!------------------------------------------------------------------------------
!:sdoc+:
!
! NAME:
!       NameOfMonth
!
! PURPOSE:
!       Elemental function to return the name of the month.
!
! CALLING SEQUENCE:
!       name = NameOfMonth( Month )
!
! INPUT ARGUMENTS:
!       Month:    The month of the year (1-12).
!                 UNITS:      N/A
!                 TYPE:       INTEGER
!                 DIMENSION:  Scalar or any rank.
!                 ATTRIBUTES: INTENT(IN)
!
! FUNCTION RESULT:
!       name:     The return value is a character string containing the
!                 name of the month.
!                 UNITS:      N/A
!                 TYPE:       CHARACTER
!                 DIMENSION:  Conformable with input Month arugment
!:sdoc-:
!------------------------------------------------------------------------------

  ELEMENTAL FUNCTION NameOfMonth( Month )
    INTEGER, INTENT(IN) :: Month
    CHARACTER(NL) :: NameOfMonth
    NameOfMonth = 'Invalid'
    IF ( Month < 1 .OR. Month > N_MONTHS ) RETURN
    NameOfMonth = MONTH_NAME( Month )
  END FUNCTION NameOfMonth


!------------------------------------------------------------------------------
!:sdoc+:
!
! NAME:
!       DayOfWeek
!
! PURPOSE:
!       Elemental function to return the name of the day of week.
!
!       NOTE:
!       - Only valid for Gregorian calendar.
!       - Since different places switched to the Gregorian calendar at
!         different times, this routine will only output day of week names
!         for dates AFTER 1918 (the year Russia adopted the Gregorian
!         calendar).
!
! CALLING SEQUENCE:
!       name = DayOfWeek ( Day, Month, Year )
!
! INPUTS:
!       Day:       The day of the month.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Scalar or any rank
!                  ATTRIBUTES: INTENT(IN)
!
!       Month:     The month of the year (1-12).
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Conformable with input Day argument.
!                  ATTRIBUTES: INTENT(IN)
!
!       Year:      The year in 4-digit format, e.g. 1997.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Conformable with input Day argument.
!                  ATTRIBUTES: INTENT(IN)
!
! FUNCTION RESULT:
!       name:      The return value is a character string containing the
!                  name of the day-of-week.
!                  UNITS:      N/A
!                  TYPE:       CHARACTER
!                  DIMENSION:  Conformable with input Day argument.
!
!:sdoc-:
!------------------------------------------------------------------------------

  ELEMENTAL FUNCTION DayOfWeek( Day, Month, Year )
    INTEGER, INTENT(IN) :: Day
    INTEGER, INTENT(IN) :: Month
    INTEGER, INTENT(IN) :: Year
    CHARACTER(NL) :: DayOfWeek
    INTEGER :: i
    DayOfWeek = 'Invalid'
    i = iDayOfWeek( Day, Month, Year )
    IF ( i == 0 ) RETURN
    DayOfWeek = DAY_NAME(i)
  END FUNCTION DayOfWeek



!##############################################################################
!##############################################################################
!##                                                                          ##
!##                      ## PRIVATE MODULE ROUTINES ##                       ##
!##                                                                          ##
!##############################################################################
!##############################################################################

!------------------------------------------------------------------------------
!
! NAME:
!       iDayOfWeek
!
! PURPOSE:
!       Elemental function to convert input numeric (e.g. DD,MM,YYYY) date
!       information to a day of week index, 1-7.
!
!       NOTE:
!       - Only valid for Gregorian calendar.
!       - Since different places switched to the Gregorian calendar at
!         different times, this routine will only output valid day of week
!         indices for dates AFTER 1918 (the year Russia adopted the Gregorian
!         calendar).
!
! CALLING SEQUENCE:
!       iDoW = iDayOfWeek ( Day, Month, Year )
!
! INPUTS:
!       Day:       The day of the month.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Scalar or any rank
!                  ATTRIBUTES: INTENT(IN)
!
!       Month:     The month of the year (1-12).
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Same as Day input
!                  ATTRIBUTES: INTENT(IN)
!
!       Year:      The year in 4-digit format, e.g. 1997.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Same as Day input
!                  ATTRIBUTES: INTENT(IN)
!
! FUNCTION RESULT:
!       iDoW:      Integer defining the day-of-week (1-7).
!                  Return value is 0 for invalid input.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Same as Day input
!
!------------------------------------------------------------------------------

  ELEMENTAL FUNCTION iDayOfWeek( Day, Month, Year ) RESULT( iDoW )
    ! Arguments
    INTEGER, INTENT(IN) :: Day
    INTEGER, INTENT(IN) :: Month
    INTEGER, INTENT(IN) :: Year
    ! Function result
    INTEGER :: iDoW
    ! Local variables
    INTEGER :: jdn

    iDoW = 0
    jdn = JulianDay( Day, Month, Year )
    IF ( jdn < 0 ) RETURN

    iDoW = MOD(jdn+1, 7) + 1
    IF ( iDoW < 1 .OR. iDoW > 7 ) iDow = 0

  END FUNCTION iDayOfWeek


!------------------------------------------------------------------------------
!
! NAME:
!       JulianDay
!
! PURPOSE:
!       Elemental function to convert input numeric (e.g. DD,MM,YYYY) date
!       information to a Julian Day Number, which is defined as the number of
!       days since noon January 1, 4713 BCE.
!
!       NOTE:
!       - Only valid for Gregorian calendar.
!       - Since different places switched to the Gregorian calendar at different
!         times, this routine will only output valid Julian day numbers for dates
!         AFTER 1918 (the year Russia adopted the Gregorian calendar).
!
! CALLING SEQUENCE:
!       jdn = JulianDay( Day, Month, Year )
!
! INPUTS:
!       Day:       The day of the month.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Scalar or any rank
!                  ATTRIBUTES: INTENT(IN)
!
!       Month:     The month of the year (1-12).
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Same as Day input
!                  ATTRIBUTES: INTENT(IN)
!
!       Year:      The year in 4-digit format, e.g. 1997.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Same as Day input
!                  ATTRIBUTES: INTENT(IN)
!
! FUNCTION RESULT:
!       jdn:       Julian Day Number.
!                  Return value is negative for invalid input.
!                  UNITS:      N/A
!                  TYPE:       INTEGER
!                  DIMENSION:  Same as Day input
!
! REFERENCES:
!   - http://en.wikipedia.org/wiki/Julian_day
!   - http://www.cs.utsa.edu/~cs1063/projects/Spring2011/Project1/jdn-explanation.html
!
!------------------------------------------------------------------------------

  ELEMENTAL FUNCTION JulianDay( Day, Month, Year ) RESULT( jdn )
    ! Arguments
    INTEGER, INTENT(IN) :: Day
    INTEGER, INTENT(IN) :: Month
    INTEGER, INTENT(IN) :: Year
    ! Function result
    INTEGER :: jdn
    ! Local variables
    INTEGER :: m, y, a

    jdn = -1
    IF ( year <= 1918 ) RETURN

    ! Compute the number of years and months since March 1, 4801 BCE
    a = (14 - month)/12       !  1 for Jan, 2 for Feb, 0 for other months.
    y = year + 4800 - a       !  Add 4800 to start counting from -4800.
    m = month + (12*a) - 3    !  Pretend the year begins in March and ends in Feb.

    jdn = day + &
          (153*m + 2)/5 + &               !  Number of days in the previous months
          (365*y) + &                     !  Duh.
          (y/4) - (y/100) + (y/400) - &   !  Number of leap years since -4800
          32045                           !  Ensure result is 0 for Jan 1, 4713 BCE.

  END FUNCTION JulianDay

END MODULE Date_Utility
