TSファイルの録画開始時刻を調べる

TSファイルは0x47で始まる188バイトのパケットごとに映像や音声、EPG情報などのデータが細切れで含まれている。これはパケット先頭から2バイト目を上位、3バイト目を下位とした値の下位13ビットに記されているパケット識別子(PID)で何のデータであるかを確認できる。
いきなり映像や音声、EPG情報などのデータを処理するのは難しそうなので、数値だけで簡単そうな現在日時を示すTOTパケット(PID:0x0014)を読んでみようと思う。最初に現れたTOTが録画開始日時とほぼ一致するハズ。
詳しくはARIB STD-B10に載ってるけど、TOTは9バイト目を上位、10バイト目を下位とした16ビットの値がMJD(修正ユリウス日)、11バイト目が2進化10進数BCD)で時、12バイト目がBCDで分、13バイト目がBCDで秒を表している。
修正ユリウス日は西暦1858年11月17日からの日数なので、西暦1858年11月17日の西暦1年1月1日からの日数である678576を足せば、西暦1年1月1日からの日数に変換できる。日数は7で割った余りから日曜日〜土曜日を表す0〜7の値が求められる。
日数は1から始まるので0から始まるように1を引き、年月の計算のために西暦1年3月1日からの日数にするべく西暦1年1月と2月の日数である31と28を引いて、4年に一度の1日多い(4×365+1=1461)うるう年、でも100年に一度(4年に一度が25回)はうるう年より1日少ない(1461×25−1=35624)普通の年、でも400年に一度(100年に一度が4回)はやはり1日多い(35624×4+1=142497)うるう年を考慮して、日数を365で割れば0から始まる年数を求められるので1年から始めるために1を足す。
月は3月から1ヶ月ごとの日数が31日、30日、31日、30日、31日(合計153日)と5ヶ月続くこと、その中で31日、30日(合計61日)が2ヶ月続くこと、その中で最初の1ヶ月は31日であることを元に0から始まる月数を求めて3月から始めるために3を足す。
最後に余った0から始まる日数を1日から始めるために1を足す。
てな訳で簡単にCでプログラム組んでみた。一切のエラーチェックはしてないのでパケットの先頭から始まらないTSファイルはマトモに処理できない。TSファイルを指定して実行すると録画開始時刻に相当する最初に見つかったTOTパケットに記された日時を表示して終了する。
ファイル末尾から逆順にパケットを読むように書き換えれば録画終了日時も求められる。後日、TSファイルの録画終了時刻を調べるってのを作ってみた。(2015/11/29追記)
日数の計算処理が間違っていた為に2016年2月29日以降の日付がズレていたので、当時書いたバッチファイルで日数を年月日に換算を元に処理を書き直した。(2016/03/03追記)

#include <stdio.h>

int main(int argc,char *argv[]) {
    FILE *fp;
    unsigned char buf[188];
    int pid,mjd,jth,jtm,jts,jdy,jdm,jdd,jdw,jds;

    char wds[7][4]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};

    /* TSファイルを開く */
    fp=fopen(argv[1],"rb");
    while(1) {
        /* 188バイトのパケット単位で読み込む */
        fread(buf,1,188,fp);
        /* PIDを求める */
        pid=((buf[1] << 8) | buf[2]) & 0x1fff;
        /* PIDがTOTならば処理する */
        if(pid==0x14) {
            /* 修正ユリウス日と時、分、秒を求める */
            mjd=(buf[8] << 8) | buf[9];
            jth=((buf[10] & 0xf0) >> 4)*10+(buf[10] & 0x0f);
            jtm=((buf[11] & 0xf0) >> 4)*10+(buf[11] & 0x0f);
            jts=((buf[12] & 0xf0) >> 4)*10+(buf[12] & 0x0f);
            /* 修正ユリウス日を西暦1年1月1日からの日数に変換する */
            jdd=mjd+678576;
            /* 日数から曜日を求める */
            jdw=jdd % 7;
            /* 日数から年、月、日を求める */
            jdd=jdd-1-31-28+365;
            jds=jdd%146097%36524%1461%365+jdd%146097/36524/4*365+jdd%146097%36524%1461/365/4*365;
            jdy=jdd/146097*400+jdd%146097/36524*100-jdd%146097/36524/4+jdd%146097%36524/1461*4+jdd%146097%36524%1461/365-jdd%146097%36524%1461/365/4;
            jdm=jds/153*5+jds%153/61*2+jds%153%61/31+3;
            jdd=jds%153%61%31+1;
            if(jdm>12) {
                jdy=jdy+1;
                jdm=jdm-12;
            }
            /* 年、月、日、曜日、時、分、秒を表示して終了する */
            printf("%04d/%02d/%02d %s %02d:%02d:%02d\n",jdy,jdm,jdd,wds[jdw],jth,jtm,jts);
            break;
        }
    }
    fclose(fp);
}