Project

General

Profile

Bug #10471

Incorrect Integer Formatting as Legacy Time

Added by Vladimir Tsichevski 11 months ago. Updated 6 months ago.

Status:
Internal Test
Priority:
Normal
Assignee:
Vladimir Tsichevski
Target version:
-
Start date:
Due date:
% Done:

100%

billable:
No
vendor_id:
GCD
case_num:
version_reported:
16123
version_resolved:
reviewer:
production:
No
env_name:
topics:

IntegerFormattedAsTime.cls (8.01 KB) Vladimir Tsichevski, 08/27/2025 04:28 PM

TestIntegerFormattedAsTime.cls (11.9 KB) Vladimir Tsichevski, 08/29/2025 02:39 PM

TestIntegerFormattedAsTime.cls (5 KB) Vladimir Tsichevski, 01/28/2026 09:22 AM


Related issues

Blocks User Interface - Bug #10075: Number format validation issues WIP

History

#1 Updated by Vladimir Tsichevski 11 months ago

In 4GL, specific formats for integer values can interpret them as time. This feature is likely kept for backward compatibility, from a time when no dedicated time types were available, making this the only way to format time values.

Per the Progress documentation:

If source is an integer and format begins HH:MM or HH:MM:SS, STRING formats the source as a time. If the hour is greater than or equal
to 12 and there is an A or an a in format, STRING subtracts 12 from the hour and converts the A or the a to a P or p (for A.M. and P.M.).
The hour 0 is treated as 12 a.m., and noon is treated as 12 p.m. If you use AM/PM format, HH is replaced by a leading blank and a digit if the hour is between 0 and 9.

h3. Examples

   format(1, "+hh:mm") = +00:01
   format(1, "+hh:mm:ss") = +00:01:00
   format(1, "hh:mm") = 00:00
   format(1, "hh:mm:ss") = 00:00:01
   format(13 * 60 * 60 + 2 * 60 + 3, "hh:mm:ss") = 13:02:03
   format(13 * 60 * 60 + 2 * 60 + 3, "hh:mm:ssa") = " 1:02:03p" 
   format(13 * 60 * 60 + 2 * 60 + 3, "hh:mm:ss a") = " 1:02:03 p" 
   format(13 * 60 * 60 + 2 * 60 + 3, "hh:mm:ss A") = " 1:02:03 P" 
   format(3 * 60 * 60 + 2 * 60 + 3, "hh:mm:ssA") = " 3:02:03A" 
   format(0 * 60 * 60 + 2 * 60 + 3, "hh:mm:ssA") = "12:02:03A" 
   format(13 * 60 * 60 + 2 * 60 + 3, "hh:mm:ss A") = " 1:02:03 P" 
   format(24 * 60 * 60 + 2 * 60 + 3, "hh:mm:ss A") = "12:02:03 A" 
   format(13 * 60 * 60 + 2 * 60 + 3, "hh:mm:ss qweAAA") = " 1:02:03 qwePAA" 

These formats are valid only for integers. When applied to decimal values, they are processed as standard numeric formats, resulting in relevant errors.

The Issue

Run this 4GL example::

  STRING(1, "+hh:mm:ss").

Expected result: +00:01:00.
FWD result: 1h:mm:ss.

It seems FWD does not recognize the special format and formats the integer value incorrectly as a regular number.

When an integer value exceeds the format's limit, the excess should be discarded. However, FWD fails to do this, allowing hours to exceed 23.

Example::

  STRING(1000 * 60 * 60 + 2 * 60 + 3, "+hh:mm").

Expected result: +02:03.
FWD result: +60002:03.

Related Issue

The code::

  STRING(1.0, "+hh:ss").

should trigger error 22, as decimal values are not supported with time formats. Instead, FWD does not raise an error and outputs 1h:ss, which is illogical.

FWD seems to avoid formatting decimals as time but does not properly detect invalid formats. This issue is already covered in #10075.

#2 Updated by Vladimir Tsichevski 11 months ago

  • Status changed from New to WIP

Update: OE formats 1 with "+hh:mm:ss" as "+00:01:00", whereas FWD continues to handle this incorrectly.

#3 Updated by Vladimir Tsichevski 11 months ago

Attached is an ABLUnit test class to evaluate integer-to-time formatting using 60 sample cases.

#4 Updated by Vladimir Tsichevski 11 months ago

  • Status changed from WIP to Review
  • % Done changed from 0 to 100

Fixed in 10471a rev. 16133. Please, review.

