本文编写于 425 天前,最后修改于 424 天前,其中某些信息可能已经过时。

前言

即将进入带学,在杂七杂八的事情搞定之后便是坐等开学了。在学校里很重要的一个东西便是课程表,我大抵看了一下目前市面上的APP,我感觉更像是大学交友平台

有一个“小爱课程表”挺简洁的,而且目前在招纳个人开发者做适配,但是我申请了两回也不知道为什么没消息,而且开发者群里好像三天两头在说产品本身拉跨...

不过此时我把目光投向了“日历”APP,想起许久之前Apple日历还不支持中国节假日的时候(怒)曾在网络上有许多日历订阅的教程,这种日历不仅能由服务端分发,并且还能够及时更新,最重要的是,在临近日程时,日历会在我的手表上提示并常驻锁定屏幕(原生万岁)
那么,开始吧!

协议

首先要弄清楚别人是怎么实现的,搜寻一番,目前广泛使用的是“iCalendar”格式的日程(好像真的和果子有很多关系),然后就是“WebCal”协议,这个协议是“WebDev”系列协议,订阅的功能就基于它。

你可以在 https://icalendar.org/
上找到“iCalendar”格式的相关信息,至于“WebCal”,由客户端处理即可,我们仅需要保证服务端返回正确的“iCalendar”格式文件。

iCalendar格式

我会根据能找到的信息,最大努力翻译出来,但是仍然可能存在错误,烦请指正。

我使用Apple日历导出了一份,以便参考:

BEGIN:VCALENDAR
VERSION:2.0
X-WR-CALNAME:飞行计划
X-APPLE-CALENDAR-COLOR:#63DA38
BEGIN:VEVENT
DESCRIPTION:起飞降落时间为航班计划时间,仅供参考。
DTEND;TZID=Asia/Shanghai:20200901T170000
DTSTART;TZID=Asia/Shanghai:20200901T150500
SUMMARY:乘坐HO1072 长沙黄花-上海浦东 当地时间15:05-17:00[航旅纵横]
URL;VALUE=URI:umetrip://OPENAPP
END:VEVENT
END:VCALENDAR

其中有部分在“iCalendar”提供的示例的内容我删去了(如动态时区)该部分内容我看着很头大,但不会影响我们简单的需求。

该文件由“BEGIN:VCALENDAR”和“END:VCALENDAR”两个头包括在一起,详细的文件格式你可以去其官网上找到更多资料。

X-WR-CALNAME 定义该日历的名称
X-APPLE-CALENDAR-COLOR 这个是Apple日历定义图标颜色的

然后是“BEGIN:VEVENT”和“END:VEVENT”段,这将定义一次事件

CREATED 创建日期
DESCRIPTION 备注/描述
SUMMARY 标题
DTEND;TZID 结束日期,其指定了时区
DTSTART;TZID 开始日期,其指定了时区
URL;VALUE 事件的url地址,可以点开

你可以使用Outlook网页版快捷测试自己的日历。
所以我照葫芦画瓢,写了一个:

BEGIN:VCALENDAR
VERSION:2.0
X-WR-CALNAME:课程表
X-APPLE-CALENDAR-COLOR:#63DA38
BEGIN:VEVENT
DESCRIPTION:教师:曹华山\n学分:5.0
DTSTART;TZID=Asia/Shanghai:20201003T160000
DTEND;TZID=Asia/Shanghai:20201003T193000
LOCATION:教学楼C404
SUMMARY:网页设计综合实训
URL;VALUE=URI:hello
END:VEVENT
END:VCALENDAR

将其保存到文本文件,并使用ics后缀名,即可尝试导入到Outlook。
你便可以看到添加后的效果,但是其中有些参数不太清楚具体起什么作用,不过只要保证缺失不影响我们的内容即可,剩下的交给日历App本身去处理最佳。


当然在后期也应该多尝试几款日历App,试试他们自带的日程有什么专有格式,能够让日历更漂亮~
有了格式,那么接下来就是尝试转换数据了~

数据转换

首先应当让你学校教务系统提供的课程表变成结构化数据,因为不同学校不一样,这里就用我学校的教务网站做例子。

现状

不禁想要骂人,怎么会有这么离谱的东西?
这个教务系统竟然给人返回一张图片???


