朋友求助,他儿子停学不停课家长在家要监督上网课,容易错过上课签到。找来了个python的课程表导入工具问我怎么整。(链接文末)。
我看了下,要自己整理excel模板,还得手动编辑json文件,又要py环境。对文科生确实不太友善的样子。

我想既然目的是生成一个可以导入outlook日历/google日历/ios日历的 ics文件,我直接看看源码是个什么结构语法吧。

py代码片段

def icsCreateAndSave():
    icsString = "BEGIN:VCALENDAR\nMETHOD:PUBLISH\nVERSION:2.0\nX-WR-CALNAME:课程表\nPRODID:-//Apple Inc.//Mac OS X 10.12//EN\nX-APPLE-CALENDAR-COLOR:#FC4208\nX-WR-TIMEZONE:Asia/Shanghai\nCALSCALE:GREGORIAN\nBEGIN:VTIMEZONE\nTZID:Asia/Shanghai\nBEGIN:STANDARD\nTZOFFSETFROM:+0900\nRRULE:FREQ=YEARLY;UNTIL=19910914T150000Z;BYMONTH=9;BYDAY=3SU\nDTSTART:19890917T000000\nTZNAME:GMT+8\nTZOFFSETTO:+0800\nEND:STANDARD\nBEGIN:DAYLIGHT\nTZOFFSETFROM:+0800\nDTSTART:19910414T000000\nTZNAME:GMT+8\nTZOFFSETTO:+0900\nRDATE:19910414T000000\nEND:DAYLIGHT\nEND:VTIMEZONE\n"
    global classTimeList, DONE_ALARMUID, DONE_UnitUID
    eventString = ""
    for classInfo in classInfoList :
        i = int(classInfo["classTime"]-1)
        className = classInfo["className"]+"|"+classTimeList[i]["name"]+"|"+classInfo["classroom"]
        endTime = classTimeList[i]["endTime"]
        startTime = classTimeList[i]["startTime"]
        index = 0
        for date in classInfo["date"]:
            eventString = eventString+"BEGIN:VEVENT\nCREATED:"+classInfo["CREATED"]
            eventString = eventString+"\nUID:"+classInfo["UID"][index]
            eventString = eventString+"\nDTEND;TZID=Asia/Shanghai:"+date+"T"+endTime
            eventString = eventString+"00\nTRANSP:OPAQUE\nX-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC\nSUMMARY:"+className
            eventString = eventString+"\nDTSTART;TZID=Asia/Shanghai:"+date+"T"+startTime+"00"
            eventString = eventString+"\nDTSTAMP:"+DONE_CreatedTime
            eventString = eventString+"\nSEQUENCE:0\nBEGIN:VALARM\nX-WR-ALARMUID:"+DONE_ALARMUID
            eventString = eventString+"\nUID:"+DONE_UnitUID
            eventString = eventString+"\nTRIGGER:"+DONE_reminder
            eventString = eventString+"\nDESCRIPTION:事件提醒\nACTION:DISPLAY\nEND:VALARM\nEND:VEVENT\n"

            index += 1
    icsString = icsString + eventString + "END:VCALENDAR"

竟然不是json也不是xml。
BEGIN:VCALENDAR 这格式好眼熟,和10多年前我做二维码名片的时候那个VCARD协议很像,经过一番搜索,果然!
那就自己做一个网页版的好了!

最终成品如下:

https://gen8.orz.com.cn/beta/classtab/

用法:
设定一周的课程安排、
设置提前提醒的时间、
设置提醒的截止日期(不设置会永远每周循环下去),
点生成,就能到一个ics文件
在iphone/ipad/win10的outlook/部分安卓手机 上打开ics文件(或浏览器下载)
即可把日程导入到日历,可以随时查看和得到提醒。

微信图片_20200312071852.png

作为变体,也适用于 周例行工作提醒、电视节目(追番)表、健身计划 提醒。

以下是实现的原理结构,前端细节就不po了,源码打包了可以下载自行研究

源码下载

有关 ICalendar (也有名为 VCalendar ) 协议可以参考维基百科:

https://zh.wikipedia.org/wiki/ICalendar

既然知道了是一个标准协议,想必我不需要重新造轮子。本着站在巨人肩膀的指导思想,检索了一下 github.com 收获不少。
在筛查了几个较为人气的 PHP 相关项目后,排除掉几个不合适的,最终选定iCal

https://github.com/markuspoerschke/iCal

项目的readme明确指出支持 VEVENT VALARM 正合我意,虽然帮助与sample文件比较缺,还好源码不难理解部分问题也有其他网友小伙伴遇到并自发的研究出了解决办法。

安装ical

该项目依赖 composer 部署安装,本人比较反感。于是自己写了个 autoload.php 放在 src 目录,include 后正常使用

autoload.php

