National UTF-8 and Code Pages
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an edition 4 is upcoming, in DIS ballot), GnuCOBOL 3.2
(28 July 2023, the latest release) and IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026), and was checked on
2026-10-20. Every GnuCOBOL example below was compiled with cobc -x -Wall (GnuCOBOL 3.2.0, fixed reference format) in a container
and run, and the output and the diagnostics shown are real. Where a feature is unfinished in GnuCOBOL the example says so and shows
the warning. The Enterprise COBOL example is labelled "z/OS; not run here": it was checked statement by statement against the
IBM manuals and was not compiled. The ISO standard is paywalled and was not read; the rules for the standard come from the vendor
manuals, which say where they follow it. National data is a COBOL 2002 feature; USAGE UTF-8 and PIC U are an IBM extension
introduced in Enterprise COBOL 6.3.
A COBOL program stores text as bytes, and the same byte means different letters in different code pages. For fifty years that was a problem only when a file moved between a mainframe (EBCDIC) and everything else (ASCII). Today the problem is also names with accents, branch libraries in other languages and JSON documents that are always UTF-8. This page explains the three families of character data a COBOL program can hold (single-byte code-page text, national UTF-16 and UTF-8), the conversions between them, the collating sequence that decides which of two strings is smaller, and how much of this GnuCOBOL 3.2 supports. The data descriptions themselves are in The PICTURE Clause and USAGE and Numeric Representation; the literal forms are in Words, Literals and Figurative Constants.
Characters, code pages and encodings
A character set is a list of characters; a coded character set (a code page) assigns each one a numeric code; the code page has a CCSID, a number from 1 to 65536 (Enterprise COBOL Programming Guide, Unicode and the encoding of language characters). A Unicode character has a code point, and an encoding (UTF-8, UTF-16) says how code points become bytes.
| Name | Encoding | Where COBOL meets it |
|---|---|---|
EBCDIC (for example CCSID 1140, the euro variant of 37) |
one byte per character, the mainframe code page |
The default for |
ASCII (for example 20127 7-bit, 819 ISO 8859-1, 1252 Windows) |
one byte per character |
The native code page of GnuCOBOL on Linux, macOS and Windows; z/OS UNIX files and data exchanged with distributed systems. |
DBCS and mixed SBCS/DBCS (for example 930, 939) |
two bytes per character, shift-out and shift-in delimiters inside a mixed string |
|
UTF-16 big-endian (CCSID 1200) |
two bytes per code unit; one unit for almost every character, two (a surrogate pair) for the rest |
|
UTF-8 (CCSID 1208) |
one to four bytes per character; the ASCII characters are unchanged |
|
Source: Enterprise COBOL 6.5 Programming Guide: Unicode and the encoding of language characters and Storage of character data
The ASCII characters have the same code in UTF-8 as in ASCII, and the letter A is the single byte X"41" there. In UTF-16 the
same letter is the two bytes NX"0041", and ABC is NX"004100420043". Enterprise COBOL keeps national data in big-endian order:
UTF-16LE data from another platform must be converted before the program treats it as national.
Collating sequence: why a sort order changes when code leaves the mainframe
A collating sequence is the order of the characters of a code page. It decides whether "a" is less than "A", where the digits
fall relative to the letters, and which character is HIGH-VALUE. The EBCDIC and ASCII orders disagree in a way that affects
programs every day:
| Order, lowest first | EBCDIC (for example CCSID 1140) | ASCII |
|---|---|---|
Letters and digits |
lowercase letters, then uppercase letters, then digits |
digits, then uppercase letters, then lowercase letters |
|
|
|
|
|
|
|
|
|
Gaps |
the 26 letters of each case are not contiguous: there are unused codes between |
letters are contiguous |
Source: Enterprise COBOL 6.5 Language Reference: EBCDIC and ASCII collating sequences and EBCDIC collating sequence
So a master file sorted on the mainframe by a key that mixes digits and letters is in a different order from the same file sorted
on a PC, and a program that compares keys, or merges two files, gives different results on the two platforms. The data is the same;
the sequence differs. The example sorts five shelf labels of the Circulation library twice: first in the native order of the
compiler (ASCII here), then with the COLLATING SEQUENCE phrase naming an alphabet defined as EBCDIC in SPECIAL-NAMES. The
table form of SORT is covered in Searching and Sorting Tables;
the file form in SORT and MERGE.
IDENTIFICATION DIVISION.
PROGRAM-ID. SHELFSORT.
* Shelf labels sorted twice: native order, then EBCDIC order.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SPECIAL-NAMES.
ALPHABET EBC IS EBCDIC.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 SHELF-TABLE.
05 SHELF-LABEL PIC X(4) OCCURS 5 TIMES
INDEXED BY S-IX.
PROCEDURE DIVISION.
MAIN-PARA.
MOVE "b-02" TO SHELF-LABEL (1)
MOVE "A-01" TO SHELF-LABEL (2)
MOVE "7-03" TO SHELF-LABEL (3)
MOVE "a-04" TO SHELF-LABEL (4)
MOVE "B-05" TO SHELF-LABEL (5)
SORT SHELF-LABEL ON ASCENDING KEY SHELF-LABEL
DISPLAY "NATIVE (ASCII) ORDER:"
PERFORM SHOW-TABLE
SORT SHELF-LABEL ON ASCENDING KEY SHELF-LABEL
COLLATING SEQUENCE IS EBC
DISPLAY "EBCDIC ORDER:"
PERFORM SHOW-TABLE
STOP RUN.
SHOW-TABLE.
PERFORM VARYING S-IX FROM 1 BY 1 UNTIL S-IX > 5
DISPLAY " " SHELF-LABEL (S-IX)
END-PERFORM.
The output is real. The digit comes first in ASCII and last in EBCDIC, and the lowercase labels come before the uppercase ones in EBCDIC only:
NATIVE (ASCII) ORDER:
7-03
A-01
B-05
a-04
b-02
EBCDIC ORDER:
a-04
b-02
A-01
B-05
7-03
Source: GnuCOBOL Programmer’s Guide: Table SORT and Enterprise COBOL 6.5 Programming Guide: Choosing alternate collating sequences
PROGRAM COLLATING SEQUENCE and ALPHABET
Two clauses set the sequence for the alphanumeric items of a whole program. The ALPHABET clause of SPECIAL-NAMES names an
alphabet; the PROGRAM COLLATING SEQUENCE clause of the OBJECT-COMPUTER paragraph says which alphabet the program uses. The
alphabet-name can also be given to the COLLATING SEQUENCE phrase of one SORT or MERGE (as above), and to CODE-SET (below) or
SYMBOLIC CHARACTERS.
An alphabet is one of:
-
STANDARD-1: the ASCII character set (ANSI INCITS X3.4); -
STANDARD-2: the ISO/IEC 646 international reference version, a 7-bit set; -
NATIVEandEBCDIC: in Enterprise COBOL both mean the EBCDIC sequence, the default when no alphabet is given; in GnuCOBOLNATIVEis the system’s own set andASCII,STANDARD-1andSTANDARD-2are treated as interchangeable; -
a literal list:
"A" ALSO "a"puts both characters at the same position,"Z" THROUGH "A"reverses a range, and a list of numbers or characters sets the order in which the characters appear. Characters that are not listed come after the listed ones, and a character may not appear twice in one alphabet.
Enterprise COBOL applies the program’s sequence to comparisons in relation conditions and condition-name conditions, to the values of
HIGH-VALUE and LOW-VALUE, to SEARCH ALL, and to SORT and MERGE unless the statement has its own COLLATING SEQUENCE phrase.
It applies only to alphabetic and alphanumeric keys: the clause does not affect comparisons that involve national or DBCS operands.
When the alphabet defines a position shared by several characters through ALSO, HIGH-VALUE is the last character listed at the
highest position and LOW-VALUE the first character listed at the lowest. Changing the EBCDIC sequence in SPECIAL-NAMES changes the
whole sequence, not only the characters named.
The next program matches a member name regardless of case, which is the textbook use of ALSO: a front-desk clerk types
ada lovelace, and the master record holds ADA LOVELACE. The comparison is between data items of the Circulation record layout
(see Copybooks and REPLACE).
IDENTIFICATION DIVISION.
PROGRAM-ID. NOCASE.
* Case-insensitive member-name match with a user alphabet.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
OBJECT-COMPUTER. CIRC-HOST
PROGRAM COLLATING SEQUENCE IS NO-CASE.
SPECIAL-NAMES.
ALPHABET NO-CASE IS
"A" ALSO "a" "B" ALSO "b" "C" ALSO "c"
"D" ALSO "d" "E" ALSO "e" "F" ALSO "f"
"G" ALSO "g" "H" ALSO "h" "I" ALSO "i"
"J" ALSO "j" "K" ALSO "k" "L" ALSO "l"
"M" ALSO "m" "N" ALSO "n" "O" ALSO "o"
"P" ALSO "p" "Q" ALSO "q" "R" ALSO "r"
"S" ALSO "s" "T" ALSO "t" "U" ALSO "u"
"V" ALSO "v" "W" ALSO "w" "X" ALSO "x"
"Y" ALSO "y" "Z" ALSO "z".
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY MEMBREC.
01 WS-SEARCH-NAME PIC X(30) VALUE "ada lovelace".
PROCEDURE DIVISION.
MAIN-PARA.
MOVE 100 TO MEMBER-ID
MOVE "ADA LOVELACE" TO MEMBER-NAME
IF MEMBER-NAME = WS-SEARCH-NAME
DISPLAY "SAME MEMBER: " MEMBER-ID
ELSE
DISPLAY "DIFFERENT NAME"
END-IF
STOP RUN.
SAME MEMBER: 000100
Source: Enterprise COBOL 6.5 Language Reference: ALPHABET clause and Specifying the collating sequence
The same compile also passes with -std=ibm. The IBM manual shows the same technique, with a complete alphabet, as the way to make
sorting and comparing treat upper and lower case alike.
Switching the whole program without editing it
GnuCOBOL can change its default sequence from the command line. cobc --help lists
-fdefault-colseq=[ASCII|EBCDIC|NATIVE] ("define default collating sequence"); the option is not described in the HTML manual of 3.2.
The program has no ALPHABET at all:
IDENTIFICATION DIVISION.
PROGRAM-ID. COLSEQ.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 W-LOWER PIC X VALUE "a".
01 W-UPPER PIC X VALUE "A".
01 W-DIGIT PIC X VALUE "1".
PROCEDURE DIVISION.
MAIN-PARA.
IF W-LOWER < W-UPPER
DISPLAY "a sorts before A"
ELSE
DISPLAY "a sorts after A"
END-IF
IF W-DIGIT < W-UPPER
DISPLAY "1 sorts before A"
ELSE
DISPLAY "1 sorts after A"
END-IF
STOP RUN.
Compiled as cobc -x -Wall colseqflag.cob the output is the ASCII one; compiled with -fdefault-colseq=EBCDIC it is the mainframe one:
a sorts after A
1 sorts before A
a sorts before A
1 sorts after A
Source: GnuCOBOL manual: Compiler options (the -fdefault-colseq option was found with cobc --help)
GnuCOBOL 3.2 pitfalls found while testing
Two behaviours of GnuCOBOL 3.2.0 differ from what its manual and the IBM manual describe, and are worth knowing before trusting a custom alphabet.
-
FUNCTION ORDis documented as the ordinal position of a character in the program’s collating sequence. In 3.2.0 it ignores the alphabet: withPROGRAM COLLATING SEQUENCEset toEBCDICor to the case-folding alphabet above,FUNCTION ORD("A")still returns 66, the native position plus one, while relation conditions andSORTdo honour the alphabet. UseORDonly for the native order. -
A relation condition between two literals is evaluated by the compiler, not at run time, and the evaluation ignores
ALSO. The program below tests the same pair of characters twice, once through data items and once through literals:
IDENTIFICATION DIVISION.
PROGRAM-ID. NOFOLD.
* Literal-to-literal test under a user alphabet (3.2 quirk).
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
OBJECT-COMPUTER. CIRC-HOST
PROGRAM COLLATING SEQUENCE IS NO-CASE.
SPECIAL-NAMES.
ALPHABET NO-CASE IS "A" ALSO "a".
DATA DIVISION.
WORKING-STORAGE SECTION.
01 W-LOWER PIC X VALUE "a".
01 W-UPPER PIC X VALUE "A".
PROCEDURE DIVISION.
MAIN-PARA.
IF W-LOWER = W-UPPER
DISPLAY "ITEMS: a = A"
END-IF
IF "a" = "A"
DISPLAY "LITERALS: a = A"
ELSE
DISPLAY "LITERALS: a NOT = A"
END-IF
STOP RUN.
This example deliberately warns. The compiler says so, and the two answers disagree:
nocasefold.cob: in paragraph 'MAIN-PARA':
nocasefold.cob:19: warning: expression 'a' EQUALS 'A' is always FALSE [-Wconstant-expression]
ITEMS: a = A
LITERALS: a NOT = A
Test with data items, as the first example does. A literal compared with a literal is rarely useful anyway.
Source: GnuCOBOL Programmer’s Guide: Alphabet-Name Clause and ORD
Files and code pages: CODE-SET and translation tables
When a file crosses the boundary the data must be translated, not only the order. On z/OS the translation is usually done by the
transfer tool (for example a text-mode file transfer that converts between EBCDIC and ASCII). Enterprise COBOL 6.5 can also convert
tagged z/OS UNIX source files automatically when the file’s code page does not match the CODEPAGE option of the compile job; that
covers program text, not the data files a program reads. Inside a program the standard tool is
the CODE-SET clause of the FD: it names an alphabet, and the runtime translates the record data between the program’s native
character set and that alphabet as it reads or writes. GnuCOBOL accepts it for sequential and line sequential files.
The next program writes one Circulation-style record through a file described with CODE-SET IS EBC, then reads the same file back
through a second description with no CODE-SET and shows the raw bytes:
IDENTIFICATION DIVISION.
PROGRAM-ID. EBCFILE.
* Write a record in EBCDIC with CODE-SET, read it back raw.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SPECIAL-NAMES.
ALPHABET EBC IS EBCDIC.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT EXPORT-FILE ASSIGN TO "ebcdic.dat"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT RAW-FILE ASSIGN TO "ebcdic.dat"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD EXPORT-FILE CODE-SET IS EBC.
01 EXPORT-REC PIC X(8).
FD RAW-FILE.
01 RAW-REC PIC X(8).
PROCEDURE DIVISION.
MAIN-PARA.
OPEN OUTPUT EXPORT-FILE
MOVE "ABC 123" TO EXPORT-REC
MOVE X"E9" TO EXPORT-REC (8:1)
WRITE EXPORT-REC
CLOSE EXPORT-FILE
OPEN INPUT RAW-FILE
READ RAW-FILE
DISPLAY "RAW BYTES: " FUNCTION HEX-OF(RAW-REC)
CLOSE RAW-FILE
STOP RUN.
The compiler labels CODE-SET unfinished in 3.2 (this example warns, and the warning is real), but the translation happens:
A, B, C, the space and 1, 2, 3 become the EBCDIC bytes C1 C2 C3 40 F1 F2 F3.
ebcdicfile.cob:16: warning: handling of CODE-SET is unfinished; implementation is likely to be changed [-Wunfinished]
RAW BYTES: C1C2C340F1F2F33A
The last byte is the Latin-1 letter X"E9" (é), a character outside the 7-bit set where the tables disagree. The default
translation table of GnuCOBOL maps it to 3A; running the same source with -febcdic-table=ebcdic500_latin1, one of the tables in
the config directory of the installation, gives 51, the EBCDIC 500 code of that letter:
RAW BYTES: C1C2C340F1F2F351
The manual of 3.2 lists the values DEFAULT, RESTRICTED-GC, IBM and GCOS for -febcdic-table; the table name above was accepted
and changed the output. A table that does not match the actual code page of the partner system corrupts the accented letters and
leaves the plain ones intact, so the corruption goes unnoticed in tests that use only plain ASCII names. The related option
-fsign=EBCDIC for the sign nibble of DISPLAY numbers is in USAGE and Numeric Representation.
National data: USAGE NATIONAL and PIC N (UTF-16)
National data stores each character as a UTF-16 code unit in big-endian order (CCSID 1200). It was added by COBOL 2002; in Enterprise COBOL it is the older of the two Unicode types. Its pieces are:
-
USAGE NATIONALwith aPICTUREofNsymbols defines a national item, oneNper character (two bytes for almost every character, four bytes for a character outside the Basic Multilingual Plane, which uses two code units). With aPICTUREofNsymbols and at least oneB,0or/it is national-edited. With a numeric-editedPICTUREsuch as-$999.99andUSAGE NATIONALit is a national numeric-edited item. National decimal and national floating-point items exist too. -
The
NSYMBOLcompiler option decides what a barePIC Nand theN"..."prefix mean.NSYMBOL(NATIONAL)(the default in 6.5) makesNnational, as the 2002 standard says;NSYMBOL(DBCS)makes it a DBCS item and a DBCS literal, for programs written before Unicode support. -
National literals are
N"text"orN'text', andNX"hhhh..."is the hexadecimal form.ALL national-literaland the figurative constantsSPACE,ZERO,QUOTE,HIGH-VALUEandLOW-VALUEtake national values in national contexts. -
GROUP-USAGE NATIONALmakes a group that contains only national items behave like one elementary national item.
Alphabetic and alphanumeric items, DBCS items and integers are converted to national implicitly by MOVE, and national is
converted to numeric by MOVE. Comparisons convert in the same way. The compiler does this using the CODEPAGE value; the
program has to take care when the data is in another code page (see below). Assigning X"C1C2" to a national item moves the
characters that the bytes represent in the CODEPAGE, not the bytes C1C2, which is why the manual recommends not using
alphanumeric hexadecimal literals where a national literal is meant.
Source: Enterprise COBOL 6.5 Programming Guide: Using national data (Unicode) in COBOL, Defining national data items, Using national literals and NSYMBOL
Converting explicitly: NATIONAL-OF and DISPLAY-OF
Two intrinsic functions convert on purpose, with an optional second argument that names the other code page by its CCSID:
-
FUNCTION NATIONAL-OF(argument-1 [argument-2])returns a national string. The first argument is alphabetic, alphanumeric, UTF-8 or DBCS; the second names the source code page. Without it the source is theCODEPAGEvalue, or 1208 when the first argument is UTF-8. -
FUNCTION DISPLAY-OF(argument-1 [argument-2])returns an alphanumeric string. The first argument is national or UTF-8; the second names the output code page (EBCDIC, ASCII, EUC or UTF-8). Without it the output is theCODEPAGEvalue. Specify 1208 to get UTF-8 bytes in aPIC Xitem.
A character that has no counterpart in the target code page does not raise an exception: it becomes a substitution character.
NATIONAL-OF produces X"001A" for a single-byte source character and X"FFFD" for a multi-byte one. DISPLAY-OF produces
X"3F" for single-byte EBCDIC, X"7F" for single-byte ASCII and Windows code pages, and for a UTF-8 output X"1A" or X"EFBFBD".
What can fail is a missing code-page table: if z/OS Unicode conversion services are not installed with the table for the
conversion, the call ends with a severe runtime error (a severity-3 Language Environment condition).
The common mistake is a conversion with the wrong code page, which succeeds silently. The Programming Guide’s own example is a
file in Greek (CCSID 875): a plain MOVE converts it as if it were in the program’s code page, so the plain letters survive and the
Greek letters turn into other characters; NATIONAL-OF(field 875) converts it correctly. If you override the code page in
DISPLAY-OF, do not use the result in an operation with an implicit conversion (an assignment to a national item, a comparison with
one): that conversion assumes the CODEPAGE code page and would convert the data a second time.
UTF-8 data: USAGE UTF-8 and PIC U (Enterprise COBOL 6.3 and later)
UTF-16 is convenient because one position is one character, but the world exchanges UTF-8 (JSON, HTML, Db2 Unicode tables, MQ
messages). Enterprise COBOL 6.3 added native UTF-8 items, and the 6.5 What’s New page lists further UTF-8 enhancements: moves from
a UTF-8 sender to numeric items, comparisons with numeric operands, and ULENGTH accepting UTF-8 arguments. There are three kinds of UTF-8
item, all declared with the U picture symbol (with USAGE UTF-8 implied or explicit):
| Kind | Declaration | Storage and behaviour |
|---|---|---|
Fixed character-length |
|
Holds 30 characters and reserves 4 bytes for each (120 bytes) because a character can take up to four. Padded with UTF-8 blanks |
Fixed byte-length |
|
Exactly 30 bytes; the number of characters varies. For compatibility with Db2, DFSORT and MQ, which have fixed byte-length UTF-8 strings. Use it as a Db2 host variable and as a sort key. |
Dynamic length |
|
No fixed character count; the byte length is that of the current content. Not allowed in a file record. See Pointers and Dynamic Storage. |
The rules around UTF-8 items:
-
A UTF-8 item may be elementary or sit in a group, including a file record (except a dynamic-length one); a group declared with
GROUP-USAGE UTF-8can contain only fixed byte-length UTF-8 items. A condition variable of an 88-level can be UTF-8. -
There is no UTF-8 edited, decimal or floating-point item:
USAGE UTF-8requires theUsymbol, so a numeric item cannot be UTF-8. -
The
VALUEclause takes alphanumeric, national or UTF-8 literals; an alphanumeric literal is converted from theCODEPAGEto UTF-8. -
UTF-8 literals are
U"text"orU'text'. The text is converted from EBCDIC, and may contain Unicode escapes:\ufollowed by four hexadecimal digits for a code point of the Basic Multilingual Plane, and\U00followed by six hexadecimal digits for any code point.U'caf\u00E9'is the word café. A backslash is written\\when it must be literal. A basic literal may produce at most 160 bytes after conversion. The hexadecimal form isUX"hh...", from 2 to 320 digits, and the bytes are checked to be valid UTF-8. -
Reference modification of a UTF-8 item counts characters, not bytes, so
U-NAME (3:2)is the third and fourth code points wherever their bytes lie. The compiler cannot know the byte offset in advance, so such expressions cost a scan at run time. -
Comparisons are byte by byte in binary, which gives the same order as comparing the national forms of the same text when all the code points are in the Basic Multilingual Plane. A UTF-8 item can be compared with alphabetic, alphanumeric, national and UTF-8 operands.
HIGH-VALUEfor UTF-8 isUX'F48FBFBF'(the code point 10FFFF) and does not compare equal toHIGH-VALUEof an alphanumeric or national item, because the value is not translated between the data types. -
MOVEconverts among alphabetic, alphanumeric, national and UTF-8; padding and truncation always occur at a character boundary.MOVEfrom UTF-8 to alphanumeric is not allowed: useDISPLAY-OF. -
Statements that support UTF-8 include
IF,EVALUATE,INITIALIZE(the default is a UTF-8 space),MOVE,STRING,UNSTRING,SORT,MERGE,JSON GENERATE,JSON PARSE,ALLOCATEandFREE.ACCEPT,INSPECT,XML GENERATEandXML PARSEdo not; move the item to a national item and use that. The Db2 precompiler cannot be used with UTF-8 items, and a UTF-8 host variable is always taken as CCSID 1208 by the Db2 coprocessor.
Source: Enterprise COBOL 6.5 Programming Guide: Using UTF-8 data (Unicode) in COBOL, Defining UTF-8 data items, Using UTF-8 literals, COBOL statements and UTF-8 data items, Db2 considerations and What’s new in Enterprise COBOL for z/OS 6.5
The Unicode intrinsic functions
When the data is kept as UTF-8 in either a UTF-8 item or a PIC X item that holds UTF-8, these functions work in characters:
| Function | Result |
|---|---|
|
the number of characters (code points) in the argument, which may be alphanumeric, UTF-8 or national; optionally of a byte range. Combining characters are counted individually, so ä written as one code point counts 1 and as |
|
the substring starting at character start with length characters. For |
|
the byte position at which the n-th character starts (zero if n is out of range). |
|
the width in bytes of the n-th character. |
|
zero if the data is valid UTF-8 (or UTF-16), otherwise the position of the first invalid element. |
|
tells whether a valid string contains a supplementary code point (above 65535), which needs two UTF-16 units; check it before converting to national if one position must be one character. |
A conversion example (z/OS; not run here)
The sketch below converts the member name of the Circulation MEMBREC layout, held as EBCDIC text, to the two Unicode forms, and takes
the character count and the first character. It was checked against the Enterprise COBOL 6.5 manuals above and was not compiled
(GnuCOBOL 3.2 cannot compile it, as the next section shows). The CBL statement sets the code page to the default 1140.
CBL CODEPAGE(1140),NSYMBOL(NATIONAL)
IDENTIFICATION DIVISION.
PROGRAM-ID. MEMBCONV.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY MEMBREC.
01 WS-NAME-NAT PIC N(30) USAGE NATIONAL.
01 WS-NAME-U8 PIC U(30) USAGE UTF-8.
01 WS-NAME-WIRE PIC X(120).
01 WS-INITIAL PIC U(1) USAGE UTF-8.
01 WS-CHARS PIC 9(4) BINARY.
PROCEDURE DIVISION.
MAIN-PARA.
* EBCDIC (CCSID 1140) to UTF-16, then UTF-16 to UTF-8
MOVE FUNCTION NATIONAL-OF(MEMBER-NAME) TO WS-NAME-NAT
MOVE WS-NAME-NAT TO WS-NAME-U8
* the same result in one implicit conversion
MOVE MEMBER-NAME TO WS-NAME-U8
* UTF-8 bytes in a PIC X item, for a wire format
MOVE FUNCTION DISPLAY-OF(WS-NAME-NAT 1208)
TO WS-NAME-WIRE
* characters, not bytes
COMPUTE WS-CHARS = FUNCTION ULENGTH
(FUNCTION TRIM(WS-NAME-U8 TRAILING))
MOVE FUNCTION USUBSTR(WS-NAME-U8 1 1) TO WS-INITIAL
STOP RUN.
For a Db2 column of type CHAR(30) in a Unicode table, the host variable would be 01 HV-NAME PIC U BYTE-LENGTH 30., whose byte
length must match the column to avoid unexpected truncation or padding.
Source: Enterprise COBOL 6.5 Programming Guide: Converting to or from UTF-8 (Unicode) representation, Converting national to UTF-8 and UTF-8 to alphanumeric (DISPLAY-OF) and Processing UTF-8 data using UTF-16 (national) data types
The figure shows the conversion routes of Enterprise COBOL 6.5. A MOVE from alphanumeric converts using the CODEPAGE; to get
alphanumeric back from national or UTF-8, DISPLAY-OF is required, and its code page argument chooses whether the result is
EBCDIC, ASCII or UTF-8 bytes.
The CODEPAGE compiler option
CODEPAGE(ccsid) (abbreviation CP) names the EBCDIC code page for compile-time and run-time operations that depend on
the encoding. The default is CODEPAGE(1140), the same as CCSID 37 plus the euro sign. It decides:
-
the encoding of alphanumeric, national, UTF-8 and DBCS literals in the source, and the default encoding of alphanumeric and DBCS data at run time;
-
conversions of alphanumeric data to or from national and UTF-8 in moves, comparisons,
NATIONAL-OFandDISPLAY-OFwhen the call gives no code page; -
DBCS names and encodings of XML documents,
INVOKEand class and method definitions, SQL host variables whenSQLCCSIDis in effect, and the source text ofEXEC SQL.
It does not change national items (always UTF-16 big-endian, CCSID 1200), UTF-8 items (always 1208), or the characters of the basic
COBOL set: the compiler always reads the dollar sign as X"5B" and the quotation mark as X"7F", as in CCSID 1140. Programs
that use DBCS names, DISPLAY-1 items or DBCS literals must compile with one of the mixed EBCDIC multibyte CCSIDs (for example 930,
939, 933, 935 or 937), and a program compiled with the TEST option must use the CCSID of the source. NATIONAL-OF, DISPLAY-OF,
XML processing and a few other operations can override the option with an explicit CCSID, but an option set wrongly for the
whole program is the more frequent cause of garbled accents on a mainframe: the source was saved in one code page and
compiled with another, so every literal is translated wrongly while the data is fine. Compiler options are collected in
Performance and Compiler Options.
What GnuCOBOL 3.2 supports
GnuCOBOL has no code-page option: the source, the data and the screen use the bytes of the system, which is UTF-8 on a modern Linux
or macOS terminal. Its support for national and UTF-8 data is partial and, in 3.2, labelled unfinished by the compiler itself. The
Programmer’s Guide says that national support is "currently only partially implemented" and that USAGE NATIONAL is "syntactically
recognized" but not supported, and cobc --list-intrinsics marks NATIONAL-OF, DISPLAY-OF and CHAR-NATIONAL as not implemented. This
table shows what GnuCOBOL 3.2.0 does in practice, found by compiling and running the programs on this page:
| Feature | Enterprise COBOL 6.5 | GnuCOBOL 3.2.0 |
|---|---|---|
|
8 bytes of UTF-16BE, padded with |
accepted with a warning that national handling is unfinished; 8 bytes, but padded with the bytes |
|
national literals |
accepted with the same kind of warning ( |
|
16 bytes of UTF-8, padded with |
accepted with no warning; 16 bytes, padded with |
|
UTF-8 item |
accepted with a warning that handling of |
|
conversion functions |
compile error: "is not implemented" |
|
Unicode functions |
not in the function list of |
|
converts using the code page |
compile error: "invalid MOVE statement" |
|
translation of file data |
works, with an "unfinished" warning |
Collating sequence, |
supported |
supported for alphanumeric data; see the pitfalls above |
The next program shows the two storage items on a real compile. The warnings are part of the evidence; the program is a probe, not a recommendation:
IDENTIFICATION DIVISION.
PROGRAM-ID. NATSTAT.
* What GnuCOBOL 3.2 does with PIC N and PIC U items.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-NAT PIC N(4).
01 WS-UTF PIC U(4).
PROCEDURE DIVISION.
MAIN-PARA.
MOVE N"AB" TO WS-NAT
DISPLAY "N BYTES " FUNCTION BYTE-LENGTH(WS-NAT)
DISPLAY "N HEX " FUNCTION HEX-OF(WS-NAT)
MOVE "José" TO WS-UTF
DISPLAY "U BYTES " FUNCTION BYTE-LENGTH(WS-UTF)
DISPLAY "U HEX " FUNCTION HEX-OF(WS-UTF)
STOP RUN.
natstatus.cob:6: warning: handling of USAGE NATIONAL is unfinished; implementation is likely to be changed [-Wunfinished]
natstatus.cob: in paragraph 'MAIN-PARA':
natstatus.cob:10: warning: handling of national literal is unfinished; implementation is likely to be changed [-Wunfinished]
N BYTES 8
N HEX 0041004220202020
U BYTES 16
U HEX 4A6F73C3A92020202020202020202020
The national item holds 0041 0042 correctly but its blanks are two-byte 2020 pairs, not the UTF-16 space 0020, which is a
difference from the IBM storage layout that a program exchanging national data would notice. The PIC U(4) item holds José
in the five bytes 4A 6F 73 C3 A9 and then blanks: it takes the bytes of the source as they come, and nothing validates them.
Code that calls the IBM Unicode functions does not compile. This example is deliberately wrong for GnuCOBOL 3.2:
IDENTIFICATION DIVISION.
PROGRAM-ID. NATFAIL.
* Deliberately wrong for GnuCOBOL 3.2: IBM Unicode functions.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-NAME PIC X(20) VALUE "José Müller".
01 WS-NAT PIC N(20).
PROCEDURE DIVISION.
MAIN-PARA.
MOVE FUNCTION NATIONAL-OF(WS-NAME) TO WS-NAT
MOVE WS-NAT TO WS-NAME
DISPLAY FUNCTION ULENGTH(WS-NAME)
DISPLAY FUNCTION USUBSTR(WS-NAME 1 4)
STOP RUN.
The real messages (the cobc output also prints the source line after each, left out here):
natfail.cob:7: warning: handling of USAGE NATIONAL is unfinished; implementation is likely to be changed [-Wunfinished]
natfail.cob:10: error: FUNCTION 'NATIONAL-OF' is not implemented
natfail.cob:11: error: invalid MOVE statement
natfail.cob:12: error: FUNCTION 'ULENGTH' unknown
natfail.cob:13: error: FUNCTION 'USUBSTR' unknown
UTF-8 text in ordinary PIC X items
The portable way to handle accents on GnuCOBOL is the one programmers used before Unicode support: keep the text in PIC X,
accept that it is UTF-8 when the terminal and the files are, and count bytes carefully. A byte of the UTF-8 encoding is a
continuation byte when its value is from 128 to 191 (X"80" to X"BF"); every other byte starts a character. So the number of characters is
the number of non-continuation bytes, and a cut must not fall in front of a continuation byte. The next program uses the
MEMBER-NAME field of the Circulation layout, shows that LENGTH counts bytes, counts characters, demonstrates what a plain
reference modification does to the text, and cuts it safely:
IDENTIFICATION DIVISION.
PROGRAM-ID. UTF8BYTE.
* UTF-8 text in a PIC X item: bytes are not characters.
DATA DIVISION.
WORKING-STORAGE SECTION.
COPY MEMBREC.
01 WS-I PIC 9(3).
01 WS-BYTES PIC 9(3).
01 WS-CHARS PIC 9(3).
01 WS-VAL PIC 9(3).
01 WS-LIMIT PIC 9(3) VALUE 4.
01 WS-SHORT PIC X(4).
PROCEDURE DIVISION.
MAIN-PARA.
MOVE 100 TO MEMBER-ID
MOVE "José Müller" TO MEMBER-NAME
COMPUTE WS-BYTES = FUNCTION LENGTH
(FUNCTION TRIM(MEMBER-NAME TRAILING))
MOVE ZERO TO WS-CHARS
PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > WS-BYTES
COMPUTE WS-VAL = FUNCTION ORD(MEMBER-NAME (WS-I:1)) - 1
IF WS-VAL < 128 OR WS-VAL > 191
ADD 1 TO WS-CHARS
END-IF
END-PERFORM
DISPLAY "BYTES: " WS-BYTES " CHARACTERS: " WS-CHARS
MOVE MEMBER-NAME (1:WS-LIMIT) TO WS-SHORT
DISPLAY "FIRST 4 BYTES: " FUNCTION HEX-OF(WS-SHORT)
PERFORM UNTIL WS-LIMIT = 0
COMPUTE WS-VAL =
FUNCTION ORD(MEMBER-NAME (WS-LIMIT + 1:1)) - 1
IF WS-VAL < 128 OR WS-VAL > 191
EXIT PERFORM
END-IF
SUBTRACT 1 FROM WS-LIMIT
END-PERFORM
MOVE SPACES TO WS-SHORT
MOVE MEMBER-NAME (1:WS-LIMIT) TO WS-SHORT
DISPLAY "SAFE CUT AT " WS-LIMIT " BYTES: [" WS-SHORT "]"
DISPLAY "UPPER-CASE: "
FUNCTION UPPER-CASE(MEMBER-NAME (1:WS-BYTES))
STOP RUN.
BYTES: 013 CHARACTERS: 011
FIRST 4 BYTES: 4A6F73C3
SAFE CUT AT 003 BYTES: [Jos ]
UPPER-CASE: JOSé MüLLER
The name José Müller has 11 characters but 13 bytes. Cutting at four bytes keeps 4A 6F 73 C3: the C3 is the first half of
é (C3 A9), and a file or a report that receives it holds an invalid UTF-8 sequence. The safe cut backs off to three bytes. The
last line shows the third pitfall: UPPER-CASE and LOWER-CASE work byte by byte, so the accented letters keep their case.
The same limits apply to the fixed-width layout itself: a PIC X(30) field holds 30 bytes, not 30 characters, so a name of 30
accented letters does not fit, and a report column built with PIC X loses alignment by one position for each accented letter.
FUNCTION LOCALE-COMPARE is listed by cobc --list-intrinsics as implemented for comparisons that follow the locale; it was
not exercised here.
Choosing a representation
| Situation | On Enterprise COBOL for z/OS | On GnuCOBOL 3.2 |
|---|---|---|
Records and files shared with other mainframe programs |
|
|
Text in many languages, processed character by character |
|
keep UTF-8 in |
Data exchanged as UTF-8 (JSON, HTML, Db2 Unicode, MQ) |
|
|
A statement that does not support UTF-8 ( |
move to a national item, use the statement, convert back |
|
A different sort order than the machine’s |
|
|
JSON generation and parsing in UTF-8 are in JSON GENERATE and PARSE; the Db2 coprocessor is in Embedded SQL and Db2; the file organizations that depend on key order are in Indexed Files and VSAM.
Where the book is outdated
The book consulted for this section (Taulli, Modern Mainframe Development, 2022, written for Enterprise COBOL 6.3) presents text as
PIC X data and does not cover national or UTF-8 data; nothing is copied from it. Three points follow from that:
-
Treating every character as one byte is the 1985 model. Since COBOL 2002 the standard has national data, and since Enterprise COBOL 6.3 IBM has
USAGE UTF-8, which the 6.5 release extends; a book that predates or omits these will not prepare you for a name with an accent. -
The collating sequence is not a property of the language but of the code page: a program that behaves on a mainframe because its keys are in EBCDIC order may change its results when it is recompiled with GnuCOBOL or moved to a PC, unless the sequence is set explicitly as shown above.
-
GnuCOBOL is often presented as a drop-in way to practice mainframe COBOL; for national and UTF-8 data in 3.2 it is not one, as the table above shows.
References
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Processing data in an international environment
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Unicode and the encoding of language characters
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Using national data (Unicode) in COBOL
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Using UTF-8 data (Unicode) in COBOL
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Specifying the collating sequence
-
Enterprise COBOL for z/OS 6.5 Language Reference: ALPHABET clause
-
Enterprise COBOL for z/OS 6.5 Language Reference: EBCDIC and ASCII collating sequences
-
Enterprise COBOL for z/OS 6.5 Language Reference: NATIONAL-OF
-
Enterprise COBOL for z/OS 6.5 Language Reference: DISPLAY-OF
-
What’s new in Enterprise COBOL for z/OS 6.5 and COBOL 6.5 with PTFs installed
-
Modern Mainframe Development (Tom Taulli, O’Reilly Media, 2022), consulted as a narrative