!===============================================================================
! SVN $Id: shr_cal_mod.F90 22297 2010-04-07 22:25:28Z kauff $
! SVN $URL: https://svn-ccsm-models.cgd.ucar.edu/csm_share/branch_tags/cesm1_0_rel_tags/cesm1_0_rel01_share3_100616/shr/shr_cal_mod.F90 $
!===============================================================================
!BOP ===========================================================================
!
! !MODULE: shr_cal_mod -- calendar module, relates elapsed days to calendar date.
!
! !DESCRIPTION:
!   These calendar routines do conversions between...
!   \begin{itemize}
!   \item the integer number of elapsed days 
!   \item the integers year, month, day (three inter-related integers)
!   \item the integer coded calendar date (yyyymmdd)
!   \end{itemize}
!   Possible uses include: a calling routine can increment the elapsed days 
!   integer and use this module to determine what the corresponding calendar 
!   date is;  this module can be used to determine how many days apart two
!   arbitrary calendar dates are.
!
! !REVISION HISTORY:
!     2001-dec-28 - B. Kauffman - created initial version, taken from cpl5
!
! !REMARKS:
!   Following are some internal assumptions.  These assumptions are somewhat
!   arbitrary -- they were chosen because they result in the simplest code given
!   the requirements of this module.  These assumptions can be relaxed as 
!   necessary: 
!   o the valid range of years is [-999,9999]
!   o elapsed days = 0 <=> January 1st, year 0000
!   o all years have 365 days (no leap years)
!     This module is hard-coded to implement a 365-day calendar, ie. there
!     are no leap years.  This module can be modified to implement a calendar
!     with leap years if this becomes desireable.  This would make the internal
!     logic of this module more complex, but would not require any change to the
!     module API or the calling code because the module API hides these details
!     from all external routines.
!
! !INTERFACE: ------------------------------------------------------------------


module shr_cal_mod 14,6

! !USES:

   use shr_kind_mod   ! kinds
   use shr_const_mod  ! constants
   use shr_sys_mod    ! system
   use shr_log_mod, only: s_loglev  => shr_log_Level
   use shr_log_mod, only: s_logunit => shr_log_Unit
   use esmf_mod

   implicit none

   private ! except

! !PUBLIC TYPES: 
 
   ! none

! !PUBLIC MEMBER FUNCTIONS:

   public :: shr_cal_set        ! converts elapsed days to coded-date
   public :: shr_cal_get        ! converts elapsed days to coded-date
   public :: shr_cal_eday2date  ! converts elapsed days to coded-date
   public :: shr_cal_eday2ymd   ! converts elapsed days to yr,month,day
   public :: shr_cal_date2ymd   ! converts coded-date   to yr,month,day
   public :: shr_cal_date2eday  ! converts coded-date   to elapsed days
   public :: shr_cal_date2julian! converts coded-date,sec to julian days
   public :: shr_cal_ymd2julian ! converts yr,month,day,sec to julian days
   public :: shr_cal_ymd2date   ! converts yr,month,day to coded-date
   public :: shr_cal_ymd2eday   ! converts yr,month,day to elapsed days
   public :: shr_cal_advDate    ! advance date/secs real seconds
   public :: shr_cal_advDateInt ! advance date/secs integer seconds
   public :: shr_cal_validDate  ! logical function: is coded-date valid?
   public :: shr_cal_validYMD   ! logical function: are yr,month,day valid?
   public :: shr_cal_validHMS   ! logical function: are hr, min, sec valid?
   public :: shr_cal_numDaysinMonth     ! number of days in a month
   public :: shr_cal_elapsDaysStrtMonth ! elapsed days on start of month
   public :: shr_cal_getDebug   ! get internal debug level
   public :: shr_cal_setDebug   ! set internal debug level

! !PUBLIC DATA MEMBERS:

   ! none

!EOP

   !----- local -----
   character(SHR_KIND_CL),save    :: calendar_type='noleap' ! calendar type
   integer(SHR_KIND_IN),parameter :: shr_cal_nvalidTypes = 3
   character(*),parameter :: &
     shr_cal_validTypes(shr_cal_nvalidTypes) = (/'noleap    ', &
                                                 '365_day   ', &
                                                 'gregorian '/)

   !--- this is the noleap calendar ---
   integer(SHR_KIND_IN),parameter :: dpy = 365
   integer(SHR_KIND_IN),parameter :: dsm(12) = &     ! elapsed Days on Start of Month
   &                    (/ 0,31,59, 90,120,151, 181,212,243, 273,304,334/)
   integer(SHR_KIND_IN),parameter :: dpm(12) = &     ! Days Per Month
   &                    (/31,28,31, 30, 31, 30,  31, 31, 30,  31, 30, 31/)


   !--- trigger internal debug output ---
   integer(SHR_KIND_IN) :: debug = 0

