FV3 Bundle
get_cal_time.F90
Go to the documentation of this file.
1 !***********************************************************************
2 !* GNU Lesser General Public License
3 !*
4 !* This file is part of the GFDL Flexible Modeling System (FMS).
5 !*
6 !* FMS is free software: you can redistribute it and/or modify it under
7 !* the terms of the GNU Lesser General Public License as published by
8 !* the Free Software Foundation, either version 3 of the License, or (at
9 !* your option) any later version.
10 !*
11 !* FMS is distributed in the hope that it will be useful, but WITHOUT
12 !* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 !* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 !* for more details.
15 !*
16 !* You should have received a copy of the GNU Lesser General Public
17 !* License along with FMS. If not, see <http://www.gnu.org/licenses/>.
18 !***********************************************************************
20 
21 ! <CONTACT EMAIL="fms@gfdl.noaa.gov">
22 ! fms
23 ! </CONTACT>
24 ! <OVERVIEW>
25 ! Given a time increment as a real number, and base time and calendar
26 ! as a character strings, returns time as a time_type variable.
27 ! </OVERVIEW>
28 
29 use fms_mod, only: error_mesg, fatal, write_version_number, lowercase, &
30  open_namelist_file, check_nml_error, stdlog, close_file, &
31  mpp_pe, mpp_root_pe
32 
33 use time_manager_mod, only: time_type, operator(+), operator(-), set_time, get_time, &
37 use mpp_mod, only: input_nml_file
38 
39 implicit none
40 private
41 
42 public :: get_cal_time
43 
44 logical :: module_is_initialized=.false. ! This module is initialized on
45  ! the first call to get_cal_time
46  ! because there is no constructor.
47 ! <NAMELIST NAME="get_cal_time_nml">
48 ! <DATA NAME="allow_calendar_conversion" TYPE="logical" DEFAULT=".true.">
49 ! This sets the default value of the optional argument named "permit_calendar_conversion" of get_cal_time.
50 ! This namelist is deprecated as of the memphis release.
51 ! If calendar conversion is not desired, then it is recommended that permit_calendar_conversion
52 ! be present in the call to get_cal_time and that it be set to .false.
53 ! </DATA>
54 
55 logical :: allow_calendar_conversion=.true.
56 
57 namelist / get_cal_time_nml / allow_calendar_conversion
58 ! </NAMELIST>
59 
60 ! Include variable "version" to be written to log file.
61 #include<file_version.h>
62 
63 contains
64 !------------------------------------------------------------------------
65 ! <FUNCTION NAME="get_cal_time">
66 ! <TEMPLATE>
67 ! get_cal_time(time_increment, units, calendar, permit_calendar_conversion)
68 ! </TEMPLATE>
69 ! <IN NAME="time_increment" TYPE="real"> A time interval.</IN>
70 ! <IN NAME="units" TYPE="character">
71 !
72 ! Examples of acceptable values of units:
73 !
74 ! 'days since 1980-01-01 00:00:00',
75 ! 'hours since 1980-1-1 0:0:0',
76 ! 'minutes since 0001-4-12'
77 !
78 ! The first word in the string must be
79 ! 'years', 'months', 'days', 'hours', 'minutes' or 'seconds'.
80 ! The second word must be 'since'
81 !
82 ! year number must occupy 4 spaces.
83 ! Number of months, days, hours, minutes, seconds may occupy 1 or 2 spaces
84 ! year, month and day must be separated by a '-'
85 ! hour, minute, second must be separated by a ':'
86 ! hour, minute, second are optional. If not present then zero is assumed.
87 !
88 ! Because months are not equal increments of time, and, for julian calendar,
89 ! neither are years, the 'years since' and 'month since' cases deserve
90 ! further explaination.
91 !
92 ! When 'years since' is used:
93 ! The year number is increased by floor(time_increment) to obtain a time T1.
94 ! The year number is increased by floor(time_increment)+1 to obtain a time T2.
95 ! The time returned is T1 + (time_increment-floor(time_increment))*(T2-T1).
96 !
97 ! When 'months since' is used:
98 ! The month number is increased by floor(time_increment). If it falls outside
99 ! to range 1 to 12 then it is adjusted along with the year number to convert
100 ! to a valid date. The number of days in the month of this date is used to
101 ! compute the time interval of the fraction.
102 ! That is:
103 ! The month number is increased by floor(time_increment) to obtain a time T1.
104 ! delt = the number of days in the month in which T1 falls.
105 ! The time returned is T1 + ((time_increment-floor(time_increment))*delt.
106 ! Two of the consequences of this scheme should be kept in mind.
107 ! -- The time since should not be from the 29'th to 31'st of a month,
108 ! since an invalid date is likely to result, triggering an error stop.
109 ! -- When time since is from the begining of a month, the fraction of a month
110 ! will never advance into the month after that which results from only
111 ! the whole number.
112 !
113 ! When NO_CALENDAR is in effect, units attribute must specify a starting
114 ! day and second, with day number appearing first
115 !
116 ! Example: 'days since 100 0' Indicates 100 days 0 seconds
117 ! </IN>
118 !
119 ! <IN NAME="calendar" TYPE="character">
120 ! Acceptable values of calendar are:
121 ! 'noleap'
122 ! '365_day'
123 ! '360_day'
124 ! 'julian'
125 ! 'thirty_day_months'
126 ! 'no_calendar'
127 ! </IN>
128 !
129 ! <IN NAME="permit_calendar_conversion" TYPE="logical, optional" DEFAULT="allow_calendar_conversion">
130 ! It is sometimes desirable to allow the value of the intent(in) argument
131 ! "calendar" to be different than the calendar in use by time_manager_mod.
132 ! If this is not desirable, then the optional variable "permit_calendar_conversion"
133 ! should be set to .false. so as to allow an error check.
134 ! When calendar conversion is done, the time returned is the time in the
135 ! time_manager's calendar, but corresponds to the date computed using the input calendar.
136 ! For example, suppose the time_manager is using the julian calendar and
137 ! the values of the input arguments of get_cal_time are:
138 ! time_increment = 59.0
139 ! units = 'days since 1980-1-1 00:00:00'
140 ! calendar = 'noleap'
141 ! Because it will use the noleap calendar to calculate the date, get_cal_time will return
142 ! value of time for midnight March 1 1980, but it will be time in the julian calendar
143 ! rather than the noleap calendar. It will never return a value of time corresponding
144 ! to anytime during the day Feb 29.
145 !
146 ! Another example:
147 ! Suppose the time_manager is using either the noleap or julian calendars,
148 ! and the values of the input arguments are:
149 ! time_increment = 30.0
150 ! units = 'days since 1980-1-1'
151 ! calendar = 'thirty_day_months'
152 ! In this case get_cal_time will return the value of time for Feb 1 1980 00:00:00,
153 ! but in the time_manager's calendar.
154 
155 ! Calendar conversion may result in a fatal error when the input calendar type is
156 ! a calendar that has more days per year than that of the time_manager's calendar.
157 ! For example, if the input calendar type is julian and the time_manager's calendar
158 ! is thirty_day_months, then get_cal_time will try to convert Jan 31 to a time in
159 ! the thirty_day_months calendar, resulting in a fatal error.
160 
161 ! Note: this option was originally coded to allow noleap calendar as input when
162 ! the julian calendar was in effect by the time_manager.
163 ! </IN>
164 !
165 !---------------------------------------------------------------------------------------------
166 
167 function get_cal_time(time_increment, units, calendar, permit_calendar_conversion)
168 real, intent(in) :: time_increment
169 character(len=*), intent(in) :: units
170 character(len=*), intent(in) :: calendar
171 logical, intent(in), optional :: permit_calendar_conversion
172 type(time_type) :: get_cal_time
173 integer :: year, month, day, hour, minute, second
174 integer :: i1, i2, i3, i4, i5, i6, increment_seconds, increment_days, increment_years, increment_months
175 real :: month_fraction
176 integer :: calendar_tm_i, calendar_in_i, namelist_unit, ierr, io, logunit
177 logical :: correct_form
178 character(len=32) :: calendar_in_c
179 character(len=64) :: err_msg
180 character(len=4) :: formt='(i )'
181 type(time_type) :: base_time, base_time_plus_one_yr, base_time_plus_one_mo
182 real :: dt
183 logical :: permit_conversion_local
184 
185 if(.not.module_is_initialized) then
186 #ifdef INTERNAL_FILE_NML
187  read (input_nml_file, get_cal_time_nml, iostat=io)
188  ierr = check_nml_error(io, 'get_cal_time_nml')
189 #else
190  namelist_unit = open_namelist_file()
191  ierr=1
192  do while (ierr /= 0)
193  read(namelist_unit, nml=get_cal_time_nml, iostat=io, end=20)
194  ierr = check_nml_error(io, 'get_cal_time_nml')
195  enddo
196  20 call close_file (namelist_unit)
197 #endif
198 
199  call write_version_number("GET_CAL_TIME_MOD", version)
200  logunit = stdlog()
201  if(mpp_pe() == mpp_root_pe()) write (logunit, nml=get_cal_time_nml)
202  module_is_initialized = .true.
203 endif
204 
205 if(present(permit_calendar_conversion)) then
206  permit_conversion_local = permit_calendar_conversion
207 else
208  permit_conversion_local = allow_calendar_conversion
209 endif
210 
211 calendar_in_c = lowercase(trim(cut0(calendar)))
212 
213 correct_form = (trim(calendar_in_c)) == 'noleap' .or. (trim(calendar_in_c)) == '365_day' .or. &
214  (trim(calendar_in_c)) == '360_day' .or. (trim(calendar_in_c)) == 'julian' .or. &
215  (trim(calendar_in_c)) == 'no_calendar'.or. (trim(calendar_in_c)) == 'thirty_day_months' .or. &
216  (trim(calendar_in_c)) == 'gregorian'
217 
218 if(.not.correct_form) then
219  call error_mesg('get_cal_time','"'//trim(calendar_in_c)//'"'// &
220  ' is not an acceptable calendar attribute. acceptable calendars are: '// &
221  ' noleap, 365_day, 360_day, julian, no_calendar, thirty_day_months, gregorian',fatal)
222 endif
223 
224 calendar_tm_i = get_calendar_type()
225 
226 if(.not.permit_conversion_local) then
227  correct_form = (trim(calendar_in_c) == 'noleap' .and. calendar_tm_i == noleap) .or. &
228  (trim(calendar_in_c) == '365_day' .and. calendar_tm_i == noleap) .or. &
229  (trim(calendar_in_c) == '360_day' .and. calendar_tm_i == thirty_day_months) .or. &
230  (trim(calendar_in_c) == 'thirty_day_months' .and. calendar_tm_i == thirty_day_months) .or. &
231  (trim(calendar_in_c) == 'julian' .and. calendar_tm_i == julian) .or. &
232  (trim(calendar_in_c) == 'no_calendar' .and. calendar_tm_i == no_calendar) .or. &
233  (trim(calendar_in_c) == 'gregorian' .and. calendar_tm_i == gregorian)
234  if(.not.correct_form) then
235  call error_mesg('get_cal_time','calendar not consistent with calendar type in use by time_manager.'// &
236  ' calendar='//trim(calendar_in_c)//'. Type in use by time_manager='//valid_calendar_types(calendar_tm_i),fatal)
237  endif
238 endif
239 
240 if (permit_conversion_local) then
241  select case (trim(calendar_in_c))
242  case ('noleap')
243  calendar_in_i = noleap
244  case ('365_day')
245  calendar_in_i = noleap
246  case ('360_day')
247  calendar_in_i = thirty_day_months
248  case ('thirty_day_months')
249  calendar_in_i = thirty_day_months
250  case ('julian')
251  calendar_in_i = julian
252  case ('no_calendar')
253  calendar_in_i = no_calendar
254  case ('gregorian')
255  calendar_in_i = gregorian
256  case default
257  call error_mesg('get_cal_time', &
258  trim(calendar_in_c)//' is an invalid calendar type (specified in call to get_cal_time)',fatal)
259  end select
260 else
261  calendar_in_i = calendar_tm_i
262 end if
263 
264 correct_form = lowercase(units(1:10)) == 'days since' .or. &
265  lowercase(units(1:11)) == 'hours since' .or. &
266  lowercase(units(1:13)) == 'minutes since' .or. &
267  lowercase(units(1:13)) == 'seconds since'
268 
269 if(calendar_in_i /= no_calendar) then
270  correct_form = correct_form .or. &
271  lowercase(units(1:11)) == 'years since' .or. &
272  lowercase(units(1:12)) == 'months since'
273 endif
274 
275 if(.not.correct_form) then
276  call error_mesg('get_cal_time',trim(units)//' is an invalid string for units.' // &
277  ' units must begin with a time unit then the word "since"' // &
278  ' Valid time units are: "seconds" "minutes", "hours", "days", and, ' // &
279  ' except when NO_CALENDAR is in effect, "months" and "years"',fatal)
280 endif
281 
282 if(calendar_in_i /= calendar_tm_i) then
283 ! switch to calendar type specified as input argument,
284 ! will switch back before returning.
285  call set_calendar_type(calendar_in_i)
286 endif
287 
288 ! index(string, substring[,back])
289 ! Returns the starting position of substring as a substring of string,
290 ! or zero if it does not occur as a substring. Default value of back is
291 ! .false. If back is .false., the starting position of the first such
292 ! substring is returned. If back is .true., the starting position of the
293 ! last such substring is returned.
294 ! Returns zero if substring is not a substring of string (regardless of value of back)
295 
296 i1 = index(units,'since') + 5
297 if(calendar_in_i == no_calendar) then
298  base_time = set_time(units(i1:len_trim(units)))
299 else
300  base_time = set_date(units(i1:len_trim(units)))
301 endif
302 
303 if(lowercase(units(1:10)) == 'days since') then
304  increment_days = floor(time_increment)
305  increment_seconds = 86400*(time_increment - increment_days)
306 else if(lowercase(units(1:11)) == 'hours since') then
307  increment_days = floor(time_increment/24)
308  increment_seconds = 86400*(time_increment/24 - increment_days)
309 else if(lowercase(units(1:13)) == 'minutes since') then
310  increment_days = floor(time_increment/1440)
311  increment_seconds = 86400*(time_increment/1440 - increment_days)
312 else if(lowercase(units(1:13)) == 'seconds since') then
313  increment_days = floor(time_increment/86400)
314  increment_seconds = 86400*(time_increment/86400 - increment_days)
315 else if(lowercase(units(1:11)) == 'years since') then
316 ! The time period between between (base_time + time_increment) and
317 ! (base_time + time_increment + 1 year) may be 360, 365, or 366 days.
318 ! This must be determined to handle time increments with year fractions.
319  call get_date(base_time, year,month,day,hour,minute,second)
320  base_time = set_date(year+floor(time_increment) ,month,day,hour,minute,second)
321  base_time_plus_one_yr = set_date(year+floor(time_increment)+1,month,day,hour,minute,second)
322  call get_time(base_time_plus_one_yr - base_time, second, day)
323  dt = (day*86400+second)*(time_increment-floor(time_increment))
324  increment_days = floor(dt/86400)
325  increment_seconds = dt - increment_days*86400
326 else if(lowercase(units(1:12)) == 'months since') then
327  month_fraction = time_increment - floor(time_increment)
328  increment_years = floor(time_increment/12)
329  increment_months = floor(time_increment) - 12*increment_years
330  call get_date(base_time, year,month,day,hour,minute,second)
331  base_time = set_date(year+increment_years,month+increment_months ,day,hour,minute,second)
332  dt = 86400*days_in_month(base_time) * month_fraction
333  increment_days = floor(dt/86400)
334  increment_seconds = dt - increment_days*86400
335 else
336  call error_mesg('get_cal_time','"'//trim(units)//'"'//' is not an acceptable units attribute of time.'// &
337  ' It must begin with: "years since", "months since", "days since", "hours since", "minutes since", or "seconds since"',fatal)
338 endif
339 
340 if (calendar_in_i /= calendar_tm_i) then
341  if(calendar_in_i == no_calendar .or. calendar_tm_i == no_calendar) then
342  call error_mesg('get_cal_time','Cannot do calendar conversion because input calendar is '// &
343  trim(valid_calendar_types(calendar_in_i))//' and time_manager is using '//trim(valid_calendar_types(calendar_tm_i))// &
344  ' Conversion cannot be done if either is NO_CALENDAR',fatal)
345  endif
346  call get_date(base_time,year, month, day, hour, minute, second)
347  get_cal_time = set_date(year,month,day,hour,minute,second) + set_time(increment_seconds, increment_days)
348  call get_date(get_cal_time,year,month,day,hour,minute,second)
349  call set_calendar_type(calendar_tm_i)
350  get_cal_time = set_date(year,month,day,hour,minute,second, err_msg=err_msg)
351  if(err_msg /= '') then
352  call error_mesg('get_cal_time','Error in function get_cal_time: '//trim(err_msg)// &
353  ' Note that the time_manager is using the '//trim(valid_calendar_types(calendar_tm_i))//' calendar '// &
354  'while the calendar type passed to function get_cal_time is '//calendar_in_c,fatal)
355  endif
356 else
357  get_cal_time = base_time + set_time(increment_seconds, increment_days)
358 endif
359 
360 end function get_cal_time
361 ! </FUNCTION>
362 !------------------------------------------------------------------------
363 function cut0(string)
364 character(len=256) :: cut0
365 character(len=*), intent(in) :: string
366 integer :: i
367 
368 cut0 = string
369 
370 do i=1,len(string)
371  if(ichar(string(i:i)) == 0 ) then
372  cut0(i:i) = ' '
373  endif
374 enddo
375 
376 return
377 end function cut0
378 !------------------------------------------------------------------------
379 end module get_cal_time_mod
Definition: fms.F90:20
subroutine, public get_date(time, year, month, day, hour, minute, second, tick, err_msg)
integer, parameter, public gregorian
integer, parameter, public noleap
logical module_is_initialized
character(len=256) function cut0(string)
type(time_type) function, public get_cal_time(time_increment, units, calendar, permit_calendar_conversion)
Definition: mpp.F90:39
integer function, public check_nml_error(IOSTAT, NML_NAME)
Definition: fms.F90:658
character(len=input_str_length), dimension(:), allocatable, target, public input_nml_file
Definition: mpp.F90:1378
subroutine, public set_calendar_type(type, err_msg)
character(len=24) function, public valid_calendar_types(ncal, err_msg)
integer function, public days_in_month(Time, err_msg)
integer, parameter, public julian
integer function, public get_calendar_type()
integer, parameter, public thirty_day_months
integer, parameter, public no_calendar
logical allow_calendar_conversion
subroutine, public get_time(Time, seconds, days, ticks, err_msg)
subroutine, public error_mesg(routine, message, level)
Definition: fms.F90:529