character to integer conversion in fortran - fortran

I have a date time value declared as character in this way "1985-01-01-00:00" and I want to extract the year, month and day as integer. I don't know the exact command in FORTRAN language.

For example:
program zz
character(20) :: ch = "1985-01-01-00:00"
integer yyyy,mm,dd
read(ch(1:4),'(i)') yyyy
read(ch(6:7),'(i)') mm
read(ch(9:10),'(i)') dd
write(*,*) yyyy, mm, dd
end program zz

Related

How to handle blank in formatted integer input

I have a file like this
1980 01 23
1982 04 30
1983 05 22
1984
1985 02 11
I tried to read data using format "(3I4)"
implicit none
integer, parameter :: FUnitIn = 10
character(len=255) :: FNameIn = "./test.txt"
integer :: FStatOpen, FStatRead
integer :: yyyy, mm, dd
open ( unit = FUnitIn, &
file = trim(FNameIn), &
access = "sequential", &
form = "formatted", &
status = "old", &
iostat = FStatOpen)
if (FStatOpen /= 0) then ! ERROR
stop
end if
do
read (FUnitIn, "(3I4)", iostat=FStatRead) yyyy, mm, dd
if (FStatRead /= 0) then
exit
else
write (6, "(3I4)") yyyy, mm, dd
end if
end do
I got three zeros for the blank line.
Are there any options to handle blank input?
You can read each line in a character variable, and check its adjusted-trimmed length if your sole goal is to skip the empty line, something like the following code (see variable record). Going beyond this simple implementation requires some extra information from you as to what behavior exactly you want to get.
implicit none
integer, parameter :: FUnitIn = 10
character(len=255) :: FNameIn = "./test.txt"
integer :: FStatOpen, FStatRead
integer :: yyyy, mm, dd
character(len=1023) :: record
open ( unit = FUnitIn, &
file = trim(FNameIn), &
access = "sequential", &
form = "formatted", &
status = "old", &
iostat = FStatOpen)
if (FStatOpen /= 0) then ! ERROR
stop
end if
do
read (FUnitIn, "(3I4)", iostat=FStatRead) record
if (FStatRead /= 0) then
exit
elseif ( len_trim(adjustl(record)) > 0 ) then
read (record, *) yyyy, mm, dd
write (6, "(3I4)") yyyy, mm, dd
end if
end do

Fortran read mixed text and numbers

