0

I have a fortran 90 function that is meant to parse a time stamp in the form day as %Y%m%d.%f where %f is fraction of a day /48 and return an array with year, month, day, fraction of day.

function parseTimeStamp (timeStamp)

    implicit none

    real, dimension(5) :: parseTimeStamp
    real, intent(in)   :: timeStamp
    real               :: date, moment
    integer            :: intdate, day, month, year, t

    date = floor(timeStamp) ! remove the part days
    parseTimeStamp(4) = timeStamp - date ! save the part days

    intdate = int(date)
    day = mod(intdate,100);   intdate = intdate / 100
    month = mod(intdate,100); intdate = intdate / 100
    year = intdate
    parseTimeStamp(1) = real(year)
    parseTimeStamp(2) = real(month)
    parseTimeStamp(3) = real(day)

  end function parseTimeStamp

The issue is the output shows fraction of the day always at 0. When printing the timestamp (!print *, timeStamp) I get the date without fraction of the day 48 times before rolling over to the next day, even when I know with 100% certainty the data being read contains the proper fraction.

ex: I am getting

20220101.0 20220101.0 20220101.0 .... 20220102.0 20220102.0 ...

instead of

20220101.0 20220101.02083 20220101.04167 .... 20220102.0 20220102.02083 ...

I've tried using several different input files and have confirmed that the input files contain the part day data.

  • 1
    A variable of type real won't have enough precision to do that. 6 sig figs if you are lucky. To avoid floating-point inaccuracy I think you would be better reading into a string and then picking off substrings. – lastchance Dec 19 '22 at 21:58
  • I need the value to be in real afterwards, as it will be used later. Do you have a good guide on converting a string to a real within fortran? I haven't been able to turn something up since seeing your recommendation. – Daniel Mutton Dec 19 '22 at 22:57
  • You seem to only be using (4) of the (5) dimensions of the parseTimeStamp. Personally I would be printing out some debug of the inputs and variables along the way. Using a double precision to do the intermediate could be worth a try. – Holmz Dec 20 '22 at 03:34
  • 1
    The first thing to try is certainly some higher kind for your `real`, see https://stackoverflow.com/questions/838310/fortran-90-kind-parameter It is up to you, whether you use `double precision`, `selected_real_kind`, `real64`, `real128`, `c_double` or something else. But try something bigger than your default `real`. If these concepts are new to you, I highly suggest studying them in detail and their implications to numerical accuracy. – Vladimir F Героям слава Dec 20 '22 at 09:54
  • For parsing text data, you really need to learn how to convert strings to numeric data and back. This has been covered multiple times on this very site as well. Search for internal files. – Vladimir F Героям слава Dec 20 '22 at 09:58
  • To address why I was only using 4 out of the 5 dimensions it was because I trimmed the function and removed portions that call external subroutines for the purpose of this post. Apologies for the beginner mistakes such as asking how to convert between string and numerical data. I primarily work within Python and R, and have only just started working in Fortran. – Daniel Mutton Dec 20 '22 at 14:45

1 Answers1

1

I think you would be better creating a user-defined type to hold your date and time variables than to try to return an array. Year, month and day are more naturally whole numbers (integer type). If you want higher precision use kind=real64 from iso_fortran_env (worth looking up: it has other goodies in that make codes portable).

program test
   use iso_fortran_env
   implicit none
   integer y, m, d           ! year, month, day
   real(real64) f            ! fraction of a day

   call parseTimeStamp( "20220101.02083", y, m, d, f )
   write( *, "( 3(a, ': ', i0, :, / ) )" ) "year", y, "month", m, "day", d
   write( *, "( a, ': ', f7.5 )" ) "fraction", f

contains

   subroutine parseTimeStamp( dateTime, year, month, day, dayfraction )
      character(len=*), intent(in) :: dateTime
      integer, intent(out) :: year, month, day
      real(real64), intent(out) :: dayfraction
      real(real64) temp

      ! *** TO DO *** check that timestamp is well formed before anything else

      read( dateTime(1:4), * ) year
      read( dateTime(5:6), * ) month
      read( dateTime(7: ), * ) temp
      day = temp
      dayfraction = temp - day

   end subroutine parseTimeStamp

end program test
year: 2022
month: 1
day: 1
fraction: 0.02083

If you want to go with a user-defined type then you can return that from a function:

module timemod
   use iso_fortran_env
   implicit none

   type DTime
      integer year
      integer month
      integer day
      real(real64) fraction  ! fraction of a day
   end type DTime

contains

   type(DTime) function parseTimeStamp( dateStamp ) result( res )
      character(len=*), intent(in) :: dateStamp
      real(real64) temp

      read( dateStamp(1:4), * ) res%year
      read( dateStamp(5:6), * ) res%month
      read( dateStamp(7: ), * ) temp
      res%day = temp
      res%fraction = temp - res%day

   end function parseTimeStamp

end module timemod

!=======================================================================

program test
   use iso_fortran_env
   use timemod
   implicit none

   write( *, "( 3( i0, / ), f7.5 )" ) parseTimeStamp( "20220101.02083" )

end program test
lastchance
  • 1,436
  • 1
  • 3
  • 12
  • Thank you very much for your response! I had to tweak it a little bit because there were parts of my code I left out as they dealt with other subroutines, but it appears to have worked like a charm. – Daniel Mutton Dec 20 '22 at 13:37