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