!***********************************************************************
!* GNU Lesser General Public License
!*
!* This file is part of the GFDL Flexible Modeling System (FMS).
!*
!* FMS is free software: you can redistribute it and/or modify it under
!* the terms of the GNU Lesser General Public License as published by
!* the Free Software Foundation, either version 3 of the License, or (at
!* your option) any later version.
!*
!* FMS is distributed in the hope that it will be useful, but WITHOUT
!* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
!* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
!* for more details.
!*
!* You should have received a copy of the GNU Lesser General Public
!* License along with FMS. If not, see .
!***********************************************************************
module get_cal_time_mod
!
! fms
!
!
! Given a time increment as a real number, and base time and calendar
! as a character strings, returns time as a time_type variable.
!
use fms_mod, only: error_mesg, FATAL, write_version_number, lowercase, &
open_namelist_file, check_nml_error, stdlog, close_file, &
mpp_pe, mpp_root_pe
use time_manager_mod, only: time_type, operator(+), operator(-), set_time, get_time, &
NO_CALENDAR, THIRTY_DAY_MONTHS, NOLEAP, JULIAN, GREGORIAN, &
set_calendar_type, get_calendar_type, set_date, &
get_date, days_in_month, valid_calendar_types
use mpp_mod, only: input_nml_file
implicit none
private
public :: get_cal_time
logical :: module_is_initialized=.false. ! This module is initialized on
! the first call to get_cal_time
! because there is no constructor.
!
!
! This sets the default value of the optional argument named "permit_calendar_conversion" of get_cal_time.
! This namelist is deprecated as of the memphis release.
! If calendar conversion is not desired, then it is recommended that permit_calendar_conversion
! be present in the call to get_cal_time and that it be set to .false.
!
logical :: allow_calendar_conversion=.true.
namelist / get_cal_time_nml / allow_calendar_conversion
!
! Include variable "version" to be written to log file.
#include
contains
!------------------------------------------------------------------------
!
!
! get_cal_time(time_increment, units, calendar, permit_calendar_conversion)
!
! A time interval.
!
!
! Examples of acceptable values of units:
!
! 'days since 1980-01-01 00:00:00',
! 'hours since 1980-1-1 0:0:0',
! 'minutes since 0001-4-12'
!
! The first word in the string must be
! 'years', 'months', 'days', 'hours', 'minutes' or 'seconds'.
! The second word must be 'since'
!
! year number must occupy 4 spaces.
! Number of months, days, hours, minutes, seconds may occupy 1 or 2 spaces
! year, month and day must be separated by a '-'
! hour, minute, second must be separated by a ':'
! hour, minute, second are optional. If not present then zero is assumed.
!
! Because months are not equal increments of time, and, for julian calendar,
! neither are years, the 'years since' and 'month since' cases deserve
! further explaination.
!
! When 'years since' is used:
! The year number is increased by floor(time_increment) to obtain a time T1.
! The year number is increased by floor(time_increment)+1 to obtain a time T2.
! The time returned is T1 + (time_increment-floor(time_increment))*(T2-T1).
!
! When 'months since' is used:
! The month number is increased by floor(time_increment). If it falls outside
! to range 1 to 12 then it is adjusted along with the year number to convert
! to a valid date. The number of days in the month of this date is used to
! compute the time interval of the fraction.
! That is:
! The month number is increased by floor(time_increment) to obtain a time T1.
! delt = the number of days in the month in which T1 falls.
! The time returned is T1 + ((time_increment-floor(time_increment))*delt.
! Two of the consequences of this scheme should be kept in mind.
! -- The time since should not be from the 29'th to 31'st of a month,
! since an invalid date is likely to result, triggering an error stop.
! -- When time since is from the begining of a month, the fraction of a month
! will never advance into the month after that which results from only
! the whole number.
!
! When NO_CALENDAR is in effect, units attribute must specify a starting
! day and second, with day number appearing first
!
! Example: 'days since 100 0' Indicates 100 days 0 seconds
!
!
!
! Acceptable values of calendar are:
! 'noleap'
! '365_day'
! '360_day'
! 'julian'
! 'thirty_day_months'
! 'no_calendar'
!
!
!
! It is sometimes desirable to allow the value of the intent(in) argument
! "calendar" to be different than the calendar in use by time_manager_mod.
! If this is not desirable, then the optional variable "permit_calendar_conversion"
! should be set to .false. so as to allow an error check.
! When calendar conversion is done, the time returned is the time in the
! time_manager's calendar, but corresponds to the date computed using the input calendar.
! For example, suppose the time_manager is using the julian calendar and
! the values of the input arguments of get_cal_time are:
! time_increment = 59.0
! units = 'days since 1980-1-1 00:00:00'
! calendar = 'noleap'
! Because it will use the noleap calendar to calculate the date, get_cal_time will return
! value of time for midnight March 1 1980, but it will be time in the julian calendar
! rather than the noleap calendar. It will never return a value of time corresponding
! to anytime during the day Feb 29.
!
! Another example:
! Suppose the time_manager is using either the noleap or julian calendars,
! and the values of the input arguments are:
! time_increment = 30.0
! units = 'days since 1980-1-1'
! calendar = 'thirty_day_months'
! In this case get_cal_time will return the value of time for Feb 1 1980 00:00:00,
! but in the time_manager's calendar.
! Calendar conversion may result in a fatal error when the input calendar type is
! a calendar that has more days per year than that of the time_manager's calendar.
! For example, if the input calendar type is julian and the time_manager's calendar
! is thirty_day_months, then get_cal_time will try to convert Jan 31 to a time in
! the thirty_day_months calendar, resulting in a fatal error.
! Note: this option was originally coded to allow noleap calendar as input when
! the julian calendar was in effect by the time_manager.
!
!
!---------------------------------------------------------------------------------------------
function get_cal_time(time_increment, units, calendar, permit_calendar_conversion)
real, intent(in) :: time_increment
character(len=*), intent(in) :: units
character(len=*), intent(in) :: calendar
logical, intent(in), optional :: permit_calendar_conversion
type(time_type) :: get_cal_time
integer :: year, month, day, hour, minute, second
integer :: i1, i2, i3, i4, i5, i6, increment_seconds, increment_days, increment_years, increment_months
real :: month_fraction
integer :: calendar_tm_i, calendar_in_i, namelist_unit, ierr, io, logunit
logical :: correct_form
character(len=32) :: calendar_in_c
character(len=64) :: err_msg
character(len=4) :: formt='(i )'
type(time_type) :: base_time, base_time_plus_one_yr, base_time_plus_one_mo
real :: dt
logical :: permit_conversion_local
if(.not.module_is_initialized) then
#ifdef INTERNAL_FILE_NML
read (input_nml_file, get_cal_time_nml, iostat=io)
ierr = check_nml_error (io, 'get_cal_time_nml')
#else
namelist_unit = open_namelist_file()
ierr=1
do while (ierr /= 0)
read(namelist_unit, nml=get_cal_time_nml, iostat=io, end=20)
ierr = check_nml_error (io, 'get_cal_time_nml')
enddo
20 call close_file (namelist_unit)
#endif
call write_version_number("GET_CAL_TIME_MOD", version)
logunit = stdlog()
if(mpp_pe() == mpp_root_pe()) write (logunit, nml=get_cal_time_nml)
module_is_initialized = .true.
endif
if(present(permit_calendar_conversion)) then
permit_conversion_local = permit_calendar_conversion
else
permit_conversion_local = allow_calendar_conversion
endif
calendar_in_c = lowercase(trim(cut0(calendar)))
correct_form = (trim(calendar_in_c)) == 'noleap' .or. (trim(calendar_in_c)) == '365_day' .or. &
(trim(calendar_in_c)) == '360_day' .or. (trim(calendar_in_c)) == 'julian' .or. &
(trim(calendar_in_c)) == 'no_calendar'.or. (trim(calendar_in_c)) == 'thirty_day_months' .or. &
(trim(calendar_in_c)) == 'gregorian'
if(.not.correct_form) then
call error_mesg('get_cal_time','"'//trim(calendar_in_c)//'"'// &
' is not an acceptable calendar attribute. acceptable calendars are: '// &
' noleap, 365_day, 360_day, julian, no_calendar, thirty_day_months, gregorian',FATAL)
endif
calendar_tm_i = get_calendar_type()
if(.not.permit_conversion_local) then
correct_form = (trim(calendar_in_c) == 'noleap' .and. calendar_tm_i == NOLEAP) .or. &
(trim(calendar_in_c) == '365_day' .and. calendar_tm_i == NOLEAP) .or. &
(trim(calendar_in_c) == '360_day' .and. calendar_tm_i == THIRTY_DAY_MONTHS) .or. &
(trim(calendar_in_c) == 'thirty_day_months' .and. calendar_tm_i == THIRTY_DAY_MONTHS) .or. &
(trim(calendar_in_c) == 'julian' .and. calendar_tm_i == JULIAN) .or. &
(trim(calendar_in_c) == 'no_calendar' .and. calendar_tm_i == NO_CALENDAR) .or. &
(trim(calendar_in_c) == 'gregorian' .and. calendar_tm_i == GREGORIAN)
if(.not.correct_form) then
call error_mesg('get_cal_time','calendar not consistent with calendar type in use by time_manager.'// &
' calendar='//trim(calendar_in_c)//'. Type in use by time_manager='//valid_calendar_types(calendar_tm_i),FATAL)
endif
endif
if (permit_conversion_local) then
select case (trim(calendar_in_c))
case ('noleap')
calendar_in_i = NOLEAP
case ('365_day')
calendar_in_i = NOLEAP
case ('360_day')
calendar_in_i = THIRTY_DAY_MONTHS
case ('thirty_day_months')
calendar_in_i = THIRTY_DAY_MONTHS
case ('julian')
calendar_in_i = JULIAN
case ('no_calendar')
calendar_in_i = NO_CALENDAR
case ('gregorian')
calendar_in_i = GREGORIAN
case default
call error_mesg('get_cal_time', &
trim(calendar_in_c)//' is an invalid calendar type (specified in call to get_cal_time)',FATAL)
end select
else
calendar_in_i = calendar_tm_i
end if
correct_form = lowercase(units(1:10)) == 'days since' .or. &
lowercase(units(1:11)) == 'hours since' .or. &
lowercase(units(1:13)) == 'minutes since' .or. &
lowercase(units(1:13)) == 'seconds since'
if(calendar_in_i /= NO_CALENDAR) then
correct_form = correct_form .or. &
lowercase(units(1:11)) == 'years since' .or. &
lowercase(units(1:12)) == 'months since'
endif
if(.not.correct_form) then
call error_mesg('get_cal_time',trim(units)//' is an invalid string for units.' // &
' units must begin with a time unit then the word "since"' // &
' Valid time units are: "seconds" "minutes", "hours", "days", and, ' // &
' except when NO_CALENDAR is in effect, "months" and "years"',FATAL)
endif
if(calendar_in_i /= calendar_tm_i) then
! switch to calendar type specified as input argument,
! will switch back before returning.
call set_calendar_type(calendar_in_i)
endif
! index(string, substring[,back])
! Returns the starting position of substring as a substring of string,
! or zero if it does not occur as a substring. Default value of back is
! .false. If back is .false., the starting position of the first such
! substring is returned. If back is .true., the starting position of the
! last such substring is returned.
! Returns zero if substring is not a substring of string (regardless of value of back)
i1 = index(units,'since') + 5
if(calendar_in_i == NO_CALENDAR) then
base_time = set_time(units(i1:len_trim(units)))
else
base_time = set_date(units(i1:len_trim(units)))
endif
if(lowercase(units(1:10)) == 'days since') then
increment_days = floor(time_increment)
increment_seconds = 86400*(time_increment - increment_days)
else if(lowercase(units(1:11)) == 'hours since') then
increment_days = floor(time_increment/24)
increment_seconds = 86400*(time_increment/24 - increment_days)
else if(lowercase(units(1:13)) == 'minutes since') then
increment_days = floor(time_increment/1440)
increment_seconds = 86400*(time_increment/1440 - increment_days)
else if(lowercase(units(1:13)) == 'seconds since') then
increment_days = floor(time_increment/86400)
increment_seconds = 86400*(time_increment/86400 - increment_days)
else if(lowercase(units(1:11)) == 'years since') then
! The time period between between (base_time + time_increment) and
! (base_time + time_increment + 1 year) may be 360, 365, or 366 days.
! This must be determined to handle time increments with year fractions.
call get_date(base_time, year,month,day,hour,minute,second)
base_time = set_date(year+floor(time_increment) ,month,day,hour,minute,second)
base_time_plus_one_yr = set_date(year+floor(time_increment)+1,month,day,hour,minute,second)
call get_time(base_time_plus_one_yr - base_time, second, day)
dt = (day*86400+second)*(time_increment-floor(time_increment))
increment_days = floor(dt/86400)
increment_seconds = dt - increment_days*86400
else if(lowercase(units(1:12)) == 'months since') then
month_fraction = time_increment - floor(time_increment)
increment_years = floor(time_increment/12)
increment_months = floor(time_increment) - 12*increment_years
call get_date(base_time, year,month,day,hour,minute,second)
base_time = set_date(year+increment_years,month+increment_months ,day,hour,minute,second)
dt = 86400*days_in_month(base_time) * month_fraction
increment_days = floor(dt/86400)
increment_seconds = dt - increment_days*86400
else
call error_mesg('get_cal_time','"'//trim(units)//'"'//' is not an acceptable units attribute of time.'// &
' It must begin with: "years since", "months since", "days since", "hours since", "minutes since", or "seconds since"',FATAL)
endif
if (calendar_in_i /= calendar_tm_i) then
if(calendar_in_i == NO_CALENDAR .or. calendar_tm_i == NO_CALENDAR) then
call error_mesg('get_cal_time','Cannot do calendar conversion because input calendar is '// &
trim(valid_calendar_types(calendar_in_i))//' and time_manager is using '//trim(valid_calendar_types(calendar_tm_i))// &
' Conversion cannot be done if either is NO_CALENDAR',FATAL)
endif
call get_date(base_time,year, month, day, hour, minute, second)
get_cal_time = set_date(year,month,day,hour,minute,second) + set_time(increment_seconds, increment_days)
call get_date(get_cal_time,year,month,day,hour,minute,second)
call set_calendar_type(calendar_tm_i)
get_cal_time = set_date(year,month,day,hour,minute,second, err_msg=err_msg)
if(err_msg /= '') then
call error_mesg('get_cal_time','Error in function get_cal_time: '//trim(err_msg)// &
' Note that the time_manager is using the '//trim(valid_calendar_types(calendar_tm_i))//' calendar '// &
'while the calendar type passed to function get_cal_time is '//calendar_in_c,FATAL)
endif
else
get_cal_time = base_time + set_time(increment_seconds, increment_days)
endif
end function get_cal_time
!
!------------------------------------------------------------------------
function cut0(string)
character(len=256) :: cut0
character(len=*), intent(in) :: string
integer :: i
cut0 = string
do i=1,len(string)
if(ichar(string(i:i)) == 0 ) then
cut0(i:i) = ' '
endif
enddo
return
end function cut0
!------------------------------------------------------------------------
end module get_cal_time_mod