This is another post inspired by a question on Stackoverflow.
The task is to take textual schedule information such as
0 24000 97200
1 52200 95400
2 0 0
3 37800 180000
4 0 0
5 48000 95400
6 0 0
and turn it into a an overview of the busy and free times over the course of the week. The end result of my attempt looks like this:
In the input data, the first number is the day of the week (Sunday = 0), the second number is the beginning of busy time in seconds since midnight, and the third number is the duration of the task in seconds. If a task does not fit in a given day, it spills over to the following day(s) until it’s done.
I used div
elements appropriately floated and given a height and width to contain free and busy blocks for each day. Clearly, this is not very semantic: Converting it to an ordered list with title attributes on each block displaying the time periods is left as an exercise to the reader (mostly because I am afraid I’ll end up wasting a lot of time styling those li
s.
I used my old standby, HTML::Template to generate the HTML. It is pretty standard fare and nicely separates the data from presentation (however, I used tpage
from Template-Toolkit to generate the escaped HTML included in this post :-)
<!doctype html>
<html>
<head>
<title>Pretty Schedule Example</title>
<style type="text/css">
.row .day { margin:0; padding:0; padding-right:1%; width:9% }
.row .container { width: 90% }
.row,
.container,
.container .busy,
.container .free
{height:1.5em;
margin:0;
padding:0;
overflow:hidden;
white-space:nowrap;
}
.row .container,
.row .day,
.container .busy,
.container .free { float:left }
.container .free { background-color:#f0f0a0 }
.container .busy { background-color:#70b070 }
.row {
margin-bottom:0.125em;
max-width:600px;
width:100%;
}
.clear { clear:both }
</style>
</head>
<body>
<TMPL_LOOP DAYS>
<div class="row clear">
<div class="day"><TMPL_VAR DAY></div>
<div class="container">
<TMPL_LOOP BLOCKS>
<div class="<TMPL_VAR CLASS>" style="width:<TMPL_VAR WIDTH>"></div>
</TMPL_LOOP>
</div>
<br class="clear">
</div>
</TMPL_LOOP>
</body>
</html>
The Perl side is also relatively straightforward (and, as a self-contained example, reads the schedule data from the __DATA__
section):
#!/usr/bin/env perl
use strict; use warnings;
use HTML::Template;
use constant ONE_MINUTE => 60;
use constant ONE_HOUR => 60 * ONE_MINUTE;
use constant ONE_DAY => 24 * ONE_HOUR;
my @days = qw(Sun Mon Tue Wed Thu Fri Sat);
my $remainder = 0;
my @rows;
while (my $line = <DATA>) {
next unless $line =~ m{
\A
( [0-6] ) \s+
( [0-9]+ ) \s+
( [0-9]+ ) \s+
\z
}x;
my ($daynum, $start, $duration) = ($1, $2, $3);
my $dayrow = make_dayrow(
$days[$daynum],
$remainder,
$start,
$duration,
);
push @rows, $dayrow->[0];
$remainder = $dayrow->[1];
}
my $tmpl = HTML::Template->new(filename => 'pretty-schedule.html');
$tmpl->param(
@rows
DAYS => \
);
print $tmpl->output;
sub make_dayrow {
my ($day, $remainder, $start, $duration) = @_;
my $row = {DAY => $day};
if ($remainder > ONE_DAY) {
$row->{BLOCKS} = [
'busy', WIDTH => '100%' }
{ CLASS =>
];return [$row, $remainder - ONE_DAY];
}
my @blocks;
my $hang = $start + $duration > ONE_DAY
$duration - (ONE_DAY - $start)
? 0
:
;
push @blocks, {
'busy',
CLASS => $remainder),
WIDTH => format_width(if $remainder > 0;
}
if ($start > $remainder) {
push @blocks, {
'free',
CLASS => $start - $remainder),
WIDTH => format_width(
}, {'busy',
CLASS => $duration - $hang),
WIDTH => format_width(
};
}
unless ($hang) {
my $taken = $start > $remainder ? $start : $remainder;
$taken += $duration;
my $leftover = ONE_DAY - $taken;
if ($leftover > 0) {
push @blocks, {
'free',
CLASS => $leftover),
WIDTH => format_width(
};
}
}
$row->{BLOCKS} = \@blocks;
return [$row, $hang];
}
sub format_width {
my ($width) = @_;
return sprintf('%.6f%%', 100 * ($width / ONE_DAY));
}
__DATA__
0 24000 97200
1 52200 95400
2 0 0
3 37800 180000
4 0 0
5 48000 95400 6 0 0
Enjoy!