#5 Updated by Vladimir Tsichevski 11 months ago

The TestIntegerFormattedAsTime class added to the testcases at rev. 1801. All tests pass with 10471a.

#6 Updated by Vladimir Tsichevski 11 months ago

  • Status changed from Review to WIP
  • % Done changed from 100 to 90

I found that the OE handles the "+hh:mm" format in a counter-intuitive manner, treating the input value as seconds and minutes rather than hours and minutes as might be expected.

Examples:

  1. STRING(59 * 60 + 3, "+hh:mm"). -> +59:03
  2. STRING(60 * 60 + 3, "+hh:mm"). -> +00:03

#7 Updated by Vladimir Tsichevski 11 months ago

The updated ABLUnit test class TestIntegerFormattedAsTime.cls is included as an attachment.

#8 Updated by Vladimir Tsichevski 11 months ago

Vladimir Tsichevski wrote:

The updated ABLUnit test class TestIntegerFormattedAsTime.cls is included as an attachment.

What has changed:

  1. A specialized method was introduced to compare CHARACTER values, as Assert:Equals may perform case-insensitive comparisons and ignores trailing whitespace.
  2. Tests were added for cases referenced in #10471-6.

#9 Updated by Vladimir Tsichevski 11 months ago

  • Status changed from WIP to Review
  • % Done changed from 90 to 100

The issue #10471-6 was resolved in 10471a rev. 16134. Please, review.

Also TestIntegerFormattedAsTime.cls was updated in testcases at rev. 1803.

#10 Updated by Vladimir Tsichevski 6 months ago

I've rebased 10471a to the latest trunk. The rev. is now 16355.

#11 Updated by Vladimir Tsichevski 6 months ago

  • Blocks Bug #10075: Number format validation issues added

#12 Updated by Vladimir Tsichevski 6 months ago

  • Status changed from Review to WIP
  • % Done changed from 100 to 90

I found another related issue:

The following statements throw error 22 ("Character number 2 of format is invalid") in OE, at position 2:

STRING(0, "+h").
STRING(0, "+hh").
STRING(0, "+hh:").
STRING(0, "+hh:m").

However, in FWD, these statements do not throw any error.

#13 Updated by Vladimir Tsichevski 6 months ago

  • Status changed from WIP to Review
  • % Done changed from 90 to 100

Vladimir Tsichevski wrote:

I found another related issue:

The following statements throw error 22 ("Character number 2 of format is invalid") in OE, at position 2:

[...]

However, in FWD, these statements do not throw any error.

These formats may look like time formats, but they are not recognized by the time-format filter in int64.java (which is correct) and are treated as normal numeric formats (which is also correct). However, the numeric format parser recognizes these formats as valid (which is not correct), so this issue is part of #10075, and this #10471 issue can be closed.

#14 Updated by Vladimir Tsichevski 6 months ago

  • Status changed from Review to WIP
  • % Done changed from 100 to 90

New issue:

A negative value -60 formatted with format hh:mm gives:

In OE: 23:59.
In FWD (10471a): 00:-1.

#15 Updated by Vladimir Tsichevski 6 months ago

And -1 formatted with +hh:mm produces:

In OE: -00:01
In FWD (10471a): +00:-1

#16 Updated by Vladimir Tsichevski 6 months ago

Special Number Formatting Specification

This document describes the legacy special time formatting applied to integer values
when the format string starts with one of the following exact prefixes:
hh:mm, hh:mm:ss, or +hh:mm
(with optional AM/PM suffix a or A in the literal suffix part).

This feature exists for backward compatibility (pre-OpenEdge 10).

Valid Format Specification

A format is recognized as a time format only if it starts with one of the following exact prefixes:

  • hh:mm
  • hh:mm:ss
  • +hh:mm

followed by an optional literal suffix (any characters, including spaces, colons, letters, punctuation, etc.).

Important distinction for + prefix

  • +hh:mm → recognized as cumulative signed time format (hours = total minutes % 60, sign preserved)
  • +hh:mm:ssnot recognized as time format
    • The :ss part is treated as literal suffix
    • Example: STRING(1, "+hh:mm:ss")+00:01:ss