!~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
contains
!~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_set - set the calenday type
!
! !DESCRIPTION:
!     Set the calendar type
!
! !REVISION HISTORY:
!     2005-May-28 - T. Craig - initial version
!
! !INTERFACE:  -----------------------------------------------------------------


subroutine shr_cal_set(ctype) 6,1

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   character(*),intent(in)  :: ctype   ! calendar type

!EOP

   !--- local ---
   integer(SHR_KIND_IN)  :: n   ! counter
   logical :: found             ! check for valid type

!-------------------------------------------------------------------------------
!
!-------------------------------------------------------------------------------

   found = .false.
   do n = 1,shr_cal_nvalidTypes
     if (trim(ctype) == trim(shr_cal_validTypes(n))) then
       calendar_type = trim(ctype)
       found = .true.
     endif
   enddo

   if (.not.found) call shr_sys_abort('shr_cal_set ERROR illegal calendar type '//trim(ctype))

end subroutine shr_cal_set

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_get - get the calenday type
!
! !DESCRIPTION:
!     Get the calendar type
!
! !REVISION HISTORY:
!     2005-May-28 - T. Craig - initial version
!
! !INTERFACE:  -----------------------------------------------------------------


subroutine shr_cal_get(ctype) 2

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   character(*),intent(out)  :: ctype   ! calendar type

!EOP

!-------------------------------------------------------------------------------
!
!-------------------------------------------------------------------------------

   ctype = calendar_type

end subroutine shr_cal_get

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_eday2date - converts elapsed days to coded-date
!
! !DESCRIPTION:
!     Converts elapsed days to coded-date.
!
! !REVISION HISTORY:
!     2001-dec-28 - B. Kauffman - initial version, taken from cpl5
!
! !INTERFACE:  -----------------------------------------------------------------


subroutine shr_cal_eday2date(eday,date) 4,3

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN),intent(in)  :: eday  ! number of elapsed days
   integer(SHR_KIND_IN),intent(out) :: date  ! coded (yyyymmdd) calendar date

!EOP

   !--- local ---
   integer(SHR_KIND_IN) :: yy,mm,dd

!-------------------------------------------------------------------------------
! ASSUMPTIONS:
!   this calendar has a year zero (but no day or month zero)
!-------------------------------------------------------------------------------

   if (debug > 1) write(s_logunit,*) 'shr_cal_eday2date_a ',eday

   if (trim(calendar_type) == 'gregorian') then
      call shr_sys_abort('ERROR shr_cal_eday2date gregorian not validated')
   endif

   call shr_cal_eday2ymd(eday,yy,mm,dd)
   call shr_cal_ymd2date(yy,mm,dd,date)

   if (debug > 1) write(s_logunit,*) 'shr_cal_eday2date_b ',date

end subroutine shr_cal_eday2date

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_eday2ymd - converts elapsed days to year/month/day.
!
! !DESCRIPTION:
!     Converts elapsed days to year/month/day.
!
! !REVISION HISTORY:
!     2001-dec-28 - B. Kauffman - initial version, taken from cpl5
!
! !INTERFACE:  -----------------------------------------------------------------


subroutine shr_cal_eday2ymd (eday,year,month,day) 4,2

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN),intent(in)  :: eday             ! elapsed days
   integer(SHR_KIND_IN),intent(out) :: year,month,day   ! calendar year,month,day

!EOP

   !--- local ---
   integer(SHR_KIND_IN) :: k,tday

!-------------------------------------------------------------------------------
! ASSUMPTIONS:
!   this calendar has a year zero (but no day or month zero)
!-------------------------------------------------------------------------------

   if (debug > 1) write(s_logunit,*) 'shr_cal_eday2ymd_a ',eday

   if (trim(calendar_type) == 'gregorian') then
      call shr_sys_abort('ERROR shr_cal_eday2date gregorian not validated')
   endif

   if (eday < 0) then
      year = -( abs(eday+1)/365 + 1)
   else
      year = eday/365       ! calendar year (note: Fortran truncation)
   endif

   tday = eday - year*365

   if (tday < 0 .or. tday > 364) then
      call shr_sys_abort('ERROR shr_cal_eday2date tday error')
   endif

   day  = mod(tday,365)  ! elapsed days within current year
   do k=1,12
     IF (day .ge. dsm(k)) month=k   ! calendar month
   end do
   day = day-dsm(month) + 1         ! calendar day

   if (debug > 1) write(s_logunit,*) 'shr_cal_eday2ymd_b ',year,month,day

