How to Convert Seconds to Hours and Minutes in PowerShell

If you’ve ever worked with logs, timers, or performance data in PowerShell, you’ve probably come across time values in seconds that aren’t very human-friendly. Reading something like “7265 seconds” doesn’t immediately tell you much unless you stop and calculate it mentally.

In this post, we’ll make that easier by learning how to convert seconds into a clear hours and minutes format using PowerShell. Whether you’re building reports, automating monitoring scripts, or just cleaning up output for better readability, this simple conversion can make your results far more useful and presentable.

Let’s start.

Understanding the Math in PowerShell

Before using any PowerShell method, here’s the underlying arithmetic:

  • Hours = [Math]::Floor($seconds / 3600)
  • Minutes = [Math]::Floor(($seconds % 3600) / 60)
  • Remaining seconds = $seconds % 60

For example, 7385 seconds = 2 hours, 3 minutes, 5 seconds.

Method 1: [TimeSpan]::FromSeconds() — Best Default Approach

The cleanest one-liner. Pass seconds directly and get a full TimeSpan object back:

$seconds  = 7385
$timespan = [TimeSpan]::FromSeconds($seconds)

Write-Host "Hours : $($timespan.Hours)"
Write-Host "Minutes : $($timespan.Minutes)"
Write-Host "Seconds : $($timespan.Seconds)"

Output:

Hours   : 2
Minutes : 3
Seconds : 5

I executed the above PowerShell script, and you can see the exact output in the screenshot below:

Convert Seconds to Hours and Minutes powershell

Key TimeSpan properties for hours and minutes:

PropertyReturns
.HoursHours component only (0–23)
.MinutesMinutes component only (0–59)
.SecondsSeconds component only (0–59)
.TotalHoursFull decimal hours (e.g. 2.0513...)
.TotalMinutesFull decimal minutes (e.g. 123.08...)
.DaysDays component for large values

Check out Check if a File’s LastWriteTime is Greater Than a Specific Date in PowerShell

Method 2: New-TimeSpan Cmdlet

The PowerShell cmdlet equivalent — same result, slightly more readable syntax:

$seconds  = 7385
$ts = New-TimeSpan -Seconds $seconds

Write-Host "Total Hours : $($ts.TotalHours)"
Write-Host "Total Minutes : $($ts.TotalMinutes)"
Write-Host "$($ts.Hours) hours, $($ts.Minutes) minutes, $($ts.Seconds) seconds"

Method 3: Format as hh:mm:ss String

Use ToString() with a custom format string to get clock-style output:

$seconds = 7385
$ts = [TimeSpan]::FromSeconds($seconds)

# HH:MM:SS format
$ts.ToString("hh\:mm\:ss")
# Output: 02:03:05

You can see the exact output in the screenshot below:

PowerShell Convert Seconds to Hours and Minutes

Other useful format patterns:

# Hours and minutes only — no seconds
$ts.ToString("hh\:mm")
# Output: 02:03

# With labels embedded in format string
$ts.ToString("hh' hours 'mm' minutes 'ss' seconds'")
# Output: 02 hours 03 minutes 05 seconds

# With days for large values
$ts.ToString("dd'\.hh\:mm\:ss'")

# Using -f operator style
"{0:hh}h:{0:mm}m:{0:ss}s" -f $ts
# Output: 02h:03m:05s

Important: Always escape the colon with a backslash inside ToString() format strings — "hh\:mm\:ss" not "hh:mm:ss". Without the backslash, : is treated as a format specifier and produces unexpected output.

Read Find Dates in Strings with PowerShell

Method 4: Pure Math (No TimeSpan)

For simple scripts where you just want the numbers without a TimeSpan object:

$seconds  = 7385

$hours = [Math]::Floor($seconds / 3600)
$minutes = [Math]::Floor(($seconds % 3600) / 60)
$secs = $seconds % 60

Write-Host "$hours hours, $minutes minutes, $secs seconds"
# Output: 2 hours, 3 minutes, 5 seconds

Zero-padded display:

$formatted = "{0:D2}:{1:D2}:{2:D2}" -f $hours, $minutes, $secs
Write-Host $formatted
# Output: 02:03:05

Check out PowerShell Get-Date -UFormat Examples

Real-World Use Case 1: Display Script Execution Time

Measure how long a script block takes and display it as hours, minutes, and seconds:

$startTime = Get-Date

# --- Your workload here ---
Start-Sleep -Seconds 7385

$elapsed = (Get-Date) - $startTime

Write-Host "Script completed in:"
Write-Host " $($elapsed.Hours)h $($elapsed.Minutes)m $($elapsed.Seconds)s"
Write-Host " Formatted: $($elapsed.ToString('hh\:mm\:ss'))"

Or with Measure-Command:

$duration = Measure-Command { Start-Sleep -Seconds 125 }

