datebook to CSV and me

This script converts Palm datebook file to CSV format. Data stored in a file is simply output to STDOUT in order.
I wrote this script 2 years ago and had forgotten it. There aren't many people to need this now, but I cannot find any other place to put it on. You can do the opposite(csv2dbk) of this with Perl, but I have not finished to brush it up.
This script conform to GPL V2.
Related link: Palm Desktop Software DATEBOOK.DAT and DATEBOOK.DBA File Structure

#!/usr/bin/perl -w

#$Id: dbk2csv.pl,v 1.2 2006/03/12 22:52:40 osamu Exp osamu $


my $version = '1.2';
my $reldate = '12/Mar/2006';

use strict;
use warnings;
use Getopt::Std;

#my $palmpath	= 'c:\Program Files\Palm';
#my $username	= 'USERNAME';

my $filepath	= 'datebook\datebook.dat';
my $timezone	= +9;					#JST

my $chr			= 1;
my $short		= 2;
my $long		= $short * 2;

our($opt_h, $opt_v, $opt_i, $opt_t);

getopts('hvit:');

$opt_h and &help();
$opt_v and &version();
$opt_t and $opt_t != 1
	   and $opt_t != 2
	   and $opt_t != 3
	   and die "Bad -t switch...\n";

my $datfname = shift;
#$datfname = "$palmpath\\$username\\$filepath" unless $datfname;
$datfname or &help();
chop $datfname if (substr($datfname, -1) eq '\\');
$datfname .= "\\$filepath" if -d $datfname;
open(DAT, $datfname) or die "Can't open $datfname: $!\n";
binmode(DAT);

read(DAT, my $buf, $chr*4) or die "Can't read: $!\n";
die "This file doesn't seem datebook dat file.\n" if($buf ne "\x00\x01\x42\x44");

&readstr('DAT');					#File Name
&readstr('DAT');					#Table String
seek(DAT, $long, 1);					#Next Free
$buf = &readnum('DAT', $long);				#Category Count
while($buf) {						#Category Entries
	seek(DAT, $long*3, 1);
	&readstr('DAT');
	&readstr('DAT');
	$buf--;
}
seek(DAT, $long, 1);					#Resource ID
my $fieldperrow = &readnum('DAT', $long);		#Field per Row
seek(DAT, $long*3 ,1);					#Rec Positions,etc.
my $fieldcount = &readnum('DAT', $short);		#Field Count

my @fieldentry;
while($fieldcount) {
	push(@fieldentry, &readnum('DAT', $short));	#Field Entry
	$fieldcount--;
}

my $numentries = &readnum('DAT', $long);		#Num Entries

print "Record ID,Status Feild,Position,Start Time,End Time,Description,Duration,Note,Untimed,Private,Category,Alarm Set,Alarm Adv Units,Alarm Adv Type,Date Exceptions,Exception entry,Repeat Event Flag,Class entry,Brand,Interval,End Date,First Day of Week,Day Index,Days Mask,Week Index,Day Number,Month Index\n" if $opt_i;

my $i = 0; my $flag = 0;
while ($numentries) {
	my $type = &readnum('DAT', $long);
	if    ($flag or $type == 3) { print &readtime('DAT').','; $flag = !$flag; }
	elsif ($type == 1) { print &readnum('DAT', $long).','; }
	elsif ($type == 5) { seek(DAT, $long, 1); print &readstr('DAT').','; }
	elsif ($type == 6) { print &readnum('DAT', $long).','; }
	elsif ($type == 8) { &rep_event; }
	else { die "impossible field type: $type"; }
	if ($i == 14) { print "\n"; $i = 0; } else { $i++; }
	$numentries--;
}

close(DAT);
exit(0);

