FMS 2025.01.02-dev
Flexible Modeling System
Loading...
Searching...
No Matches
fms_diag_time_utils.F90
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!***********************************************************************
19!> @defgroup fms_diag_time_utils_mod fms_diag_time_utils_mod
20!> @ingroup diag_manager
21!! @brief fms_diag_time_utils contains functions and subroutines necessary for the
22!! <TT>diag_manager_mod</TT> related to time handling.
23!! @author Uriel Ramirez
24
25!> @addtogroup fms_diag_time_utils_mod
26!> @{
27module fms_diag_time_utils_mod
28
29use time_manager_mod, only: time_type, increment_date, increment_time, get_calendar_type, no_calendar, leap_year, &
30 get_date, get_time, operator(>), operator(<), operator(-), set_date, set_time
31use diag_data_mod, only: end_of_run, every_time, diag_seconds, diag_minutes, diag_hours, diag_days, diag_months, &
32 diag_years, use_clock_average
33USE constants_mod, ONLY: seconds_per_day, seconds_per_hour, seconds_per_minute
34use fms_mod, only: fms_error_handler
35use mpp_mod, only: mpp_error, fatal
36
37implicit none
38private
39
40public :: diag_time_inc
41public :: get_time_string
42public :: get_date_dif
43public :: set_time_type
44
45contains
46
47 !> @brief Return the next time data/file is to be written based on the frequency and units.
48 TYPE(time_type) FUNCTION diag_time_inc(time, output_freq, output_units, err_msg)
49 TYPE(time_type), INTENT(in) :: time !< Current model time.
50 INTEGER, INTENT(in) :: output_freq !< Output frequency number value.
51 INTEGER, INTENT(in) :: output_units !< Output frequency unit.
52 CHARACTER(len=*), INTENT(out), OPTIONAL :: err_msg !< Function error message.
53 !! An empty string indicates the next output
54 !! time was found successfully.
55
56 if (use_clock_average) then
57 diag_time_inc = diag_clock_time_inc(time, output_freq, output_units, err_msg)
58 else
59 diag_time_inc = diag_forecast_time_inc(time, output_freq, output_units, err_msg)
60 endif
61 end function diag_time_inc
62
63 !> @brief Determine the next time data/file is to be written based on the frequency and units using the clock.
64 !! For example, if doing daily averages and the input time is day1_hour3, the output time will be day2_hour0.
65 !! @return the next time data/file is to be written
66 TYPE(time_type) function diag_clock_time_inc(time, output_freq, output_units, err_msg)
67 TYPE(time_type), INTENT(in) :: time !< Current model time.
68 INTEGER, INTENT(in) :: output_freq !< Output frequency number value.
69 INTEGER, INTENT(in) :: output_units !< Output frequency unit.
70 CHARACTER(len=*), INTENT(out), OPTIONAL :: err_msg !< Function error message.
71 !! An empty string indicates the next output
72 !! time was found successfully.
73 CHARACTER(len=128) :: error_message_local !< Local variable to store the error_message
74 integer :: cyear !< The current year stored in the time type
75 integer :: cmonth !< The current month stored in the time type
76 integer :: cday !< The current day stored in the time type
77 integer :: chour !< The current hour stored in the time type
78 integer :: cmin !< The current minute stored in the time type
79 integer :: csecond !< The current second stored in the time type
80 type(time_type) :: my_time !< Time set at the begining of the <output_freq>
81
82 IF ( PRESENT(err_msg) ) err_msg = ''
83 error_message_local = ''
84
85 IF ( get_calendar_type() == no_calendar) then
86 error_message_local = 'If using use_clock_average =.TRUE., your calendar must be set.'
87 IF ( fms_error_handler('diag_clock_time_inc',error_message_local,err_msg) ) RETURN
88 endif
89
90 ! special values for output frequency are -1 for output at end of run
91 ! and 0 for every timestep. Need to check for these here?
92 ! Return zero time increment, hopefully this value is never used
93 IF ( output_freq == end_of_run .OR. output_freq == every_time ) THEN
95 RETURN
96 END IF
97
98 call get_date(time, cyear, cmonth, cday, chour, cmin, csecond)
99
100 select case (output_units)
101 case (diag_seconds)
102 my_time = set_date(cyear, cmonth, cday, chour, cmin, csecond) !< set my_time to the begining of the hour
103 diag_clock_time_inc = increment_date(my_time, 0, 0, 0, 0, 0, output_freq, err_msg=error_message_local)
104 case (diag_minutes)
105 my_time = set_date(cyear, cmonth, cday, chour, cmin, 0) !< set my_time to the begining of the hour
106 diag_clock_time_inc = increment_date(my_time, 0, 0, 0, 0, output_freq, 0, err_msg=error_message_local)
107 case (diag_hours)
108 my_time = set_date(cyear, cmonth, cday, chour, 0, 0) !< set my_time to the begining of the hour
109 diag_clock_time_inc = increment_date(my_time, 0, 0, 0, output_freq, 0, 0, err_msg=error_message_local)
110 case (diag_days)
111 my_time = set_date(cyear, cmonth, cday, 0, 0, 0) !< set my_time to the begining of the day
112 diag_clock_time_inc = increment_date(my_time, 0, 0, output_freq, 0, 0, 0, err_msg=error_message_local)
113 case (diag_months)
114 my_time = set_date(cyear, cmonth, 1, 0, 0, 0) !< set my_time to the begining of the month
115 diag_clock_time_inc = increment_date(my_time, 0, output_freq, 0, 0, 0, 0, err_msg=error_message_local)
116 case (diag_years)
117 my_time = set_date(cyear, 1, 1, 0, 0, 0) !< set my_time to the begining of the year
118 diag_clock_time_inc = increment_date(my_time, output_freq, 0, 0, 0, 0, 0, err_msg=error_message_local)
119 end select
120
121 end function diag_clock_time_inc
122
123 !> @brief Determine the next time data/file is to be written based on the frequency and units using forecast time.
124 !! For example, if doing daily averages and the input time is day1_hour3, the output time will be day2_hour3.
125 !! @return the next time data/file is to be written
126 TYPE(time_type) function diag_forecast_time_inc(time, output_freq, output_units, err_msg)
127 TYPE(time_type), INTENT(in) :: time !< Current model time.
128 INTEGER, INTENT(in) :: output_freq !< Output frequency number value.
129 INTEGER, INTENT(in) :: output_units !< Output frequency unit.
130 CHARACTER(len=*), INTENT(out), OPTIONAL :: err_msg !< Function error message.
131 !! An empty string indicates the next output
132 !! time was found successfully.
133
134 CHARACTER(len=128) :: error_message_local !< Local variable to store the error_message
135
136 integer :: cyear !< The current year stored in the time type
137 integer :: cmonth !< The current month stored in the time type
138 integer :: cday !< The current day stored in the time type
139 integer :: chour !< The current hour stored in the time type
140 integer :: cmin !< The current minute stored in the time type
141 integer :: csecond !< The current second stored in the time type
142
143 IF ( PRESENT(err_msg) ) err_msg = ''
144 error_message_local = ''
145
146 ! special values for output frequency are -1 for output at end of run
147 ! and 0 for every timestep. Need to check for these here?
148 ! Return zero time increment, hopefully this value is never used
149 IF ( output_freq == end_of_run .OR. output_freq == every_time ) THEN
151 RETURN
152 END IF
153
154 ! Make sure calendar was not set after initialization
155 IF ( output_units == diag_seconds ) THEN
156 IF ( get_calendar_type() == no_calendar ) THEN
157 diag_forecast_time_inc = increment_time(time, output_freq, 0, err_msg=error_message_local)
158 ELSE
159 diag_forecast_time_inc = increment_date(time, 0, 0, 0, 0, 0, output_freq, err_msg=error_message_local)
160 END IF
161 ELSE IF ( output_units == diag_minutes ) THEN
162 IF ( get_calendar_type() == no_calendar ) THEN
163 diag_forecast_time_inc = increment_time(time, nint(output_freq*seconds_per_minute), 0, &
164 &err_msg=error_message_local)
165 ELSE
166 diag_forecast_time_inc = increment_date(time, 0, 0, 0, 0, output_freq, 0, err_msg=error_message_local)
167 END IF
168 ELSE IF ( output_units == diag_hours ) THEN
169 IF ( get_calendar_type() == no_calendar ) THEN
170 diag_forecast_time_inc = increment_time(time, nint(output_freq*seconds_per_hour), 0, &
171 &err_msg=error_message_local)
172 ELSE
173 diag_forecast_time_inc = increment_date(time, 0, 0, 0, output_freq, 0, 0, err_msg=error_message_local)
174 END IF
175 ELSE IF ( output_units == diag_days ) THEN
176 IF (get_calendar_type() == no_calendar) THEN
177 diag_forecast_time_inc = increment_time(time, 0, output_freq, err_msg=error_message_local)
178 ELSE
179 diag_forecast_time_inc = increment_date(time, 0, 0, output_freq, 0, 0, 0, err_msg=error_message_local)
180 END IF
181 ELSE IF ( output_units == diag_months ) THEN
182 IF (get_calendar_type() == no_calendar) THEN
183 error_message_local = 'output units of months NOT allowed with no calendar'
184 ELSE
185 diag_forecast_time_inc = increment_date(time, 0, output_freq, 0, 0, 0, 0, err_msg=error_message_local)
186 END IF
187 ELSE IF ( output_units == diag_years ) THEN
188 IF ( get_calendar_type() == no_calendar ) THEN
189 error_message_local = 'output units of years NOT allowed with no calendar'
190 ELSE
191 call get_date(time, cyear, cmonth, cday, chour, cmin, csecond)
192 if (cmonth .eq. 2 .and. cday .eq. 29) then
193 !! TODO this is a hack, the leap year issue should be fixed inside increment_date instead
194 !! increment_date should also be updated to work in cases like when the frequency is 1 month and you
195 !! are starting from 1/31
196 ! This is a leap year, so increment the date from 2/28 instead
197 diag_forecast_time_inc = increment_date(set_date(cyear, cmonth, 28, chour, cmin, csecond), &
198 output_freq, 0, 0, 0, 0, 0, err_msg=error_message_local)
199 else
200 diag_forecast_time_inc = increment_date(time, output_freq, 0, 0, 0, 0, 0, err_msg=error_message_local)
201 endif
202 END IF
203 ELSE
204 error_message_local = 'illegal output units'
205 END IF
206
207 IF ( error_message_local /= '' ) THEN
208 IF ( fms_error_handler('diag_forecast_time_inc',error_message_local,err_msg) ) RETURN
209 END IF
210 END FUNCTION diag_forecast_time_inc
211
212 !> @brief This function determines a string based on current time.
213 !! This string is used as suffix in output file name
214 !! @return Character(len=128) get_time_string
215 CHARACTER(len=128) FUNCTION get_time_string(filename, current_time)
216 CHARACTER(len=*), INTENT(in) :: filename !< File name.
217 TYPE(time_type), INTENT(in) :: current_time !< Current model time.
218
219 INTEGER :: yr1 !< get from current time
220 INTEGER :: mo1 !< get from current time
221 INTEGER :: dy1 !< get from current time
222 INTEGER :: hr1 !< get from current time
223 INTEGER :: mi1 !< get from current time
224 INTEGER :: sc1 !< get from current time
225 INTEGER :: yr2 !< for computing next_level time unit
226 INTEGER :: dy2 !< for computing next_level time unit
227 INTEGER :: hr2 !< for computing next_level time unit
228 INTEGER :: mi2 !< for computing next_level time unit
229 INTEGER :: yr1_s !< actual values to write string
230 INTEGER :: mo1_s !< actual values to write string
231 INTEGER :: dy1_s !< actual values to write string
232 INTEGER :: hr1_s !< actual values to write string
233 INTEGER :: mi1_s !< actual values to write string
234 INTEGER :: sc1_s !< actual values to write string
235 INTEGER :: abs_day !< component of current_time
236 INTEGER :: abs_sec !< component of current_time
237 INTEGER :: days_per_month(12) = (/31,28,31,30,31,30,31,31,30,31,30,31/)
238 INTEGER :: julian_day, i, position, len, first_percent
239 CHARACTER(len=1) :: width !< width of the field in format write
240 CHARACTER(len=10) :: format
241 CHARACTER(len=20) :: yr !< string of current time (output)
242 CHARACTER(len=20) :: mo !< string of current time (output)
243 CHARACTER(len=20) :: dy !< string of current time (output)
244 CHARACTER(len=20) :: hr !< string of current time (output)
245 CHARACTER(len=20) :: mi !< string of current time (output)
246 CHARACTER(len=20) :: sc !< string of current time (output)
247 CHARACTER(len=128) :: filetail
248
249 format = '("_",i*.*)'
250 CALL get_date(current_time, yr1, mo1, dy1, hr1, mi1, sc1)
251 len = len_trim(filename)
252 first_percent = index(filename, '%')
253 filetail = filename(first_percent:len)
254 ! compute year string
255 position = index(filetail, 'yr')
256 IF ( position > 0 ) THEN
257 width = filetail(position-1:position-1)
258 yr1_s = yr1
259 format(7:9) = width//'.'//width
260 WRITE(yr, format) yr1_s
261 yr2 = 0
262 ELSE
263 yr = ' '
264 yr2 = yr1 - 1
265 END IF
266 ! compute month string
267 position = index(filetail, 'mo')
268 IF ( position > 0 ) THEN
269 width = filetail(position-1:position-1)
270 mo1_s = yr2*12 + mo1
271 format(7:9) = width//'.'//width
272 WRITE(mo, format) mo1_s
273 ELSE
274 mo = ' '
275 END IF
276 ! compute day string
277 IF ( len_trim(mo) > 0 ) THEN ! month present
278 dy1_s = dy1
279 dy2 = dy1_s - 1
280 ELSE IF ( len_trim(yr) >0 ) THEN ! no month, year present
281 ! compute julian day
282 IF ( mo1 == 1 ) THEN
283 dy1_s = dy1
284 ELSE
285 julian_day = 0
286 DO i = 1, mo1-1
287 julian_day = julian_day + days_per_month(i)
288 END DO
289 IF ( leap_year(current_time) .AND. mo1 > 2 ) julian_day = julian_day + 1
290 julian_day = julian_day + dy1
291 dy1_s = julian_day
292 END IF
293 dy2 = dy1_s - 1
294 ELSE ! no month, no year
295 CALL get_time(current_time, abs_sec, abs_day)
296 dy1_s = abs_day
297 dy2 = dy1_s
298 END IF
299 position = index(filetail, 'dy')
300 IF ( position > 0 ) THEN
301 width = filetail(position-1:position-1)
302 FORMAT(7:9) = width//'.'//width
303 WRITE(dy, format) dy1_s
304 ELSE
305 dy = ' '
306 END IF
307 ! compute hour string
308 IF ( len_trim(dy) > 0 ) THEN
309 hr1_s = hr1
310 ELSE
311 hr1_s = dy2*24 + hr1
312 END IF
313 hr2 = hr1_s
314 position = index(filetail, 'hr')
315 IF ( position > 0 ) THEN
316 width = filetail(position-1:position-1)
317 format(7:9) = width//'.'//width
318 WRITE(hr, format) hr1_s
319 ELSE
320 hr = ' '
321 END IF
322 ! compute minute string
323 IF ( len_trim(hr) > 0 ) THEN
324 mi1_s = mi1
325 ELSE
326 mi1_s = hr2*60 + mi1
327 END IF
328 mi2 = mi1_s
329 position = index(filetail, 'mi')
330 IF(position>0) THEN
331 width = filetail(position-1:position-1)
332 format(7:9) = width//'.'//width
333 WRITE(mi, format) mi1_s
334 ELSE
335 mi = ' '
336 END IF
337 ! compute second string
338 IF ( len_trim(mi) > 0 ) THEN
339 sc1_s = sc1
340 ELSE
341 sc1_s = nint(mi2*seconds_per_minute) + sc1
342 END IF
343 position = index(filetail, 'sc')
344 IF ( position > 0 ) THEN
345 width = filetail(position-1:position-1)
346 format(7:9) = width//'.'//width
347 WRITE(sc, format) sc1_s
348 ELSE
349 sc = ' '
350 ENDIF
351 get_time_string = trim(yr)//trim(mo)//trim(dy)//trim(hr)//trim(mi)//trim(sc)
352 END FUNCTION get_time_string
353
354 !> @brief Return the difference between two times in units.
355 !! @return Real get_data_dif
356 REAL function get_date_dif(t2, t1, units)
357 TYPE(time_type), INTENT(in) :: t2 !< Most recent time.
358 TYPE(time_type), INTENT(in) :: t1 !< Most distant time.
359 INTEGER, INTENT(in) :: units !< Unit of return value.
360
361 INTEGER :: dif_seconds, dif_days
362 TYPE(time_type) :: dif_time
363
364 IF ( t2 < t1 ) CALL mpp_error(fatal, 'diag_util_mod::get_date_dif '//&
365 &'in variable t2 is less than in variable t1')
366
367 dif_time = t2 - t1
368
369 CALL get_time(dif_time, dif_seconds, dif_days)
370
371 IF ( units == diag_seconds ) THEN
372 get_date_dif = dif_seconds + seconds_per_day * dif_days
373 ELSE IF ( units == diag_minutes ) THEN
374 get_date_dif = 1440 * dif_days + dif_seconds / seconds_per_minute
375 ELSE IF ( units == diag_hours ) THEN
376 get_date_dif = 24 * dif_days + dif_seconds / seconds_per_hour
377 ELSE IF ( units == diag_days ) THEN
378 get_date_dif = dif_days + dif_seconds / seconds_per_day
379 ELSE IF ( units == diag_months ) THEN
380 CALL mpp_error(fatal, 'diag_util_mod::get_date_dif months not supported as output units')
381 ELSE IF ( units == diag_years ) THEN
382 CALL mpp_error(fatal, 'diag_util_mod::get_date_dif years not supported as output units')
383 ELSE
384 CALL mpp_error(fatal, 'diag_util_mod::diag_date_dif illegal time units')
385 END IF
386 END FUNCTION get_date_dif
387
388!> @brief Sets up a time_type based on 6 member array of integers defining the
389!! [year month day hour min sec]
390subroutine set_time_type(time_int, time)
391 integer, intent(in) :: time_int(6) !< The time in the format [year month day hour min second]
392 type(time_type), intent(inout) :: time !< The time converted to the time_type
393
394 integer :: year !< Year of the time type
395 integer :: month !< Month of the time type
396 integer :: day !< Day of the time type
397 integer :: hour !< Hour of the time type
398 integer :: minute !< Minute of the time type
399 integer :: second !< Second of the time type
400
401 year = time_int(1)
402 month = time_int(2)
403 day = time_int(3)
404 hour = time_int(4)
405 minute = time_int(5)
406 second = time_int(6)
407
408 ! Set up the time type for time passed in
409 IF ( get_calendar_type() /= no_calendar ) THEN
410 IF ( year==0 .OR. month==0 .OR. day==0 ) THEN
411 call mpp_error(fatal, 'fms_diag_time_utils_mod::set_time_type'//&
412 & 'The year/month/day can not equal zero')
413 END IF
414 time = set_date(year, month, day, hour, minute, second)
415 ELSE
416 ! No calendar - ignore year and month
417 time = set_time(nint(hour*seconds_per_hour)+nint(minute*seconds_per_minute)+second, &
418 & day)
419 END IF
420end subroutine set_time_type
421end module fms_diag_time_utils_mod
logical use_clock_average
.TRUE. if the averaging of variable is done based on the clock For example, if doing daily averages a...
character(len=128) function, public get_time_string(filename, current_time)
This function determines a string based on current time. This string is used as suffix in output file...
real function, public get_date_dif(t2, t1, units)
Return the difference between two times in units.
subroutine, public set_time_type(time_int, time)
Sets up a time_type based on 6 member array of integers defining the [year month day hour min sec].
type(time_type) function diag_clock_time_inc(time, output_freq, output_units, err_msg)
Determine the next time data/file is to be written based on the frequency and units using the clock....
type(time_type) function diag_forecast_time_inc(time, output_freq, output_units, err_msg)
Determine the next time data/file is to be written based on the frequency and units using forecast ti...
Error handler.
Definition mpp.F90:382
integer function, public get_calendar_type()
Returns default calendar type for mapping from time to date.
type(time_type) function, public increment_time(time, seconds, days, ticks, err_msg, allow_neg_inc)
Increments a time by seconds and days.
subroutine, public get_time(time, seconds, days, ticks, err_msg)
Returns days and seconds ( < 86400 ) corresponding to a time. err_msg should be checked for any error...
logical function, public leap_year(time, err_msg)
Returns true if the year corresponding to the input time is a leap year (for default calendar)....
subroutine, public get_date(time, year, month, day, hour, minute, second, tick, err_msg)
Gets the date for different calendar types. Given a time_interval, returns the corresponding date und...
Given an input date in year, month, days, etc., creates a time_type that represents this time interva...
Given some number of seconds and days, returns the corresponding time_type.
Type to represent amounts of time. Implemented as seconds and days to allow for larger intervals.