program monthly_means_one_station
  !
  ! Calculation of monthly mean values of one variable for one station
  !
  ! The monthly means are calculated from daily data for each UTC hour in
  ! (hour1, hour1+hour_step, ..., hour2) separately
  !
  ! If the number of observations is less than min_n_obs, the monthly means are 
  ! set as missing
  !
  !------------------------------------------------
  ! Jouni Räisänen, July 2023
  !--------------------------------------------------
  !
  ! INPUT FILE: infile
  ! 
  !    This is a text file produced by an odb sql select command
  !
  !    The first line is a header. The remaining lines contain
  !
  !    1. station code
  !    2. year
  !    3. month
  !    4. day
  !    5. hour
  !    6. variable code
  !    7. data value
  !
  ! OUTPUT FILE: outfile
  ! 
  !    This is a text file that can be converted to .odb   
  !
  !    The first line is a header. The remaining lines contain
  !
  !    1. station code
  !    2. longitude
  !    3. latitude
  !    4. year
  !    5. month
  !    6. hour
  !    7. variable code
  !    8. number of observations
  !    9. data value
!---------------------------------------------------------
  !
  !    Notes on the output file:
  !
  !    The longitude and latitude are given as namelist values
  ! 
  !    station code may be either a 3-character string or an integer
  !    (depending on the namelist parameter l_code_in_char
  !
!-------------------------------------------------------------
  !
  !  namelist parameters:
  !
  !   
  ! infile = input file (see above) 
  ! outfile = output file (see above)
  ! longitude = station longitude
  ! latitude = station latitude
  ! hour1 = first UTC hour for which monthly means are calculated
  ! hour2 = last UTC hour ---------------------------------------
  ! hour_step = interval of UTC hours ---------------------------
  ! l_round_hour : if .true., hours are rounded to the nearest
  !                output UTC hour (otherwise, non-match hours are ignored)
  ! l_code_in_char : if .true., the station code is read and write as a 3-char string
  !                  Otherwise, it is assumed to be an integer.   
  ! min_n_obs = minimum number of observations required for calculating a monthly mean
  !             (detault: 10)
  ! miss = missing value code (default: -9.99e6). All observations f with 
  !                                               abs(f) >= abs(miss) are treated as missing
  !
!----------------------------------------------------------
  
  implicit none
  character*160 :: infile,outfile
  character*200 :: headerline,dataline
  integer,parameter :: nhour=24  ! number of UTC hours per day
  integer,parameter :: ndays_max=31 ! maximum number of days per month
  real :: f(nhour,ndays_max),fmean(nhour) ! individual observation and monthly means
  integer :: nobs(nhour) ! number of observations per day
  real :: miss ! missing value code
  integer :: variable ! variable code
  integer :: yyear,mmonth,dday,hhour  ! time as read from infile
  integer :: year,month,day,hour ! time after eventual rounding of hours
  integer :: hour1,hour2,hour_step ! UTC hours used
  logical :: l_round_hour ! rounding of hours to nearest output hour?
  integer :: station_int ! station code as integer (synop stations & RHARM)
  character*3 :: station_char ! station code as string (GRUAN soundings)
  logical :: l_code_in_char ! choice of the type of station code
  real :: longitude,latitude ! station coordinates (given in namelist)
  integer :: prev_year,prev_month
  real :: f1 ! individual data value read from file
  integer :: i
  integer :: min_n_obs ! minimum number of observations required per month
  
  namelist/param/infile,outfile,longitude,latitude,&
          hour1,hour2,hour_step,l_round_hour,l_code_in_char,min_n_obs,miss
  min_n_obs=10
  miss=-9.99e6
  read (*,nml=param)
  !
  ! Open the input and output files
  !
  open(unit=1,file=trim(infile),status='old',form='formatted')
  open(unit=2,file=trim(outfile),form='formatted')
  !
  ! Write the header to the output file
  !
  if(l_code_in_char)then
   headerline='station@hdr:string longitude@hdr:real latitude@hdr:real year@hdr:integer'//&
              ' month@hdr:integer hour@hdr:integer variable@hdr:integer n_data@body:integer'//&
              ' value@body:real'
else
   headerline='station@hdr:integer longitude@hdr:real latitude@hdr:real year@hdr:integer'//&
              ' month@hdr:integer hour@hdr:integer variable@hdr:integer n_data@body:integer'//&
              ' value@body:real'
endif

   write(2,*)trim(headerline)  
!
!-------------------------------------------------------------------------------------------- 
!   
  nobs=0
  fmean=0
  prev_year=0
  prev_month=0
  read(1,*)    ! The first line in the input file is a header
!
! Reading the input file + writing the monthly means when a new month starts  
!  
  do while (.true.)
  if(l_code_in_char)then   
1    read(1,*,err=1,end=3)station_char,yyear,mmonth,dday,hhour,variable,f1
  else
2    read(1,*,err=2,end=3)station_int,yyear,mmonth,dday,hhour,variable,f1
  endif
!
! Rounding of hours?
!  
  if(l_round_hour)then
            call round_hour(yyear,mmonth,dday,hhour,year,month,day,hour,&
                 hour1,hour2,hour_step,nhour)                 
  else
     year=yyear
     month=mmonth
     day=dday
     hour=hhour
  endif
!
! Calculation of monthly means when a new month starts  
!  
     if((month.gt.prev_month).or.(year.gt.prev_year))then
        if(prev_month.gt.0)then
           do i=hour1,hour2,hour_step
              if(nobs(i+1).gt.min_n_obs)then
                 fmean(i+1)=fmean(i+1)/nobs(i+1)
              else
                 fmean(i+1)=miss
              endif
              if(l_code_in_char)then
              write(2,'(A3,2F16.6,1X,I4,1X,I2,1X,I2,1X,I3,1X,I4,F16.6)')&
                   station_char,longitude,latitude,prev_year,prev_month,i,variable,nobs(i+1),fmean(i+1)
           else
              write(2,'(I6,2F16.6,1X,I4,1X,I2,1X,I2,1X,I3,1X,I4,F16.6)')&
                   station_int,longitude,latitude,prev_year,prev_month,i,variable,nobs(i+1),fmean(i+1)
           endif           
           enddo
        endif        
        prev_year=year
        prev_month=month
        nobs=0
        fmean=0.        
     endif
!
! Updating the counters for sum and number of observations
!     
     if(f1.gt.miss)then
       nobs(hour+1)=nobs(hour+1)+1
       fmean(hour+1)=fmean(hour+1)+f1
     endif
  enddo
  
3 continue  ! The program ends up here when the whole file has been read
  !
  ! Write the data for the last month
  !
  do i=hour1,hour2,hour_step
     if(nobs(i+1).gt.0)then
       fmean(i+1)=fmean(i+1)/nobs(i+1)
     else
       fmean(i+1)=miss
    endif
    if(l_code_in_char)then
     write(2,'(A3,2F16.6,1X,I4,1X,I2,1X,I2,1X,I3,1X,I4,F16.6)')&
          station_char,longitude,latitude,prev_year,prev_month,i,variable,nobs(i+1),fmean(i+1)
     else
     write(2,'(I6,2F16.6,1X,I4,1X,I2,1X,I2,1X,I3,1X,I4,F16.6)')&
          station_int,longitude,latitude,prev_year,prev_month,i,variable,nobs(i+1),fmean(i+1)
     endif
  enddo
  close (1)
  close (2)
  
  end program monthly_means_one_station

!-------------------------------------------------------------------!
!        
subroutine round_hour(yyear,mmonth,dday,hhour,year,month,day,hour,&
     hour1,hour2,hour_step,nhour)
!
! Rounding of time to the nearest hour in (hour1,hour1+hour_step,...,hour2)
!  
! time in input: yyear,mmonth,dday,hhour
! time in output: year,month,day
  
implicit none
integer :: year,month,day,hour
integer :: yyear,mmonth,dday,hhour
integer :: hour1,hour2,hour_step
integer :: ndays_month(12)
integer :: hour_index
integer :: nhour
ndays_month=31
ndays_month(4)=30
ndays_month(6)=30
ndays_month(9)=30
ndays_month(11)=30
ndays_month(2)=28
if((mod(yyear,4).eq.0.and.mod(yyear,100).gt.0).or.(mod(yyear,400).eq.0))then
  ndays_month(2)=29
endif
year=yyear
month=mmonth
day=dday
!
! Round the hour to the nearest output hour
!
hour_index=nint(real(hhour-hour1)/hour_step)
hour=hour1+hour_step*hour_index
   if(hour.ge.nhour)then ! hhour was rounded forward to next day
      hour=hour1
      day=dday+1
      if(day.gt.ndays_month(month))then
         day=1
         month=month+1
         if(month.gt.12)then
            month=1
            year=yyear+1
         endif
      endif
   endif
   if(hour.lt.0)then ! hhour was rounded backward to previous day
   hour=hour2
   day=dday-1
     if(day.eq.0)then
        month=month-1
        if(month.eq.0)then
           month=12
           year=yyear-1
        endif                  
        day=ndays_month(month)
     endif
  endif
   
return
end subroutine round_hour
