OK, I've now got a working pre-alpha of datetime as SQL. There's no
installer, docs or anything. Just read the code. You'll need to install
SQL::Statement to get it to work. Table and Select columns don't work,
all that currently matters is the WHERE clause. So now you can get a
spanset with:
SELECT spanset FROM eternity WHERE year>=1901 AND year < 2001 AND
month=12 and day=25
Which will return the spanset of 20th Century Christmases!
Have fun, I'm posting this now because I might not get back onto it for
a few days or a week. What do people think? Could it actually be useful?
Cheers!
Rick
use SQL::Statement;
use DateTime::Span;
use DateTime;
# Turn SQL into a data structure
my $parser = SQL::Parser->new('AnyData');
my $stmt = SQL::Statement->new("SELECT spanset FROM eternity WHERE year <= 2004 AND year > 2001 AND NOT year = 2003 AND day = 12", $parser);
# Turn the Data structure into a span set
my $set = get_a_set($stmt->{where_clause});
# Output the start-of-spans
my $iter = $set->iterator;
while ($dt = $iter->next) {
print $dt->start->datetime . "\n";
}
# The workings
sub get_a_set {
my $stmt = shift;
my $set;
if ($stmt->{arg1}{arg1}) {
my $set1 = get_a_set($stmt->{arg1});
my $set2 = get_a_set($stmt->{arg2});
my $op = $stmt->{op};
if ($op eq 'AND') {
$set = $set1->intersection($set2);
}
elsif ($op eq 'OR') {
$set = $set1->union($set2);
}
} else {
#print Dumper($stmt);
my $column = ($stmt->{arg1}->{type} eq 'column')
? $stmt->{arg1}->{value}
: ($stmt->{arg2}->{type} eq 'column')
? $stmt->{arg2}->{value}
: undef;
die("No column specified") unless $column;
my $value = ($stmt->{arg1}->{type} eq 'number')
? $stmt->{arg1}->{value}
: ($stmt->{arg2}->{type} eq 'number')
? $stmt->{arg2}->{value}
: undef;
die("No value specified") unless $value;
my $op = $stmt->{op};
if ($op eq '>') {
$set = nan_set($value+1, 100_000_000) if $column eq 'NANOSECOND';
$set = sec_set($value+1, 61) if $column eq 'SECOND';
$set = min_set($value+1, 60) if $column eq 'MINUTE';
$set = hur_set($value+1, 24) if $column eq 'HOUR';
$set = dow_set($value+1, 7) if $column eq 'DOW';
$set = day_set($value+1, 31) if $column eq 'DAY';
$set = mon_set($value+1, 12) if $column eq 'MONTH';
$set = qtr_set($value+1, 4) if $column eq 'QUARTER';
$set = DateTime::Span->from_datetimes(start => DateTime->new( year => $value+1 )) if $column eq 'YEAR';
}
elsif ($op eq '<') {
$set = nan_set(0, $value-1) if $column eq 'NANOSECOND';
$set = sec_set(0, $value-1) if $column eq 'SECOND';
$set = min_set(0, $value-1) if $column eq 'MINUTE';
$set = hur_set(0, $value-1) if $column eq 'HOUR';
$set = dow_set(1, $value-1) if $column eq 'DOW';
$set = day_set(1, $value-1) if $column eq 'DAY';
$set = mon_set(1, $value-1) if $column eq 'MONTH';
$set = qtr_set(1, $value-1) if $column eq 'QUARTER';
$set = DateTime::Span->from_datetimes(before => DateTime->new( year => $value )) if $column eq 'YEAR';
}
elsif ($op eq '=') {
$set = nan_set($value) if $column eq 'NANOSECOND';
$set = sec_set($value) if $column eq 'SECOND';
$set = min_set($value) if $column eq 'MINUTE';
$set = hur_set($value) if $column eq 'HOUR';
$set = dow_set($value) if $column eq 'DOW';
$set = day_set($value) if $column eq 'DAY';
$set = mon_set($value) if $column eq 'MONTH';
$set = qtr_set($value) if $column eq 'QUARTER';
$set = DateTime::Span->from_datetimes(start => DateTime->new( year => $value ), before => DateTime->new( year => $value+1 )) if $column eq 'YEAR';
}
elsif ($op eq '>=') {
$set = nan_set($value, 100_000_000) if $column eq 'NANOSECOND';
$set = sec_set($value, 61) if $column eq 'SECOND';
$set = min_set($value, 60) if $column eq 'MINUTE';
$set = hur_set($value, 24) if $column eq 'HOUR';
$set = dow_set($value, 7) if $column eq 'DOW';
$set = day_set($value, 31) if $column eq 'DAY';
$set = mon_set($value, 12) if $column eq 'MONTH';
$set = qtr_set($value, 4) if $column eq 'QUARTER';
$set = DateTime::Span->from_datetimes(start => DateTime->new( year => $value )) if $column eq 'YEAR';
}
elsif ($op eq '<=') {
$set = nan_set(0, $value) if $column eq 'NANOSECOND';
$set = sec_set(0, $value) if $column eq 'SECOND';
$set = min_set(0, $value) if $column eq 'MINUTE';
$set = hur_set(0, $value) if $column eq 'HOUR';
$set = dow_set(1, $value) if $column eq 'DOW';
$set = day_set(1, $value) if $column eq 'DAY';
$set = mon_set(1, $value) if $column eq 'MONTH';
$set = qtr_set(1, $value) if $column eq 'QUARTER';
$set = DateTime::Span->from_datetimes(before => DateTime->new( year => $value+1 )) if $column eq 'YEAR';
} else {
die "Unknown operator: $op";
}
#print Dumper($set);
if($stmt->{neg}) {
$set = $set->complement();
}
}
return $set
}
sub nan_set { part_set('nanosecond', 'second', @_); }
sub sec_set { part_set('second', 'minute', @_); }
sub min_set { part_set('minute', 'hour', @_); }
sub min_set { part_set('minute', 'hour', @_); }
sub hur_set { part_set('hour', 'day', @_); }
sub day_set { part_set('day', 'month', @_); }
sub mon_set { part_set('month', 'year', @_); }
sub part_set {
my ($unit1, $unit2, $from, $to) = @_;
$to ||= $from;
my $set = DateTime::Set->from_recurrence(
recurrence => sub {
#warn "PRE: " . $_[0]->datetime . "\n";
my $dt = shift;
my $next = $dt->clone;
$next->add( $unit1.'s' => 1 );
#print "POTENTIAL: This: " . $dt->ymd . ", Next: " . $next->ymd . "\n";
if ($next->$unit1 > $to or $next->$unit2 != $dt->$unit2) {
# print "ROLLOVER: " . $dt->ymd . "\n";
$dt->set( $unit1 => $from );
# print " TO: " . $dt->ymd . "\n";
$dt->add( $unit2.'s' => 1);
# print " AND: " . $dt->ymd . "\n";
} else {
$dt->add( $unit1.'s' => 1);
}
#warn "POST: " . $dt->datetime . "\n";
return $dt;
}
);
return $set;
}