Introduction
- Why: If you manage time-dependent logic in SailPoint Identity Security Cloud (ISC), you likely know the pain of Daylight Saving Time. When you use a transform that calculates an offset based on hours, this logic shifts by an hour as soon as Daylight Saving Time begins or ends.
- Problem: Hardcoding offsets like
+5hin adateMathtransform works perfectly until the time changes. Once Daylight Saving Time occurs, your local midnight calculations, grace periods, and lifecycle states start triggering an hour early or late. - Goal: A dynamic solution utilizing scheduled Workflows to automatically update base timezone Transforms when Daylight Saving Time transitions occur. This ensures all nested date calculations are always accurate.
Note: This post focuses specifically on Eastern Time (EST/EDT) to illustrate the concept, rather than general timezone management. If your tenant supports global users across multiple timezones, you would incorporate a lookup transform to map the correct offset based on the user’s location.
Solution Overview
- Tech Stack: 2 Workflows + 2 Base Transforms.
- High-Level Flow: We establish two foundational transforms:
EST To UTCandUTC To EST. All other time-based transforms reference these two base transforms instead of hardcoding their own offsets. We then create two scheduled Workflows, one for entering Daylight Saving Time and one for exiting. These workflows automatically run on the specific Sundays when time changes and use the ISC API to update thedateMathexpressions inside our base transforms.
The Base Components
1. Base Transforms
First, you need to create the two base transforms that will act as the single source of truth for your timezone offset.
EST To UTC Transform:
{
"id": "d9df51b1-5d08-43ac-9e8a-7069e17282b7",
"name": "EST To UTC",
"type": "dateMath",
"attributes": {
"expression": "+5h"
},
"internal": false
}
UTC To EST Transform:
{
"id": "71f3f59a-8172-4cc4-a08e-b0960c8e7604",
"name": "UTC To EST",
"type": "dateMath",
"attributes": {
"expression": "-5h"
},
"internal": false
}
EST To UTC.json (166 Bytes)
UTC To EST.json (166 Bytes)
2. Automated Workflows
Next, we configure the workflows that handle the offset adjustments. The “Enter Daylight Saving Time” workflow triggers yearly on the second Sunday of March, while the “Exit Daylight Saving Time” workflow triggers on the first Sunday of November.
Both workflows utilize the sp:http action to send PUT requests to the /transforms/v1/{id} endpoint, updating the dateMath expressions (e.g., changing -5h to -4h). The JSON body payload for the API call looks like this:
{
"name": "EST To UTC",
"type": "dateMath",
"attributes": {
"expression": "+4h"
}
}
If the API calls fail or succeed, the workflow catches the result and sends an alert email to the IAM team. This ensures there are no silent failures and the team is well-aware that the offset adjustments went through smoothly!
Workflow Configurations:
ExitDaylightSavings20260717.json (7.1 KB)
EnterDaylightSavings20260717.json (7.1 KB)
Transform Use Cases
Now that the foundation is set, you can build reliable, timezone-aware transforms. Here are a few practical examples of how to utilize the base transforms. Notice how each date math expression leverages rounding /d to dynamically interact with midnight.
Important Note: In these examples, the accountAttribute fetches are wrapped in a firstValid to handle empty fields and prevent AttributePromotionException errors. They are then wrapped in a dateFormat to standardize the incoming string into ISO8601. The dateMath engine strictly requires ISO8601 formatted strings to perform timezone math successfully!
1. Get Current Local Midnight
Standardizes a date so that it evaluates exactly to local midnight rather than shifting by UTC hours. The core of this logic uses the dateMath expression "/d", which rounds the provided date down to the start of the day (midnight).
Employee - Get Current Local Midnight.json (1.1 KB)
{
"id": "e065a639-cb0c-40e5-9dfc-969ba9287ef6",
"name": "Employee - Get Current Local Midnight",
"type": "reference",
"attributes": {
"id": "EST To UTC",
"input": {
"type": "dateMath",
"attributes": {
"expression": "/d",
"input": {
"type": "reference",
"attributes": {
"id": "UTC To EST",
"input": {
"type": "dateFormat",
"attributes": {
"inputFormat": "MM/dd/yyyy",
"outputFormat": "ISO8601",
"input": {
"type": "firstValid",
"attributes": {
"values": [
{
"type": "accountAttribute",
"attributes": {
"attributeName": "terminationDate",
"sourceName": "Employee Directory"
}
},
"12/31/2099"
]
}
}
}
}
}
}
}
}
},
"internal": false
}
2. Cloud Life Cycle State
Compares the user’s termination date against the current local date to calculate their lifecycle state. The magic here is using the dateMath expression "now" wrapped with the EST To UTC / UTC To EST logic and rounding down via "/d". This ensures evening timezone offsets do not trigger terminations a day early.
Employee - CloudLifeCycleState.json (1.7 KB)
{
"id": "903eb362-3e8a-4bf0-90c7-19578010b607",
"name": "Employee - CloudLifeCycleState",
"type": "static",
"attributes": {
"requiresPeriodicRefresh": true,
"isTerminated": {
"type": "dateCompare",
"attributes": {
"firstDate": {
"type": "reference",
"attributes": {
"id": "UTC To EST",
"input": {
"type": "dateFormat",
"attributes": {
"inputFormat": "MM/dd/yyyy",
"outputFormat": "ISO8601",
"input": {
"type": "firstValid",
"attributes": {
"values": [
{
"type": "accountAttribute",
"attributes": {
"attributeName": "terminationDate",
"sourceName": "Employee Directory"
}
},
"12/31/2099"
]
}
}
}
}
}
},
"secondDate": {
"type": "dateMath",
"attributes": {
"expression": "/d",
"input": {
"type": "reference",
"attributes": {
"id": "UTC To EST",
"input": {
"type": "dateMath",
"attributes": {
"expression": "now"
}
}
}
}
}
},
"operator": "lte",
"positiveCondition": "yes",
"negativeCondition": "no"
}
},
"value": "#if($isTerminated == 'yes')inactive#{else}active#end"
},
"internal": false
}
3. HireDate (First Hour of Start Day)
Uses local timezone boundaries to reliably calculate the first hour of a user’s start day, such as 1:00 AM local time. This is done by appending +1h to the date rounding: "/d+1h".
Employee - HireDate.json (1.1 KB)
{
"id": "1f4b79ec-75fa-4160-8cd5-c77f028f4777",
"name": "Employee - HireDate",
"type": "reference",
"attributes": {
"id": "EST To UTC",
"input": {
"type": "dateMath",
"attributes": {
"expression": "/d+1h",
"input": {
"type": "reference",
"attributes": {
"id": "UTC To EST",
"input": {
"type": "dateFormat",
"attributes": {
"inputFormat": "MM/dd/yyyy",
"outputFormat": "ISO8601",
"input": {
"type": "firstValid",
"attributes": {
"values": [
{
"type": "accountAttribute",
"attributes": {
"attributeName": "startDate",
"sourceName": "Employee Directory"
}
},
"12/31/2099"
]
}
}
}
}
}
}
}
}
},
"internal": false
}
4. End of Business Day Disablement
Calculates exactly 5:00 PM (17:00) in the local timezone for a given date to enforce grace periods before disabling an account. The dateMath logic dynamically adds 17 hours after rounding: "+17h/d".
Employee - End of Business Day Disablement.json (1.1 KB)
{
"id": "39e49f44-041f-438a-b6db-bd92923674db",
"name": "Employee - End of Business Day Disablement",
"type": "reference",
"attributes": {
"id": "EST To UTC",
"input": {
"type": "dateMath",
"attributes": {
"expression": "+17h/d",
"input": {
"type": "reference",
"attributes": {
"id": "UTC To EST",
"input": {
"type": "dateFormat",
"attributes": {
"inputFormat": "MM/dd/yyyy",
"outputFormat": "ISO8601",
"input": {
"type": "firstValid",
"attributes": {
"values": [
{
"type": "accountAttribute",
"attributes": {
"attributeName": "endDate",
"sourceName": "Employee Directory"
}
},
"12/31/2099"
]
}
}
}
}
}
}
}
}
},
"internal": false
}
5. Contractor Expiration Warning
Calculates exactly 8:00 AM local time, 7 days prior to a contractor’s end date, ensuring expiration warnings go out at the correct time regardless of the season. This expression is slightly more complex, utilizing "-7d/d+8h" to manipulate the target time precisely.
Employee - Contractor Expiration Warning.json (1.1 KB)
{
"id": "17d5e722-41ac-466e-949b-6632b1d3b3a4",
"name": "Employee - Contractor Expiration Warning",
"type": "reference",
"attributes": {
"id": "EST To UTC",
"input": {
"type": "dateMath",
"attributes": {
"expression": "-7d/d+8h",
"input": {
"type": "reference",
"attributes": {
"id": "UTC To EST",
"input": {
"type": "dateFormat",
"attributes": {
"inputFormat": "MM/dd/yyyy",
"outputFormat": "ISO8601",
"input": {
"type": "firstValid",
"attributes": {
"values": [
{
"type": "accountAttribute",
"attributes": {
"attributeName": "endDate",
"sourceName": "Employee Directory"
}
},
"12/31/2099"
]
}
}
}
}
}
}
}
}
},
"internal": false
}
6. Inactive for 90 Days
Rounds the user’s last login date down to local midnight and subtracts 90 days to establish an exact, timezone-aware threshold for stale account cleanups. We achieve this by simply subtracting 90 days from the rounded date: "-90d/d".
Employee - Inactive for 90 Days.json (1.1 KB)
{
"id": "4b29f531-0cf8-4b3d-8023-c0275b87a916",
"name": "Employee - Inactive for 90 Days",
"type": "reference",
"attributes": {
"id": "EST To UTC",
"input": {
"type": "dateMath",
"attributes": {
"expression": "-90d/d",
"input": {
"type": "reference",
"attributes": {
"id": "UTC To EST",
"input": {
"type": "dateFormat",
"attributes": {
"inputFormat": "MM/dd/yyyy",
"outputFormat": "ISO8601",
"input": {
"type": "firstValid",
"attributes": {
"values": [
{
"type": "accountAttribute",
"attributes": {
"attributeName": "lastLogin",
"sourceName": "Employee Directory"
}
},
"12/31/2099"
]
}
}
}
}
}
}
}
}
},
"internal": false
}
7. Standardize Date Format to UTC
Properly ingests localized date strings and shifts them to represent local midnight in UTC format so ISC correctly anchors the date. This requires no custom math expressions, only standardizing the incoming format before it runs through the EST To UTC transform.
Employee - Standardize Date Format to UTC.json (741 Bytes)
{
"id": "4adcfbd5-7769-4cb8-9ffd-d5aaee069e95",
"name": "Employee - Standardize Date Format to UTC",
"type": "reference",
"attributes": {
"id": "EST To UTC",
"input": {
"type": "dateFormat",
"attributes": {
"inputFormat": "MM/dd/yyyy",
"outputFormat": "ISO8601",
"input": {
"type": "firstValid",
"attributes": {
"values": [
{
"type": "accountAttribute",
"attributes": {
"attributeName": "startDate",
"sourceName": "Employee Directory"
}
},
"12/31/2099"
]
}
}
}
}
},
"internal": false
}
Considerations & Best Practices: requiresPeriodicRefresh
You might notice that the Cloud Life Cycle State example uses the "requiresPeriodicRefresh": true attribute as a boolean. This is an important parameter when working with time-dependent logic.
What it does:
It allows the transform to be checked at the nightly refresh, even if no upstream account data changed. Note that ISC will always perform the daily 8 AM refresh whether this flag is set or not. It is designed for values that can change organically over time.
When to use it:
- Time-dependent logic (e.g., age/tenure bands, grace-period flags, or “isContractEndingIn30Days”).
- Calculations using “now” or the current date.
- Any output that should update on a cadence regardless of new source events.
When NOT to use it:
- Simple mappings like
accountAttribute,firstValid, or string manipulation that only change when source attributes change. - Static defaults and normalization that do not depend on time.
Be selective when enabling this flag. Turning it on everywhere forces the Identity Refresh process to do extra work for no benefit, which can noticeably increase your refresh duration and queue load.
Testing and Validation
When implementing scheduled transform updates, testing is critical to ensure a seamless transition and prevent unexpected disruptions to identity lifecycles. It is highly recommended to:
- Test Scheduled Updates: Verify that your workflows correctly authenticate and successfully update the base transforms by running manual tests in a sandbox environment prior to the actual transition dates.
- Validate Date Math: Manually test the date math examples (e.g., local midnight, end of business day disablement) with both standard and daylight saving offsets to confirm your time logic yields the expected outcomes.
- Monitor DST Transitions: During the actual time change, closely monitor and confirm expected behavior around the transition dates. Ensure no scheduled terminations, grace periods, or lifecycle states trigger prematurely or belatedly.
Conclusion
- Summary: Handling timezone offsets and Daylight Saving Time inside ISC transforms does not have to be a manual headache. By centralizing your offsets into two base transforms and automating their updates via scheduled workflows, you can build a robust date calculation architecture that works year-round.
- Call to Action: Have you approached timezone management differently in your tenant? If you have any questions or alternative approaches, feel free to drop a comment below. I am happy to help expand on this setup!