end subroutine shr_cal_eday2ymd 

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_date2ymd - converts coded-date to year/month/day.
!
! !DESCRIPTION:
!     Converts coded-date (yyyymmdd) to year/month/day.
!
! !REVISION HISTORY:
!     2001-dec-28 - B. Kauffman - initial version, taken from cpl5
!
! !INTERFACE:  -----------------------------------------------------------------


subroutine shr_cal_date2ymd (date,year,month,day) 24

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN),intent(in)  :: date             ! coded-date (yyyymmdd)
   integer(SHR_KIND_IN),intent(out) :: year,month,day   ! calendar year,month,day

!EOP

   integer(SHR_KIND_IN) :: tdate   ! temporary date

!-------------------------------------------------------------------------------
!
!-------------------------------------------------------------------------------

   if (debug > 1) write(s_logunit,*) 'shr_cal_date2ymd_a ',date

   tdate = abs(date)
   year =int(     tdate       /10000)
   if (date < 0) year = -year
   month=int( mod(tdate,10000)/  100)
   day  =     mod(tdate,  100) 

   if (.not. shr_cal_validYMD(year,month,day)) then
      if (s_loglev > 0) write(s_logunit,*) "(shr_cal_date2ymd) ERROR: invalid date = ", &
         date,year,month,day
   endif

   if (debug > 1) write(s_logunit,*) 'shr_cal_date2ymd_b ',year,month,day

end subroutine shr_cal_date2ymd 

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_date2eday - converts coded-date to elapsed days
!
! !DESCRIPTION:
!     Converts coded-date to elapsed days
!
! !REVISION HISTORY:
!     2001-dec-28 - B. Kauffman - initial version, taken from cpl5
!
! !INTERFACE:  -----------------------------------------------------------------


subroutine shr_cal_date2eday(date,eday) 11,3

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN),intent(in ) :: date            ! coded (yyyymmdd) calendar date
   integer(SHR_KIND_IN),intent(out) :: eday            ! number of elapsed days

!EOP

   !--- local ---
   integer(SHR_KIND_IN) :: year,month,day

!-------------------------------------------------------------------------------
! NOTE:
!   elapsed days since yy-mm-dd = 00-01-01, with 0 elapsed seconds
!-------------------------------------------------------------------------------

   if (debug > 1) write(s_logunit,*) 'shr_cal_date2eday_a ',date

   call shr_cal_date2ymd(date,year,month,day)
   if (.not. shr_cal_validYMD(year,month,day)) then
      call shr_sys_abort(' shr_cal_date2eday invalid ymd')
   endif
   call shr_cal_ymd2eday(year,month,day,eday)

   if (debug > 1) write(s_logunit,*) 'shr_cal_date2eday_b ',eday

end subroutine shr_cal_date2eday

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_date2julian - converts coded-date to julian day of year
!
! !DESCRIPTION:
!     Converts coded-date to julian day of year
!
! !REVISION HISTORY:
!     2009-Oct-23 - T. Craig - initial version
!
! !INTERFACE:  -----------------------------------------------------------------


subroutine shr_cal_date2julian(date,sec,jday) 1,3

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN),intent(in ) :: date            ! coded (yyyymmdd) calendar date
   integer(SHR_KIND_IN),intent(in ) :: sec             ! seconds
   real   (SHR_KIND_R8),intent(out) :: jday            ! julian day of year

!EOP

   !--- local ---
   integer(SHR_KIND_IN) :: year,month,day

!-------------------------------------------------------------------------------
! NOTE:
!   julian day of year since yy-01-01-00000
!-------------------------------------------------------------------------------

   if (debug > 1) write(s_logunit,*) 'shr_cal_date2julian_a ',date,sec

   call shr_cal_date2ymd(date,year,month,day)
   if (.not. shr_cal_validYMD(year,month,day)) then
      call shr_sys_abort(' shr_cal_date2julian invalid ymd')
   endif
   call shr_cal_ymd2julian(year,month,day,sec,jday)

   if (debug > 1) write(s_logunit,*) 'shr_cal_date2julian_b ',jday

end subroutine shr_cal_date2julian

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_ymd2julian - converts y,m,d,s to julian day of year
!
! !DESCRIPTION:
!     Converts y,m,d,s to julian day of year
!
! !REVISION HISTORY:
!     2009-Oct-23 - T. Craig - initial version
!
! !INTERFACE:  -----------------------------------------------------------------


