DEV Community

InterSystems Developer for InterSystems

Posted on Originally published at community.intersystems.com

DTL Deep Dive: Conditionals on Length, ReplaceStr, Special Character Cleaning, and Timestamp Formatting

Hi Community,

Data Transformation Language (DTL) is one of those tools that appears deceptively simple at first—drag a source field, drop it on a target, and you are done. However, once you start working on real-world integration projects, you quickly realize that raw field mapping only gets you so far. The true challenge begins when you need to validate field lengths, clean up incoming strings, strip unwanted characters from IDs, or produce timestamps in a specific format required by a downstream system.

In this article, we will move beyond the basics and explore four practical DTL scenarios that occur regularly in real-world integrations. Each section is built around a single sample HL7 message, allowing you to follow along in your own environment and see the results for yourself.

What We Will Cover:

  • Creating a DTL from scratch in the Management Portal.
  • Conditional logic based on field length ($Length, $Extract).
  • Stripping and replacing text using ReplaceStr.
  • Stripping special characters from a Patient ID with chained ReplaceStr.
  • Reformatting HL7 timestamps into a custom display format.

Prerequisites

  • InterSystems IRIS or HealthShare with an Interoperability-enabled namespace.
  • Access to the Management Portal.
  • Basic familiarity with HL7 v2 message structure.

Note: While all examples use HL7 v2 ADT messages, the same DTL functions apply equally to XML, JSON, or custom message classes.


What Is a DTL?

A DTL (Data Transformation Language) in InterSystems IRIS is a class that defines how to map and transform message fields — either between different message formats or within the same message type. You build it visually in the Management Portal, and IRIS generates the underlying ObjectScript code.

Each transformation consists of actions. The most common ones are the following:

  • Assign: Maps or sets a field value.
  • If: Handles conditional branching.
  • Code: Allows you to write ObjectScript directly.
  • Foreach: Iterates over repeating segments.

The Sample HL7 Message

All four scenarios in this article use the same incoming ADT A01 message. Keeping this consistent is vital, since you rarely get to transform only one field at a time in production.

Here is our sample message:

MSH|^~\&|SENDING_APP|SENDING_FAC|RECEIVING_APP|RECEIVING_FAC|20250315143022||ADT^A01|MSG00001|P|2.5
EVN|A01|20250315143022
PID|1||MRN#001/2025^^^HOSP||VanDerBerghMontgomeryXYZABC^John^M||19850101|M|||123 Main Street^^Lahore^Punjab^54000^PK||+92-03001234567|||M||ACC001|
PV1|1|I|WARD-A^Room101^Bed1|E|||DOC001^Smith^James|||SUR|||||||V01|ACC001||||||||||||||||||||||||||20250315143022
Enter fullscreen mode Exit fullscreen mode

Items each field carries that matters for our examples:

  • PID-5.1 (Family Name): VanDerBerghMontgomeryXYZABC (27 characters; intentionally exceeds the 25-character limit).
  • PID-3.1 (Patient ID): MRN#001/2025 contains # and / characters that the target system cannot accept
  • PID-13 (Phone Number): +92-03001234567 (Includes an unwanted country code prefix ).
  • PV1-44 (Admit Date/Time): 20250315143022 (Standard HL7 DTM format; requires conversion to 15/03/2025 14:30).

Note: Keep this message open in a text editor as you work through each section.


Creating a DTL from Scratch

Before jumping into transformations, let us set up the DTL in which all four examples will live.

Navigate to the DTL editor:

Go to Management Portal → Interoperability → Build → Data Transformations

Click New to create a new transformation.

Fill in the dialog:

  • Package: Demo
  • Name: ADTTransform
  • Description: DTL deep dive demo
  • Source: EnsLib.HL7.Message set DocType to 2.5:ADT_A01
  • Target: EnsLib.HL7.Message set DocType to 2.5:ADT_A01

Click OK. The visual DTL editor will open with an empty canvas, showing the source on the left and the target on the right.

At the top of the canvas you will see a toolbar with action buttons: Assign, If, Code, Foreach, and others. This is where we add each transformation. Click Save once before adding any actions. This compiles the class and avoids losing work.

Now we are ready to build.


Scenario 1: Conditional Logic Based on Field Length

The Problem

The target system only accepts a patient's family name up to 25 characters. Anything longer than that triggers a silent rejection. Our sample message sends VanDerBerghMontgomeryXYZABC to PID-5.1, which is 27 characters.

The rule: if the family name exceeds 25 characters, truncate it to 25 and append * so the receiving team can flag and review it. If it is within the limit, pass it through unchanged.

Adding the If Action

First, set target = source so that the source data is assigned to the target message.
In the DTL canvas, click the + button to add an action and choose If.

Set the condition to the following:

$Length(source.{PID:PatientName(1).FamilyName}) > 25
Enter fullscreen mode Exit fullscreen mode

$Length returns the character count of a string. You can use it directly in DTL conditions without any special wrapper.

Inside the true branch (the If side), add an Assign action:

  • Property: target.{PID:PatientName(1).FamilyName}
  • Value: $Extract(source.{PID:PatientName(1).FamilyName},1,25) _ "*"

$Extract(string, start, end) pulls characters by position, meaning that $Extract(value,1,25) takes the first 25 characters. The _ "*" appends the asterisk using ObjectScript's string concatenation operator.

In the Else branch, add another Assign as a passthrough:

  • Property: target.{PID:PatientName(1).FamilyName}
  • Value: source.{PID:PatientName(1).FamilyName}

