TimeSpan.FromSeconds() 是最稳妥的秒转 TimeSpan 方式,自动处理进位、正负数及超24小时;格式化时用 "h:mm:ss" 显示累计时长,避免 "hh" 导致模24错误;手动拼接需用 TotalHours 取整、Minutes/Seconds 直接格式化。

用 TimeSpan.FromSeconds() 最稳妥
直接把秒数转成 TimeSpan,再格式化输出,是 C# 里最不容易出错的方式。它自动处理进位(比如 3661 秒 → 1 小时 1 分 1 秒),也不用自己算除法取余。
常见错误是手写 hours = seconds / 3600 这类计算,结果整数除法截断、负秒数崩溃、或忽略 TimeSpan 自带的标准化逻辑。
- 正数、零、负秒都支持 ——
TimeSpan.FromSeconds(-125)得到-00:02:05 - 超过 24 小时也正常:3600 * 25 = 90000 秒 →
25:00:00(不是01:00:00) - 避免用
DateTime加减来“模拟”:比如new DateTime().AddSeconds(n).ToString("HH:mm:ss"),会受本地时区和日期溢出影响
ToString("h\:mm\:ss") 和 ToString("hh\:mm\:ss") 的区别
格式字符串里的 h 和 hh 看似只差一个字母,但行为完全不同:前者是“无前导零的小时”,后者是“两位小时(不足补零)”。更关键的是,它们对 >24 小时的值表现不一致。
示例:TimeSpan.FromHours(25.5)(即 25 小时 30 分):
-
.ToString("h\:mm\:ss")→"25:30:00"(正确显示总小时) -
.ToString("hh\:mm\:ss")→"01:30:00"(误当成 25.5 % 24 = 1.5 小时) - 所以只要想显示“累计时长”,必须用单
h,且加转义符\:防止冒号被解析为时间分隔符
需要“01:02:03”这种固定三位数?别硬套 hh
很多人想要总时长也带前导零(比如 5 分 3 秒显示为 00:05:03),但又不敢用 hh 怕超 24 小时出错。这时候不能妥协用 hh,而应手动控制字段宽度。
推荐做法:拆开取 TotalHours、Minutes、Seconds,再用字符串格式化拼接:
var ts = TimeSpan.FromSeconds(3663);
string formatted = $"{(int)ts.TotalHours:D2}:{ts.Minutes:D2}:{ts.Seconds:D2}"; // "01:01:03"注意:TotalHours 是 double,要转 int 截断(不是四舍五入);Minutes 和 Seconds 是已归一化的 0–59 范围,可直接用。
- 别用
ts.Hours:它只返回“剩余小时”(即ts.TotalHours % 24),25 小时变成 1 - 如果秒数可能为负,
TotalHours也是负的,D2会报错 —— 需先取绝对值再格式化,或改用条件判断
性能敏感场景下,避免反复创建 TimeSpan
在高频循环(如每帧更新倒计时 UI)中,TimeSpan.FromSeconds(n) 每次都 new 对象,GC 压力比纯数学计算高。这时可退回到手动计算,但必须绕过常见坑:
- 用
Math.DivRem一次算出商和余数,比连写/和%更安全(尤其负数) - 对负秒数,先记符号,再用
Math.Abs算绝对值,最后补负号 - 示例:
int totalSec = -3661; bool negative = totalSec < 0; int absSec = Math.Abs(totalSec); int hours = absSec / 3600; int rem = Math.DivRem(absSec, 3600, out hours); int mins = rem / 60; int secs = rem % 60; string result = $"{(negative ? "-" : "")}{hours:D2}:{mins:D2}:{secs:D2}";
真正容易被忽略的,是负秒数的格式一致性 —— UI 上显示 -01:01:01 还是 0-01:01:01,得跟产品定义对齐,代码里漏掉符号处理,上线后才发现对不上设计稿


















