FMS  2025.02
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, set_time
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 public :: set_time_type
44 
45 contains
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
94  diag_clock_time_inc = time
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]
390 subroutine 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
420 end subroutine set_time_type
421 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...
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].
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...
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.