脑瓜子嗡嗡的,是怕有人学你课程表吗(捂脸)
看了一下请求按钮绑定的事件

function ChkValue()
            {
                var vXZBJ;
                var d='';
                var schoolcode="12300";
                try{
                    if(schoolcode == "10533" || schoolcode == "10842" || schoolcode == "10691")//中南大学、三门峡职业技术学院、云南民族大学
                       vXZBJ=document.all.Sel_BJ.value;
                    else
                      vXZBJ=document.all.Sel_XZBJ.value;
                    if((vXZBJ=="")||(vXZBJ=="Nothing")){
                        alert('需选定行政班级!');
                        if(schoolcode == "10533" || schoolcode == "10842" || schoolcode == "10691")//中南大学、三门峡职业技术学院、云南民族大学
                           document.all.Sel_BJ.focus();
                        else
                           document.all.Sel_XZBJ.focus();
                        return false;
                    }
                    else if(document.all.txt_yzm.value=="" && d=="")
                    {
                        alert("须录入验证码!");
                        document.all.txt_yzm.focus();
                        return false;
                    }
                    else{
                        form.method='post';
                        form.action='KBFB_ClassSel_rpt.aspx';
                        form.target='frmRpt';
                        form.submit();
                        return true;
                    }
                }catch(e){}
            }

阿哲,不知道是哪来的外包系统(好拉跨)...
处理图片肯定不是优先方案,随后我找到这个教务系统提供了按课程和教室查询的方式,返回的是文字..(绝了)

便从这里试着入手吧~

按课程查询

获取整个课程列表后,就可以循环查询了,但是,这个神奇的教务系统,每个验证码只准你查询9次???这算啥??
没啥办法,先硬着头皮弄一些测试数据出来再说。

经过一番折腾,生成了这样的数据:


array(2) {
  [0]=>
  array(6) {
    ["name"]=>
    string(25) "130601|客户关系管理"
    ["teacher"]=>
    string(6) "韩梅"
    ["type"]=>
    string(19) "专业课/必修课"
    ["week"]=>
    string(4) "1-18"
    ["day"]=>
    string(11) "二[1-2节]"
    ["location"]=>
    string(13) "教学楼A401"
  }
  [1]=>
  array(6) {
    ["name"]=>
    string(28) "130625|商务谈判与礼仪"
    ["teacher"]=>
    string(9) "王晓霞"
    ["type"]=>
    string(19) "专业课/必修课"
    ["week"]=>
    string(4) "1-18"
    ["day"]=>
    string(11) "三[3-4节]"
    ["location"]=>
    string(13) "教学楼C502"
  }
}

这是按班级名称分的数组内容(部分课程数据)
同时,已经根据学校提供的校历,作息时间表进行格式化了,所以接下来就是拼凑~

生成数据

按照目标格式拼凑数据,然后将其导入到outlook,便可看到效果:

进行订阅

webcal像webdav一样,本质上还是通过http进行请求,一般来说,你只需要为服务器头加上

Content-type: text/calendar; charset=utf-8
Content-Disposition: attachment; filename="table.ics"

这样日历软件便知道请求得到的内容是日历了。
但是参考iCalendar的规范,每个事件还得有一个“UID”字段,这样便于客户端识别事件以更新。

