Home / Power Automate Tutorial / Day 8
Day 8 — Dates, times and time zones
Power Automate runs on UTC and your users do not. Almost every date bug comes from that one sentence.
Now
@utcNow()
@utcNow('dd-MM-yyyy')
@utcNow('dddd')
The clock here is frozen
This playground pretends it is always 18 March 2026, 09:30 UTC, so every example gives the same answer each time you run it and the lesson text stays true. In a real flow
utcNow() is the actual moment the action ran.Format strings
| Code | Gives | On 18 Mar 2026, 09:30 |
|---|---|---|
yyyy | Four-digit year | 2026 |
MM | Month number, padded | 03 |
MMM | Short month | Mar |
MMMM | Full month | March |
dd | Day, padded | 18 |
dddd | Day name | Wednesday |
HH | Hour, 24-hour | 09 |
mm | Minute | 30 |
Capital MM is month, small mm is minute
This is the mistake everybody makes.
dd-MM-yyyy is a date. dd-mm-yyyy puts the minutes where the month should be, and because the answer still looks like a date nobody notices until the report is out.@formatDateTime(utcNow(), 'dd-MM-yyyy') @formatDateTime(utcNow(), 'dd-mm-yyyy') @formatDateTime(utcNow(), 'dd MMM yyyy') @formatDateTime(utcNow(), 'dddd dd MMMM yyyy') @formatDateTime(utcNow(), 'yyyy-MM-dd HH:mm')
Line two is the wrong one. It reads 18-30-2026, because 30 is the minutes.
Time zones
@utcNow() @convertTimeZone(utcNow(), 'UTC', 'India Standard Time') @convertTimeZone(utcNow(), 'UTC', 'India Standard Time', 'dd MMM yyyy HH:mm') @convertTimeZone(utcNow(), 'UTC', 'India Standard Time', 'HH:mm')
09:30 UTC is 15:00 in India - five and a half hours ahead.
Convert once, at the edge
Do the arithmetic in UTC and convert only when you show a human the answer - in an email body, a file name, a report title. Converting early and then adding days to a converted value is how you end up an hour out twice a year in countries that use daylight saving.
India has no daylight saving, so the offset is always +5:30. That makes it easier than most, but the habit is still worth having.
India has no daylight saving, so the offset is always +5:30. That makes it easier than most, but the habit is still worth having.
Date arithmetic
@addDays(utcNow(), 7, 'dd MMM yyyy') @addDays(utcNow(), -1, 'dd MMM yyyy') @addHours(utcNow(), 48, 'dd MMM HH:mm') @addToTime(utcNow(), 3, 'Month', 'dd MMM yyyy') @addToTime(utcNow(), 1, 'Year', 'dd MMM yyyy') @startOfDay(utcNow()) @startOfMonth(utcNow(), 'dd MMM yyyy')
Negative numbers go backwards
There is no
subtractDays(). addDays(utcNow(), -1) is yesterday, and that is the accepted way to write it.Comparing dates
ISO dates sort correctly as text, which means the ordinary comparison functions work on them without any conversion.
@less(triggerBody()?['DueDate'], utcNow()) @greater(triggerBody()?['StartDate'], utcNow()) @formatDateTime(triggerBody()?['DueDate'], 'dd MMM yyyy') @if(less(triggerBody()?['DueDate'], utcNow()), 'Overdue', 'Still open')
Compare full ISO strings, not formatted ones
less('15-03-2026', '18-03-2026') compares text character by character and gives the wrong answer as soon as the months differ. Always compare the raw ISO value - 2026-03-15T00:00:00Z - and format only for display.A report window
A scheduled flow that reports on "everything since yesterday" is the most common date job there is.
{
"trigger": { "name": "Every weekday at 9am IST", "type": "Recurrence", "body": {} },
"actions": [
{ "name": "Window start", "type": "Compose", "input": "@startOfDay(addDays(utcNow(), -1))" },
{ "name": "Window end", "type": "Compose", "input": "@startOfDay(utcNow())" },
{ "name": "Report title", "type": "Compose",
"input": "MIS report for @{formatDateTime(addDays(utcNow(), -1), 'dd MMM yyyy')}" },
{ "name": "File name", "type": "Compose",
"input": "MIS_@{formatDateTime(addDays(utcNow(), -1), 'yyyy-MM-dd')}.xlsx" },
{ "name": "Rows", "type": "Compose", "input": [
{ "Id": 1, "Created": "2026-03-17T11:20:00Z", "Amount": 52000 },
{ "Id": 2, "Created": "2026-03-17T18:45:00Z", "Amount": 9500 },
{ "Id": 3, "Created": "2026-03-18T08:05:00Z", "Amount": 14500 }
] },
{ "name": "Yesterday only", "type": "FilterArray",
"from": "@outputs('Rows')",
"where": "@and(greaterOrEquals(item()?['Created'], outputs('Window start')), less(item()?['Created'], outputs('Window end')))" },
{ "name": "Count", "type": "Compose", "input": "@length(outputs('Yesterday only'))" }
]
}
Two of the three rows are from 17 March. The third is from this morning, so it belongs in tomorrow’s report - which is exactly why the window has a start and an end.
File names want yyyy-MM-dd
Name a file with
dd-MM-yyyy and a folder full of them sorts by day-of-month, so January and December interleave. yyyy-MM-dd sorts chronologically on its own. Same for anything you might later sort as text.Try these yourself
- Print today in three different formats, including the day name.
- Work out what 09:30 UTC is in Indian time, and write the expression for it.
- Build a file name for last month’s report, in a form that sorts correctly.
- Write a condition that is true when a due date has passed.
- Explain the difference between MM and mm, and what breaks when you confuse them.
