01 / LOG ANALYSIS AND INDICATORS · LESSON 02

Put events in a UTC window

Compare timestamps as instants, including offsets and exact time-window boundaries.

12 min · learn at your pace

The same instant, different clocks

A timestamp ending in Z is expressed in UTC. An explicit offset such as +11:00 tells you how its clock differs from UTC. In this dataset, 20:02:00+11:00 and 09:02:00Z describe the same instant. Comparing their text alphabetically would give a misleading order.

Parse each value as [DateTimeOffset]. It retains the supplied offset and supports comparisons between instants. These fixtures always include an offset. In a real import, reject or explicitly interpret timestamps with no timezone instead of silently assuming your workstation's local time.

Define the edges

Use a half-open interval: include the start and exclude the end. An event at 09:00 belongs to [09:00,09:15); one at 09:15 belongs to the next window. Adjacent windows then avoid double counting their shared boundary.

$instant = [DateTimeOffset]::Parse('2026-10-12T20:02:00+11:00')
$instant.ToUniversalTime().ToString('o')

Keep the original timestamp beside your UTC representation. That makes the transformation reviewable and preserves what the export actually said. A normalised value helps sorting; it does not improve a source clock that was wrong.

The exercise includes successful and failed logons. Its question is about time only: do not carry the previous lesson's failed-logon filter into this task.

Go deeper

Read the official reference.

Try an example.ps1
[DateTimeOffset]::Parse('2026-10-12T20:02:00+11:00').ToUniversalTime().ToString('o')
YOUR TURN

Put it into practice.

From auth.csv, emit every event in [2026-10-12T09:00:00Z, 2026-10-12T09:15:00Z). Each object needs RecordId, OriginalTime (the unchanged Time string), and UtcTime (an ISO 8601 string with UTC offset). Include all event types. Order does not matter.

See an explained solution
One way to solve it.ps1
$start = [DateTimeOffset]::Parse('2026-10-12T09:00:00Z')
$end = [DateTimeOffset]::Parse('2026-10-12T09:15:00Z')
foreach ($event in Import-Csv '/home/user/auth.csv') {
    $time = [DateTimeOffset]::Parse($event.Time)
    if ($time -ge $start -and $time -lt $end) {
        [pscustomobject]@{ RecordId=$event.RecordId; OriginalTime=$event.Time; UtcTime=$time.ToUniversalTime().ToString('o') }
    }
}

DateTimeOffset comparisons account for each offset. The half-open condition includes A02, converts A03 correctly, and leaves A10 for the next window.