Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Format Type

The Format type represents a SAS display format or informat. It provides parsing and construction of format specifications.

Overview

SAS formats control how values are displayed or read:

FormatDescriptionExample Output
DATE9.Date format15JAN2024
8.2Numeric with decimals123.45
$CHAR200.Character formatHello World
BEST12.Best numeric representation123456789012

Creating Formats

Parsing from String

use xportrs::Format;
fn main() -> xportrs::Result<()> {
// Date format
let date_fmt = Format::parse("DATE9.")?;
assert_eq!(date_fmt.name(), "DATE");
assert_eq!(date_fmt.length(), 9);

// Numeric format with decimals
let num_fmt = Format::parse("8.2")?;
assert_eq!(num_fmt.name(), "");
assert_eq!(num_fmt.length(), 8);
assert_eq!(num_fmt.decimals(), 2);

// Character format
let char_fmt = Format::parse("$CHAR200.")?;
assert_eq!(char_fmt.name(), "$CHAR");
assert_eq!(char_fmt.length(), 200);
assert!(char_fmt.is_character());
Ok(())
}

Using Constructors

use xportrs::Format;
// Numeric format
let num = Format::numeric(8, 2);
assert_eq!(num.length(), 8);
assert_eq!(num.decimals(), 2);

// Character format
let char_fmt = Format::character(200);
assert_eq!(char_fmt.name(), "$CHAR");
assert_eq!(char_fmt.length(), 200);

From NAMESTR Fields

When reading XPT files, formats are reconstructed from NAMESTR fields:

use xportrs::Format;
// Reconstruct from XPT fields
let format = Format::from_namestr(
    "DATE    ",  // nform (8 bytes, space-padded)
    9,           // nfl (format length)
    0,           // nfd (format decimals)
    1,           // nfj (justification: 0=left, 1=right)
);

assert_eq!(format.name(), "DATE");
assert_eq!(format.length(), 9);

Format Properties

use xportrs::Format;
fn main() -> xportrs::Result<()> {
let format = Format::parse("$CHAR200.")?;

// Format name (may include $ prefix)
let name: &str = format.name();  // "$CHAR"

// Name without $ prefix
let stripped: &str = format.name_without_prefix();  // "CHAR"

// Total display width
let length: usize = format.length();  // 200

// Decimal places
let decimals: usize = format.decimals();  // 0

// Is it a character format?
let is_char: bool = format.is_character();  // true

// Display representation
println!("{}", format);  // "$CHAR200."
Ok(())
}

Common Format Patterns

Date Formats

use xportrs::Format;
fn main() -> xportrs::Result<()> {
// Standard date formats
let date9 = Format::parse("DATE9.")?;      // 15JAN2024
let date7 = Format::parse("DATE7.")?;      // 15JAN24
let yymmdd = Format::parse("YYMMDD10.")?;  // 2024-01-15
let e8601 = Format::parse("E8601DA10.")?;  // 2024-01-15
Ok(())
}

DateTime Formats

use xportrs::Format;
fn main() -> xportrs::Result<()> {
let datetime = Format::parse("DATETIME20.")?;  // 15JAN2024:14:30:00
let e8601dt = Format::parse("E8601DT19.")?;    // 2024-01-15T14:30:00
Ok(())
}

Numeric Formats

use xportrs::Format;
fn main() -> xportrs::Result<()> {
// Bare numeric format
let bare = Format::parse("8.")?;    // 8 characters, 0 decimals
let decimal = Format::parse("8.2")?;  // 8 characters, 2 decimals

// Named numeric formats
let best = Format::parse("BEST12.")?;    // Best representation
let comma = Format::parse("COMMA10.2")?; // Comma-separated
Ok(())
}

Character Formats

use xportrs::Format;
fn main() -> xportrs::Result<()> {
// Character formats start with $
let char200 = Format::parse("$CHAR200.")?;
let char40 = Format::parse("$40.")?;  // Shorthand for $CHAR40.
Ok(())
}

Using Formats with Columns

Setting Format on Column

use xportrs::{Column, ColumnData, Format};
fn main() -> xportrs::Result<()> {
let data = ColumnData::F64(vec![Some(1.0)]);
// Using Format object
let col = Column::new("AESTDTC", data.clone())
    .with_format(Format::character(19));

// Parsing from string
let col = Column::new("AESTDT", data.clone())
    .with_format_str("DATE9.")?;

// Using constructor
let col = Column::new("VALUE", data)
    .with_format(Format::numeric(8, 2));
Ok(())
}

Setting Informat

Informats control how data is read:

use xportrs::{Column, ColumnData, Format};
fn main() -> xportrs::Result<()> {
let data = ColumnData::F64(vec![Some(1.0)]);
let col = Column::new("RAWDATE", data)
    .with_informat(Format::parse("DATE9.")?);
Ok(())
}

Format in XPT Files

When written to XPT, formats are stored in the NAMESTR record:

FieldSizeDescription
nform8 bytesFormat name (space-padded)
nfl2 bytesFormat length
nfd2 bytesFormat decimals
nfj2 bytesJustification (0=left, 1=right)
use xportrs::{Column, ColumnData, Format, Xpt};
fn main() -> xportrs::Result<()> {
let col = Column::new("AESTDT", ColumnData::F64(vec![Some(23391.0)]))
    .with_format_str("DATE9.")?;

// When written, NAMESTR will contain:
// nform = "DATE    "
// nfl = 9
// nfd = 0
// nfj = 1 (right-justified)
Ok(())
}

Format Validation

Invalid format strings return errors:

use xportrs::Format;
// Missing period
let result = Format::parse("DATE9");
assert!(result.is_err());

// Invalid syntax
let result = Format::parse("INVALID");
assert!(result.is_err());

// Empty string
let result = Format::parse("");
assert!(result.is_err());

Display and Debug

use xportrs::Format;
fn main() -> xportrs::Result<()> {
let format = Format::parse("DATE9.")?;

// Display: canonical format string
println!("{}", format);  // "DATE9."

// Debug: detailed representation
println!("{:?}", format);  // Format { name: "DATE", length: 9, ... }
Ok(())
}

Common Traits

use xportrs::Format;
fn main() -> xportrs::Result<()> {
let format = Format::parse("DATE9.")?;

// Clone
let format2 = format.clone();

// PartialEq
assert_eq!(Format::parse("DATE9.")?, Format::parse("DATE9.")?);

// Debug
println!("{:?}", format);

// Display
println!("{}", format);
Ok(())
}

FDA Format Recommendations

[!TIP] The FDA recommends avoiding custom SAS formats. Use standard formats like DATE9., DATETIME20., or simple numeric formats.

Recommended formats:

TypeRecommended Format
Date (numeric)DATE9.
DateTime (numeric)DATETIME20.
Time (numeric)TIME8.
Numeric8., 8.2
Character$CHAR200., $40.

Avoid:

  • Custom user-defined formats
  • Formats requiring external catalogs
  • Regional-specific formats