subroutine shr_cal_ymd2julian(year,month,day,sec,jday) 1,1

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN),intent(in ) :: year            ! year
   integer(SHR_KIND_IN),intent(in ) :: month           ! month
   integer(SHR_KIND_IN),intent(in ) :: day             ! day
   integer(SHR_KIND_IN),intent(in ) :: sec             ! seconds
   real   (SHR_KIND_R8),intent(out) :: jday            ! julian day of year

!EOP

   !--- local ---

!-------------------------------------------------------------------------------
! NOTE:
!   julian day of year since yy-01-01-000000
!-------------------------------------------------------------------------------

   if (debug > 1) write(s_logunit,*) 'shr_cal_ymd2julian_a ',year,month,day,sec

   jday = shr_cal_elapsDaysStrtMonth(year,month) + day + sec/SHR_CONST_CDAY
! tcraig 12/7/09, not needed
!!   jday = mod(jday,real(dpy,SHR_KIND_R8))

   if (debug > 1) write(s_logunit,*) 'shr_cal_ymd2julian_b ',jday

end subroutine shr_cal_ymd2julian

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_ymd2date - converts year, month, day to coded-date
!
! !DESCRIPTION:
!     Converts  year, month, day to coded-date
!
! !REVISION HISTORY:
!     2001-dec-28 - B. Kauffman - initial version, taken from cpl5
!
! !INTERFACE:  -----------------------------------------------------------------


subroutine shr_cal_ymd2date(year,month,day,date) 15,7

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN),intent(in ) :: year,month,day  ! calendar year,month,day
   integer(SHR_KIND_IN),intent(out) :: date            ! coded (yyyymmdd) calendar date

!EOP

   !--- local ---

!-------------------------------------------------------------------------------
! NOTE:
!   this calendar has a year zero (but no day or month zero)
!-------------------------------------------------------------------------------

   if (debug > 1) write(s_logunit,*) 'shr_cal_ymd2date_a ',year,month,day

   if (.not. shr_cal_validYMD(year,month,day)) then
      call shr_sys_abort(' shr_cal_ymd2date invalid ymd')
   endif

   date = abs(year)*10000 + month*100 + day  ! coded calendar date
   if (year < 0) date = -date

   if (debug > 1) write(s_logunit,*) 'shr_cal_ymd2date_b ',date

end subroutine shr_cal_ymd2date

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_ymd2eday - converts year, month, day to elapsed days
!
! !DESCRIPTION:
!     Converts  year, month, day to elapsed days
!
! !REVISION HISTORY:
!     2001-dec-28 - B. Kauffman - initial version, taken from cpl5
!
! !INTERFACE:  -----------------------------------------------------------------


subroutine  shr_cal_ymd2eday(year,month,day,eday) 3

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN),intent(in ) :: year,month,day  ! calendar year,month,day
   integer(SHR_KIND_IN),intent(out) :: eday            ! number of elapsed days

!EOP

   !--- local ---
   type(ESMF_Time) :: ltime
   integer(SHR_KIND_IN) :: eday0
   integer :: rc

!-------------------------------------------------------------------------------
! NOTE:
!   elapsed days since yy-mm-dd = 00-01-01, with 0 elapsed seconds
!-------------------------------------------------------------------------------

   if (debug > 1) write(s_logunit,*) 'shr_cal_ymd2eday_a ',year,month,day

   if (.not. shr_cal_validYMD(year,month,day)) then
      call shr_sys_abort(' shr_cal_ymd2eday invalid ymd')
   endif

   if (trim(calendar_type) == 'gregorian') then
#ifdef USE_ESMF_LIB 
      call ESMF_TimeSet(ltime,yy=year,mm=month,dd=day,s=0,calendarType=ESMF_CAL_GREGORIAN,rc=rc)
      if(rc /= ESMF_SUCCESS) call ESMF_Finalize(rc=rc, terminationflag=ESMF_ABORT)
      call ESMF_TimeGet(ltime,d=eday,rc=rc)
      if(rc /= ESMF_SUCCESS) call ESMF_Finalize(rc=rc, terminationflag=ESMF_ABORT)
#else
      call shr_sys_abort('ERROR: shr_cal gregorian requires USE_ESMF_LIB')
#endif
   else
      eday = year*365 + dsm(month) + (day-1)
   endif

   if (debug > 1) write(s_logunit,*) 'shr_cal_ymd2eday_b ',eday

