Commits

Toby Inkster  committed 9228ab2

release 0.001 of datetime functions

  • Participants

Comments (0)

Files changed (13)

+############################################################################
+## Changes #################################################################
+############################################################################
+
+RDF-Query-Functions-Buzzword-DateTime
+=====================================
+
+Created:      2011-01-05
+Home page:    <http://search.cpan.org/dist/RDF-Query-Functions-Buzzword-DateTime/>
+Bug tracker:  <http://rt.cpan.org/Dist/Display.html?Queue=RDF-Query-Functions-Buzzword-DateTime>
+Maintainer:   Toby Inkster <mailto:tobyink@cpan.org>
+
+0.001 [2011-01-05]
+
+#############################################################
+
+@prefix :        <http://usefulinc.com/ns/doap#> .
+@prefix dcs:     <http://ontologi.es/doap-changeset#> .
+@prefix dc:      <http://purl.org/dc/terms/> .
+@prefix foaf:    <http://xmlns.com/foaf/0.1/> .
+@prefix my:      <http://purl.org/NET/cpan-uri/dist/RDF-Query-Functions-Buzzword-DateTime/> .
+@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix toby:    <http://tobyinkster.co.uk/#> .
+@prefix xsd:     <http://www.w3.org/2001/XMLSchema#> .
+
+#############################################################
+
+<>
+
+	dc:title         "Changes" ;
+	dc:description   "Revision history for Perl extension RDF::Query::Functions::Buzzword::DateTime."@en ;
+	dc:subject       my:project ;
+	dc:creator       toby:i .
+
+#############################################################
+
+my:v_0-001
+
+	a               :Version ;
+	dc:issued       "2011-01-05"^^xsd:date ;
+	:revision       "0.001"^^xsd:string ;
+	:file-release   <http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-Query-Functions-Buzzword-DateTime-0.001.tar.gz> ;
+	rdfs:comment    "Original version"@en .
+
+#############################################################
+
+my:project
+
+	a               :Project ;
+	:name           "RDF-Query-Functions-Buzzword-DateTime" ;
+	:shortdesc      "plugin for buzzword.org.uk datetime functions"@en ;
+	:programming-language  "Perl" ;
+	:homepage       <http://search.cpan.org/dist/RDF-Query-Functions-Buzzword-DateTime/> ;
+	:download-page  <http://search.cpan.org/dist/RDF-Query-Functions-Buzzword-DateTime/> ;
+	:bug-database   <http://rt.cpan.org/Dist/Display.html?Queue=RDF-Query-Functions-Buzzword-DateTime> ;
+	:repository     [ a :SVNRepository ; :browse <http://goddamn.co.uk/viewvc/perlmods/RDF-Query-Functions-Buzzword-DateTime/> ] ;
+	:maintainer     toby:i ;
+	:developer      toby:i ;
+	:documenter     toby:i ;
+	:tester         toby:i ;
+	:created        "2011-01-05"^^xsd:date ;
+	:license        <http://dev.perl.org/licenses/> ;
+	:release        my:v_0-001 ;
+	rdfs:seeAlso    <http://buzzword.org.uk/2011/functions/datetime> .
+
+#############################################################
+
+toby:i
+
+	a               foaf:Person ;
+	foaf:name       "Toby Inkster" ;
+	foaf:homepage   <http://tobyinkster.co.uk/> ;
+	foaf:page       <http://search.cpan.org/~tobyink/> ;
+	foaf:mbox       <mailto:tobyink@cpan.org> ;
+	<http://www.w3.org/2002/07/owl#sameAs> <http://purl.org/NET/cpan-uri/person/tobyink> .
+
+#############################################################
+<?xml version="1.0" encoding="utf-8"?>
+<rdf:RDF xmlns:dbug="http://ontologi.es/doap-bugs#" xmlns:dc="http://purl.org/dc/terms/" xmlns:dcs="http://ontologi.es/doap-changeset#" xmlns:doap="http://usefulinc.com/ns/doap#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
+<rdf:Description rdf:nodeID="rD0773AD618D411E0826DE2FF3D595B2Cr0">
+	<doap:browse rdf:resource="http://goddamn.co.uk/viewvc/perlmods/RDF-Query-Functions-Buzzword-DateTime/"/>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#SVNRepository"/>
+</rdf:Description>
+<rdf:Description rdf:about="file:///home/tai/src/perlmods/RDF-Query-Functions-Buzzword-DateTime/Changes.ttl">
+	<dc:creator rdf:resource="http://tobyinkster.co.uk/#i"/>
+	<dc:description xml:lang="en">Revision history for Perl extension RDF::Query::Functions::Buzzword::DateTime.</dc:description>
+	<dc:subject rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-Query-Functions-Buzzword-DateTime/project"/>
+	<dc:title>Changes</dc:title>
+</rdf:Description>
+<rdf:Description rdf:about="http://purl.org/NET/cpan-uri/dist/RDF-Query-Functions-Buzzword-DateTime/project">
+	<doap:bug-database rdf:resource="http://rt.cpan.org/Dist/Display.html?Queue=RDF-Query-Functions-Buzzword-DateTime"/>
+	<doap:created rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2011-01-05</doap:created>
+	<doap:developer rdf:resource="http://tobyinkster.co.uk/#i"/>
+	<doap:documenter rdf:resource="http://tobyinkster.co.uk/#i"/>
+	<doap:download-page rdf:resource="http://search.cpan.org/dist/RDF-Query-Functions-Buzzword-DateTime/"/>
+	<doap:homepage rdf:resource="http://search.cpan.org/dist/RDF-Query-Functions-Buzzword-DateTime/"/>
+	<doap:license rdf:resource="http://dev.perl.org/licenses/"/>
+	<doap:maintainer rdf:resource="http://tobyinkster.co.uk/#i"/>
+	<doap:name>RDF-Query-Functions-Buzzword-DateTime</doap:name>
+	<doap:programming-language>Perl</doap:programming-language>
+	<doap:release rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-Query-Functions-Buzzword-DateTime/v_0-001"/>
+	<doap:repository rdf:nodeID="rD0773AD618D411E0826DE2FF3D595B2Cr0"/>
+	<doap:shortdesc xml:lang="en">plugin for buzzword.org.uk datetime functions</doap:shortdesc>
+	<doap:tester rdf:resource="http://tobyinkster.co.uk/#i"/>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#Project"/>
+	<rdfs:seeAlso rdf:resource="http://buzzword.org.uk/2011/functions/datetime"/>
+</rdf:Description>
+<rdf:Description rdf:about="http://purl.org/NET/cpan-uri/dist/RDF-Query-Functions-Buzzword-DateTime/v_0-001">
+	<dc:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2011-01-05</dc:issued>
+	<doap:file-release rdf:resource="http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-Query-Functions-Buzzword-DateTime-0.001.tar.gz"/>
+	<doap:revision rdf:datatype="http://www.w3.org/2001/XMLSchema#string">0.001</doap:revision>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#Version"/>
+	<rdfs:comment xml:lang="en">Original version</rdfs:comment>
+</rdf:Description>
+<rdf:Description xmlns:ns1="http://www.w3.org/2002/07/owl#" rdf:about="http://tobyinkster.co.uk/#i">
+	<rdf:type rdf:resource="http://xmlns.com/foaf/0.1/Person"/>
+	<ns1:sameAs rdf:resource="http://purl.org/NET/cpan-uri/person/tobyink"/>
+	<foaf:homepage rdf:resource="http://tobyinkster.co.uk/"/>
+	<foaf:mbox rdf:resource="mailto:tobyink@cpan.org"/>
+	<foaf:name>Toby Inkster</foaf:name>
+	<foaf:page rdf:resource="http://search.cpan.org/~tobyink/"/>
+</rdf:Description>
+</rdf:RDF>
+Changes
+Changes.ttl
+Changes.xml
+Makefile.PL
+MANIFEST
+MANIFEST.SKIP
+README
+META.yml
+SIGNATURE
+
+t/00sig.t
+t/01basic.t
+
+inc/Module/AutoInstall.pm
+inc/Module/Install/AutoInstall.pm
+inc/Module/Install/Base.pm
+inc/Module/Install/Can.pm
+inc/Module/Install/DOAPChangeSets.pm
+inc/Module/Install/Fetch.pm
+inc/Module/Install/Include.pm
+inc/Module/Install/Makefile.pm
+inc/Module/Install/Metadata.pm
+inc/Module/Install.pm
+inc/Module/Install/ReadmeFromPod.pm
+inc/Module/Install/Win32.pm
+inc/Module/Install/WriteAll.pm
+inc/Test/Signature.pm
+
+lib/RDF/Query/Functions/Buzzword/DateTime.pm

File MANIFEST.SKIP

+^Makefile$
+^blib/
+^pm_to_blib
+^blibdirs
+\.svn
+^example.*\.pl$
+^[^/]+\.(tar\.gz|tar\.bz2|tgz|tbz2|tbz|zip|tar)$
+^MYMETA.yml
+---
+abstract: 'plugin for buzzword.org.uk datetime functions'
+author:
+  - 'Toby Inkster <tobyink@cpan.org>'
+build_requires:
+  ExtUtils::MakeMaker: 6.42
+  Module::Signature: 0.66
+  Test::More: 0.61
+configure_requires:
+  ExtUtils::MakeMaker: 6.42
+distribution_type: module
+generated_by: 'Module::Install version 1.00'
+keywords:
+  - SPARQL
+  - query
+  - extension
+  - functions
+  - date
+  - time
+  - datetime
+  - now
+  - plugin
+  - RDF
+  - Semantic
+  - Web
+  - SemWeb
+license: perl
+meta-spec:
+  url: http://module-build.sourceforge.net/META-spec-v1.4.html
+  version: 1.4
+name: RDF-Query-Functions-Buzzword-DateTime
+no_index:
+  directory:
+    - inc
+    - t
+recommends:
+  RDF::Query::Functions::Buzzword::Util: 0
+requires:
+  DateTime: 0
+  DateTime::Format::Duration: 0
+  DateTime::Format::ISO8601: 0
+  DateTime::Format::Natural: 0
+  RDF::Query: 2.903
+  Scalar::Util: 0
+  common::sense: 0
+resources:
+  bugtracker: http://rt.cpan.org/Dist/Display.html?Queue=RDF-Query-Functions-Buzzword-DateTime
+  homepage: http://search.cpan.org/dist/RDF-Query-Functions-Buzzword-DateTime/
+  license: http://dev.perl.org/licenses/
+  repository: http://goddamn.co.uk/viewvc/perlmods/RDF-Query-Functions-Buzzword-DateTime/
+version: 0.001
+use strict;
+use warnings;
+
+use inc::Module::Install;
+
+my $dist = 'RDF-Query-Functions-Buzzword-DateTime';
+my $fn   = "lib/$dist.pm"; $fn =~ s#-#/#g;
+
+name                $dist;
+perl_version_from   $fn;
+version_from        $fn;
+abstract_from       $fn;
+readme_from         $fn;
+author              'Toby Inkster <tobyink@cpan.org>';
+license             'perl';
+
+requires            'common::sense'      => 0;
+requires            'DateTime'           => 0;
+requires            'DateTime::Format::Duration' => 0;
+requires            'DateTime::Format::ISO8601'  => 0;
+requires            'DateTime::Format::Natural'  => 0;
+test_requires       'Module::Signature'  => '0.66';
+requires            'RDF::Query'         => '2.903';
+recommends          'RDF::Query::Functions::Buzzword::Util' => 0;
+requires            'Scalar::Util'       => 0;
+test_requires       'Test::More'         => '0.61';
+
+resources(
+	'homepage'   => "http://search.cpan.org/dist/$dist/",
+	'repository' => "http://goddamn.co.uk/viewvc/perlmods/$dist/",
+	'bugtracker' => "http://rt.cpan.org/Dist/Display.html?Queue=$dist",
+	);
+
+keywords(qw[SPARQL query extension functions date time datetime now plugin RDF Semantic Web SemWeb]);
+
+write_doap_changes;
+write_doap_changes_xml;
+
+include 'Test::Signature';
+auto_install;
+WriteAll(
+	'meta' => 1,
+	'sign' => 1,
+	);

File RDF-Query-Functions-Buzzword-DateTime-0.001.tar.gz

Binary file added.
+NAME
+    RDF::Query::Functions::Buzzword::DateTime - plugin for buzzword.org.uk
+    datetime functions
+
+SYNOPSIS
+    TODO - update synopsis
+
+      use RDF::TrineShortcuts qw[:all];
+      use Data::Dumper;
+  
+      my $data = rdf_parse(<<'TURTLE', type=>'turtle');
+      @prefix foaf: <http://xmlns.com/foaf/0.1/> .
+      @prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+      @prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .
+
+      <http://tobyinkster.co.uk/#i>
+        foaf:birthday "1980-06-01"^^xsd:date .
+      TURTLE
+
+      $r = rdf_query(<<'SPARQL', $data);
+      PREFIX foaf: <http://xmlns.com/foaf/0.1/>
+      PREFIX dt:   <http://buzzword.org.uk/2011/functions/datetime#>
+      PREFIX xsd:  <http://www.w3.org/2001/XMLSchema#>
+      SELECT
+        (dt:now() AS ?now)
+        (dt:today() AS ?today)
+        ?bday
+        (dt:format_duration(
+          dt:difference(dt:now(), ?bday),
+          "%Y years, %m months")
+          AS ?age)
+        (dt:add(?bday, "P10Y"^^xsd:duration) AS ?tenthbday)
+        (dt:strftime(?bday, "%a, %d %b %Y"@en) AS ?fmtbday)
+        (dt:strtodate("1/6/1980"@en-us) AS ?guessamericandate)
+        (dt:strtodate("1/6/1980"@en) AS ?guessenglishdate)
+      WHERE
+      {
+        ?person foaf:birthday ?bday .
+      }
+      SPARQL
+  
+      print Dumper(flatten_iterator($r, literal_as=>'ntriples'));
+
+DESCRIPTION
+    This is a plugin for RDF::Query providing a number of extension
+    functions.
+
+    *   http://buzzword.org.uk/2011/functions/datetime#add
+
+        Given an xsd:dateTime or xsd:date, and an xsd:duration, adds the
+        duration to the datetime. Returns an xsd:date if it was passed an
+        xsd:date and the xsd:duration didn't specify any hours, minutes or
+        seconds. Returns an xsd:dateTime otherwise.
+
+    *   http://buzzword.org.uk/2011/functions/datetime#difference
+
+        Given two xsd:dateTime or xsd:date literals, returns an xsd:duration
+        representing the difference between them.
+
+    *   http://buzzword.org.uk/2011/functions/datetime#format_duration
+
+        Given an xsd:duration and a literal formatting string, returns a
+        formatted duration. See DateTime::Format::Duration.
+
+    *   http://buzzword.org.uk/2011/functions/datetime#now
+
+        Returns the current xsd:dateTime, with supposed nanosecond
+        precision. If called multiple times in the same SPARQL query, will
+        always return the same instant.
+
+    *   http://buzzword.org.uk/2011/functions/datetime#strftime
+
+        Takes a xsd:datetime and a literal formatting string and returns a
+        formattted date. See DateTime.
+
+    *   http://buzzword.org.uk/2011/functions/datetime#strtodate
+
+        Attempts to parse an arbitrary literal using natural language and
+        convert it into an xsd:date. Smart enough to tell the difference
+        between "1/6/1980"@en-us and "1/6/1980"@en-gb.
+
+        Can safely be passed an existing xsd:date or xsd:dateTime.
+
+    *   http://buzzword.org.uk/2011/functions/datetime#strtotime
+
+        As per "strtodate" but returns an xsd:dateTime.
+
+        "add", "difference" and "strftime" all implicitly call "strtotime"
+        on their xsd:dateTime arguments, which means they don't need to be
+        given strict xsd:date/dateTime input.
+
+    *   http://buzzword.org.uk/2011/functions/datetime#today
+
+        Like "now" but returns an xsd:date.
+
+SEE ALSO
+    RDF::Query, RDF::Query::Functions::Buzzword::Util.
+
+    DateTime.
+
+    <http://perlrdf.org/>.
+
+AUTHOR
+    Toby Inkster <tobyink@cpan.org>.
+
+COPYRIGHT
+    Copyright 2011 Toby Inkster
+
+    This library is free software; you can redistribute it and/or modify it
+    under the same terms as Perl itself.
+
+use lib "lib";
+use RDF::TrineShortcuts qw[:all];
+use Data::Dumper;
+
+my $data = rdf_parse(<<'TURTLE', type=>'turtle');
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+
+<http://tobyinkster.co.uk/#i>
+	foaf:birthday "1980-06-01"^^<http://www.w3.org/2001/XMLSchema#date> .
+TURTLE
+
+print Dumper(flatten_iterator(rdf_query(<<'SPARQL', $data), literal_as=>'ntriples'));
+PREFIX foaf: <http://xmlns.com/foaf/0.1/>
+PREFIX util: <http://buzzword.org.uk/2011/functions/util#>
+PREFIX dt:   <http://buzzword.org.uk/2011/functions/datetime#>
+PREFIX xsd:  <http://www.w3.org/2001/XMLSchema#>
+SELECT
+	(dt:now() AS ?now)
+	(dt:today() AS ?today)
+	?bday
+	(dt:format_duration(dt:difference(dt:now(), ?bday), "%Y years, %m months") AS ?age)
+	(dt:add(?bday, "P10Y"^^xsd:duration) AS ?tenthbday)
+	(dt:strtotime("yesterday morning"@en) AS ?yesterdaymorning)
+	(dt:strftime(?bday, "%a, %d %b %Y"@en) AS ?fmtbday)
+	(dt:strtodate("1/6/1980"@en-gb) AS ?guessbday)
+WHERE
+{
+	?person foaf:birthday ?bday .
+}
+SPARQL
+
+

File lib/RDF/Query/Functions/Buzzword/DateTime.pm

+package RDF::Query::Functions::Buzzword::DateTime;
+
+our $VERSION = '0.001';
+
+use common::sense;
+use DateTime;
+use DateTime::Format::Duration;
+use DateTime::Format::ISO8601;
+use DateTime::Format::Natural;
+use RDF::Query::Error qw(:try);
+use Scalar::Util qw(blessed reftype refaddr looks_like_number);
+
+sub _NS
+{
+	my ($prefix) = @_;
+	return sub {
+		return $prefix . $_[0];
+		};
+}
+
+sub _DateTime
+{
+	my ($node) = @_;
+	
+	throw RDF::Query::Error::TypeError(-text => "Expected literal.")
+		unless (blessed($node) and $node->isa('RDF::Trine::Node::Literal'));
+
+	my $XSD = _NS('http://www.w3.org/2001/XMLSchema#');
+	
+	if ($node->literal_datatype eq $XSD->('dateTime')
+	or  $node->literal_datatype eq $XSD->('date'))
+	{
+		my $iso = DateTime::Format::ISO8601->new;
+		my $dt;
+		local $@ = undef;
+		eval { $dt = $iso->parse_datetime($node->literal_value); };
+		throw RDF::Query::Error::FilterEvaluationError(-text => $@) if $@;
+		return $dt;
+	}
+
+	my $dt;
+	eval {
+		(my $lang = $node->literal_value_language) =~ s/[\-\_].*$//;
+		# English (US, Canada, Philippines and Belize) = assume M/D/Y.
+		# Spanish (US, Philippines) = assume M/D/Y.
+		# Tagalog = assume M/D/Y.
+		# All else, assume D/M/Y
+		# Really should add Y/M/D for countries where it's more common
+		my $format = ($node->literal_value_language =~ /^(en-(us|ca|ph|bz)|es-(us|ph)|tl)/i) ? 'm/d/y' : 'd/m/y';
+		my $p = DateTime::Format::Natural->new(
+			lang   => $lang,
+			format => $format,
+			);
+		$dt = $p->parse_datetime($node->literal_value);
+		$dt = undef unless $p->success;
+	};
+	return $dt if $dt;
+
+	throw RDF::Query::Error::FilterEvaluationError(-text => "Format unrecognised.");
+}
+
+sub _Duration
+{
+	my ($node) = @_;
+	
+	throw RDF::Query::Error::TypeError(-text => "Expected literal.")
+		unless (blessed($node) and $node->isa('RDF::Trine::Node::Literal'));
+
+	my $XSD = _NS('http://www.w3.org/2001/XMLSchema#');
+
+	if ($node->literal_datatype eq $XSD->('duration')
+	and $node->literal_value =~ /^
+			\s*
+			([\+\-])?          # Potentially negitive...
+			P                  # Period of...
+			(?:([\d\.]*)Y)?    # n Years
+			(?:([\d\.]*)M)?    # n Months
+			(?:([\d\.]*)W)?    # n Weeks
+			(?:([\d\.]*)D)?    # n Days
+			(?:                 
+				T               # And a time of...
+				(?:([\d\.]*)H)? # n Hours
+				(?:([\d\.]*)M)? # n Minutes
+				(?:([\d\.]*)S)? # n Seconds
+			)?
+			\s*
+			/ix)
+	{
+		my $X = {};
+		$X->{'I'}   = $1;
+		$X->{'y'}   = $2;
+		$X->{'m'}   = $3;
+		$X->{'w'}   = $4;
+		$X->{'d'}   = $5;
+		$X->{'h'}   = $6;
+		$X->{'min'} = $7;
+		$X->{'s'}   = $8;
+		$X->{'n'}   = 0;
+		
+		# Handle fractional
+		no strict;
+		foreach my $frac (qw(y=12.m m=30.d w=7.d d=24.h h=60.min min=60.s s=1000000000.n))
+		{
+			my ($big, $mult, $small) = split /[\=\.]/, $frac;
+			next unless ($X->{$big} =~ /\./);
+			
+			my $int_part  = int($X->{$big});
+			my $frac_part = $X->{$big} - $int_part;
+			
+			$X->{$big}    =  $int_part;
+			$X->{$small} += ($mult * $frac_part);
+		}
+		use strict;
+		$X->{'n'} = int($X->{'n'});
+		
+		# Construct and return object.
+		my $dur = DateTime::Duration->new(
+			years       => $X->{'y'}||0,
+			months      => $X->{'m'}||0,
+			weeks       => $X->{'w'}||0,
+			days        => $X->{'d'}||0,
+			hours       => $X->{'h'}||0,
+			minutes     => $X->{'min'}||0,
+			seconds     => $X->{'s'}||0,
+			nanoseconds => $X->{'n'}||0
+		);
+		
+		return $X->{'I'} eq '-' ? $dur->inverse : $dur;
+	}
+
+	throw RDF::Query::Error::FilterEvaluationError(-text => "Format unrecognised.");
+}
+
+sub _ISO_Duration
+{
+	my ($d) = @_;
+	my $str;
+	
+	# We coerce weeks into days and nanoseconds into fractions of a second
+	# for compatibility with xsd:duration.
+	
+	if ($d->is_negative)
+		{ $str .= '-P'; }
+	else
+		{ $str .= 'P'; }
+		
+	if ($d->years)
+		{ $str .= $d->years.'Y'; }
+
+	if ($d->months)
+		{ $str .= $d->months.'M'; }
+
+	if ($d->weeks || $d->days)
+		{ $str .= ($d->days + (7 * $d->weeks)).'D'; }
+
+	$str .= 'T';
+
+	if ($d->hours)
+		{ $str .= $d->hours.'H'; }
+
+	if ($d->minutes)
+		{ $str .= $d->minutes.'M'; }
+
+	if ($d->seconds || $d->nanoseconds)
+		{ $str .= ($d->seconds + ($d->nanoseconds / 1000000000)).'S'; }
+		
+	$str =~ s/T$//;
+	
+	return $str;
+}
+
+sub install
+{
+	my $XSD = _NS('http://www.w3.org/2001/XMLSchema#');
+	my $DT  = _NS('http://buzzword.org.uk/2011/functions/datetime#');
+
+	$RDF::Query::functions{ $DT->('now') } ||= sub {
+		my ($query) = @_;
+		$query->{_query_cache}{ $DT->('now') } ||= DateTime->now;
+		my $now = $query->{_query_cache}{ $DT->('now') };		
+		return RDF::Query::Node::Literal->new($now->strftime('%FT%T.%9N%z'), undef, $XSD->('dateTime'));
+	};
+
+	$RDF::Query::functions{ $DT->('today') } ||= sub {
+		my ($query) = @_;
+		$query->{_query_cache}{ $DT->('today') } ||= DateTime->now;
+		my $now = $query->{_query_cache}{ $DT->('today') };
+		return RDF::Query::Node::Literal->new($now->strftime('%F'), undef, $XSD->('date'));
+	};
+
+	$RDF::Query::functions{ $DT->('difference') } ||= sub {
+		my ($query, $dt1, $dt2) = @_;		
+		$dt1 = _DateTime($dt1);
+		$dt2 = _DateTime($dt2);
+		my $diff = $dt1->subtract_datetime($dt2);
+		return RDF::Query::Node::Literal->new(_ISO_Duration($diff), undef, $XSD->('duration'));
+	};
+
+	$RDF::Query::functions{ $DT->('add') } ||= sub {
+		my ($query, $dt1x, $durx) = @_;		
+		my $dt1 = _DateTime($dt1x);
+		my $dur = _Duration($durx);
+		my $rv = $dt1->add_duration($dur);
+		return RDF::Query::Node::Literal->new($rv->strftime('%F'), undef, $XSD->('date'))
+			if ($dt1x->literal_datatype eq $XSD->('date') and $durx !~ /T/i);
+		return RDF::Query::Node::Literal->new($rv->strftime('%FT%T.%9N%z'), undef, $XSD->('dateTime'));
+	};
+
+	$RDF::Query::functions{ $DT->('strftime') } ||= sub {
+		my ($query, $dtx, $fmt) = @_;		
+		my $dt = _DateTime($dtx);
+		throw RDF::Query::Error::TypeError(-text => "Expected literal.")
+			unless (blessed($fmt) and $fmt->isa('RDF::Trine::Node::Literal'));
+		
+		return RDF::Query::Node::Literal->new(
+			$dt->strftime($fmt->literal_value),
+			$fmt->literal_value_language,
+			$fmt->literal_datatype,
+			);
+	};
+
+	$RDF::Query::functions{ $DT->('format_duration') } ||= sub {
+		my ($query, $durx, $fmt) = @_;		
+		my $dur = _Duration($durx);
+		throw RDF::Query::Error::TypeError(-text => "Expected literal.")
+			unless (blessed($fmt) and $fmt->isa('RDF::Trine::Node::Literal'));
+		
+		my $formatter = DateTime::Format::Duration->new(normalise => 1, pattern => $fmt->literal_value);
+		return RDF::Query::Node::Literal->new(
+			$formatter->format_duration($dur),
+			$fmt->literal_value_language,
+			$fmt->literal_datatype,
+			);
+	};
+
+	$RDF::Query::functions{ $DT->('strtotime') } ||= sub {
+		my ($query, $dt1x) = @_;
+		my $dt1;
+		eval { $dt1 = _DateTime($dt1x); };
+		return undef unless $dt1;
+		return RDF::Query::Node::Literal->new($dt1->strftime('%FT%T.%9N%z'), undef, $XSD->('dateTime'));
+	};
+
+	$RDF::Query::functions{ $DT->('strtodate') } ||= sub {
+		my ($query, $dt1x) = @_;
+		my $dt1;
+		eval { $dt1 = _DateTime($dt1x); };
+		return undef unless $dt1;
+		return RDF::Query::Node::Literal->new($dt1->strftime('%F'), undef, $XSD->('date'));
+	};
+
+} #/sub install
+
+
+1;
+
+__END__
+
+=head1 NAME
+
+RDF::Query::Functions::Buzzword::DateTime - plugin for buzzword.org.uk datetime functions
+
+=head1 SYNOPSIS
+
+B<TODO> - update synopsis
+
+  use RDF::TrineShortcuts qw[:all];
+  use Data::Dumper;
+  
+  my $data = rdf_parse(<<'TURTLE', type=>'turtle');
+  @prefix foaf: <http://xmlns.com/foaf/0.1/> .
+  @prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+  @prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .
+
+  <http://tobyinkster.co.uk/#i>
+    foaf:birthday "1980-06-01"^^xsd:date .
+  TURTLE
+
+  $r = rdf_query(<<'SPARQL', $data);
+  PREFIX foaf: <http://xmlns.com/foaf/0.1/>
+  PREFIX dt:   <http://buzzword.org.uk/2011/functions/datetime#>
+  PREFIX xsd:  <http://www.w3.org/2001/XMLSchema#>
+  SELECT
+    (dt:now() AS ?now)
+    (dt:today() AS ?today)
+    ?bday
+    (dt:format_duration(
+      dt:difference(dt:now(), ?bday),
+      "%Y years, %m months")
+      AS ?age)
+    (dt:add(?bday, "P10Y"^^xsd:duration) AS ?tenthbday)
+    (dt:strftime(?bday, "%a, %d %b %Y"@en) AS ?fmtbday)
+    (dt:strtodate("1/6/1980"@en-us) AS ?guessamericandate)
+    (dt:strtodate("1/6/1980"@en) AS ?guessenglishdate)
+  WHERE
+  {
+    ?person foaf:birthday ?bday .
+  }
+  SPARQL
+  
+  print Dumper(flatten_iterator($r, literal_as=>'ntriples'));
+
+=head1 DESCRIPTION
+
+This is a plugin for RDF::Query providing a number of extension functions.
+
+=over
+
+=item * http://buzzword.org.uk/2011/functions/datetime#add
+
+Given an xsd:dateTime or xsd:date, and an xsd:duration, adds the
+duration to the datetime. Returns an xsd:date if it was passed an
+xsd:date and the xsd:duration didn't specify any hours, minutes or
+seconds. Returns an xsd:dateTime otherwise.
+
+=item * http://buzzword.org.uk/2011/functions/datetime#difference
+
+Given two xsd:dateTime or xsd:date literals, returns an xsd:duration
+representing the difference between them.
+
+=item * http://buzzword.org.uk/2011/functions/datetime#format_duration
+
+Given an xsd:duration and a literal formatting string, returns a
+formatted duration. See L<DateTime::Format::Duration>.
+
+=item * http://buzzword.org.uk/2011/functions/datetime#now
+
+Returns the current xsd:dateTime, with supposed nanosecond precision.
+If called multiple times in the same SPARQL query, will always return
+the same instant.
+
+=item * http://buzzword.org.uk/2011/functions/datetime#strftime
+
+Takes a xsd:datetime and a literal formatting string and returns
+a formattted date. See L<DateTime>.
+
+=item * http://buzzword.org.uk/2011/functions/datetime#strtodate
+
+Attempts to parse an arbitrary literal using natural language and
+convert it into an xsd:date. Smart enough to tell the difference
+between "1/6/1980"@en-us and "1/6/1980"@en-gb.
+
+Can safely be passed an existing xsd:date or xsd:dateTime.
+
+=item * http://buzzword.org.uk/2011/functions/datetime#strtotime
+
+As per C<strtodate> but returns an xsd:dateTime.
+
+C<add>, C<difference> and C<strftime> all implicitly call C<strtotime>
+on their xsd:dateTime arguments, which means they don't need to be
+given strict xsd:date/dateTime input.
+
+=item * http://buzzword.org.uk/2011/functions/datetime#today
+
+Like C<now> but returns an xsd:date.
+
+=back
+
+=head1 SEE ALSO
+
+L<RDF::Query>,
+L<RDF::Query::Functions::Buzzword::Util>.
+
+L<DateTime>.
+
+L<http://perlrdf.org/>.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT
+
+Copyright 2011 Toby Inkster
+
+This library is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+use lib 'inc';
+use Test::More tests => 1;
+use Test::Signature;
+signature_ok();
+use Test::More tests => 1;
+use RDF::Query;
+
+ok(
+	defined $RDF::Query::functions{'http://buzzword.org.uk/2011/functions/datetime#now'},
+	"Module::Pluggable found me!",
+	);