Skip to content

Commit

Permalink
Merge pull request #336 from esmf-org/document_timeget_reals
Browse files Browse the repository at this point in the history
Document behavior of double precision outputs in ESMF_TimeGet and ESMF_TimeIntervalGet

As discussed in esmf-org/esmf-support#494, behavior differs for double precision outputs compared with integer outputs, and this wasn't documented.

This PR adds some unit tests to better characterize / demonstrate this behavior, and improves the documentation of this behavior.

Resolves esmf-org/esmf-support#494
  • Loading branch information
billsacks authored Dec 17, 2024
2 parents f393771 + c380efa commit 7b5c03e
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 2 deletions.
10 changes: 10 additions & 0 deletions src/Infrastructure/TimeMgr/interface/ESMF_Time.F90
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,16 @@ subroutine ESMF_TimeGet(time, keywordEnforcer, &
! and {\tt ESMF\_TimeGet(yy = year, s=seconds)} would return
! {\tt year = 2004}, {\tt seconds = 2772000} (770 * 3600).
!
! However, double precision time units are not considered in this normalization:
! Double precision time units are still bound by units of a day or larger, but double
! precision time units of an hour or smaller neither bind nor are bound by other time
! units of an hour or smaller (either integer or double precision). For example,
! with the same time setting as above (2:00 am on February 2, 2004),
! {\tt ESMF\_TimeGet(dd=day, h_r8=hours_r8, s=seconds)} would return
! {\tt day = 2}, {\tt hours_r8 = 2.0}, {\tt seconds = 7200}, and
! {\tt ESMF\_TimeGet(dd=day, h=hours, s_r8=seconds_r8)} would return
! {\tt day = 2}, {\tt hours = 2}, {\tt seconds_r8 = 7200.0}.
!
! For {\tt timeString}, {\tt timeStringISOFrac}, {\tt dayOfWeek},
! {\tt midMonth}, {\tt dayOfYear}, {\tt dayOfYear\_intvl}, and
! {\tt dayOfYear\_r8} described below, valid calendars are Gregorian,
Expand Down
16 changes: 16 additions & 0 deletions src/Infrastructure/TimeMgr/interface/ESMF_TimeInterval.F90
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,10 @@ subroutine ESMF_TimeIntervalGetDur(timeinterval, keywordEnforcer, &
! {\tt days = 1}, {\tt seconds = 0},
! whereas {\tt ESMF\_TimeIntervalGet(s = seconds)} would return
! {\tt seconds = 86400}.
! However, double precision time units are not considered in this normalization:
! Double precision time units are still bound by units of a day or larger, but double
! precision time units of an hour or smaller neither bind nor are bound by other time
! units of an hour or smaller (either integer or double precision).
!
! For timeString, converts {\tt ESMF\_TimeInterval}'s value into
! partial ISO 8601 format PyYmMdDThHmMs[:n/d]S. See ~\cite{ISO} and
Expand Down Expand Up @@ -1313,6 +1317,10 @@ subroutine ESMF_TimeIntervalGetDurStart(timeinterval, startTimeIn, &
! {\tt days = 1}, {\tt seconds = 0},
! whereas {\tt ESMF\_TimeIntervalGet(s = seconds)} would return
! {\tt seconds = 86400}.
! However, double precision time units are not considered in this normalization:
! Double precision time units are still bound by units of a day or larger, but double
! precision time units of an hour or smaller neither bind nor are bound by other time
! units of an hour or smaller (either integer or double precision).
!
! For timeString, converts {\tt ESMF\_TimeInterval}'s value into
! partial ISO 8601 format PyYmMdDThHmMs[:n/d]S. See ~\cite{ISO} and
Expand Down Expand Up @@ -1548,6 +1556,10 @@ subroutine ESMF_TimeIntervalGetDurCal(timeinterval, calendarIn, &
! {\tt days = 1}, {\tt seconds = 0},
! whereas {\tt ESMF\_TimeIntervalGet(s = seconds)} would return
! {\tt seconds = 86400}.
! However, double precision time units are not considered in this normalization:
! Double precision time units are still bound by units of a day or larger, but double
! precision time units of an hour or smaller neither bind nor are bound by other time
! units of an hour or smaller (either integer or double precision).
!
! For timeString, converts {\tt ESMF\_TimeInterval}'s value into
! partial ISO 8601 format PyYmMdDThHmMs[:n/d]S. See ~\cite{ISO} and
Expand Down Expand Up @@ -1785,6 +1797,10 @@ subroutine ESMF_TimeIntervalGetDurCalTyp(timeinterval, calkindflagIn, &
! {\tt days = 1}, {\tt seconds = 0},
! whereas {\tt ESMF\_TimeIntervalGet(s = seconds)} would return
! {\tt seconds = 86400}.
! However, double precision time units are not considered in this normalization:
! Double precision time units are still bound by units of a day or larger, but double
! precision time units of an hour or smaller neither bind nor are bound by other time
! units of an hour or smaller (either integer or double precision).
!
! For timeString, converts {\tt ESMF\_TimeInterval}'s value into
! partial ISO 8601 format PyYmMdDThHmMs[:n/d]S. See ~\cite{ISO} and
Expand Down
71 changes: 69 additions & 2 deletions src/Infrastructure/TimeMgr/tests/ESMF_TimeUTest.F90
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ program ESMF_TimeUTest

! individual test result code
integer :: rc, H, M, S, MM, DD, YY
real(ESMF_KIND_R8) :: D_r8, H_r8, M_r8, S_r8

! individual test name
character(ESMF_MAXSTR) :: name
Expand Down Expand Up @@ -74,8 +75,8 @@ program ESMF_TimeUTest
logical :: bool
integer :: dayOfYear, dayOfWeek, D, sD, sN, MS, NS, &
US
real(ESMF_KIND_R8) :: NS_r8, S_r8, US_r8, MS_r8
real(ESMF_KIND_R8) :: dayOfYear_r8, M_r8, D_r8, H_r8
real(ESMF_KIND_R8) :: NS_r8, US_r8, MS_r8
real(ESMF_KIND_R8) :: dayOfYear_r8
integer(ESMF_KIND_I8) :: year, SN_I8, SD_i8

! instantitate some general times and timeintervals
Expand Down Expand Up @@ -146,6 +147,72 @@ program ESMF_TimeUTest

!print *, "startTime = ", timeString19

! ----------------------------------------------------------------------------
!NEX_UTest
! If you don't ask for all coarser time units, then the full remaining time gets
! packed into the units you ask for
write(name, *) "Get Time Test - hours without all coarser time units"
write(failMsg, *) " Did not return correct un-normalized hours or ESMF_SUCCESS"
! Do ask for years here, because otherwise we get the arbitrary baseline for years
! folded into the result; but don't ask for any units between years and hours:
call ESMF_TimeGet(startTime, yy=YY, h=H, rc=rc)
! Hours = (28 days)*(24 hours/day) + 12
call ESMF_Test((YY==2004 .and. H==684 .and. rc==ESMF_SUCCESS), name, failMsg, result, ESMF_SRCLINE)

! ----------------------------------------------------------------------------
!NEX_UTest
! When asking for double precision time variables, they are still normalized by
! days, but they are NOT normalized by time units finer than days. Furthermore, all
! times include the full, fractional time (so hours, minutes and seconds all give
! the same time value, just in different units).
write(name, *) "Get Time Test - real-valued time variables with int-valued date variables"
write(failMsg, *) " Did not return correct time values or ESMF_SUCCESS"
call ESMF_TimeGet(startTime, yy=YY, mm=MM, dd=DD, h_r8=H_r8, m_r8=M_r8, s_r8=S_r8, rc=rc)
! Note that times are normalized by days; all times include the full time - with the
! same value, just in different units
! S = (12*60 + 17)*60 + 58
! M = ((12*60 + 17)*60 + 58) / 60
! H = (((12*60 + 17)*60 + 58) / 60) / 60
call ESMF_Test((YY==2004 .and. MM==1 .and. DD==29 .and. &
abs(H_r8 - 12.299444444444445d0) < 1d-14 .and. &
abs(M_r8 - 737.9666666666667d0) < 1d-12 .and. &
abs(S_r8 - 44278.0d0) < 1d-11 .and. &
rc==ESMF_SUCCESS), name, failMsg, result, ESMF_SRCLINE)

! ----------------------------------------------------------------------------
!NEX_UTest
! Double precision time units include the full time even when asking for some time
! units in integer. This contrasts with the behavior when asking for all things in
! integer, where the finer time units are normalized by the coarser time units.
write(name, *) "Get Time Test - mix of coarser int-valued and finer real-valued time variables"
write(failMsg, *) " Did not return correct time values or ESMF_SUCCESS"
call ESMF_TimeGet(startTime, yy=YY, mm=MM, dd=DD, h=H, m_r8=M_r8, s_r8=S_r8, rc=rc)
! Note that real-valued times include the full time - with the same value, just in
! different units
! S = (12*60 + 17)*60 + 58
! M = ((12*60 + 17)*60 + 58) / 60
call ESMF_Test((YY==2004 .and. MM==1 .and. DD==29 .and. H==12 .and. &
abs(M_r8 - 737.9666666666667d0) < 1d-12 .and. &
abs(S_r8 - 44278.0d0) < 1d-11 .and. &
rc==ESMF_SUCCESS), name, failMsg, result, ESMF_SRCLINE)

! ----------------------------------------------------------------------------
!NEX_UTest
! When asking for coarser time units as double precision, days are still used to
! normalize finer-valued time units, but time variables (e.g., hours) in double
! precision are NOT used to normalize finer-valued time units in integer. This
! contrasts with the behavior when asking for all things in integer, where the finer
! time units are normalized by the coarser time units.
write(name, *) "Get Time Test - mix of coarser real-valued and finer int-valued time variables"
write(failMsg, *) " Did not return correct time values or ESMF_SUCCESS"
call ESMF_TimeGet(startTime, yy=YY, mm=MM, dd=DD, h_r8=H_r8, m=M, s=S, rc=rc)
! M = (12 hours)*(60 minutes/hour) + 17
! S is normalized by M, so simply the remaining seconds, 58
call ESMF_Test((YY==2004 .and. MM==1 .and. DD==29 .and. &
abs(H_r8 - 12.299444444444445d0) < 1d-14 .and. &
M==737 .and. S==58 .and. &
rc==ESMF_SUCCESS), name, failMsg, result, ESMF_SRCLINE)

! ----------------------------------------------------------------------------
!NEX_UTest

Expand Down

0 comments on commit 7b5c03e

Please sign in to comment.