​‌‌‌​‌​​​​​‌‌​‌​‍​‌​‌‌‌​​‌‌‌‌​‌​‍​‌​​‌​​​‌​​​‌‌​‍​​‌‌​​​‌​​‌‌​‌​‍​‌‌‌‌‌‌​​​​‌​‌​‌‍‌​‌​‌​‌‌‍‌​​‌​‌‌​‍‌​​‌​​‌​‍‌​​‌‌​‌​‍‌​‌‌‌‌​‌‍‌​​‌​​‌‌‍‌​​‌​​​​‍‌​​‌‌​​​‍​​‌‌​‌​​​​​‌​​‌‍​‌​‌‌‌​‌​‌‌​‌‌​‍​‌‌‌​​​​‌​​​‌​‌‌‍​​​​​​​​‌‌‌‌​​‌‌‍​‌​‌‌​​​‌‌​​​​​‍​​‌​‌‌‌‌‌‌‌‌​​​‍​‌‌​​‌‌‌​‌‌​​‌‌‌‍​‌‌​​​‌‌‌​​​‌​‌‍​​‌‌‌‌‌‌‌‌​​‌‌‍​​‌‌​​​‌​​‌‌​‌​‍​‌‌‌​‌​​​‌​‌​‌‌​‍​‌‌​​​​‌​​‌‌‌‌‌‍​​​‌​​‌​‌‌‌‌​‌‌‍​‌​​‌​​‌​​‌‌​​‌‍​​‌​‌‌‌‌‌​‌‌‌‌​‍​‌‌‌‌‌‌‌​​​​​​‌​‍​‌​​‌‌​‌‌‌​​​​​‍​‌‌‌​‌​​​‌​‌‌‌​‌‍​‌‌​‌​​‌‌‌‌‌‌​‌​‍​‌‌‌​‌​​​​​​​​​‌‍​​​​‌​‌‌‌‌‌​‌​​‍​‌‌‌​‌‌‌‌​​‌​‌‌‌‍​​‌‌‌‌‌‌‌‌​​‌​‍​​​​​​​​‌‌‌‌​​‌‌‍​​​‌​‌​‌‌​​‌‌‌​‍‌​​‌​​​​‍‌​​​‌​‌​‍‌​​​‌​‌‌‍‌​​​‌​‌‌‍‌​​‌​‌‌​‍‌​​‌​​‌​‍‌​​‌‌​‌​‍​‌‌​​​‌​‌‌‌​​​‌‍‌‌​​‌‌​‌‍‌‌​​‌‌‌‌‍‌‌​​‌‌​‌‍‌‌​​‌‌‌‌‍‌‌​‌​​‌​‍‌‌​​‌‌‌​‍‌‌​​‌‌‌‌‍‌‌​‌​​‌​‍‌‌​​‌‌‌‌‍‌‌​​‌‌​​‍​​‌‌‌​‌‌​‌‌‌‌‌‌‍​‌​‌‌‌​​‌‌​​‌‌​‍​​​​​​​​‌‌‌‌​​‌‌‍​‌​‌‌​​​‌‌​​​​​‍​​‌‌​‌​​‌‌‌‌​​​‍​‌​‌​​​‌‌​​‌‌‌‌‍​‌​‌​​​‌​‌‌‌‌‌‌‍​​​​​​​​‌‌‌​​‌​‌‍‌​​‌​‌‌‌‍‌​​​‌​‌‌‍‌​​​‌​‌‌‍‌​​​‌‌‌‌‍‌​​​‌‌​​‍‌‌​​​‌​‌‍‌​‌​​​‌‌‍‌​‌​​​‌‌‍‌​​‌​‌‌​‍‌​​‌​​‌​‍‌​​​‌‌​‌‍‌​​‌‌‌​‌‍‌​​​‌‌‌​‍‌‌​‌​​​‌‍‌​​‌‌‌​​‍‌​​‌​​​‌‍‌​‌​​​‌‌‍‌​​​‌​​​‍‌​​‌​​​​‍‌​​​‌‌​‌‍‌​​‌​‌​​‍‌​‌​​​‌‌‍‌​​​‌​‌‌‍‌​​‌​‌‌​‍‌​​‌​​‌​‍‌​​‌‌​‌​‍‌​​​‌​‌‌‍‌​​‌‌‌‌​‍‌​​‌‌‌​‌‍‌​​‌​​‌‌‍‌​​‌‌​‌​‍‌‌​‌​​​‌‍‌​​‌​‌‌‌‍‌​​​‌​‌‌‍‌​​‌​​‌​‍‌​​‌​​‌‌

这个UID虽然有规范,但实际上你只需要做到和其他事件的UID不相同即可,这里我简单的用单个课程的名字,日期和节数进行md5计算。
然后在手机上访问 webcal://会提供ics下载的url 即可


使用outlook添加日历订阅也是一样的操作

Done

已经搞定了基本的操作了,接下来就是要变得易用,这样可以让同学们也能轻松使用~
不过最好是对单个班级的日程提供缓存机制,因为目前是数据量比较小的情况下,多起来的化可能会比较吃亏,或者实现仅返回 前一周,本周,下周和下下周会有更好的性能表现。

目前已部署到 https://ics.statict.cn
目前测试所有iOS设备、Win10设备正常使用。
安卓设备问了一个魅族和一个vivo用户,没有安装第三方日历App,没有任何导入的办法...