Ignition Popup Calendar Not Updating Daily: Shift Time Fix

Ryan Tanaka6 min read
HMI / SCADAOther ManufacturerTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

The shift start and end calendars still show yesterday's date, or last week's. The shift-active expression tag goes false after midnight and stays false. Closing and reopening the window makes it right again for one day. On a station that never closes the window, the shift window never moves forward.

Drop the Fixes That Don't Hold

Each of these gets tried first. None survives a 24/7 station.

  • Typing a fixed date into the Popup Calendar (for example 2015/09/29 08:00:00 AM). That is a static value. It stays until someone types another one.
  • Binding the calendar to dateFormat(now(0), "yyyy-M-dd 08:00:00"). This updates only when the window opens. now(0) means a poll rate of zero: evaluate once, never again.
  • Raising the poll rate to now(3600000). The date does roll over within an hour of midnight. But every hourly evaluation writes 08:00:00 back into the calendar and wipes any time the operator entered.
  • Stretching the poll to 8 hours or more. Fewer overwrites, but the date can lag midnight by up to the full poll interval. You trade one fault for another.
  • Comparing hours only: dateExtract(now(), "hour") >= {[~]start hour} && dateExtract(now(), "hour") <= {[~]end hour}. It works for whole hours. It has no minute resolution. Because of the <=, an end hour of 16 keeps the tag true until 16:59:59.

Understand Why the Date Freezes

An expression binding re-evaluates for two reasons: a referenced property or tag changes, or its poll timer fires. now() takes a poll rate argument in milliseconds. That argument is the only thing that makes a time-based expression run again on a window nobody touches.

  • now(0): no poll. It evaluates when the binding starts, which means when the window opens.
  • now(1000): it re-evaluates every second.
  • now(3600000): it re-evaluates every hour.

A property binding is also one-way. The expression owns the property. Every evaluation overwrites whatever the operator put there. A live date and an operator-edited time cannot share one bound calendar property. That is the actual conflict, and no poll rate resolves it.

Symptom Cause
Date never changes on a window left open Static entry, or binding uses now(0)
Date correct only after reopening the window now(0) evaluates once at binding start
Operator's shift time snaps back to 08:00:00 Polling binding overwrites the bound property on every evaluation
Date changes hours after midnight Poll interval too long (8 h or more)
Shift tag ignores minutes, stays true through the end hour Hour-only dateExtract comparison with inclusive <=

Split the Date From the Time

Start here. Apply this rule: now() supplies the date and nothing else. The operator supplies hour, minute and second in separate numeric fields. An expression joins the two into a timestamp string. The shift tag compares full timestamps.

The date part changes only at midnight. The time part changes only when the operator edits it. The poll can run every second and never overwrite operator input, because the operator never edits the bound property.

Build the Shift Window

  1. Add numeric entry fields for hour, minute and seconds. Use one set for the shift start and one set for the shift end. The working build used components named hour, minute and seconds on the Root Container.
  2. Add a label and bind its text to the composed timestamp:
    dateFormat(now(), "yyyy-M-dd " + {Root Container.hour.intValue} + ":" + {Root Container.minute.intValue} + ":" + {Root Container.seconds.intValue})
    now() with no argument keeps polling. Only the date comes from it. The time fields come from the operator.
  3. Repeat step 2 for the end boundary using the end-time fields.
  4. Write each composed string into its tag, startDate and endDate. The shift expression tag reads these tags.
  5. Create the shift-active expression tag:
    now() >= todate({[~]startDate}) && now() <= todate({[~]endDate})

You may only need a time of day, with no date. In that case, skip the strings and compare minutes since midnight. The tag names below are placeholders. Substitute your own.

(dateExtract(now(), "hour") * 60 + dateExtract(now(), "minute")) >= ({[~]<start hour tag>} * 60 + {[~]<start minute tag>})
&&
(dateExtract(now(), "hour") * 60 + dateExtract(now(), "minute")) < ({[~]<end hour tag>} * 60 + {[~]<end minute tag>})

Verify the Rollover Without Waiting for Midnight

  1. Shrink the test to one minute. Bind a scratch label to dateFormat(now(1000), "yyyy-M-dd 08:mm:00"). The minute field should tick once per minute while the rest of the string stays fixed. This proves that a polled now() updates on a window left open.
  2. Set the start time two minutes ahead and the end time four minutes ahead. Watch the shift tag go true at the start boundary and false after the end boundary.
  3. Check the quality of the shift tag, not just its value. If todate() cannot parse the string, the tag shows an error quality instead of a clean false.
  4. Leave the window open across one real midnight. Confirm that the label date increments and the operator-entered time stays put.

Avoid the Recurring Traps

  • Overnight shifts. If the end time is earlier than the start time and both strings carry today's date, the window is never true. Compose the end with the next day's date. In the minutes-of-day version, swap the && for || when the end is less than the start.
  • Unpadded fields. The concatenation produces strings like 2015-9-29 8:5:0. Confirm that todate() parses your exact format on your system. If it fails, pad each field to two digits with numberFormat(value, "00").
  • Two clocks. The label runs in the client and uses the client's clock. The expression tag runs on the gateway and uses the gateway's clock. If the time zones or clocks differ, the shift boundaries shift by that offset. Compare both clocks before you blame the expression.
  • Inclusive end. With <=, the tag stays true through the end second. If the next shift starts at that same second, both shift tags are true together. Use < on the end boundary.
  • Binding the calendar itself. Never bind an operator-editable calendar to a polling now(). Keep entry fields unbound, and put the binding on the composed label or tag.

FAQ

What happens if I use now(0) in an Ignition expression binding?

It evaluates once, when the binding starts, which usually means when the window opens. It never evaluates again. On a window left open around the clock, the date stays frozen until someone closes and reopens the window.

What happens if I set now() to poll every hour on a popup calendar binding?

The date rolls over within an hour of midnight. However, every poll writes the fixed time, such as 08:00:00, back into the calendar and wipes operator edits. Move the operator's time into separate unbound numeric fields and join them with the now() date in a label or tag.

What happens if the shift tag still won't roll over after the fix?

First check the tag quality, the client and gateway clocks, and whether todate() parses the composed string. If the label date increments at midnight but the tag stays stale with good quality, or todate() rejects a string that matches the format, stop rewriting expressions. Record the gateway version, the exact expressions, the tag diagnostics and the related gateway log entries, then open a case with Inductive Automation support.

Back to blog