end subroutine  shr_cal_ymd2eday

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_advDate - advances a date and seconds with a delta time
!
! !DESCRIPTION:
!     Advances a date and seconds with a delta time
!
! !REVISION HISTORY:
!     2009-Jun-09 - T. Craig - allows delta < 0
!     2005-Jun-10 - B. Kauffman - bug fix, simplified algorithm
!     2005-May-15 - T. Craig - initial version 
!
! !INTERFACE:  -----------------------------------------------------------------


subroutine shr_cal_advDate(delta,units,dateIN,secIN,dateOUT,secOUT,calendar) 3,6

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   real   (SHR_KIND_R8) ,intent(in)  :: delta     ! time increment
   character(*)         ,intent(in)  :: units     ! units of increment
   integer(SHR_KIND_IN) ,intent(in)  :: dateIN    ! base date, yyyymmdd
   real   (SHR_KIND_R8) ,intent(in)  :: secIN     ! base seconds
   integer(SHR_KIND_IN) ,intent(out) :: dateOUT   ! new date, yyyymmdd
   real   (SHR_KIND_R8) ,intent(out) :: secOUT    ! new seconds
   character(*),optional,intent(in)  :: calendar  ! calendar type

!EOP

   !--- local ---
   real   (SHR_KIND_R8)   :: dSec    ! delta-sec: advance date this many seconds
   integer(SHR_KIND_IN)   :: dDay    ! advance this many whole days
   real   (SHR_KIND_R8)   :: rSec    ! advance this many "remainder seconds"
   integer(SHR_KIND_IN)   :: eDay    ! date as elapsed dates from ref date
   integer(SHR_KIND_IN)   :: dayadjust ! time adjustment to support negative delta
   character(SHR_KIND_CL) :: calOrig ! original calendar type

   !--- formats ---
   character(*),parameter :: subName = "(shr_cal_advDate)"
   character(*),parameter :: F00 = "('(shr_cal_advDate) ',a,i5)"
   character(*),parameter :: F02 = "('(shr_cal_advDate) ',a,i8.8,f10.3)"
   
!-------------------------------------------------------------------------------
! NOTE:
!-------------------------------------------------------------------------------

   call shr_cal_get(calOrig)

   !--- allow the temporary use of an alternate calendar ---
   if (present(calendar)) call shr_cal_set(calendar)

   !--- calculate delta-time in seconds ---
   if     (trim(units) == 'days'   ) then
      dSec = delta * SHR_CONST_CDAY
   elseif (trim(units) == 'hours'  ) then
      dSec = delta * 3600.0_SHR_KIND_R8
   elseif (trim(units) == 'minutes') then
      dSec = delta *   60.0_SHR_KIND_R8
   elseif (trim(units) == 'seconds') then
      dSec = delta *    1.0_SHR_KIND_R8
   else
      call shr_sys_abort(' ERROR: unrecognized time units '//trim(units))
   endif

   !--- starting from dateIN and zero seconds...      ---
   !--- advance (secIN + dSec) seconds to arrive at output date ---

   call shr_cal_date2eday(dateIN,eDay) ! starting from this eDay...
   dSec = dSec + secIN                 ! advance this many seconds

   !--- if dSec lt 0 then add dayadjust to dSec to make it positive
   !--- and subtract dayadjust from dDay to correct it.
   if (dSec < 0.0_SHR_KIND_R8) then
      dayadjust = int(abs(dSec)/SHR_CONST_CDAY) + 1
      dSec = dSec + dayadjust*SHR_CONST_CDAY
      dDay = int(dSec/SHR_CONST_CDAY) - dayadjust
      rSec = mod(dSec,SHR_CONST_CDAY)
   else
      dDay = int(dSec/SHR_CONST_CDAY)     ! advance this many whole days...
      rSec = mod(dSec,SHR_CONST_CDAY)     ! ...plus this many secs (less than a day)
   endif

   call shr_cal_eday2date(eDay+dDay,dateOUT)
   secOUT = rSec    

   if (debug>0) then
      if (present(calendar)) then
         if (s_loglev > 0) write(s_logunit,*) subName," units,delta,calendar=",trim(units),delta,' ',trim(calendar)
      else
         if (s_loglev > 0) write(s_logunit,*) subName," units,delta="         ,trim(units),delta
      endif
      if (s_loglev > 0) write(s_logunit,F02) "dateIN ,secIN =",dateIN ,secIN
      if (s_loglev > 0) write(s_logunit,F02) "dateOUT,secOUT=",dateOUT,secOUT
   end if

   call shr_cal_set(calOrig)

end subroutine shr_cal_advDate

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_advDateInt - advances a date and seconds with a delta time
!
! !DESCRIPTION:
!     Advances a date and seconds with a delta time
!
! !REVISION HISTORY:
!     2009-???-?? - ?? - replicated from shr_cal_advDate()
!
! !INTERFACE:  -----------------------------------------------------------------


subroutine shr_cal_advDateInt(delta,units,dateIN,secIN,dateOUT,secOUT,calendar) 1,6

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN) ,intent(in)  :: delta     ! time increment
   character(*)         ,intent(in)  :: units     ! units of increment
   integer(SHR_KIND_IN) ,intent(in)  :: dateIN    ! base date, yyyymmdd
   integer(SHR_KIND_IN) ,intent(in)  :: secIN     ! base seconds
   integer(SHR_KIND_IN) ,intent(out) :: dateOUT   ! new date, yyyymmdd
   integer(SHR_KIND_IN) ,intent(out) :: secOUT    ! new seconds
   character(*),optional,intent(in)  :: calendar  ! calendar type

