fitbit导出文件是utf-8无bom编码的非标准csv,需用csvhelper按rfc 4180解析;字段含逗号、换行、嵌套引号,首行为带空格中文标题;须显式映射列名、处理跨日睡眠、用log id去重,并统一sleep type大小写。

Fitbit导出文件是什么格式?直接用StreamReader读会出错
Fitbit导出的ZIP包里全是CSV,但不是标准CSV:字段含逗号、换行、引号嵌套,且首行是带空格的中文标题(如"Date","Calories Out"),部分字段值本身带双引号和换行符。用File.ReadAllLines()或手动Split(',')必乱码或解析错行。
- 必须用支持RFC 4180的CSV解析器,.NET原生不带,推荐
CsvHelper(NuGet安装CsvHelper) - 别用
Encoding.Default——导出文件是UTF-8无BOM,Windows默认ANSI会崩掉中文日期和活动名 - 解压ZIP不能只取顶层文件:健康数据在
export-*.zip/activities/、sleep/等子目录,需递归遍历
CsvHelper怎么映射Fitbit的CSV字段?字段名大小写和空格要对齐
Fitbit CSV列名带空格、大小写混用(如"Activity Calories")、甚至有括号("Steps (Total)"),CsvHelper默认按属性名匹配会失败。必须显式配置映射,不能依赖自动推断。
- 定义POCO类时,属性名用PascalCase,但加
[Name("Activity Calories")]特性一一对应 - 日期字段全是
"MM/dd/yyyy HH:mm"格式,需注册DateTimeConverter并指定"M/d/yyyy H:mm"(注意单M单d,Fitbit不用前导零) - 数值字段如
"Distance (mi)"可能为空字符串或"-",需自定义TypeConverter返回0.0或null
var config = new CsvConfiguration(CultureInfo.InvariantCulture) {
Delimiter = ",",
HasHeaderRecord = true,
Encoding = Encoding.UTF8
};
config.TypeConverterOptionsCache.GetOptions<DateTime>().Formats = new[] { "M/d/yyyy H:mm" };
解析睡眠数据时"Sleep Start Time"和"Sleep End Time"为什么拼不出完整DateTime?
Fitbit导出的睡眠CSV里,"Sleep Start Time"和"Sleep End Time"是纯时间(如"22:30"),而"Date"是日期(如"10/5/2023")。两者不在同一字段,也不能简单拼接——跨日睡眠(如23:45→7:20)会导致End时间早于Start。
- 必须先解析
Date为DateTime.Date,再解析两个时间字段为TimeSpan - 若
EndTime ,说明End属于次日,需给<code>Date.AddDays(1) - 别忽略
"Duration (ms)"字段——它比计算差值更可靠,尤其当用户手动编辑过睡眠记录
导出文件里有重复记录或时间重叠,C#怎么去重又不丢数据?
Fitbit Web导出有时会把同一天的多次活动拆成多行,也可能因同步延迟产生毫秒级时间重叠;但真实活动不可能完全重复(比如两行都是“Walking”且开始/结束时间一致)。不能简单按StartTime + ActivityName去重。
- 优先用
"Log ID"字段(如果存在)——这是Fitbit服务端唯一标识,比时间戳更可信 - 若无
Log ID(如旧版导出),按Date + ActivityName + Duration (ms)三者组合哈希去重 - 警惕“合并活动”:Fitbit App里手动合并的跑步记录,在CSV中仍保留原始多行,此时应保留
Duration (ms)最大的那行,而非最早的时间戳
实际处理时,最麻烦的是睡眠数据里的“nap”和“main sleep”混在同一CSV里,但"Sleep Type"字段值不统一(有时是"Nap",有时是"nap",甚至空),得先.Trim().ToLowerInvariant()再判断——这个细节90%的人第一次都会漏。