Write-Host "Duration: $($duration.Hours)h $($duration.Minutes)m $($duration.Seconds)s"
Write-Host "Formatted: $($duration.ToString('hh\:mm\:ss'))"

Real-World Use Case 2: System Uptime in Hours and Minutes

$os     = Get-CimInstance Win32_OperatingSystem
$uptime = (Get-Date) - $os.LastBootUpTime

Write-Host "System Uptime"
Write-Host " Days : $($uptime.Days)"
Write-Host " Hours : $($uptime.Hours)"
Write-Host " Minutes : $($uptime.Minutes)"
Write-Host " Formatted: $($uptime.Days)d $($uptime.Hours)h $($uptime.Minutes)m"
Write-Host " Total hours: $([Math]::Round($uptime.TotalHours, 1))"

I executed the above PowerShell script, and you can see the exact output in the screenshot below:

How to Convert Seconds to Hours and Minutes PowerShell

Check out PowerShell Get-Date Month Name

Real-World Use Case 3: Convert a Seconds Column in a CSV

A monitoring tool, call center platform, or video streaming report exports durations as raw seconds. Convert them to hours and minutes for readable reporting:

# Sample CSV (durations.csv):
# TaskName,DurationSeconds
# Backup Job,7385
# Log Cleanup,245
# Report Gen,10920

Import-Csv "C:\Data\durations.csv" -Encoding UTF8 |
Select-Object TaskName,
DurationSeconds,
@{N='Hours'; E={ ([TimeSpan]::FromSeconds([int]$_.DurationSeconds)).Hours }},
@{N='Minutes'; E={ ([TimeSpan]::FromSeconds([int]$_.DurationSeconds)).Minutes }},
@{N='Seconds'; E={ ([TimeSpan]::FromSeconds([int]$_.DurationSeconds)).Seconds }},
@{N='Formatted'; E={ [TimeSpan]::FromSeconds([int]$_.DurationSeconds).ToString("hh\:mm\:ss") }} |
Export-Csv "C:\Output\durations_formatted.csv" -NoTypeInformation -Encoding UTF8 |
Format-Table -AutoSize

Output:

TaskName    DurationSeconds Hours Minutes Seconds Formatted
-------- --------------- ----- ------- ------- ---------
Backup Job 7385 2 3 5 02:03:05
Log Cleanup 245 0 4 5 00:04:05
Report Gen 10920 3 2 0 03:02:00

Real-World Use Case 4: Multi-Machine Uptime Report

Pull uptime from multiple machines and format as hours and minutes:

$computers = @("Server01", "Server02", "Server03")

$report = foreach ($computer in $computers) {
try {
$os = Get-CimInstance Win32_OperatingSystem -ComputerName $computer -ErrorAction Stop
$uptime = (Get-Date) - $os.LastBootUpTime

[PSCustomObject]@{
Computer = $computer
LastBoot = $os.LastBootUpTime.ToString("yyyy-MM-dd HH:mm")
UptimeDays = $uptime.Days
UptimeHours = $uptime.Hours
UptimeMins = $uptime.Minutes
UptimeFmt = "$($uptime.Days)d $($uptime.Hours)h $($uptime.Minutes)m"
TotalHours = [Math]::Round($uptime.TotalHours, 1)
}
}
catch {
[PSCustomObject]@{
Computer = $computer
LastBoot = "N/A"
UptimeFmt = "Unreachable"
TotalHours = 0
}
}
}

$report | Format-Table -AutoSize
$report | Export-Csv "C:\Reports\uptime.csv" -NoTypeInformation -Encoding UTF8

Check out PowerShell Get Difference Between Two Dates in Minutes

Real-World Use Case 5: Convert Event Log Duration

Calculate how long ago an event occurred and display in hours and minutes:

Get-WinEvent -LogName System -MaxEvents 10 | ForEach-Object {
$age = (Get-Date) - $_.TimeCreated
[PSCustomObject]@{
EventId = $_.Id
TimeCreated = $_.TimeCreated.ToString("yyyy-MM-dd HH:mm:ss")
AgoHours = $age.Hours
AgoMinutes = $age.Minutes
AgoFmt = "$($age.Days)d $($age.Hours)h $($age.Minutes)m ago"
}
} | Format-Table -AutoSize

Handle Large Values (Days)

When seconds exceed 86,400 (one full day), the .Hours property resets to 0 and .Days increments. Always account for days in long-duration scenarios:

$seconds = 100000   # 1 day, 3 hours, 46 minutes, 40 seconds

$ts = [TimeSpan]::FromSeconds($seconds)

Write-Host "Days : $($ts.Days)"
Write-Host "Hours : $($ts.Hours)"
Write-Host "Minutes : $($ts.Minutes)"
Write-Host "Seconds : $($ts.Seconds)"

