forked from SunsetYe66/ClasstableToIcal
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathical_generator.py
186 lines (167 loc) · 7.74 KB
/
ical_generator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# coding: utf-8
# Author: SunsetYe inherited from ChanJH
# Website: ChanJH <chanjh.com>, SunsetYe <github.com/sunsetye66>
# Contact: SunsetYe <me # sunsetye.com>
import json
import sys
from datetime import datetime, timedelta
from uuid import uuid4 as uid
import socket
class GenerateCal:
def __init__(self):
# 定义全局参数
self.first_week = "20200224" # 第一周周一的日期
self.inform_time = 25 # 提前 N 分钟提醒
self.g_name = f'{datetime.now().strftime("%Y.%m")} 课程表@{socket.gethostname()}' # 全局课程表名
self.g_color = "#ff9500" # 预览时的颜色(可以在 iOS 设备上修改)
self.a_trigger = ""
# 读取文件,返回 dict(class_timetable) 时间表
try:
with open("conf_classTime.json", 'r', encoding='UTF-8') as f:
self.class_timetable = json.loads(f.read())
f.close()
except:
print("时间配置文件 conf_classTime.json 似乎有点问题")
sys.exit()
# 读取文件,返回 dict(class_info) 课程信息
try:
with open("conf_classInfo.json", 'r', encoding='UTF-8') as f:
self.class_info = json.loads(f.read())
f.close()
except:
print("课程配置文件 conf_classInfo.json 似乎有点问题")
sys.exit()
# 获取内网ip用于最后的提示
def get_host_ip(self):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(('119.29.29.29', 80))
ip = s.getsockname()[0]
finally:
s.close()
return ip
def set_attribute(self):
self.first_week = input("请输入第一周周一的日期, 格式为 YYYYMMDD, 如 20200224: ") # 第一周周一的日期
c = 0
while c == 0:
self.inform_time = input("请输入提前提醒时间, 以分钟计; 若不需要提醒请输入 N: ")
try:
self.inform_time = int(self.inform_time) # 提前 N 分钟提醒
if self.inform_time <= 60:
self.a_trigger = f'-P0DT0H{self.inform_time}M0S'
elif 60 < self.inform_time <= 1440:
minutes = self.inform_time % 60
hours = self.inform_time // 60
self.a_trigger = f'-P0DT{hours}H{minutes}M0S'
else:
minutes = self.inform_time % 60
hours = (self.inform_time // 60) - 24
days = self.inform_time // 1440
self.a_trigger = f'-P{days}DT{hours}H{minutes}M0S'
c = 1
except ValueError:
if self.inform_time in "nN":
self.a_trigger = ""
c = 1
else:
print("输入数字有误!")
def main_process(self):
utc_now = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
weekdays = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"]
# 开始操作,先写入头
ical_begin_base = f'''BEGIN:VCALENDAR
VERSION:2.0
X-WR-CALNAME:{self.g_name}
X-APPLE-CALENDAR-COLOR:{self.g_color}
X-WR-TIMEZONE:Asia/Shanghai
BEGIN:VTIMEZONE
TZID:Asia/Shanghai
X-LIC-LOCATION:Asia/Shanghai
BEGIN:STANDARD
TZOFFSETFROM:+0800
TZOFFSETTO:+0800
TZNAME:CST
DTSTART:19700101T000000
END:STANDARD
END:VTIMEZONE
'''
try:
with open(f"res-{str(utc_now)}.ics", "w", encoding='UTF-8') as f: # 追加要a
f.write(ical_begin_base)
f.close()
except:
print("写入失败. 可能是没有权限, 请重试. ")
sys.exit()
else:
print("文件头写入成功. ")
initial_time = datetime.strptime(self.first_week, "%Y%m%d") # 将开始时间转换为时间对象
i = 1
for obj in self.class_info:
# 计算课程第一次开始的日期 first_time_obj,公式:7*(开始周数-1) (//把第一周减掉) + 周几 - 1 (没有周0,等于把周一减掉)
try:
delta_time = 7 * (obj['StartWeek'] - 1) + obj['Weekday'] - 1
except TypeError:
print("请检查 Excel 中是否有无用行, 并删除 conf_classInfo.json 后重新运行 Excel 读取器及 iCal 生成器. ")
sys.exit()
if obj['WeekStatus'] == 1: # 单周
if obj["StartWeek"] % 2 == 0: # 若单周就不变,双周加7
delta_time += 7
elif obj['WeekStatus'] == 2: # 双周
if obj["StartWeek"] % 2 != 0: # 若双周就不变,单周加7
delta_time += 7
first_time_obj = initial_time + timedelta(days=delta_time) # 处理完单双周之后 first_time_obj 就是真正开始的日期
if obj["WeekStatus"] == 0: # 处理隔周课程
extra_status = "1"
else:
extra_status = f'2;BYDAY={weekdays[int(obj["Weekday"] - 1)]}' # BYDAY 是周 N,隔周重复需要带上
try: # 尝试处理纯数字的课程序号
obj["ClassSerial"] = str(int(obj["ClassSerial"]))
serial = f'教学班号: {obj["ClassSerial"]}'
except ValueError:
obj["ClassSerial"] = str(obj["ClassSerial"])
serial = f'教学班号:{obj["ClassSerial"]}'
except KeyError: # 如果没有这个 key,直接略过
serial = ""
# 计算课程第一次开始、结束的时间,后面使用RRule重复即可,格式类似 20200225T120000
final_stime_str = first_time_obj.strftime("%Y%m%d") + "T" + \
self.class_timetable[str(int(obj['ClassStartTimeId']))]["startTime"]
final_etime_str = first_time_obj.strftime("%Y%m%d") + "T" + \
self.class_timetable[str(int(obj['ClassEndTimeId']))]["endTime"]
delta_week = 7 * int(obj["EndWeek"] - obj["StartWeek"])
stop_time_obj = first_time_obj + timedelta(days=delta_week + 1)
stop_time_str = stop_time_obj.strftime("%Y%m%dT%H%M%SZ") # 注意是utc时间,直接+1天处理
# 教师可选,在此做判断
try:
teacher = f'教师: {obj["Teacher"]}\t'
except KeyError:
teacher = ""
# 生成此次循环的 event_base
if self.a_trigger:
_alarm_base = f'''BEGIN:VALARM\nACTION:DISPLAY\nDESCRIPTION:This is an event reminder
TRIGGER:{self.a_trigger}\nX-WR-ALARMUID:{uid()}\nUID:{uid()}\nEND:VALARM\n'''
else:
_alarm_base = ""
_ical_base = f'''\nBEGIN:VEVENT
CREATED:{utc_now}\nDTSTAMP:{utc_now}\nSUMMARY:{obj["ClassName"]}
DESCRIPTION:{teacher}{serial}\nLOCATION:{obj["Classroom"]}
TZID:Asia/Shanghai\nSEQUENCE:0\nUID:{uid()}\nRRULE:FREQ=WEEKLY;UNTIL={stop_time_str};INTERVAL={extra_status}
DTSTART;TZID=Asia/Shanghai:{final_stime_str}\nDTEND;TZID=Asia/Shanghai:{final_etime_str}
X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC\n{_alarm_base}END:VEVENT\n'''
# 写入文件
with open(f"res-{str(utc_now)}.ics", "a", encoding='UTF-8') as f:
f.write(_ical_base)
print(f"第{i}条课程信息写入成功. ")
i += 1
f.close()
# 拼合头尾
with open(f"res-{str(utc_now)}.ics", "a", encoding='UTF-8') as f:
f.write("\nEND:VCALENDAR")
print(f"尾部信息写入成功. ")
f.close()
final_inform = f'''
最终文件 res-{str(utc_now)}.ics 已生成, 导入到各日历软件即可使用. '''
print(final_inform)
if __name__ == "__main__":
p = GenerateCal()
p.set_attribute()
p.main_process()