!EOP

   !--- local ---
   real   (SHR_KIND_R8)   :: dSec    ! delta-sec: advance date this many seconds
   integer(SHR_KIND_IN)   :: dDay    ! advance this many whole days
   real   (SHR_KIND_R8)   :: rSec    ! advance this many "remainder seconds"
   integer(SHR_KIND_IN)   :: eDay    ! date as elapsed dates from ref date
   integer(SHR_KIND_IN)   :: dayadjust ! time adjustment to support negative delta
   character(SHR_KIND_CL) :: calOrig ! original calendar type

   !--- formats ---
   character(*),parameter :: subName = "(shr_cal_advDateInt)"
   character(*),parameter :: F00 = "('(shr_cal_advDateInt) ',a,i5)"
   character(*),parameter :: F02 = "('(shr_cal_advDateInt) ',a,i8.8,f10.3)"
   
!-------------------------------------------------------------------------------
! NOTE:
!-------------------------------------------------------------------------------

   call shr_cal_get(calOrig)

   !--- allow the temporary use of an alternate calendar ---
   if (present(calendar)) call shr_cal_set(calendar)

   !--- calculate delta-time in seconds ---
   if     (trim(units) == 'days'   ) then
      dSec = delta * SHR_CONST_CDAY
   elseif (trim(units) == 'hours'  ) then
      dSec = delta * 3600.0_SHR_KIND_R8
   elseif (trim(units) == 'minutes') then
      dSec = delta *   60.0_SHR_KIND_R8
   elseif (trim(units) == 'seconds') then
      dSec = delta *    1.0_SHR_KIND_R8
   else
      call shr_sys_abort(' ERROR: unrecognized time units '//trim(units))
   endif

   !--- starting from dateIN and zero seconds...      ---
   !--- advance (secIN + dSec) seconds to arrive at output date ---

   call shr_cal_date2eday(dateIN,eDay) ! starting from this eDay...
   dSec = dSec + secIN                 ! advance this many seconds

   !--- if dSec lt 0 then add dayadjust to dSec to make it positive
   !--- and subtract dayadjust from dDay to correct it.
   if (dSec < 0.0_SHR_KIND_R8) then
      dayadjust = int(abs(dSec)/SHR_CONST_CDAY) + 1
      dSec = dSec + dayadjust*SHR_CONST_CDAY
      dDay = int(dSec/SHR_CONST_CDAY) - dayadjust
      rSec = mod(dSec,SHR_CONST_CDAY)
   else
      dDay = int(dSec/SHR_CONST_CDAY)     ! advance this many whole days...
      rSec = mod(dSec,SHR_CONST_CDAY)     ! ...plus this many secs (less than a day)
   endif

   call shr_cal_eday2date(eDay+dDay,dateOUT)
   secOUT = nint(rSec)

   if (debug>0) then
      if (present(calendar)) then
         if (s_loglev > 0) write(s_logunit,*) subName," units,delta,calendar=",trim(units),delta,' ',trim(calendar)
      else
         if (s_loglev > 0) write(s_logunit,*) subName," units,delta="         ,trim(units),delta
      endif
      if (s_loglev > 0) write(s_logunit,F02) "dateIN ,secIN =",dateIN ,secIN
      if (s_loglev > 0) write(s_logunit,F02) "dateOUT,secOUT=",dateOUT,secOUT
   end if

   call shr_cal_set(calOrig)

end subroutine shr_cal_advDateInt

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_validDate - determines if coded-date is a valid date
!
! !DESCRIPTION:
!    Determines if the given coded-date is a valid date.
!
! !REVISION HISTORY:
!     2001-dec-28 - B. Kauffman - initial version, taken from cpl5
!
! !INTERFACE:  -----------------------------------------------------------------


logical function shr_cal_validDate(date)  1,2

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN),intent(in ) :: date            ! coded (yyyymmdd) calendar date