# Wrong — shows only 03:46:40, ignoring the day
$ts.ToString("hh\:mm\:ss")

# Correct — includes days
$ts.ToString("dd'.'hh\:mm\:ss") # 01.03:46:40
"{0}d {1}h {2}m" -f $ts.Days, $ts.Hours, $ts.Minutes # 1d 3h 46m

Check out PowerShell (Get-Date).AddDays(0) [With Examples]

Handle Milliseconds

For values from Measure-Command, performance counters, or API timers that include fractional seconds:

$ms = 9385750   # milliseconds

$ts = [TimeSpan]::FromMilliseconds($ms)

Write-Host "Hours : $($ts.Hours)"
Write-Host "Minutes : $($ts.Minutes)"
Write-Host "Seconds : $($ts.Seconds)"
Write-Host "Formatted: $($ts.ToString('hh\:mm\:ss\.fff'))"
# Output: 02:36:25.750

A Complete Reusable ConvertTo-HoursMinutes Function

Here is a complete reusable function that you can use in PowerShell.

function ConvertTo-HoursMinutes {
<#
.SYNOPSIS
Converts seconds to hours, minutes, and seconds with multiple output formats.
.PARAMETER Seconds
Input value in seconds. Accepts pipeline input.
.PARAMETER Format
HHMMSS → "02:03:05"
HHMM → "02:03"
Readable → "2 hours, 3 minutes, 5 seconds"
Short → "2h 3m 5s"
Object → PSCustomObject with all components
.EXAMPLE
ConvertTo-HoursMinutes -Seconds 7385 -Format Readable
.EXAMPLE
@(3600, 7200, 10800) | ConvertTo-HoursMinutes -Format Short
#>

[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[double]$Seconds,

[ValidateSet("HHMMSS","HHMM","Readable","Short","Object")]
[string]$Format = "HHMMSS"
)

process {
$ts = [TimeSpan]::FromSeconds($Seconds)

switch ($Format) {
"HHMMSS" {
if ($ts.Days -gt 0) {
"$($ts.Days)d " + $ts.ToString("hh\:mm\:ss")
} else {
$ts.ToString("hh\:mm\:ss")
}
}
"HHMM" {
if ($ts.Days -gt 0) {
"$($ts.Days)d " + $ts.ToString("hh\:mm")
} else {
$ts.ToString("hh\:mm")
}
}
"Readable" {
$parts = @()
if ($ts.Days -gt 0) { $parts += "$($ts.Days) day$(if($ts.Days -ne 1){'s'})" }
if ($ts.Hours -gt 0) { $parts += "$($ts.Hours) hour$(if($ts.Hours -ne 1){'s'})" }
if ($ts.Minutes -gt 0) { $parts += "$($ts.Minutes) minute$(if($ts.Minutes -ne 1){'s'})" }
if ($ts.Seconds -gt 0) { $parts += "$($ts.Seconds) second$(if($ts.Seconds -ne 1){'s'})" }
if ($parts.Count -eq 0) { "0 seconds" } else { $parts -join ", " }
}
"Short" {
$parts = @()
if ($ts.Days -gt 0) { $parts += "$($ts.Days)d" }
if ($ts.Hours -gt 0) { $parts += "$($ts.Hours)h" }
if ($ts.Minutes -gt 0) { $parts += "$($ts.Minutes)m" }
if ($ts.Seconds -gt 0) { $parts += "$($ts.Seconds)s" }
if ($parts.Count -eq 0) { "0s" } else { $parts -join " " }
}
"Object" {
[PSCustomObject]@{
InputSeconds = $Seconds
Days = $ts.Days
Hours = $ts.Hours
Minutes = $ts.Minutes
Seconds = $ts.Seconds
TotalHours = [Math]::Round($ts.TotalHours, 4)
TotalMinutes = [Math]::Round($ts.TotalMinutes, 4)
Formatted_HHMMSS = if ($ts.Days -gt 0) { "$($ts.Days)d " + $ts.ToString("hh\:mm\:ss") } else { $ts.ToString("hh\:mm\:ss") }
Formatted_Short = $(
$p = @()
if ($ts.Days -gt 0) { $p += "$($ts.Days)d" }
if ($ts.Hours -gt 0) { $p += "$($ts.Hours)h" }
if ($ts.Minutes -gt 0) { $p += "$($ts.Minutes)m" }
if ($ts.Seconds -gt 0) { $p += "$($ts.Seconds)s" }
if ($p.Count -eq 0) { "0s" } else { $p -join " " }
)
}
}
}
}
}

Usage Examples

# Clock format
ConvertTo-HoursMinutes -Seconds 7385 -Format HHMMSS
# 02:03:05

# Hours and minutes only
ConvertTo-HoursMinutes -Seconds 7385 -Format HHMM
# 02:03

