Why a date widget shows blank or the wrong day

A date widget turns its input string into a Date object using dateFormat, which defaults to yyyy-MM-dd HH:mm:ss.SSS, and inputTimeZone, which defaults to UTC. If the string doesn't match dateFormat, the widget displays empty. If the zone is wrong, the date can show a day early or late.

Model business objects are the usual case. Workday notes that model component REST APIs return yyyy-MM-dd'T'HH:mm:ss.SSS'Z', which isn't the default. In Workday's examples, such a value displays correctly at the default day precision, and they add a matching dateFormat as soon as the widget shows the time:

{
  "type": "date",
  "id": "submittedOn",
  "label": "Submitted",
  "datePrecision": "SECOND",
  "dateFormat": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
  "displayTimeZone": "<% userTimeZone %>",
  "value": "<% equipmentRequest.submittedDate %>"
}

All format strings use Java SimpleDateFormat letters. MM is month and mm is minutes, HH is a 24-hour clock and hh a 12-hour one, and text in single quotes is literal.

Display: what you control on view pages and what you don't on edit pages

The same date widget formats differently depending on the page type.

On a view page, dateDisplayPattern sets the shown format. The default is MM/dd/yyyy, or MM/dd/yyyy hh:mm:ss AM/PM once datePrecision includes time. displayTimeZone sets the zone, and it also defaults to UTC.

On an edit page, dateDisplayPattern is ignored. The widget uses the signed-in user's preferred display language. To have it use a language's date format, the language code must be listed in languages[] in the app's Site Metadata (SMD). Workday's example adds nl-NL so Dutch users see dd-MM-yyyy.

datePrecision decides whether time shows at all. Without it the widget shows only the day. If you put a time in dateDisplayPattern without setting datePrecision to HOUR, MINUTE, SECOND or MILLISECOND, the time displays as 12:00 AM.

A timestamp stored in UTC and shown with the default displayTimeZone of UTC can land on the previous or next calendar day for a user far from Greenwich. Set displayTimeZone to <% userTimeZone %>, the predefined variable for the signed-in user's zone, whenever the value has a time component.

Submitting a date: always format it yourself

A date widget's value is a Date object holding the date, time and zone. It isn't a string in the format the endpoint expects, so format it in valuesOut with one of the two documented functions.

format(dateFormat) formats in UTC:

"valuesOut": [
  {
    "value": "<% self.value.format('yyyy-MM-dd') %>",
    "valueOutBinding": "postExpense.date"
  }
]

formatWithTimeZone(dateFormat, timeZone) formats in a given zone. Workday recommends storing date-times in UTC, so use it only when the target really needs local time.

Inside a PMD script string, the quotes around literal letters must be escaped, written 'yyyy-MM-dd\\'T\\'HH:mm:ss.SSS\\'Z\\''. And because 'Z' is quoted, it's literal text, not a time zone. Workday's own formatWithTimeZone(..., 'US/Eastern') example stores 2021-01-31T11:05:33.000Z, which is Eastern time carrying a Z suffix that claims UTC. If you format in a non-UTC zone, drop the literal Z so the stored value doesn't claim to be UTC.

To clear a date in a script, set it to null, not an empty string: startDate.setValue(null).

When a date arrives as a string in some other format, such as a query parameter or a third-party API field, turn it into a Date with date:parse(dateString, dateFormat). For example, date:parse('2020-07-01 15:10:45', 'yyyy-MM-dd HH:mm:ss').

Why a number field won't accept 25 or -3

The number widget's input limits are restrictive by default. These attributes apply on edit pages:

Attribute Default Maximum
AttributemaximumValueDefault10Maximumnone stated
AttributeminimumValueDefault0Maximumnone stated
AttributefractionDigitsDefault0Maximum12
AttributetotalDigitsDefault12Maximum15

So an unconfigured number field accepts whole numbers from 0 to 10. When you set totalDigits and fractionDigits, the implied maximum becomes 10^(totalDigits − fractionDigits) − 1/10^fractionDigits, so 5 and 2 give 999.99. Workday's own guidance for negatives is to set minimumValue to a large negative number, such as -1000000.

{
  "type": "number",
  "id": "quantity",
  "label": "Quantity",
  "minimumValue": 1,
  "maximumValue": 500,
  "fractionDigits": 0,
  "valueOutBinding": "createEquipmentRequest.quantity"
}

The format attribute on number controls only how the value displays on an edit page and follows GWT NumberFormat grammar. Workday's example "format": "#'%" displays a value of 25 with a literal percent sign after it. The submitted value is still the number.

Currency: let the currency code drive precision

The currency widget formats its number from currencyCode and the user's locale, including the decimal places and symbols. It doesn't convert between currencies, so pass the already-converted amount. In Workday's example, the amount comes back as an object with value and currency fields, and the widget maps those two straight across:

{
  "type": "currency",
  "label": "Amount",
  "currencyCode": "<% expenseLine.totalAmount.currency %>",
  "value": "<% expenseLine.totalAmount.value %>",
  "showCurrencySymbol": "true",
  "enabled": "false"
}

The currency attributes have their own rules:

  • Use either currencyCode or fractionDigits. With a valid code, the currency's own precision applies, and fractionDigitsOverride changes it. Without a code, fractionDigits decides, and it defaults to zero.
  • showCurrencySymbol works only on view pages. Workday says not to set it on edit pages.
  • maximumValue defaults to 10 and minimumValue to 0 here too. An editable amount field needs both set.
  • To change the currency from a script, use setCurrencyCode('EUR'), with an uppercase three-letter code. getCurrencyCode() reads it.
  • Workday's guidance also says you can override the default currency format with a format attribute by setting useDefaultCurrencyFormat to false.

Formatting numbers and dates inside text

When a value goes into a label, a text widget or a message rather than its own widget, format it in the script. number:format uses Java DecimalFormat patterns. Workday's examples: number:format(0.12, '#%') gives 12%, and number:format(-1234.567, '#,###.00;(#,###.00)') gives (1,234.57). For a date, use date:format(someDate, 'dd/MM/yyyy') or the instance form someDate.format('E') for a weekday name.

For arithmetic that has to round predictably, such as a line total, use number:toBigDecimal(expression, scale, roundingMode) with a Java rounding mode like HALF_UP or HALF_EVEN. Don't rely on display formatting to round a value you'll also submit.

The text widget's own format attribute is an input mask (# for a digit, @ for a letter), not a number or date pattern. Workday notes that it affects only what the user sees on an edit page, so the outbound value must still be formatted in script.

Test dates that expose day swaps and time zone shifts

Check the stored value as well as the screen. Submit a test record with an unambiguous date such as the 13th of a month, which makes day/month swaps obvious, and a time near midnight, which exposes zone shifts. Then read the record back through its GET endpoint. The stored string should match the endpoint's expected format, and the date should be the one the user picked in their own time zone. For numbers and currency, enter the largest and smallest values the business allows, including a negative if that's valid. Any one the widget rejects points to a maximumValue, minimumValue or fractionDigits you haven't set. For rules the attributes can't express, such as an end date after a start date, use the pattern in Extend onSubmit and onChange validation.

For the same problem inside an orchestration, where formats are set with Orchestrate expression functions instead of PMD, see date and time zone formatting in Orchestrate.

Sources checked