sub rep_event {
	my $buf = &readnum('DAT', $short);		#Date Exceptions
	print "$buf,";
	while ($buf) {
		print &readtime('DAT');			#Exception entry
		$buf--;
		print ';' if $buf;
	}
	print ',';
	my $rep_ev = &readnum('DAT', $short);		#Repeat Event Flag
	print "$rep_ev,";
	return unless $rep_ev;
	my $str;
	if ($rep_ev == hex('ffff')) {
		seek(DAT, $short, 1);			#Constant
		$buf = &readnum('DAT', $short);		#Length
		read(DAT, $str, $buf);
		print "$str";				#Class Name
	}
	print ',';
	if ($rep_ev <= hex('8000')) {
		print &readnum('DAT', $long);
		print ',';
		print &readnum('DAT', $long);
		print ',';
	}
	my $brand = &readnum('DAT', $long);		#Brand
	print "$brand,";
	print &readnum('DAT', $long).',';		#Interval
	print &readtime('DAT').',';			#End Date
	print &readnum('DAT', $long).',';		#First Day of Week
	#Brand-Data
	die "Brand-Data error: $brand" if ($brand <= 0 or $brand > 6);
	return if $brand == 6;
	if ($brand <= 3) { print &readnum('DAT', $long); }
	print ',';					#Day Index
	if ($brand == 2) { print &readnum('DAT', $chr); }
	print ',';					#Day Mask
	if ($brand == 3) { print &readnum('DAT', $long); }
	print ',';					#Week Index
	if ($brand >= 4) { print &readnum('DAT', $long); }
	print ',';					#Day Number
	if ($brand == 5) { print &readnum('DAT', $long); }
							#Month Index
}

sub readstr {
	my $fh = shift;
	my $str;
	read($fh, my $buf, $chr);
	return '' if $buf eq "\x00";
	if($buf eq "\xff") {
		$buf = &readnum($fh, $short);
		read($fh, $str, $buf);
	} else {
		read($fh, $str, vec($buf,0,8));
	}
	$str =~ s/\,/\./g;
	return $str;
}

sub readnum {
	my $fh = shift;
	my $byte = shift;
	my @bytes;
	my $i = $byte;
	while ($i) {
		read($fh, my $buf, 1);
		unshift(@bytes, $buf);
		$i--;
	}
	return vec(join('', @bytes), 0, $byte*8);
}

sub readtime {
	my $fh = shift;
	my $buf = &readnum($fh, $long);
	return $buf unless $opt_t;
	my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
		= localtime($buf);
	if($opt_t == 1) {
		return sprintf ("%04d/%02d/%02d\(%s\) %02d:%02d:%02d",
			$year + 1900, $mon + 1, $mday, 
			('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')[$wday],
			$hour, $min, $sec);
	} elsif($opt_t == 2) {
		return sprintf ("%02d:%02d:%02d %s.%02d/%s/%04d",
			$hour, $min, $sec,
			('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')[$wday], $mday,
			('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
			 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')[$mon],
			$year + 1900);
	} elsif($opt_t == 3) {
		return $buf/86400+25569+$timezone/24;
	} else {
		print "\nSomething strange...";
		exit(1);
	}
}

sub help {
	print <<EOF;

Usage
$0 [-t [date_format_type] -i -h -v] target_datebook_file

This script converts Palm datebook file to CSV format.
Data stored in a file is simply output to STDOUT in order.

Switches
t: Outputs date and time data in easy-to-read format.
	date_format_type: 1 yyyy/mm/dd(www) hh:mm:ss
	date_format_type: 2 hh:mm:ss www.dd/mmm/yyyy
i: Outputs index at the first line.
h: Outputs this help.
v: Outputs version infomation.

If 'target_datebook_file' looks like a directory, 
'/datebook/datebook.dat' string is automatically appended
at the tail.
So in many cases, it is only necessary to set this 
'"C:\\Program Files\\Palm\\(USERNAME)"'.
EOF
	exit(0);
}

sub version {
	print "\n$0 Ver.$version $reldate by Ken \"OSAMU\" Sugii\n";
	exit(0);
}

keywords:予定表 クリエ 変換 エクセル アウトルック CLIE Outlook Excel