<?php
function ical_autoload($className)
{
    $classPath = explode('\\', $className);
    if ($classPath[0] != 'Eluceo') {
        return;
    }
    $filePath = dirname(__FILE__) . '/' . implode('/', $classPath) . '.php';
    
    if (file_exists($filePath)) {
        require_once($filePath);
    }
}
spl_autoload_register('ical_autoload');

使用ical

这是一个创建ics文件的接口,返回json格式,如果创建ics成功,json内附带 link 值为 ics文件下载地址

getics.php


require(dirname(__FILE__).'/class/ical/src/autoload.php');
date_default_timezone_set('PRC');

//三项传入参数
$str_enddate = _POST("enddate",date("Y-m-d",time()+3600*24*180)); //截止日期,不设置时默认6个月
$reminder = _POST("reminder"); //不做正确性校验,不正确则不处理
$rows = _POST("rows");//日程表二维数组每行包含 起止时间标 ctime, 周一至日的 课程名cnames数组 和 课室crooms数组

if(!$rows){
    endjson(500,"缺少参数rows");
}


$now = date("Y-m-d H:i:s");
$ts_tom = strtotime(date("Y-m-d",time()+3600*24));
$ts_mon = strtotime(date("Y-m-d",time()-3600*24*(((int) date("N")) - 1 ))); //取得本周的周一的 ts 

$vc = new \Eluceo\iCal\Component\Calendar('RY课程表');
//循环规则
$vr = new \Eluceo\iCal\Property\Event\RecurrenceRule();
$vr->setFreq(\Eluceo\iCal\Property\Event\RecurrenceRule::FREQ_WEEKLY)
    ->setUntil(new \DateTime($str_enddate)) //跳出循环日期
    ->setInterval(1); 


//提醒 分别是 提前 10分钟、30分钟、1小时、2小时、1天
//触发时间值 ["-PT10M","-PT30M","-PT1H","-PT2H","-P1D"]
if($reminder){
    $va = new \Eluceo\iCal\Component\Alarm();
    $va ->setAction('DISPLAY')
        ->setTrigger($reminder) 
        ->setDescription('课程提醒');
}else{
    $va = null;
}    


foreach($rows as $r){
    
    //处理时间
    $ct_parts = explode("-",$r["ctime"]);
    if(count($ct_parts)>=2){
        $ct_start = trim($ct_parts[0]);
        $ct_end = trim($ct_parts[1]);
        
        $dcount = 0;
        foreach($r["cnames"] as $cname){
            $str_date = date("Y-m-d", $ts_mon + 1 + $dcount*3600*24);
            $str_room = $r["crooms"][$dcount];
            
            if($cname){ //没有课程名称的不处理
                unset($ve);
                $ve = new \Eluceo\iCal\Component\Event();

                $ve ->setDtStart(new \DateTime($str_date." ".$ct_start))
                    ->setDtEnd(new \DateTime($str_date." ".$ct_end))
                    ->setCategories(['课程表'])
                    ->setLocation($str_room)
                    ->setRecurrenceRule($vr)                        
                    ->setSummary($cname);
                
                if($reminder)    
                    $ve ->addComponent($va);    
                
                $vc->addComponent($ve);                    
            }
                                    
            $dcount++;
        }
        
    }else{}//起止时间无法解析跳过    
}



//直接输出的方式
//header('Content-Type: text/calendar; charset=utf-8');
//header('Content-Disposition: attachment; filename="cal.ics"');
//echo $vc->render();

//保存文件
$savepath = "ics/classtab".date("YmdHis").mt_rand(10000,99999).".ics";
$link = "https://gen8.orz.com.cn/beta/classtab/".$savepath;
file_put_contents($savepath,$vc->render()); //写入文件

//输出结果
endjson(200,"ok",array("link"=>$link));

//工具函数/////////////

/**
 * endjson()
 * 一个给json接口使用的标准化输出json的函数
 * 使用后会直接中止脚本的后续输出,会die($json) 
 * @param integer $code
 * @param string $desc
 * @param array $extraArray
 * @return void
 */
function endjson($code,$desc,$extraArray=null){
    unset($json);
    $json = array();
    $json["code"]=$code;
    $json["desc"]=$desc;
    if(is_array($extraArray)){
        foreach($extraArray as $k => $v){
            $json[$k] = $v;
        }
    }
    
    die(json_encode($json));   
}

function _POST($key,$def=null){
    return isset($_POST[$key])?$_POST[$key]:$def;
}
function _GET($key,$def=null){
    return isset($_GET[$key])?$_GET[$key]:$def;
}

主要涉及的3个关键元素是 VEVENT 日程事件, VALARAM 提醒, RecurrenceRule 周循环规则。
分别对应上文源码中的 $ve, $va, $vr 。将其对应地设置嵌套入 $vc 中最后生成ics文件。


相关的文章:
《教你把课程表导入日历》 https://sspai.com/post/39645

标签: php, python, iCalendar, 课程表

添加新评论