Testing It

Save and compile the DTL.
Paste the sample message and click the Test button.

In the output message, PID-5.1 should show VanDerBerghMontgomeryXYZAB* , which is exactly 25 characters plus the asterisk.


Scenario 2: Using ReplaceStr to Clean Field Values

The Problem

PID-13 (Phone Number) in our sample comes through as +92-03001234567. The target system only wants the local number, no country code, no dash. We need to strip +92- from every phone number before passing it on.

The ReplaceStr Function

ReplaceStr is a static method on the Ens.Util.FunctionSet class. It takes three arguments: the original string, what to find, and what to replace it with. Passing an empty string as the third argument deletes the matching text.

You can call it directly inside an Assign value field in the DTL.

Adding the Assign Action

Click + and add an Assign action:

  • Property: target.{PID:PhoneNumberHome(1).TelephoneNumber}
  • Value: ##class(Ens.Util.FunctionSet).ReplaceStr(source.{PID:PhoneNumberHome(1).TelephoneNumber},"+92-","")

Testing It

Save and compile the DTL.

Paste the sample message and click the Test button. In the output, PID-13 should show 03001234567; the prefix should be gone.

Another Practical Use

ReplaceStr is used not only for removing prefixes. If you need to replace a code value, say your source sends M or F for gender, but the target expects Male or Female, a Code action with explicit If/ElseIf logic is the cleanest approach:

Set sex = source.{PID:AdministrativeSex}
If sex = "M" { Set target.{PID:AdministrativeSex} = "Male" }
ElseIf sex = "F" { Set target.{PID:AdministrativeSex} = "Female" }
Else { Set target.{PID:AdministrativeSex} = sex }
Enter fullscreen mode Exit fullscreen mode

Nested ReplaceStr calls can work for simple substitutions, but be careful, ReplaceStr(value,"M","Male") would also match the M inside Female on a second pass if you are not careful with ordering.


Scenario 3: Stripping Special Characters from Patient ID

The Problem

Look at PID-3.1 in our sample message. The Patient ID comes through as MRN#001/2025. The # and / characters were likely generated by a legacy system that used them internally as separators. The target system expects a clean alphanumeric ID and will reject or misroute the message if these characters are present.

We need to strip both characters so that MRN#001/2025 can become MRN0012025. We should use two chained Assign actions here — one for each character to remove.

First Assign — strip the #:
Click + and add an Assign action:

  • Property: target.{PID:PatientIdentifierList(1).IDNumber}
  • Value: ##class(Ens.Util.FunctionSet).ReplaceStr(source.{PID:PatientIdentifierList(1).IDNumber},"#","")

Second Assign — strip the / from what the first Assign already wrote:

Click + and add an Assign action:

  • Property: target.{PID:PatientIdentifierList(1).IDNumber}
  • Value: ##class(Ens.Util.FunctionSet).ReplaceStr(target.{PID:PatientIdentifierList(1).IDNumber},"/","")

Note: The second Assign reads from the target, not the source, since the first Assign already wrote the #-cleaned value there; now we clean it further.

Testing It

Save and compile the DTL.

Paste the sample message and click the Test button. In the output, PID-3.1 should show MRN0012025 with both the # and / removed.


Scenario 4: Reformatting HL7 Timestamps

The Problem

PV1-44 (Admit Date/Time) in our sample comes in as 20250315143022, which is a standard HL7 DTM format (YYYYMMDDHHMMSS). The target system wants a human-readable format: 15/03/2025 14:30 (DD/MM/YYYY HH:MM).

Why $Extract Works Well Here

HL7 DTM timestamps are fixed-position strings, which makes them ideal for $Extract. The positions are always the same:

Component     Characters      How to Extract
Year     1 – 4 $Extract(dt,1,4)
Month     5 – 6 $Extract(dt,5,6)
Day     7 – 8 $Extract(dt,7,8)
Hour     9 – 10 $Extract(dt,9,10)
Minute        11 – 12 $Extract(dt,11,12)

Adding the Assign Action

Click + at the root level and add an Assign action:

  • Property: target.{PV1:AdmitDateTime.Time}
  • Value:  $Extract(source.{PV1:AdmitDateTime},7,8)_"/"_$Extract(source.{PV1:AdmitDateTime},5,6)_"/"_$Extract(source.{PV1:AdmitDateTime},1,4)_" "_$Extract(source.{PV1:AdmitDateTime},9,10)_":"_$Extract(source.{PV1:AdmitDateTime},11,12)

Testing It

Save and compile the DTL.

Paste the sample message and click the Test button. PV1-44 in the output should read 15/03/2025 14:30.



Putting It All Together

Our final DTL has four action blocks, each targeting a different field in the same ADT A01 message:

  1. If checks $Length of PID-5.1 and either truncates with * or passes it through.
  2. Assign uses ReplaceStr to strip +92- from PID-13.
  3. First Code chains two ReplaceStr calls to remove # and / from PID-3.1.
  4. Second Code reformats PV1-44 from HL7 DTM to DD/MM/YYYY HH:MM.

Run the full sample message through the tester one final time and verify all four fields in the output at once.


Conclusion

DTL becomes genuinely powerful once you start combining it with such ObjectScript functions as $Length, $Extract, $ZStrip, and utility methods like ReplaceStr. The four patterns in this article are not edge cases; they show up on practically every real integration project.

The habit of building is to test each transformation before it goes anywhere near a live production. The DTL tester handles most of it, and the terminal is always there for quick logic verification when needed.

Thanks!


Top comments (0)