.PAGE SIZE 60, 80 .RIGHT MARGIN 80 .TITLE Advanced DATATRIEVE Record Definitions .SUBTITLE B.#Z.#Lederman .FIGURE 3 .CENTER Advanced DATATRIEVE Record Definitions .BLANK.CENTER B.#Z.#Lederman .BLANK.CENTER ITT World Communications .BLANK.CENTER New York, NY 10004-2464 .NOTE Abstract This session is intended to illustrate some of the more advanced features of DATATRIEVE record definitions. Lower case text indicates commands typed in by a user, upper case is printed by DTR or is material stored in the CDD. Please keep in mind that most examples are "stripped down", showing only the fields necessary to illustrate the principles being demonstrated: "real" applications would require additional fields, and in most cases more descriptive field names. Most of these examples use advanced features found in VAX-DTR and DTR-20, and unfortunately will not work in DTR-11 (or PRO-DTR). .END NOTE .PARAGRAPH Reading a file whose records differ in length and field layout is a common problem. In the following sample file, there are records whose total length is not given directly by a field in the record. .BLANK.NO JUSTIFY.NO FILL.comment .test page 15 $ type var.seq .BLANK 01 10 bytes. 02 15 byte record 01 10 bytes. 03 This is 20 bytes... 02 15 byte record 04 This is 25 bytes long... 01 10 bytes. 03 This is 20 bytes... 02 15 byte record 01 10 bytes. 04 This is 25 bytes long... 01 10 bytes. .BLANK.JUSTIFY.FILL You can just define a text field the length of the longest record, but you get "Record too Short..." error messages, and the short records are padded with blanks or zeroes. Also, it would be hard to look at the individual data items within each record. A first try at a better record definition could be: .BLANK.NO JUSTIFY.NO FILL.comment .test page 35 DTR> show var__rec .BLANK RECORD VAR__REC 01 VAR__REC. 10 TYPE PIC 99 EDIT__STRING Z9. 10 REC__LEN COMPUTED BY TYPE VIA VAR__LEN__TAB. 10 TOP. 15 VARIABLE OCCURS 0 TO 30 TIMES DEPENDING ON REC__LEN. 20 VTEXT PIC X. 10 A REDEFINES TOP. 20 FILLER PIC X. 20 NBRA PIC 99 EDIT__STRING Z9. 20 FILLER PIC X. 20 TXTA PIC X(6). 10 B REDEFINES TOP. 20 FILLER PIC X. 20 NBRB PIC 99 EDIT__STRING Z9. 20 FILLER PIC X. 20 TXTB PIC X(11). 10 C REDEFINES TOP. 20 FILLER PIC X. 20 TXTC1 PIC X(7). 20 FILLER PIC X. 20 NBRC PIC 99 EDIT__STRING Z9. 20 FILLER PIC X. 20 TXTC2 PIC X(8). 10 D REDEFINES TOP. 20 FILLER PIC X. 20 TXTD1 PIC X(7). 20 FILLER PIC X. 20 NBRD PIC 99 EDIT__STRING Z9. 20 FILLER PIC X. 20 TXTD2 PIC X(13). ; .BLANK.JUSTIFY.FILL This record definition depends upon a table that converts the record type to a record length. This happens to be in a domain table in this example, but could also be in a dictionary table. .BLANK.NO JUSTIFY.NO FILL.comment .test page 8 DTR> show var__tab__rec .BLANK RECORD VAR__TAB__REC 01 VAR__TAB__REC. 10 TYPE PIC 99 EDIT__STRING Z9. 10 LENGTH PIC 99 EDIT__STRING Z9. ; .BLANK.comment .test page 6 DTR> show var__len__tab .BLANK TABLE VAR__LEN__TAB FROM VAR__TAB__DOM USING TYPE : LENGTH END__TABLE .BLANK.comment .test page 9 DTR> print var__tab__dom .BLANK TYPE LENGTH .BLANK 1 10 2 15 3 20 4 25 .BLANK If you print this domain, you get the first field by default. .BLANK DTR> print var .BLANK REC TYPE LEN VTEXT .BLANK 1 10 1 0 .BLANK b y t e s . 2 15 1 5 .BLANK b y t e .BLANK r e c o r d . . . .BLANK.JUSTIFY.FILL and so on. This is very useful in cases where you want to get each character in the record separately, such as for "parsing" data, and you get the length of the text without having to add an FN$STR__LENGTH function to DTR. However, if you want all of the data in a single field: .BLANK.NO JUSTIFY.NO FILL.comment .test page 21 DTR> for var print a .BLANK NBRA TXTA .BLANK 10 bytes. 15 byte r 10 bytes. Illegal ASCII numeric "Th". 0 s is 2 15 byte r Illegal ASCII numeric "Th". 0 s is 2 10 bytes. Illegal ASCII numeric "Th". 0 s is 2 15 byte r 10 bytes. Illegal ASCII numeric "Th". 0 s is 2 10 bytes. .BLANK.JUSTIFY.FILL and the same happens for all other REDEFINEd fields, because the numeric fields don't "line up". One alternative is to use a CHOICE statement in a procedure to get the proper field to print out. (You can also use IF-THEN-ELSE statements to accomplish the same result, and that approach will also work with DTR-11, but CHOICE is more compact.) .BLANK.NO JUSTIFY.NO FILL.comment .test page 14 DTR> show var-print .BLANK PROCEDURE VAR__PRINT FOR VAR BEGIN PRINT TYPE, CHOICE OF TYPE = 1 THEN A TYPE = 2 THEN B TYPE = 3 THEN C TYPE = 4 THEN D ELSE " " END__CHOICE END END__PROCEDURE .BLANK.comment .test page 18 DTR> :var-print .BLANK TYPE .BLANK 1 10 bytes. 2 15 byte record 1 10 bytes. 3 This is 20 bytes... 2 15 byte record 4 This is 25 bytes long... 1 10 bytes. 3 This is 20 bytes... 2 15 byte record 1 10 bytes. 4 This is 25 bytes long... 1 10 bytes. .BLANK.JUSTIFY.FILL This suits many applications, but is sometimes inconvenient. An alternative is a record definition (actually a VIEW) that will print out the proper fields by default. .BLANK.NO JUSTIFY.NO FILL.comment .test page 20 DOMAIN VARI OF VAR USING 01 VARIX OCCURS FOR VAR. 10 TYPE FROM VAR. 10 REC__LEN FROM VAR. 02 AV OCCURS FOR VAR WITH TYPE = 1. 10 NBRA FROM VAR. 10 TXTA FROM VAR. 02 BV OCCURS FOR VAR WITH TYPE = 2. 10 NBRB FROM VAR. 10 TXTB FROM VAR. 02 CV OCCURS FOR VAR WITH TYPE = 3. 10 TXTC1 FROM VAR. 10 NBRC FROM VAR. 10 TXTC2 FROM VAR. 02 DV OCCURS FOR VAR WITH TYPE = 4. 10 TXTD1 FROM VAR. 10 NBRD FROM VAR. 10 TXTD2 FROM VAR. ; .BLANK .comment .test page 21 DTR> print vari .BLANK REC TYPE LEN NBRA TXTA NBRB TXTB TXTC1 NBRC TXTC2 TXTD1 NBRD TX 1 10 10 bytes. 15 byte record This is 20 bytes... This is 25 bytes 10 bytes. 15 byte record This is 20 bytes... This is 25 bytes 10 bytes. 15 byte record 10 bytes. 10 bytes. 2 15 10 bytes. 15 byte record This is 20 bytes... This is 25 bytes 10 bytes. 15 byte record This is 20 bytes... This is 25 bytes 10 bytes. 15 byte record 10 bytes. 10 bytes. 1 10 10 bytes. 15 byte record This is 20 bytes... This is 25 bytes 10 bytes. 15 byte record This is 20 bytes... This is 25 bytes 10 bytes. 15 byte record 10 bytes. 10 bytes. .BLANK Execution terminated by operator. .RIGHT MARGIN 80 .BLANK.JUSTIFY.FILL This has the slight drawback that, since there is nothing which identifies unique records in this example, all records of a given type are obtained for each record in the view. In cases where there was an additional field with a unique key, this would not be a problem: in this case, however, some additional work can solve the problem. .BLANK.NO JUSTIFY.NO FILL.comment .test page 13 DTR> show print-first-vari .BLANK PROCEDURE PRINT__FIRST__VARI DECLARE N PIC 9. N = 1 FOR VARI BEGIN WHILE N = 1 BEGIN N = N + 1 PRINT END END END__PROCEDURE .BLANK.comment .test page 11 DTR> :print-first-vari .BLANK REC TYPE LEN NBRA TXTA NBRB TXTB TXTC1 NBRC TXTC2 TXTD1 NBRD TX 1 10 10 bytes. 15 byte record This is 20 bytes... This is 25 bytes 10 bytes. 15 byte record This is 20 bytes... This is 25 bytes 10 bytes. 15 byte record 10 bytes. 10 bytes. .BLANK This is one way to one complete set of records. Another method is: .BLANK DTR> find vari .BLANK [12 records found] .BLANK The following is not quite what we want. .BLANK DTR> for current print av .BLANK NBRA TXTA .BLANK 10 bytes. 10 bytes. 10 bytes. 10 bytes. 10 bytes. 10 bytes. 10 bytes. 10 bytes. 10 bytes. 10 bytes. 10 bytes. .BLANK Execution terminated by operator. .BLANK but you can do this: .BLANK DTR> select first .BLANK Now, you can do some interesting things, like separating the different groups of similar records. .BLANK.comment .test page 10 DTR> print av .BLANK NBRA TXTA .BLANK 10 bytes. 10 bytes. 10 bytes. 10 bytes. 10 bytes. .BLANK.comment .test page 8 DTR> print bv .BLANK NBRB TXTB .BLANK 15 byte record 15 byte record 15 byte record .BLANK.comment .test page 7 DTR> print cv .BLANK TXTC1 NBRC TXTC2 .BLANK This is 20 bytes... This is 20 bytes... .BLANK.comment .test page 7 DTR> print dv .BLANK TXTD1 NBRD TXTD2 .BLANK This is 25 bytes long... This is 25 bytes long... .BLANK.JUSTIFY.FILL Normally I would discourage the use of FIND and SELECT, but in this case it can be used to sort and separate all records of a given type. .BLANK Still, this is not quite what we were looking for. If you can put a CHOICE statement into a procedure, why not put it into the record definition. .BLANK.NO JUSTIFY.NO FILL DTR> show cvar__rec .BLANK RECORD CVAR__REC USING 01 CVAR__REC. 10 TYPE PIC 99 EDIT__STRING Z9. 10 REC__LEN COMPUTED BY TYPE VIA VAR__LEN__TAB. 10 FILLER PIC X. 10 TOP. 15 VARIABLE OCCURS 0 TO 30 TIMES DEPENDING ON REC__LEN. 20 FILLER PIC X. 10 A REDEFINES TOP. 20 ANBR PIC 99. 20 FILLER PIC X. 20 ATXT PIC X(6). 10 B REDEFINES TOP. 20 BNBR PIC 99. 20 FILLER PIC X. 20 BTXT PIC X(11). 10 C REDEFINES TOP. 20 CTXT1 PIC X(8). 20 CNBR PIC 99. 20 CTXT2 PIC X(9). 10 D REDEFINES TOP. 20 DTXT1 PIC X(8). 20 DNBR PIC 99. 20 DTXT2 PIC X(14). 10 TEXT COMPUTED BY CHOICE OF TYPE = 1 THEN ATXT TYPE = 2 THEN BTXT TYPE = 3 THEN CTXT1 ||| CTXT2 TYPE = 4 THEN DTXT1 ||| DTXT2 ELSE "" END__CHOICE. 10 NUMBER EDIT__STRING Z9 COMPUTED BY CHOICE OF TYPE = 1 THEN ANBR TYPE = 2 THEN BNBR TYPE = 3 THEN CNBR TYPE = 4 THEN DNBR ELSE 0 END__CHOICE. ; .BLANK.comment .test page 18 DTR> print cvar .BLANK REC TYPE LEN TEXT NUMBER .BLANK 1 10 bytes. 10 2 15 byte record 15 1 10 bytes. 10 3 20 This is bytes... 20 2 15 byte record 15 4 25 This is bytes long... 25 1 10 bytes. 10 3 20 This is bytes... 20 2 15 byte record 15 1 10 bytes. 10 4 25 This is bytes long... 25 1 10 bytes. 10 .BLANK Just to prove that NUMBER is really numeric .BLANK.comment .test page 15 DTR> for cvar print fn$log10(number) .BLANK 1.000 1.176 1.000 1.301 1.176 1.398 1.000 1.301 1.176 1.000 1.398 1.000 .BLANK.JUSTIFY.FILL Now we finally have the data in the form we want. Something which is not visible when you look at this print-out on paper is that the field TEXT always prints out the length of the actual field: it does not pad short records with spaces or zeroes which is what would happen if you just defined one field of 25 bytes (you also don't get the "Record too Short" error messages). .PARAGRAPH There are a number of applications where data validation in the record definition is desired. In this example, the employee number contains a sort of "check sum", where the last two digits are the sum of the first two. This sort of thing is sometimes done to verify that the data does not contains errors (I'd rather depend on the operating system facilities, but some people would prefer this). This particular check sum is a bit crude, and done only to demonstrate the methods which may be used. If you were going to do this a lot, it would be worthwhile to define a new FN$--- function to do the computation, especially if the check method was more complicated such as some sort of "rule of 11", but not everyone wants to add functions to DTR. The interesting part of all this is that you can define a VALID#IF clause to work on parts of the same field it validates, and that the fields used can be defined after the VALID#IF clause. .BLANK.NO JUSTIFY.NO FILL.comment .test page 13 DTR>show empno__rec .BLANK RECORD EMPNO__REC 01 EMPNO__REC. 10 EMPLOYEE__NUMBER PIC 99999 VALID IF CK = (N1 + N2 + N3). 10 NBRS REDEFINES EMPLOYEE__NUMBER. 20 N1 PIC 9. 20 N2 PIC 9. 20 N3 PIC 9. 20 CK PIC 99. ; .BLANK.comment .test page 12 DTR> print empno .BLANK EMPLOYEE NUMBER .BLANK 12306 65617 98724 11002 00101 32308 .BLANK.JUSTIFY.FILL Something that users don't always realize is that a COMPUTED#BY field can be anywhere in the record definition, and does not have to be computed from fields that come "ahead" of it in the definition. DTR will read and parse the entire record definition to resolve all field names before doing anything with the record: thus, a field can, in some cases, even be computed by itself. .PARAGRAPH With this definition, you can prevent invalid numbers from being stored. .BLANK.NO JUSTIFY.NO FILL.comment .test page 6 DTR> store empno Enter EMPLOYEE__NUMBER: 32301 .BLANK Validation error for field EMPLOYEE__NUMBER. Re-enter EMPLOYEE__NUMBER: 32308 .BLANK.JUSTIFY.FILL You can also find out if all the numbers currently in the domain are still valid (something which a normal VALID#IF won't do): .BLANK.NO JUSTIFY.NO FILL.comment .test page 12 DTR> for empno print ck, (n1 + n2 + n3) .BLANK CK .BLANK 06 6 17 17 24 24 02 2 01 1 08 8 08 8 .BLANK Now look at what happens if an invalid number is present in the domain. .BLANK.comment .test page 12 DTR> print empno .BLANK EMPLOYEE NUMBER .BLANK 12306 65617 98724 11002 00101 32301 [this number is invalid] .BLANK.comment .test page 7 DTR> print empno with ck ne (n1 + n2 + n3) .BLANK EMPLOYEE NUMBER .BLANK 32301 .BLANK.JUSTIFY.FILL We can use DTR to go in and fix any checksums. (I would advise looking at the data first to be certain it really is valid, unless you want to do something like this to add checksums to data that was stored previously without checksums.) .BLANK.NO JUSTIFY.NO FILL.comment .test page 5 DTR> ready empno modify DTR> for empno with ck ne (n1 + n2 + n3) begin CON> modify empno using ck = n1 + n2 + n3 CON> end .comment .test page 11 DTR> print empno .BLANK EMPLOYEE NUMBER .BLANK 12306 65617 98724 11002 00101 32308 .JUSTIFY.FILL.PARAGRAPH While thinking up stuff for this presentation, I came up with the following example which, quite frankly, I didn't think would work. .BLANK.NO JUSTIFY.NO FILL .comment .test page 8 DTR> show sci__rec .BLANK RECORD SCI__REC 01 SCI__REC. 10 SCI__NOT USAGE REAL EDIT__STRING 99.99. 10 N2 COMPUTED BY *."N2". ; .BLANK.JUSTIFY.FILL Depending upon how you access the domain, you can be prompted for N2 once per record (might be used to make the system pause during loops), once per domain, or not at all. .BLANK.NO JUSTIFY.NO FILL.comment .test page 10 DTR> for sci print sci__not .BLANK SCI__NOT .BLANK 00.01 00.88 01.20 09.80 23.40 .BLANK.comment .test page 12 DTR> print sci Enter N2: 30 .BLANK SCI__NOT N2 .BLANK 00.01 30 00.88 30 01.20 30 09.80 30 23.40 30 .BLANK DTR> for sci print sci__rec .BLANK SCI NOT N2 .BLANK Enter N2: 30 .BLANK 00.01 30 Enter N2: 20 .BLANK 00.88 20 Enter N2: 10 .BLANK 01.20 10 Enter N2: 1 .BLANK 09.80 1 Enter N2: 0 .BLANK 23.40 0 .BLANK.JUSTIFY.FILL Having done this, I'm not at all sure what I would use it for, but I'm sure someone will think of something someday. Knowledge never goes to waste. .PARAGRAPH Some COMPUTED#BY fields are more useful than others. For example, if several departments share a data base and you want to make sure that each department enters the correct sequence of numbers (this example assumes a valid range of numbers for each department, just to make it more difficult): .BLANK.NO JUSTIFY.NO FILL DTR> show po__rec .BLANK RECORD PO__REC 01 PO__REC. 10 DEPT PIC XXX. 10 PO__NUMBER PIC 99999 VALID IF 1 = CHOICE OF (DEPT = "AAA" AND PO__CHECK BETWEEN 01 AND 20) THEN 1; (DEPT = "BBB" AND PO__CHECK BETWEEN 21 AND 40) THEN 1; (DEPT = "CCC" AND PO__CHECK BETWEEN 41 AND 60) THEN 1; ELSE 0 END__CHOICE. 10 PO__CHECK REDEFINES PO__NUMBER. 20 DEPT__NO PIC 99. ; .BLANK DTR> print po .BLANK PO DEPT NUMBER .BLANK AAA 01001 BBB 21001 .BLANK DTR> store po Enter DEPT: AAA Enter PO__NUMBER: 01002 DTR> store po Enter DEPT: BBB Enter PO__NUMBER: 01003 .BLANK Validation error for field PO__NUMBER. Re-enter PO__NUMBER: 21002 .BLANK.JUSTIFY.FILL This isn't bad, but it could be better. Why store the department number and verify it, when you could change the record definition and force it to always be correct? (This time I'm assuming one prefix per department.) .BLANK.NO JUSTIFY.NO FILL DTR> show po__rec .BLANK RECORD PO__REC 01 PO__REC. 10 DEPT PIC XXX VALID IF DEPT = "AAA", "BBB", "CCC". 10 HIDEIT. 20 FILLER PIC 999. 10 REAL__STUFF REDEFINES HIDEIT. 20 DEPT__SEQ PIC 999. 10 PO__NUMBER PIC 99999 COMPUTED BY CHOICE OF DEPT = "AAA" THEN DEPT__SEQ + 01000 DEPT = "BBB" THEN DEPT__SEQ + 02000 DEPT = "CCC" THEN DEPT__SEQ + 03000 ELSE "00000" END__CHOICE. ; .BLANK We can also force the sequence number to be correct. .BLANK DTR> show store-po .BLANK PROCEDURE STORE__PO DECLARE MAXSEQ PIC 999. DECLARE TMPDEP PIC XXX. TMPDEP = FN$UPCASE(*."Department") MAXSEQ = MAX(DEPT__SEQ) OF PO WITH DEPT = TMPDEP STORE PO USING BEGIN DEPT = TMPDEP DEPT__SEQ = MAXSEQ + 1 END END__PROCEDURE .BLANK DTR> :store-po Enter Department: bbb DTR> print po .BLANK PO DEPT NUMBER .BLANK BBB 02001 BBB 02002 AAA 01001 BBB 02003 .BLANK DTR> :store-po Enter Department: bbb DTR> print po .BLANK PO DEPT NUMBER .BLANK BBB 02001 BBB 02002 AAA 01001 BBB 02003 BBB 02004 .BLANK.JUSTIFY.FILL If you try to store a department which has no records yet, you get an error message, but you also get the correct result anyway .BLANK.NO JUSTIFY.NO FILL DTR> :store-po Enter Department: ccc .BLANK Can't take MAX, MIN, or AVERAGE of zero objects. DTR> print po .BLANK PO DEPT NUMBER .BLANK BBB 02001 BBB 02002 AAA 01001 BBB 02003 BBB 02004 CCC 03001 .JUSTIFY.FILL.PARAGRAPH Something which I have run into, and which others have asked for at past Q_&A sessions, is how to get non-VMS date strings into the VMS/DTR date type, especially when you are not able to restructure the data. The following very non-standard date and time is the type of data I've actually encountered. .BLANK.NO JUSTIFY.NO FILL.comment .test page 7 $ type date.seq .BLANK 86:01:02 1003A 85:03:14 120P 86:09:29 1100P 86:11:11 332A .BLANK.comment .test page 19 DTR> show date__rec .BLANK RECORD DATE__REC 01 DATE__REC. 10 INPUT. 20 I__YEAR PIC 99. 20 FILLER PIC X. 20 I__MONTH PIC XX. 20 FILLER PIC X. 20 I__DAY PIC 99. 20 FILLER PIC X. 20 T__HOUR PIC XX. 20 I__HOUR PIC 99 COMPUTED BY T__HOUR. 20 I__MINUIT PIC 99. 20 I__AP PIC X. 10 O__DATE COMPUTED BY FN$DATE(I__DAY | "-" | I__MONTH VIA MONTH__TABLE | "-19" | I__YEAR). ; .BLANK.JUSTIFY.FILL The date part is easy: you just need a table to turn the numeric month into an upper case alphanumeric month. .BLANK.NO JUSTIFY.NO FILL DTR> show month__table .BLANK TABLE MONTH__TABLE 01 : "JAN", 02 : "FEB", 03 : "MAR", 04 : "APR", 05 : "MAY", 06 : "JUN", 07 : "JUL", 08 : "AUG", 09 : "SEP", 10 : "OCT", 11 : "NOV", 12 : "DEC" END__TABLE .BLANK.comment .test page 10 DTR> print datei .BLANK I I I T I I I O YEAR MONTH DAY HOUR HOUR MINUIT AP DATE .BLANK 86 01 02 10 10 03 A 2-Jan-1986 85 03 14 1 01 20 P 14-Mar-1985 86 09 29 11 11 00 P 29-Sep-1986 86 11 11 3 03 32 A 11-Nov-1986 .BLANK.JUSTIFY.FILL Not too bad: but when you have to add the time things get a little bit more complicated. I've shown only the hour and minuit here, but you can add seconds and fractions of a second as well. Note that I'm also using FILLER to hide the input fields, so by default only the wanted fields print. .BLANK.NO JUSTIFY.NO FILL DTR> show date__rec .BLANK RECORD DATE__REC 01 DATE__REC. 10 HIDEIT. 20 FILLER PIC X(14). 10 INPUT REDEFINES HIDEIT. 20 I__YEAR PIC 99. 20 FILLER PIC X. 20 I__MONTH PIC XX. 20 FILLER PIC X. 20 I__DAY PIC 99. 20 FILLER PIC X. 20 T__HOUR PIC XX. 20 I__HOUR PIC 99 COMPUTED BY T__HOUR. 20 I__MINUIT PIC 99. 20 I__AP PIC X. 20 A__HOUR COMPUTED BY CHOICE OF (I__AP = "A" AND T__HOUR = 12) THEN 00 (I__AP = "P" AND T__HOUR < 12) THEN T__HOUR + 12 ELSE T__HOUR END__CHOICE. 20 B__TIME COMPUTED BY ((A__HOUR * 60) + I__MINUIT) * 600000000. 10 O__DATE COMPUTED BY FN$DATE(I__DAY | "-" | I__MONTH VIA MONTH__TABLE | "-19" | I__YEAR ||| FN$TIME(B__TIME)). ; .BLANK.JUSTIFY.FILL The hard part is converting the AM/PM time to a 24 hour time, then getting it to print in the proper format. There are a number of ways it might be done depending upon the exact input format: in this case I convert the hour and minute to "clunks", then use FN$TIME to put it back to characters long enough to use FN$DATE to put the date and time back to clunks. This might seem a bit "clunky", but it's actually the easiest way to get it to work every time. The alternative is to make all of the fields "print" in the FN$DATE function the way the day and year do. (It is sometimes also possible to do this sort of thing in DTR-11: though there are no FN$--- functions, DTR-11 will handle dates with embedded times in clunks.) .BLANK.NO JUSTIFY.NO FILL.comment .test page 11 DTR> print datei .BLANK O__DATE .BLANK 2-Jan-1986 14-Mar-1985 29-Sep-1986 11-Nov-1986 7-Aug-1986 4-Jul-1976 .BLANK.comment .test page 12 DTR> for datei print i__hour, i__minuit, i__ap, fn$time(o__date) .BLANK I I I HOUR MINUIT AP FN$TIME .BLANK 10 03 A 10:03:00.00 01 20 P 13:20:00.00 11 00 P 23:00:00.00 03 32 A 03:32:00.00 12 01 A 00:01:00.00 12 58 P 12:58:00.00 .BLANK.JUSTIFY.FILL The net result is that O__DATE now contains the complete date and time in VMS format, and all of the normal DTR Boolean comparisons will work.