!EOP

   !--- local ---
   integer(SHR_KIND_IN) :: year,month,day
   integer(SHR_KIND_IN) :: tdate

!-------------------------------------------------------------------------------
!
!-------------------------------------------------------------------------------

   call shr_cal_date2ymd(date,year,month,day)
   shr_cal_validDate = shr_cal_validYMD(year,month,day)

end function shr_cal_validDate

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_validYMD - determines if year, month, day is a valid date
!
! !DESCRIPTION:
!    Determines if the given year, month, and day indicate a valid date.
!
! !REVISION HISTORY:
!     2001-dec-28 - B. Kauffman - initial version, taken from cpl5
!
! !INTERFACE:  -----------------------------------------------------------------


logical function shr_cal_validYMD(year,month,day) 3

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN),intent(in ) :: year,month,day  ! calendar year,month,day

!EOP

   !--- local ---

!-------------------------------------------------------------------------------
!
!-------------------------------------------------------------------------------

   shr_cal_validYMD = .true.
   if (year  < -999) shr_cal_validYMD = .false.
   if (year  > 9999) shr_cal_validYMD = .false.
   if (month <    1) shr_cal_validYMD = .false.
   if (month >   12) shr_cal_validYMD = .false.
   if (day   <    1) shr_cal_validYMD = .false.
!  - tcraig, to support gregorian calendar
!   if (shr_cal_validYMD) then
!      if (day > dpm(month)) shr_cal_validYMD = .false.
!   endif

end function shr_cal_validYMD

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_validHMS - determines if hour, min, sec is valid
!
! !DESCRIPTION:
!    Determines if the given hour, min, sec is valid
!
! !REVISION HISTORY:
!     2005-May-15 - T. Craig - initial version
!
! !INTERFACE:  -----------------------------------------------------------------


logical function shr_cal_validHMS(hr,min,sec)

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN),intent(in ) :: hr, min, sec   ! hour, minute, second

!EOP

   !--- local ---

!-------------------------------------------------------------------------------
!
!-------------------------------------------------------------------------------

   shr_cal_validHMS = .true.
   if (hr  <    0) shr_cal_validHMS = .false.
   if (hr  >   23) shr_cal_validHMS = .false.
   if (min <    0) shr_cal_validHMS = .false.
   if (min >   59) shr_cal_validHMS = .false.
   if (sec <    0) shr_cal_validHMS = .false.
   if (sec >   60) shr_cal_validHMS = .false.

end function shr_cal_validHMS

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_numDaysInMonth - return the number of days in a month.
!
! !DESCRIPTION:
!    Deturn the number of days in a month.
!
! !REVISION HISTORY:
!     2002-sep-18 - B. Kauffman - initial version.
!
! !INTERFACE:  -----------------------------------------------------------------


integer function shr_cal_numDaysInMonth(year,month) 1,10

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN),intent(in ) :: year,month  ! calendar year,month

!EOP

   type(ESMF_time) :: ltime1,ltime2
   integer(SHR_KIND_IN) :: eday1,eday2
   integer :: rc

!-------------------------------------------------------------------------------
!
!-------------------------------------------------------------------------------

   if (trim(calendar_type) == 'gregorian') then
#ifdef USE_ESMF_LIB 
      call ESMF_TimeSet(ltime1,yy=year,mm=month,dd=1,s=0,calendarType=ESMF_CAL_GREGORIAN,rc=rc)
      if(rc /= ESMF_SUCCESS) call ESMF_Finalize(rc=rc, terminationflag=ESMF_ABORT)
      if (month < 12) then
         call ESMF_TimeSet(ltime2,yy=year,mm=month+1,dd=1,s=0,calendarType=ESMF_CAL_GREGORIAN,rc=rc)
         if(rc /= ESMF_SUCCESS) call ESMF_Finalize(rc=rc, terminationflag=ESMF_ABORT)
      else
         call ESMF_TimeSet(ltime2,yy=year+1,mm=1,dd=1,s=0,calendarType=ESMF_CAL_GREGORIAN,rc=rc)
         if(rc /= ESMF_SUCCESS) call ESMF_Finalize(rc=rc, terminationflag=ESMF_ABORT)
      endif
      call ESMF_TimeGet(ltime1,d=eday1,rc=rc)
      call ESMF_TimeGet(ltime2,d=eday2,rc=rc)
      shr_cal_numDaysInMonth = eday2-eday1
      call shr_sys_abort('ERROR shr_cal_numDaysInMonth gregorian not validated')