I am using Fortran 90 to read a file that contains data in the following format
number# 125 var1= 2 var2= 1 var3: 4
.
.
.
.
number# 234 var1= 3 var2= 5 var3: 1
I tried the following command and works fine
read (2,*) tempstr , my_param(1), tempstr , my_param(2), tempstr , my_param(3)
Problem is when the numbers become larger and there is no space between string and number, i.e. the data looks as following:
number# 125 var1= 2 var2=124 var3: 4
I tried
read (2,512) my_param(1), my_param(2), my_param(3)
512 format('number#', i, 'var1=', i, 'var2=', i, 'var3:', i)
It reads all number as zero
I can't switch to some other language. The data set is huge, so I can't pre-process it. Also, the delimiters are not the same every time.
Can someone please help with the problem?
Thanks in advance
First up, 720 thousand lines is not too much for pre-processing. Tools like sed and awk work mostly on a line-by-line basis, so they scale really well.
What I have actually done was to convert the data in such a way that I could use namelists:
$ cat preprocess.sed
# Add commas between values
# Space followed by letter -> insert comma
s/ \([[:alpha:]]\)/ , \1/g
# "number" is a key word in Fortran, so replace it with num
s/number/num/g
# Replace all possible data delimitors with the equals character
s/[#:]/=/g
# add the '&mydata' namelist descriptor to the beginning
s/^/\&mydata /1
# add the namelist closing "/" character to the end of the line:
s,$,/,1
$ sed -f preprocess.sed < data.dat > data.nml
Check that the data was correctly preprocessed:
$ tail -3 data.dat
number#1997 var1=114 var2=130 var3:127
number#1998 var1=164 var2=192 var3: 86
number#1999 var1=101 var2= 48 var3:120
$ tail -3 data.nml
&mydata num=1997 , var1=114 , var2=130 , var3=127/
&mydata num=1998 , var1=164 , var2=192 , var3= 86/
&mydata num=1999 , var1=101 , var2= 48 , var3=120/
Then you can read it with this fortran program:
program read_mixed
implicit none
integer :: num, var1, var2, var3
integer :: io_stat
namelist /mydata/ num, var1, var2, var3
open(unit=100, file='data.nml', status='old', action='read')
do
read(100, nml=mydata, iostat=io_stat)
if (io_stat /= 0) exit
print *, num, var1, var2, var3
end do
close(100)
end program read_mixed
While I still stand with my original answer, particularly because the input data is already so close to what a namelist file would look like, let's assume that you really can't make any preprocessing of the data beforehand.
The next best thing is to read in the whole line into a character(len=<enough>) variable, then extract the values out of that with String Manipulation. Something like this:
program mixed2
implicit none
integer :: num, val1, val2, val3
character(len=50) :: line
integer :: io_stat
open(unit=100, file='data.dat', action='READ', status='OLD')
do
read(100, '(A)', iostat=io_stat) line
if (io_stat /= 0) exit
call get_values(line, num, val1, val2, val3)
print *, num, val1, val2, val3
end do
close(100)
contains
subroutine get_values(line, n, v1, v2, v3)
implicit none
character(len=*), intent(in) :: line
integer, intent(out) :: n, v1, v2, v3
integer :: idx
! Search for "number#"
idx = index(line, 'number#') + len('number#')
! Get the integer after that word
read(line(idx:idx+3), '(I4)') n
idx = index(line, 'var1') + len('var1=')
read(line(idx:idx+3), '(I4)') v1
idx = index(line, 'var2') + len('var3=')
read(line(idx:idx+3), '(I4)') v2
idx = index(line, 'var3') + len('var3:')
read(line(idx:idx+3), '(I4)') v3
end subroutine get_values
end program mixed2
Please note that I have not included any error/sanity checking. I'll leave that up to you.

Call a subroutine for a list of points instead of a single point

I have a certain piece of code in fortran. The code takes 'pq' as an input from the user and is a single point. Instead of doing this I want to read a set of points 'pq' from a file points.txt and run it for those number of points instead of just one single user input. Is it possible? The code is as follows:
program prop
use module
implicit none
character(len=80) :: ErrorMsg
character(2) :: xy
real(8) :: Conc(20) = 0.d0
character(len=20) :: fn, fl
real(8) :: Mmolar, Tcritical, Pcritical, Tmininimum, Tmaximum, x, y
call Init_module()
write(*,*) 'Insert the gas name:'
read(*,*) fn
write(*,*) 'Insert the gas library:'
read(*,*) fl
write(*,*) 'Insert the copule pq:'
read(*,*) pq
write(*,*) 'Insert the value of ', pq(1:1)
read(*,*) x
write(*,*) 'Insert the value of ', pq(2:2)
read(*,*) y
write(*,*) 'Pres = ', Pres( pq, x, y, ErrorMsg)
write(*,*) 'Temp = ', Temperature( pq, x, y, ErrorMsg)
call ReleaseObjects()
end program prop
Instead of reading pq as a single point x,y from the user in the above code, I want to read a set of points from file.txt, for example 50 points and then run subroutines Pres and Temperature.
Each line of the file contains one point x,y and x and y in each line are separated by a few space characters.
The first few lines of file.txt are:
Ts
500
0.04781564 159.81587875
0.20396084 165.46398084
0.08159885 166.81382894
0.03879184 164.17497877
0.12585959 165.37000305
0.09895530 165.95997769
0.10389518 170.74235496
It must be noted that the length and the sign of the floating numbers can vary. The file.txt is originally written through python with the formatting for x, y being '%-12.8f %-12.8f\n'%. I have the following code to try and read the file but am not able to read from the 3rd line onwards:
real, allocatable :: x(:),y(:)
integer :: np
open(12,file=trim('file.txt'),status='old', &
access='sequential', form='formatted', action='read' )
read(12,*)pq
write(*,*)'pq:', pq
read(12,*)np
write(*,*)'number of points:',np
allocate (x(np))
allocate (y(np))
do i=1,np
read(12,*)x(i),y(i)
write(*,*)x(i),y(i)
enddo
Instead of using the READ statement with the asterisk (*) as the first argument asking for an user input, use a file identifier. You need to OPEN your file containing the set of points, assuming it is ASCII :
OPEN(UNIT=10,FILE=file.txt,ACTION='read',STATUS='old')
I think the arguments of this command are quite explanatory.
Then assuming your file contains multiple lines with x and y values, you can read each line of your file by doing :
READ(10,*) x,y
If you have multiple points to read, just use a DO if you know the number of points to read, a DO WHILE otherwise. To take your example with 50 points, something like this should work :
OPEN(UNIT=10,FILE=file.txt,ACTION='read',STATUS='old') ! Open file
DO i=1,50
READ(10,*) x,y
write(*,*) 'Pres = ', Pres( pq, x, y, ErrorMsg)
write(*,*) 'Temp = ', Temperature( pq, x, y, ErrorMsg)
END DO
CLOSE(10) ! Close file
EDIT
Your suggestion is almost correct. You forgot to declare pq as a character(len=2). You should not have been able to pass line 1 because of that.
As I said, there is a space separator that is naturally treated by a asterisk as a format. Anyway, if you want to exactly match the format, use the same with which you wrote your data. Reading your format Python, I assume you wrote two floats with a space separator, and indeed if you count the number of character of your digits :
0.04781564 159.81587875
^^^^^^^^^^^^|^^^^^^^^^^^^
1 12|1 12
|
space
which gives the following format in Fortran :
read(12,'(f12.8,1X,f12.8)') x(i),y(i)
X means a space separator in Fortran formats.
Then you can write you data onscreen with the same format to check :
write(*,'(f12.8,1X,f12.8)') x(i),y(i)
It gives :
pq:Ts
number of points: 500
0.04781564 159.81587219
0.20396084 165.46397400
0.08159885 166.81382751
0.03879184 164.17497253
0.12585959 165.37001038
0.09895530 165.95997620
0.10389518 170.74235535
You may have noticed that you lost precision on the last digits. It is because you have declared a simple real (4 bytes). Switch your real to 8 bytes with real(kind=8) or real*8 according to your compiler (be aware, not the right way to do it, not portable but sufficient in your case)
Do not forget to close your file when you are done dealing with it :
close(12)

Fortran-- reading columns with specific time stamps

I have this data set and I would like to read it using Fortran and apply a few operations . I have presented the data set below. Here's a short description of the data set. The data set starts of with a time stamp and the subsequent lines (around 60 or 120)are the data points recorded at that time, which is again followed by a time stamp and data and so on so forth. Each file has around 30 observations with 15 different time stamps(i.e two adjacent observations have the same time stamps). I now want to read this file and perform operations such as addition or subtraction of columns on the data points with the same time stamps.
Here the '6:31: 5', '6:27: 9' are the time stamps followed by the data.
Please help as I am unable to figure out a way to do this.
Thank you very much.
Your question leaves a lot of ambiguity. The way I understand it is that you want to read a file where the contents of a line might be one of two different things: a value, or a time stamp.
And you don't know which one it is before you read it.
The best way, I think, is to first read the line into a string.
character(len=20) :: line
read(1001, '(A)') line
Then, based on the first character in that string, you can deduce which one it is.
if (line(1:1) == "'") then
! It's a time stamp
else
! It's a new value
end if
If it's a value, you can read it straight out of the line variable:
read(line, *) val
Reading the time stamp is more complicated, because I don't quite know where the colons are inside it. If the hours are all single digits, you could get away with something like this:
read(line, '(X, I1, X, I2, X, I2)') hour, minute, second
But because there are hours that can't be written with a single digit, I rather doubt that.
So I suggest that you create a subroutine that uses the INDEX function to locate the colons, and then slice the line to read each value separately.
hm = index(line, ':', back=.FALSE.) ! hour-minute separator
ms = index(line, ':', back=.TRUE.) ! minute-second separator
read(line(2:hm-1), *) hour
read(line(hm+1:ms-1), *) minute
read(line(ms+1:), *) second
All put together:
program timestamp
implicit none
integer, parameter :: runit = 1001
integer :: io_err
character(len=20) :: line
real :: val
integer :: hour, minute, second
open(unit=runit, file='data.txt', action='READ', status='OLD', iostat=io_err)
if (io_err /= 0) then
write(*, *) "Unable to open file"
stop 1
end if
do
read(runit, '(A)', iostat=io_err) line
if (io_err == -1) then
write(*, *) "End of file"
exit
end if
if (line(1:1) == "'") then
call read_timestamp(line, hour, minute, second)
write(*, '(A, 2(I2.2, ":"), I2.2)') "New timestamp: ", hour, minute, second
else
read(line, *) val
write(*, *) "Data: ", val
end if
end do
close(runit)
contains
subroutine read_timestamp(line, hour, minute, second)
implicit none
character(len=*), intent(in) :: line
integer, intent(out) :: hour, minute, second
integer :: hm, ms ! Indices of separators
hm = index(line, ':', back=.FALSE.)
ms = index(line, ':', back=.TRUE.)
read(line(2:hm-1), *) hour
read(line(hm+1:ms-1), *) minute
read(line(ms+1:), *) second
return
end subroutine read_timestamp
end program timestamp

How to write output to a string in fortran?

I need to write a formated output to a string DTSTR. It use to work under layhe fortran but not gfortran
INTEGER*2 MON,DAY,YEAR,HR,MINUTE,SEC,HUND
CHARACTER DY*2
CHARACTER DTSTR*24
COMMON /RD/ DTSTR
...
...
WRITE(DTSTR,10)MON,DAY,YEAR,HR,MINUTE,DY,' ]'
10 FORMAT('[ ',I2,'-',I2.2,'-',I4,2X,I2,':',I2.2,1X,2A2)
it empty just a empty line. If i use following it correctly output. But i want to store this string. Is it possible to do that with gnu fortran.
WRITE(*,10)MON,DAY,YEAR,HR,MINUTE,DY,' ]'
update
I am trying to compile following file. I think the problem might be with the COMMON.
PROGRAM HELO
CALL DOTIME
WRITE(*,5700)DTSTR
5700 FORMAT(24X,A24/)
END
SUBROUTINE DOTIME
C
IMPLICIT NONE
INTEGER*2 MON,DAY,YEAR,HR,MINUTE,SEC,HUND
CHARACTER DY*2
CHARACTER DTSTR*24
COMMON /RD/ DTSTR
integer values(8)
call date_and_time(VALUES=values)
YEAR = values(1)
MON = values(2)
DAY = values(3)
HR = values(5)
MINUTE = values(6)
SEC = values(7)
HUND = values(8)
C =================================================
C
C Incompitable function => CALL GETDAT(YEAR,MON,DAY)
C Incompitable function => GETTIM(HR,MINUTE,SEC,HUND)
IF(HR .GE. 12)THEN
IF(HR .NE. 12)HR=HR-12
DY='PM'
ELSE
DY='AM'
ENDIF
WRITE(DTSTR,10)MON,DAY,YEAR,HR,MINUTE,DY,' ]'
10 FORMAT('[ ',I2,'-',I2.2,'-',I4,2X,I2,':',I2.2,1X,2A2)
RETURN
END
Hmm? It works just fine for me:
program testwrite
implicit none
INTEGER :: MON,DAY,YEAR,HR,MINUTE,SEC,HUND
CHARACTER(LEN=2) :: DY
CHARACTER(LEN=24) :: DTSTR
MON = 4
DAY = 27
YEAR= 2010
HR = 13
MINUTE = 27
SEC = 0
HUND = 0
DY ='WE'
WRITE(DTSTR,10)MON,DAY,YEAR,HR,MINUTE,DY,' ]'
10 FORMAT('[ ',I2,'-',I2.2,'-',I4,2X,I2,':',I2.2,1X,2A2)
print *,'<',trim(DTSTR),'>'
end program testwrite
gives
<[ 4-27-2010 13:27 WE ]>
just as one would expect. Works with several versions of gfortran I have kicking around.
Update: Yes, the problem is in your common block. The common block isn't declared in the main program. But really, it's much simpler and much, much better practice just to pass the string as an argument:
PROGRAM HELO
IMPLICIT NONE
CHARACTER(LEN=24) :: DTSTR
CALL DOTIME(DTSTR)
WRITE(*,5700)DTSTR
5700 FORMAT(24X,A24/)
END
SUBROUTINE DOTIME(DTSTR)
C
IMPLICIT NONE
INTEGER*2 MON,DAY,YEAR,HR,MINUTE,SEC,HUND
CHARACTER DY*2
CHARACTER(LEN=24), INTENT(OUT) :: DTSTR