# Full readable string
ConvertTo-HoursMinutes -Seconds 7385 -Format Readable
# 2 hours, 3 minutes, 5 seconds

# Short compact format
ConvertTo-HoursMinutes -Seconds 7385 -Format Short
# 2h 3m 5s

# Full object with all components
ConvertTo-HoursMinutes -Seconds 7385 -Format Object

# Pipeline from array
@(3600, 7200, 7385, 86400, 90061) | ConvertTo-HoursMinutes -Format Readable
# 1 hour
# 2 hours
# 2 hours, 3 minutes, 5 seconds
# 1 day
# 1 day, 1 hour, 1 minute, 1 second

# Pipeline from CSV column
Import-Csv "C:\Data\tasks.csv" |
ForEach-Object { $_.Duration = ConvertTo-HoursMinutes -Seconds $_.DurationSecs -Format Short; $_ } |
Format-Table

Check out Find Files Modified After a Specific Date Using PowerShell

Quick Reference

Here are all the methods.

GoalCode
Hours component([TimeSpan]::FromSeconds($s)).Hours
Minutes component([TimeSpan]::FromSeconds($s)).Minutes
Total decimal hours([TimeSpan]::FromSeconds($s)).TotalHours
Format as hh:mm:ss[TimeSpan]::FromSeconds($s).ToString("hh\:mm\:ss")
Format as hh:mm[TimeSpan]::FromSeconds($s).ToString("hh\:mm")
Readable label string"{0}h {1}m {2}s" -f $ts.Hours, $ts.Minutes, $ts.Seconds
From milliseconds[TimeSpan]::FromMilliseconds($ms).ToString("hh\:mm\:ss")
Script execution time(Measure-Command { ... }).ToString("hh\:mm\:ss")
System uptime((Get-Date) - (Get-CimInstance Win32_OS).LastBootUpTime)

Key Mistakes You Need to Avoid

Here are some key mistakes you need to avoid.

1. Using .Hours instead of .TotalHours for the complete value

.Hours returns the hours component (0–23) only. For 100000 seconds, .Hours returns 3 (the hours portion of “1 day, 3 hours”), while .TotalHours returns 27.77 (the complete duration in hours). Use .TotalHours when you want the full value, .Hours when you want the component.

2. Not escaping the colon in ToString() format strings

$ts.ToString("hh:mm:ss") produces wrong output. The colon must be escaped: $ts.ToString("hh\:mm\:ss"). This is the single most common formatting mistake with TimeSpan.

3. Ignoring days for large second values

86400 seconds = 1 full day. At this point .Hours resets to 0 and .Days becomes 1. If your display only shows hours and minutes, a 25-hour duration will wrongly display as 01:00. Always check .Days and include it in output for any use case that might exceed 24 hours.

4. Forgetting to cast CSV string values

[TimeSpan]::FromSeconds($_.DurationSeconds) fails when $_.DurationSeconds is a string (as all CSV fields are). Always cast first: [TimeSpan]::FromSeconds([int]$_.DurationSeconds) or [double]$_.DurationSeconds.

5. Using Get-Date formatting for durations

Get-Date formats work on actual clock times, not durations. Trying to format a TimeSpan through date format strings produces garbage output. Always use TimeSpan.ToString() or the -f operator with TimeSpan format specifiers.

Read Find Files Modified Between Dates Using PowerShell

Best Practices

Here are some best practices that you can follow.

  • Use [TimeSpan]::FromSeconds() as your default — clean, .NET native, works on all PowerShell versions
  • Always escape colons in format strings — "hh\:mm\:ss" every time, without exception
  • Use .TotalHours / .TotalMinutes for calculations, .Hours / .Minutes for display components
  • Always account for .Days in any use case that could exceed 86,400 seconds — uptime, backup durations, SLA tracking
  • Cast CSV seconds columns to [int] or [double] before passing to FromSeconds()
  • Use Measure-Command instead of manual Get-Date subtraction for measuring script execution time — it’s cleaner and built for that purpose
  • Round .TotalHours when displaying — [Math]::Round($ts.TotalHours, 1) avoids ugly floating-point strings like 2.08472222222222

Conclusion

Converting seconds to hours and minutes in PowerShell is most cleanly done with [TimeSpan]::FromSeconds() — it gives you named component properties (.Hours.Minutes.Seconds) plus total decimal values (.TotalHours.TotalMinutes) and a flexible ToString() formatter.

The reusable ConvertTo-HoursMinutes function in this tutorial covers all five output formats — clock string, readable label, compact short form, hours-only, and a full object — and works natively in pipelines for bulk CSV processing, uptime reporting, and performance metric formatting.

Do let me know if this helps.

You may also like the following tutorials:

100 PowerShell cmdlets download free

100 POWERSHELL CMDLETS E-BOOK

FREE Download an eBook that contains 100 PowerShell cmdlets with complete script and examples.