在 2023 年底的时候,我开始准备记录自己每天的时间是怎么安排的。
其实对于我自己来说,最大的驱动力来自记录自己的睡眠。毕竟作为「研究生」,平时也没有白天一定要做什么事情的需求(除了开会),生物钟经常颠倒,睡觉也睡的不好。如果能够自己统计一些具体的数据,或许可以分析分析怎么才能让自己的生物钟正常点,每天睡的时间也能多一点。
其实当时我也调研了一些可能的 app 去做时间记录,但是我自己可能是一个比较挑剔的人,根据我的以下需求,确实是很难有满足我要求的第三方工具了:
- 能导出 CSV 数据:别人画的图毕竟受限,想好看我有了数据可以自己 Python 画
- 足够方便:随时随地记录,而且点击要少,时间要短(一定一定不能有诸如开屏广告的玩意儿)
- 可自由定制类型:我可以只把时间分成两类(生活+工作),也可以细分到每一个工作项目,每一种生活类型(甚至玩哪个游戏),选择哪种方案是我的自由,想简单可简单,想复杂可复杂
好吧,说到这儿,我说实话已经不太可能找别人开发的方案了。
基于快捷指令的时间记录「app」
聊几句我自己的背景:我是做生信的,代码能力不强,也就玩玩 Python 的水平,写 iOS app 是绝对不行的。不过本段涉及的算法技术只有 if-else,其他部分大家真的随便看看就懂了。
既然都想自己搞了,我就把目光投向了 iOS 上的快捷指令。
在讲快捷指令之前,我甚至还尝试过一个更离谱的做法,写一段 Python 程序,然后手机执行代码,把记录结果远程发送到电脑。(这听着也太麻烦了吧)
一阵摸索后,我发现快捷指令比我想象中好用太多,其实本质上就是傻瓜式编程的软件,或者可以理解成现在流行的「无代码编程」。而且作为颜值控,快捷指令居然能生成一个类似于 app 的图标放在主屏,甚至这个图标还可以用自己的图片!能拥有自己定制的 logo 真是太美好了。
那么到这里,我们已经准备好了实现的途径,现在来列一下实际的需求:
- 能导出 CSV 数据:app 的输出存放在便签中,作为纯文本数据,其实已经很容易读取;
- 足够方便:每次记录时间点击屏幕三次(包括打开 app),不能再多了;
- 可自由定制类型:方便地添加/删除记录的类型。
于是我的流程便是:
手动打开 app ➡️ 手动选择大类 (Work/Life) ➡️ 手动选择小类 (Relax/Gaming/Sleep/Travel) ➡️ 自动生成一段文字 ➡️ 自动添加到某个便签的最后一行
上图就是实际操作的界面,两次点击(算上打开 app 三次)就可以了,真的很方便。而且我还换了一个好看的 logo,这样我就更有动力去点它了
输出的便签长这样:
Major: [Life]. Minor: [Life: Relax]. Starting at: [Jul 26, 2024 at 05:38]
Major: [Life]. Minor: [Life: Sleep]. Starting at: [Jul 26, 2024 at 08:01]
Major: [Life]. Minor: [Life: Relax]. Starting at: [Jul 26, 2024 at 12:00]
Major: [Life]. Minor: [Life: Relax]. Starting at: [Jul 27, 2024 at 12:04]
养成习惯还是需要一些过程的,平时的我需要睡觉前点一下,起床点一下,开始干活点一下,准备吃饭点一下,说白了还是小有点麻烦。所以我一开始分的类多,后面其实分类就越来越少了,无非就分休息/睡觉/工作这几个了。
统计结果可视化
既然都有了统计的结果,我们就可以根据记录的时间来做图了。我最近还是比较关心我每天睡的好不好,特别是每天到底睡够了没。
于是我就写了一段 Python 画图的代码,看看从今年第一天到现在(八月)每天睡了几个小时:
哎,上两周睡的也太惨了😭
如果统计一个直方图的话:
群友锐评 1:睡眠时间能拉出以小时为单位的正态有点狠啊
群友锐评 2:感觉我要是用钟老师的 colorbar,这一张热图都是蓝的
不过我自己感觉还算良好,居然我大半部分时间都有错过六小时睡眠,我已经很满足了。
后话
我自己算是一个比较爱折腾的人,能为了记录个时间折腾半天也是没谁了 hhh。
其实除了看睡了几个小时,我之前还统计过我自己做的每个项目到底花了多少时间。等最后有了回报之后,我就能统计下我的时间/产出比了。还有一段时间我总觉得我的人生浪费在了无穷无尽无聊的开会中,我当时也统计了下我一周到底花了多少时间在开会上(真的很多!),那次之后我就开始翘会了,能不开就别开了
这个 TimeTracker 已经变成我生活中有趣的一部分了,今天分享给大家,要是感兴趣的话也欢迎大家来交流和尝试
Source code
画图的代码就不公开了,大家多多培养自己的艺术细胞吧
读便签数据:
# load timetracker data
# the line format: Major: [MAJOR_TYPE]. Minor: [MINOR_TYPE]. Starting at: [MONTH DAY, YEAR at TIME]
def load_timetracker_records(file_path):
"""Load timetracker records from a file
Args:
file_path (str): The path to the file
Returns:
pd.DataFrame: A DataFrame containing the records
- major: The major activity
- minor: The minor activity
- start_time: The starting time
- end_time: The ending time
"""
major_activities = []
minor_activities = []
start_times = []
with open('timetracker.txt', 'r') as f:
# skip first line (first line is "time tracker")
f.readline()
for line in f:
major, minor, start_time = re.findall(r"\[([^\]]+)\]", line)
start_time = datetime.strptime(start_time, "%b %d, %Y at %H:%M")
# add to lists
major_activities.append(major)
minor_activities.append(minor)
start_times.append(start_time)
end_times = start_times[1:] + ["None"]
return pd.DataFrame({
'major': major_activities,
'minor': minor_activities,
'start_time': start_times,
'end_time': end_times
})
统计每天睡眠时长:
# create a blank calendar
# result: a list of day names, a list of start of day time, a list of end of day time
calendar_name_list = []
calendar_start_of_day_time_list = []
calendar_end_of_day_time_list = []
# generate calendar data
for i in range((time_end_date - time_start_date).days):
date = time_start_date + timedelta(days=i)
start_of_day = date.replace(hour=0, minute=0)
end_of_day = start_of_day + timedelta(days=1)
calendar_name_list.append(date.strftime("%b %d, %Y"))
calendar_start_of_day_time_list.append(start_of_day)
calendar_end_of_day_time_list.append(end_of_day)
sleep_calendar = [0 for _ in range(len(calendar_name_list))] # sleep calendar record sleep time for each day
# enumerate through time tracker records
for i in range(len(data)):
record = data.iloc[i]
major = record['major']
minor = record['minor']
start_time = record['start_time']
end_time = record['end_time']
# if the record is a sleep record
if major == "Life" and minor == "Life: Sleep":
# find the corresponding day
for j in range(len(calendar_name_list)):
if start_time >= calendar_start_of_day_time_list[j] and start_time < calendar_end_of_day_time_list[j]:
if end_time >= calendar_start_of_day_time_list[j] and end_time < calendar_end_of_day_time_list[j]: # if the sleep record is within the same day
duration = end_time - start_time
duration_hours = duration.total_seconds() / 3600
sleep_calendar[j] += duration_hours
elif end_time >= calendar_end_of_day_time_list[j]: # if the sleep record spans multiple days
duration_today = calendar_end_of_day_time_list[j] - start_time
duration_today_hours = duration_today.total_seconds() / 3600
sleep_calendar[j] += duration_hours
remaining_duration = end_time - calendar_end_of_day_time_list[j]
remaining_duration_hours = remaining_duration.total_seconds() / 3600
for k in range(j+1, len(calendar_name_list)):
if remaining_duration_hours > 0:
if remaining_duration >= timedelta(days=1):
sleep_calendar[k] += 24
remaining_duration -= timedelta(days=1)
remaining_duration_hours = remaining_duration.total_seconds() / 3600
else:
sleep_calendar[k] += remaining_duration_hours
remaining_duration_hours = 0
else:
break
else:
raise Exception("Invalid sleep record: " + str(record))
sleep_calendar = np.array(sleep_calendar)
# append np.nan to be the 7*n
n = 7
if len(sleep_calendar) % n != 0:
sleep_calendar_reshape = np.append(sleep_calendar, [np.nan for _ in range(n - len(sleep_calendar) % n)])
sleep_calendar_reshape = sleep_calendar_reshape.reshape(-1, n)
else:
sleep_calendar_reshape = sleep_calendar.reshape(-1, n)
> 关注 少数派小红书,感受精彩数字生活 🍃
> 实用、好用的 正版软件,少数派为你呈现 🚀