#else
      call shr_sys_abort('ERROR: shr_cal gregorian requires USE_ESMF_LIB')
#endif
   else
      shr_cal_numDaysInMonth = dpm(month)
   endif
      

end function shr_cal_numDaysInMonth

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_elapsDaysStrtMonth - return the number of elapsed days
!            at start of month
!
! !DESCRIPTION:
!    Return the number of elapsed days at start of a month.
!
! !REVISION HISTORY:
!     2002-Oct-29 - R. Jacob - initial version
!
! !INTERFACE:  -----------------------------------------------------------------


integer function shr_cal_elapsDaysStrtMonth(year,month) 2,9

   implicit none

! !INPUT/OUTPUT PARAMETERS:

   integer(SHR_KIND_IN),intent(in ) :: year,month  ! calendar year,month

!EOP

   integer(SHR_KIND_IN) :: yr,eday1,eday2
   integer :: rc
   type(ESMF_Time) :: ltime1,ltime2

!-------------------------------------------------------------------------------
!
!-------------------------------------------------------------------------------

   if (trim(calendar_type) == 'gregorian') then
#ifdef USE_ESMF_LIB 
      call ESMF_TimeSet(ltime1,yy=year,mm=1,dd=1,s=0,calendarType=ESMF_CAL_GREGORIAN,rc=rc)
      if(rc /= ESMF_SUCCESS) call ESMF_Finalize(rc=rc, terminationflag=ESMF_ABORT)
      call ESMF_TimeSet(ltime2,yy=year,mm=month,dd=1,s=0,calendarType=ESMF_CAL_GREGORIAN,rc=rc)
      if(rc /= ESMF_SUCCESS) call ESMF_Finalize(rc=rc, terminationflag=ESMF_ABORT)
      call ESMF_TimeGet(ltime1,yy=yr,d=eday1,rc=rc)
      if(rc /= ESMF_SUCCESS) call ESMF_Finalize(rc=rc, terminationflag=ESMF_ABORT)
      call ESMF_TimeGet(ltime2,yy=yr,d=eday2,rc=rc)
      if(rc /= ESMF_SUCCESS) call ESMF_Finalize(rc=rc, terminationflag=ESMF_ABORT)
      shr_cal_elapsDaysStrtMonth = eday2 - eday1
#else
      call shr_sys_abort('ERROR: shr_cal gregorian requires USE_ESMF_LIB')
#endif
   else
      shr_cal_elapsDaysStrtMonth = dsm(month)
   endif

end function shr_cal_elapsDaysStrtMonth

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_setDebug -- Set local shr_cal debug level
!
! !DESCRIPTION:
!    Set local shr\_cal debug level, 0 = production
!    \newline
!    General Usage: call shr\_cal\_setDebug(2)
!
! !REVISION HISTORY:
!     2005-May-10  - B. Kauffman - first version
!
! !INTERFACE: ------------------------------------------------------------------


subroutine shr_cal_setDebug(level)

  implicit none

! !INPUT/OUTPUT PARAMETERS:

  integer,intent(in) :: level

!EOP

  !--- formats ---
  character(*),parameter :: subName =   "(shr_cal_setDebug) "
  character(*),parameter :: F00     = "('(shr_cal_setDebug) ',a) "
  character(*),parameter :: F01     = "('(shr_cal_setDebug) ',a,i4) "

!-------------------------------------------------------------------------------
!
!-------------------------------------------------------------------------------

  debug = level
  if (s_loglev > 0) write(s_logunit,F01) "debug level reset to ",level

end subroutine shr_cal_setDebug

!===============================================================================
!BOP ===========================================================================
!
! !IROUTINE: shr_cal_getDebug -- get shr_cal internal debug level
!
! !DESCRIPTION:
!    Get shr_cal internal debug level, 0 = production
!    \newline
!    General Usage: call shr\_cal\_getDebug(level)
!
! !REVISION HISTORY:
!     2005-May-10  - B. Kauffman - first version
!
! !INTERFACE: ------------------------------------------------------------------


subroutine shr_cal_getDebug(level)

  implicit none

! !INPUT/OUTPUT PARAMETERS:

  integer,intent(out) :: level

!EOP

  !--- formats ---
  character(*),parameter :: subName =   "(shr_cal_getDebug) "
  character(*),parameter :: F00     = "('(shr_cal_getDebug) ',a) "

!-------------------------------------------------------------------------------
!
!-------------------------------------------------------------------------------

  level = debug

end subroutine shr_cal_getDebug

!===============================================================================
!===============================================================================

end module shr_cal_mod