Valid format rules:

  • Placeholders h, m, s are case-insensitive
    • HH:MM:SS = hh:mm:ss = Hh:Mm:Ss = hH:mM:sS
  • Minutes part must be exactly two placeholders (mm, MM, Mm, etc.)
  • Seconds part must be exactly two placeholders (ss, SS, Ss, etc.) — only allowed in non-plus formats
  • Single m or s (e.g. hh:m, hh:mm:s, hh:m:s) is invalid
  • Incomplete patterns (e.g. h, hh, hh:, hh:m) are invalid
  • Any characters after the recognized prefix (e.g. x in hh:mmx) are treated as literal suffix — not invalid
  • AM/PM indicator (a or A) is optional and may appear anywhere in the suffix
  • AM/PM indicator is case-sensitive for output:
    • A → uppercase A / P
    • a → lowercase a/ p
  • Trailing spaces in the format string are not trimmed — they are copied literally to the output

If the format does not match one of the exact prefixes above, it is not considered a time format at all. Parsing continues as a standard numeric format, and any errors are raised according to normal numeric format validation rules (e.g. error 22 for invalid characters, error 148 for no digit placeholders).

Formatting Rules

Integer values (seconds since midnight) are formatted according to the following rules:

24-Hour vs 12-Hour Mode

  • Without a/A: always 24-hour clock, hours padded with zero → 00:00:00 to 23:59:59 range.
  • With a/A: 12-hour clock with AM/PM indicator
  • Hour 0 → displayed as 12
  • Hours 1–11 → displayed as-is (leading space if single digit in some cases)
  • Hours 12 → displayed as 12
  • Hours 13–23 → subtract 12, set PM

Midnight and Noon (value = 0 or 43200 seconds)

  • 24-hour formats (no AM/PM): 00:00:00 / 00:00:00
  • 12-hour formats (with a/A): 12:00:00 / 12:00:00 (AM for midnight, PM for noon)

Negative Values

  • Non-plus formats (hh:mm, hh:mm:ss): count backward from midnight
  • Equivalent to (value % 86400 + 86400) % 86400
  • Examples:
  • -123:59:59
  • -6023:59:00
  • -2000018:26:40
  • Plus formats (+hh:mm): show negative cumulative time
  • Absolute value used for calculation
  • Original sign preserved
  • "hh" field = total minutes % 60 (wraps every 60 hours)
  • Examples:
  • -1-00:01
  • -60-01:00
  • -200000-33:20

Large Values (> 86400 seconds)

  • Non-plus formats: modulo 86400 seconds → time-of-day display
  • Examples:
  • 12345610:17:36
  • 100000123402:07:14
  • Plus formats: no modulo on hours — cumulative display
  • "hh" field still modulo 60
  • Example:
  • 1000001234+07:14

Suffix Handling

Any literal characters after the time prefix are preserved exactly, including trailing spaces.

  • Trailing spaces in the format string are not trimmed — they are copied literally to the output
  • This allows preserving alignment or padding in display fields
  • AM/PM indicator may appear anywhere in the suffix

Examples:

  • hh:mm:ss 00:00:00 (3 trailing spaces preserved)
  • +hh:mm +00:00 (2 trailing spaces preserved)
  • hh:mm:ssabc 00:00:00abc (3 trailing spaces after literal suffix)
  • hh:mm:ssxA13:02:03xP (AM/PM anywhere in suffix)
  • +hh:mm:ss+00:01:ss ( :ss is literal suffix)

Restrictions

  • These formats are only valid for integer values
  • Applying them to decimal values results in an error (e.g. error 22)
  • They are not supported in FILL-IN widgets

#17 Updated by Vladimir Tsichevski 6 months ago

  • Status changed from WIP to Review
  • % Done changed from 90 to 100

All known issues fixed in 10471a rev. 16356. Please, review.

#18 Updated by Vladimir Tsichevski 6 months ago

Updated ABLUnit tests attached.

#19 Updated by Hynek Cihlar 6 months ago

  • Status changed from Review to Internal Test

Code review 10471a. The changes look good. Vladimir, please GUI regression test. I'm queueing this up for ChUI regression testing.

#20 Updated by Hynek Cihlar 6 months ago

Vladimir, do the attached test cases cover all the issues found?

#21 Updated by Hynek Cihlar 6 months ago

10471a passed ChUI regression tests.

#22 Updated by Vladimir Tsichevski 6 months ago

Hynek Cihlar wrote:

Vladimir, do the attached test cases cover all the issues found?

Yes, the latest version of TestIntegerFormattedAsTime.cls attached to this task contains all the formatting cases I can imagine.

#23 Updated by Vladimir Tsichevski 6 months ago

Hynek Cihlar wrote:

10471a passed ChUI regression tests.

I rebased 10471a and re-tested with TestIntegerFormattedAsTime.cls. All tests passed.

Also available in: Atom PDF