ISO8601 Date Format and Timezone Handling in Lifecycle State Transforms

Hi all,

I am trying to understand how ISC handles timezones when evaluating dates in Lifecycle State transforms.

In some cases, I had to add an extra day to the comparison (for example, using endDate + 3d instead of endDate + 2d) to keep a user in the expected state for the full grace period. Also, the operator does not change the situation either (lt, lte, gt, gte).

This makes me wonder how ISC evaluates date-only values after they are converted to ISO8601:

  • Does ISC always treat these dates as midnight (00:00:00) UTC?
  • Is there any recommended approach for implementing grace periods (for example, comparing now against endDate + X days versus comparing now - X days against endDate) to avoid off-by-one-day issues?

Here an extract of the transform that I had to adjust in order to keep the business’s logic valid.

The expected logic is:

Active: startDate <= now <= endDate

Disabled: endDate < now <= endDate + 2 days

Terminated: now > endDate + 2 days

This is part of the condition:

#elseif($isAfterOrOnStartDate=='yes' && ($isBeforeOrOnEndDate=='yes' || $endDate=='N/A'))active#elseif($isAfterEndDate=='yes' && $grace_period=='yes')disabled#elseif($grace_period=='no' && $endDate != 'N/A')terminated#end"

Here a part where I add to add a day to make sure that the identity stayed in the state active even the last day of work:

 "isBeforeOrOnEndDate": {

            "type": "firstValid",

            "attributes": {

                "values": [

                    {

                        "type": "dateCompare",

                        "attributes": {

                            "firstDate": "now",

                            "secondDate": {

                                "type": "dateMath",

                                "attributes": {

                                    "expression": "+1d",

                                    "input": {

                                        "type": "dateFormat",

                                        "attributes": {

                                            "input": {

                                                "type": "identityAttribute",

                                                "attributes": {

                                                    "name": "endDate"

                                                }

                                            },

                                            "inputFormat": "yyyy-MM-dd",

                                            "outputFormat": "ISO8601"

                                        }

                                    }

                                }

                            },

                            "operator": "lt",

                            "positiveCondition": "yes",

                            "negativeCondition": "no"

                        }

                    },

                    "N/A"

                ]

            }

        },

Same scenario, but for the disabled state:

 "grace_period": {

            "type": "firstValid",

            "attributes": {

                "values": [

                    {

                        "type": "dateCompare",

                        "attributes": {

                            "requiresPeriodicRefresh": true,

                            "firstDate": "now",

                            "secondDate": {

                                "type": "dateMath",

                                "attributes": {

                                    "expression": "+3d",

                                    "input": {

                                        "type": "dateFormat",

                                        "attributes": {

                                            "input": {

                                                "type": "identityAttribute",

                                                "attributes": {

                                                    "name": "endDate"

                                                }

                                            },

                                            "inputFormat": "yyyy-MM-dd",

                                            "outputFormat": "ISO8601"

                                        }

                                    }

                                }

                            },

                            "operator": "lt",

                            "positiveCondition": "yes",

                            "negativeCondition": "no"

                        }

                    },

                    "N/A"

                ]

            }

        },

Thank you.

If you are using the now keyword, I believe that always returns UTC time.

Hey @VittoriaVizzari

@MattUribe is right: now is UTC. Your converted endDate is UTC too, so the extra day is not a timezone issue.

A yyyy-MM-dd value becomes midnight at the start of that day, for example 2026-09-30T00:00:00.000Z. On the last working day, now is already past that time. So now <= endDate is false for the whole day. lt and lte only differ at exactly midnight, which is why changing the operator had no effect.

Your +1d and +3d are correct. To check “on or before day X”, compare against the start of the next day:

State Comparison
Active now LT endDate + 1d
Disabled now LT endDate + 3d
Terminated now GTE endDate + 3d

as another note,

  • The dateMath docs say its output is yyyy-MM-dd'T'HH:mm and should be converted to ISO8601 before you use it in dateCompare. Wrap it in a dateFormat with that inputFormat and "outputFormat": "ISO8601".

  • The state only changes at the next identity refresh after midnight UTC. Set requiresPeriodicRefresh on the outer lifecycle state transform so every check runs nightly, not just the grace period.

Hello,

Thank you both for your responses. The behavior was unexpected at first, but I believe I have now managed to handle both scenarios correctly.

I also noticed that ISC seems to handle the comparison more reliably when I apply the offset to the current date (for example, now - 2 days) rather than to the end date. Since the current date always includes a timestamp, the comparison appears to be more consistent, whereas our end date values do not contain any time information.

That said, I still feel there may be some risk of unexpected behavior in edge cases, so I think I will just go for the +1 day each time.

Thank you again! :slight_smile: