zpl.tools
Real-time clock

^SO - Set Offset (for Real-Time Clock)

Defines the secondary or tertiary clock as a fixed offset from the primary one, for printing a second time zone or an expiry date.

A printer has one clock but three readings of it. ^SO defines the second and third as offsets from the primary, which lets a label print a best-before date without a calculation by the sender. Set the offset to the shelf life, and the printer does the date arithmetic. The printer reads each offset against the primary clock on every run, so one format stays correct on every later run.

Syntax

^SOa,b,c,d,e,f,g

Support

Supportedparsed: full · state: full · rendered: full

Every documented parameter renders the same output Labelary produces.

Labelary renders this command.

Parameters

aClock to setrequired · one of a set

Which of the two offset clocks this command defines. Use ^ST for the primary clock.

Invalid value
enough to make the whole command be ignored. A number other than 2 or 3 discards the command, so that clock stays undefined (measured 2026-08-16).
Test coverage
No documented value is exercised by a test (2, 3). The invalid-value rule is pinned by 2 tests.
2
Secondary clock
3
Tertiary clock
bMonths offsetoptional · integer

How many months to add to the primary clock for this secondary or tertiary clock. Negative values move the clock backwards.

Default
0
Accepted range
-32000 to 32000
Invalid value
clamped to the nearest allowed value. Clamped to the bound. A non-numeric value counts as zero (measured 2026-08-16).
Test coverage
Bounds pinned: past what the shape can hold (99999999999999999999). No test at the minimum or maximum. The invalid-value rule is pinned by 1 test.
cDays offsetoptional · integer

How many days to add to the primary clock for this secondary or tertiary clock. Negative values move the clock backwards.

Default
0
Accepted range
-32000 to 32000
Invalid value
clamped to the nearest allowed value. Clamped to the bound. A non-numeric value counts as zero (measured 2026-08-16).
Test coverage
No test at the minimum, maximum or one step outside the range. The invalid-value rule is pinned by 1 test.
dYears offsetoptional · integer

How many years to add to the primary clock for this secondary or tertiary clock. Negative values move the clock backwards.

Default
0
Accepted range
-32000 to 32000
Invalid value
clamped to the nearest allowed value. Clamped to the bound. A non-numeric value counts as zero (measured 2026-08-16).
Test coverage
No test at the minimum, maximum or one step outside the range. The invalid-value rule is not pinned by a test.
eHours offsetoptional · integer

How many hours to add to the primary clock for this secondary or tertiary clock. Negative values move the clock backwards.

Default
0
Accepted range
-32000 to 32000
Invalid value
clamped to the nearest allowed value. Clamped to the bound. A non-numeric value counts as zero (measured 2026-08-16).
Test coverage
No test at the minimum, maximum or one step outside the range. The invalid-value rule is not pinned by a test.
fMinutes offsetoptional · integer

How many minutes to add to the primary clock for this secondary or tertiary clock. Negative values move the clock backwards.

Default
0
Accepted range
-32000 to 32000
Invalid value
clamped to the nearest allowed value. Clamped to the bound. A non-numeric value counts as zero (measured 2026-08-16).
Test coverage
No test at the minimum, maximum or one step outside the range. The invalid-value rule is not pinned by a test.
gSeconds offsetoptional · integer

How many seconds to add to the primary clock for this secondary or tertiary clock. Negative values move the clock backwards.

Default
0
Accepted range
-32000 to 32000
Invalid value
clamped to the nearest allowed value. Clamped to the bound. A non-numeric value counts as zero (measured 2026-08-16).
Test coverage
No test at the minimum, maximum or one step outside the range. The invalid-value rule is not pinned by a test.

Behavior

^SO prints nothing by itself. It prepares a clock for ^FC to read. ^FC's second and third arguments name the indicator characters for the secondary and tertiary clocks, so the indicator ties a field to a clock. A field with the secondary indicator and no ^SO2 in the format reads an undefined clock.

The guide accepts only one ^SO2 per label. A label that needs two different offsets must use ^SO3 for the second, which is why the tertiary clock exists.

The printer adds all six offsets together, so ^SO2,1,15 is one month and fifteen days ahead. A negative value runs the clock backwards, which is how a "manufactured on" date follows from a print date.

Order matters. Put the ^SO, and the ^ST it offsets from, ahead of the fields that read them, because the parser reads a format in sequence.

Tested behavior

Differences from the specification

Labelary against the ZPL II guide

Where Labelary — our compatibility target — behaves differently from the printed specification.

  • An offset clock no ^SO defines reads the printer's own clock, not the primary clock, so ^ST leaves it where it is (measured 2026-08-16). — not pinned by a test
  • The offset resolves at the command. An ^ST after an ^SO moves the primary clock alone (measured 2026-08-16). — not pinned by a test
  • A month or year offset clamps the day into its target month, months first: 31 January 2023 with ^SO2,1,,1 is 28 February 2024 (measured 2026-08-16). — not pinned by a test

Example

A packing date and a best-before date one week later, both derived from a single clock reading:

^XA
^ST11,12,2023,22,50,00,M
^SO2,,7
^FO40,40^A0N,40,40^FC%,{^FDPacked: %m/%d/%Y^FS
^FO40,110^A0N,40,40^FC%,{^FDBest before: {m/{d/{Y^FS
^XZ

Open in viewer