Commits

Toby Inkster committed 27bc26d

initial version

  • Participants
  • Tags 0.001

Comments (0)

Files changed (15)

+use inc::Module::Package 'RDF:standard';
+

File examples/duri.pl

+use URI::duri;
+
+my $u = URI::duri->new("duri:2001-01-01T12:34:56.789+01:http://example.net/foo#bar");
+
+my @fields = qw(
+	scheme opaque path fragment as_string as_iri canonical secure authority
+	path path_query query userinfo host ihost port host_port default_port
+	datetime datetime_string embedded_uri
+);
+
+foreach my $field (@fields)
+{
+	if (not $u->can($field))
+	{
+		printf "%16s : can't\n" => $field;
+		next;
+	}
+	
+	my $value = eval { $u->$field };
+	if (defined $value)
+		{ $value = qq{"$value"} }
+	else
+		{ $value = 'undef' }
+	
+	printf "%16s : %s\n" => $field, $value;
+}
+

File lib/URI/_duri_tdb.pm

+package URI::_duri_tdb;
+
+use 5.010;
+use strict;
+use warnings;
+use utf8;
+
+BEGIN {
+	$URI::_duri_tdb::AUTHORITY = 'cpan:TOBYINK';
+	$URI::_duri_tdb::VERSION   = '0.001';
+}
+
+use Carp;
+use DateTime::Incomplete;
+use POSIX qw[floor];
+use Scalar::Util qw[blessed reftype];
+
+use base 'URI';
+
+my $re_datetime = qr{
+	(?<year>\d{4})
+	(?:
+		\-(?<month>\d{2})
+		(?:
+			\-(?<day>\d{2})
+			(?:
+				T(?<hour>\d{2}):(?<minute>\d{2})
+				(?:
+					:(?<second> \d{2} (?:\.\d+)? )
+				)?
+			)?
+		)?
+	)?
+	(?<time_zone>
+		[Z] |
+		[+-]\d{2}:\d{2} |
+		[+-]\d{4} |
+		[+-]\d{2}
+	)?
+}ix;
+
+sub new
+{
+	my $param = $_[1];
+	
+	if (not ref $param)
+		{ goto \&_new_from_string }
+	elsif (reftype $param eq 'HASH')
+		{ goto \&_new_from_hashref }
+
+	croak "cannot construct URI::duri object";
+}
+
+sub _new_from_string
+{
+	my ($class, $str) = @_;
+	my $self = bless \$str => $class;
+	$self->_deconstruct;
+	return $self;
+}
+
+sub _new_from_hashref
+{
+	my ($class, $hashref) = @_;
+	
+	my $str  = $class->_preferred_scheme . ':2001:urn:example:1';
+	my $self = bless \$str => $class;
+
+	if ($hashref->{datetime_string})
+		{ $self->datetime_string($self->{datetime_string}) }
+	elsif ($hashref->{datetime})
+		{ $self->datetime($self->{datetime}) }
+	else
+		{ $self->datetime(DateTime->now) }
+	
+	exists $hashref->{embedded_uri}
+		or croak "need embedded_uri hash key";
+	$self->embedded_uri($hashref->{embedded_uri});
+	
+	return $self;
+}
+
+sub _parse_datetime
+{
+	my ($self, $str) = @_;
+	
+	confess "_parse_datetime called with undefined argument" unless defined $str;
+	
+	if ($str =~ /^$re_datetime$/)
+	{
+		my %parts = %+;
+		if (defined $parts{time_zone} 
+		and lc $parts{time_zone} eq 'z')
+		{
+			$parts{time_zone} = 'UTC';
+		}
+		elsif (defined $parts{time_zone})
+		{
+			$parts{time_zone} =~ s/://;
+			$parts{time_zone} .= '00'
+				if length $parts{time_zone} == 3;
+		}
+		
+		if (defined $parts{second}
+		and $parts{second} > floor $parts{second})
+		{
+			my $frac = $parts{second} - floor $parts{second};
+			$parts{second}     = floor $parts{second};
+			$parts{nanosecond} = $frac * 1_000_000_000;
+		}
+		
+		return DateTime::Incomplete->new(%parts);
+	}
+	
+	croak "datetime does not match regular expression";
+}
+
+sub _serialize_datetime
+{
+	my ($self, $dt) = @_;
+	
+	if ($dt->isa('DateTime::Incomplete'))
+	{
+		croak "datetime has no year"
+			unless $dt->has_year;
+		
+		my $str = sprintf('%04d' => $dt->year);
+		my $tz  = '';
+		
+		if ($dt->has_time_zone and $dt->time_zone->is_utc)
+			{ $tz = 'Z' }
+		elsif ($dt->has_time_zone and $dt->time_zone->is_floating)
+			{ $tz = '' }
+		elsif ($dt->has_time_zone)
+			{ croak "non-UTC timezone specified" }
+		
+		$dt->has_month
+			? do { $str .= sprintf('-%02d' => $dt->month) }
+			: return $str.$tz;
+			
+		$dt->has_day
+			? do { $str .= sprintf('-%02d' => $dt->day) }
+			: return $str.$tz;
+		
+		$dt->has_hour && $dt->has_minute
+			? do { $str .= sprintf('T%02d:%02d' => $dt->hour, $dt->minute) }
+			: return $str.$tz;
+		
+		$dt->has_second
+			? do { $str .= sprintf(':%02d' => $dt->second) }
+			: return $str.$tz;
+		
+		$dt->has_nanosecond && $dt->nanosecond > 0
+			? do { $str .= sprintf('.%09d' => $dt->nanosecond); $str =~ s/0+$//; }
+			: return $str.$tz;
+		
+		return $str.$tz;
+	}
+	elsif ($dt->isa('DateTime'))
+	{
+		unless ($dt->time_zone->is_floating or $dt->time_zone->is_utc)
+		{
+			$dt->set_time_zone('UTC');
+		}
+		
+		my $str = $dt->strftime('%FT%T.%9N');
+		$str =~ s/0+$//;
+		$str =~ s/\.$//;
+		
+		if ($dt->time_zone->is_utc)
+		{
+			$str .= 'Z';
+		}
+		
+		return $str;
+	}
+	
+	confess "can't serialize";
+}
+
+sub datetime
+{
+	my $self = shift;
+	
+	if (@_)
+	{
+		my $dt = shift;
+		croak "expected DateTime object"
+			unless (
+				blessed($dt) and
+				$dt->isa('DateTime') || $dt->isa('DateTime::Incomplete')
+			);
+		$self->datetime_string($self->_serialize_datetime($dt), 1);
+	}
+	
+	$self->_parse_datetime($self->datetime_string);
+}
+
+sub datetime_string
+{
+	my $self  = shift;
+	my @parts = $self->_deconstruct;
+	
+	if (@_)
+	{
+		my ($dt, $skip_check) = @_;
+		unless ($skip_check)
+		{
+			$dt =~ /^$re_datetime$/
+				or croak "string '$dt' cannot be parsed as a DateTime: $@";
+		}
+		$parts[1] = $dt;
+		$self->_reconstruct(@parts);
+	}
+	
+	return $parts[1];
+}
+
+sub embedded_uri
+{
+	my $self  = shift;
+	my @parts = $self->_deconstruct;
+	
+	if (@_)
+	{
+		my $uri = shift;
+		$parts[2] = blessed($uri) ? $uri : URI->new("$uri");
+		$self->_reconstruct(@parts);
+	}
+	
+	return URI->new($parts[2]);
+}
+
+sub _reconstruct
+{
+	my $self = shift;	
+	$$self = sprintf('%s:%s:%s', @_);
+	return $self;
+}
+
+sub _deconstruct
+{
+	my $self = shift;
+	
+	if (my @r = ($$self =~ m{
+		^
+		(?<scheme>[A-Za-z][A-Za-z0-9+-]*)
+		\:
+		(?<datetime>$re_datetime)
+		\:
+		(?<embedded>.+)
+		$
+	}x))
+	{
+		return @+{qw< scheme datetime embedded >};
+	}
+	
+	else
+	{
+		confess "couldn't match regexp";
+	}
+}
+
+__PACKAGE__

File lib/URI/duri.pm

+package URI::duri;
+
+use 5.010;
+use strict;
+
+BEGIN {
+	$URI::duri::AUTHORITY = 'cpan:TOBYINK';
+	$URI::duri::VERSION   = '0.001';
+}
+
+use base 'URI::_duri_tdb';
+use constant _preferred_scheme => 'duri';
+
+__PACKAGE__
+__END__
+
+=head1 NAME
+
+URI::duri - the duri URI scheme
+
+=head1 SYNOPSIS
+
+ my $uri = URI->new('duri:2012:http://tobyinkster.co.uk/');
+ say $uri->embedded_uri;
+
+=head1 DESCRIPTION
+
+The dated URI scheme is defined in an Internet Draft
+L<http://tools.ietf.org/html/draft-masinter-dated-uri-10>. Dated URIs
+include a date and an embedded URI. They identify the same resource that
+was identified by the embedded URI at the given date.
+
+This module brings support for the duri URI scheme to the L<URI>
+framework.
+
+=head2 Constructor
+
+The constructor can be called in two forms:
+
+=over
+
+=item C<< new($string) >>
+
+=item C<< new(\%hash) >>
+
+=back
+
+When called with a string argument, B<must> be a URI string conforming
+to the dated URI Internet Draft.
+
+If called with a hashref argument, the hash B<must> have a key
+C<embedded_uri> which is a string or URI object. It B<may> have a key
+C<datetime_string> which is a string representing a datetime in the
+format required by the dated URI specification; alternatively it
+B<may> have a key C<datetime> which is a L<DateTime> or (better)
+L<DateTime::Incomplete> object; if neither are present, then the
+current time is used instead.
+
+=head2 Methods
+
+The following accessors are provided:
+
+=over
+
+=item C<< datetime >>
+
+=item C<< datetime($object) >>
+
+Get/set the URI's datetime as a DateTime::Incomplete object.
+
+=item C<< datetime_string >>
+
+=item C<< datetime_string($string) >>
+
+Get/set the URI's datetime as a literal string.
+
+=item C<< embedded_uri >>
+
+=item C<< embedded_uri($uri) >>
+
+Get/set the embedded URI as a URI object. (The setter may also be called
+with a plain string.)
+
+=back
+
+The following methods are inherited from L<URI> and make sense to use:
+
+=over
+
+=item C<< scheme >>
+
+=item C<< scheme($string) >>
+
+Get/set the URI scheme.
+
+=item C<< as_string >>
+
+Get the URI as a string.
+
+=item C<< as_iri >>
+
+Get the URI as a Unicode string.
+
+=item C<< canonical >>
+
+Get the URI as a canonical string.
+
+=item C<< secure >>
+
+Returns false, though the method doesn't make much sense. One URI is no
+more secure than another; it is B<protocols> that can be secure or 
+insecure.
+
+=item C<< eq($uri) >>
+
+Tests if this URI is equal to another.
+
+=back
+
+The following methods are also inherited from URI, but don't make much
+sense to use: C<opaque>, C<path>, C<fragment>. It generally makes more
+sense to inspect the embedded URI:
+
+ say $duri->embedded_uri->fragment;
+
+=head1 BUGS
+
+Please report any bugs to
+L<http://rt.cpan.org/Dist/Display.html?Queue=URI-duri>.
+
+=head1 SEE ALSO
+
+L<URI>, L<URI::tdb>.
+
+L<http://tools.ietf.org/html/draft-masinter-dated-uri-10>.
+
+L<http://www.perlrdf.org/>.
+
+L<DateTime::Incomplete>.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tdb:2012:http://metacpan.org/author/TOBYINKE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=head1 DISCLAIMER OF WARRANTIES
+
+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+

File lib/URI/tdb.pm

+package URI::tdb;
+
+use 5.010;
+use strict;
+
+BEGIN {
+	$URI::tdb::AUTHORITY = 'cpan:TOBYINK';
+	$URI::tdb::VERSION   = '0.001';
+}
+
+use base 'URI::_duri_tdb';
+use constant _preferred_scheme => 'tdb';
+
+__PACKAGE__
+__END__
+
+=head1 NAME
+
+URI::tdb - the tdb URI scheme
+
+=head1 SYNOPSIS
+
+ my $uri = URI->new('tdb:2012:http://tobyinkster.co.uk/');
+ say $uri->embedded_uri;
+
+=head1 DESCRIPTION
+
+The dated URI scheme is defined in an Internet Draft
+L<http://tools.ietf.org/html/draft-masinter-dated-uri-10>. Dated URIs
+include a date and an embedded URI. They identify the same resource that
+was identified by the embedded URI at the given date.
+
+tdb URIs take a slightly different approach, identifying the "thing
+described by" the resource. In the example given in the SYNOPSIS, the tdb
+URI doesn't identify a web page; it identifies a person.
+
+This module brings support for the tdb URI scheme to the L<URI> framework.
+
+This module provides an exactly identical interface to L<URI::duri> with
+the exception of differences described in the "Differences from URI::duri"
+section below.
+
+=head2 Differences from URI::duri
+
+None.
+
+=head1 BUGS
+
+Please report any bugs to
+L<http://rt.cpan.org/Dist/Display.html?Queue=URI-duri>.
+
+=head1 SEE ALSO
+
+L<URI>, L<URI::duri>.
+
+L<http://tools.ietf.org/html/draft-masinter-dated-uri-10>.
+
+L<http://www.perlrdf.org/>.
+
+L<DateTime::Incomplete>.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tdb:2012:http://metacpan.org/author/TOBYINKE<gt>.
+
+=head1 COPYRIGHT AND LICENCE
+
+This software is copyright (c) 2012 by Toby Inkster.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=head1 DISCLAIMER OF WARRANTIES
+
+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+

File meta/changes.ttl

+# This file acts as the project's changelog.
+
+@prefix :        <http://usefulinc.com/ns/doap#> .
+@prefix dcs:     <http://ontologi.es/doap-changeset#> .
+@prefix dc:      <http://purl.org/dc/terms/> .
+@prefix dist:    <http://purl.org/NET/cpan-uri/dist/URI-duri/> .
+@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd:     <http://www.w3.org/2001/XMLSchema#> .
+
+dist:project :release dist:v_0-001 .
+dist:v_0-001
+	a               :Version ;
+	dc:issued       "2012-06-16"^^xsd:date ;
+	:revision       "0.001"^^xsd:string ;
+	:file-release   <http://backpan.cpan.org/authors/id/T/TO/TOBYINK/URI-duri-0.001.tar.gz> ;
+	rdfs:label      "Initial release" .
+

File meta/doap.ttl

+# This file contains general metadata about the project.
+
+@prefix :        <http://usefulinc.com/ns/doap#> .
+@prefix dc:      <http://purl.org/dc/terms/> .
+@prefix foaf:    <http://xmlns.com/foaf/0.1/> .
+@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd:     <http://www.w3.org/2001/XMLSchema#> .
+
+<http://purl.org/NET/cpan-uri/dist/URI-duri/project>
+	a               :Project ;
+	:programming-language "Perl" ;
+	:name           "URI-duri" ;
+	:shortdesc      "the duri and tdb URI schemes" ;
+	:homepage       <https://metacpan.org/release/URI-duri> ;
+	:download-page  <https://metacpan.org/release/URI-duri> ;
+	:repository     [ a :HgRepository ; :browse <https://bitbucket.org/tobyink/p5-uri-duri> ] ;
+	:bug-database   <http://rt.cpan.org/Dist/Display.html?Queue=URI-duri> ;
+	:created        "2012-06-15"^^xsd:date ;
+	:license        <http://dev.perl.org/licenses/> ;
+	:developer      [ a foaf:Person ; foaf:name "Toby Inkster" ; foaf:mbox <mailto:tobyink@cpan.org> ] .
+
+<http://dev.perl.org/licenses/>
+	dc:title        "the same terms as the perl 5 programming language system itself" .
+

File meta/makefile.ttl

+# This file provides instructions for packaging.
+
+@prefix : <http://purl.org/NET/cpan-uri/terms#> .
+
+<http://purl.org/NET/cpan-uri/dist/URI-duri/project>
+	:perl_version_from _:main;
+	:version_from _:main;
+	:readme_from _:main;
+	:requires "DateTime::Incomplete", "URI";
+	:test_requires "Test::More 0.61".
+
+_:main
+	<http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#fileName> "lib/URI/duri.pm".
+
+use Test::More tests => 6;
+
+BEGIN { use_ok('URI::duri') };
+BEGIN { use_ok('URI::tdb') };
+
+my $duri = new_ok 'URI::duri', ['duri:2012:urn:example'];
+isa_ok $duri, 'URI';
+
+my $tdb = new_ok 'URI::tdb', ['duri:2012:urn:example'];
+isa_ok $tdb, 'URI';
+use lib '../lib';
+use Data::Dumper;
+use Test::More tests => 115;
+use URI;
+use URI::duri;
+
+my @uris = (
+	URI->new('duri:2000:http://example.com/'), ####################### 0
+	URI->new('duri:2000-01:http://example.com/'),                    # 1
+	URI->new('duri:2000-01-01:http://example.com/'), ################# 2
+	URI->new('duri:2000-01-01T12:34:http://example.com/'),           # 3
+	URI->new('duri:2000-01-01T12:34:56:http://example.com/'), ######## 4
+	URI->new('duri:2000-01-01T12:34:56.789:http://example.com/'),    # 5
+	URI->new('duri:2000-01-01Z:http://example.com/'), ################ 6
+	URI->new('duri:2000-01-01T12:34Z:http://example.com/'),          # 7
+	URI->new('duri:2000-01-01T12:34:56Z:http://example.com/'), ####### 8
+	URI->new('duri:2000-01-01T12:34:56.789Z:http://example.com/'),   # 9
+);
+
+foreach (0 .. 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_year,
+		"$uris[$_] has year",
+	);
+	is(
+		$uris[$_]->datetime->year,
+		'2000',
+		"$uris[$_] has correct year",
+	);
+}
+
+foreach (1 .. 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_month,
+		"$uris[$_] has month",
+	);
+	is(
+		$uris[$_]->datetime->month,
+		'01',
+		"$uris[$_] has correct month",
+	);
+}
+
+foreach (2 .. 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_day,
+		"$uris[$_] has day",
+	);
+	is(
+		$uris[$_]->datetime->day,
+		'01',
+		"$uris[$_] has correct day",
+	);
+}
+
+
+foreach (3, 4, 5, 7, 8, 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_hour &&
+		$uris[$_]->datetime->has_minute,
+		"$uris[$_] has hour and minute",
+	);
+	is(
+		$uris[$_]->datetime->hour,
+		'12',
+		"$uris[$_] has correct hour",
+	);
+	is(
+		$uris[$_]->datetime->minute,
+		'34',
+		"$uris[$_] has correct minute",
+	);
+}
+
+foreach (4, 5, 8, 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_second,
+		"$uris[$_] has second",
+	);
+	is(
+		$uris[$_]->datetime->second,
+		'56',
+		"$uris[$_] has correct second",
+	);
+}
+
+foreach (5, 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_nanosecond,
+		"$uris[$_] has nanosecond",
+	);
+	ok(
+		abs($uris[$_]->datetime->nanosecond - 789_000_000) < 5,
+		"$uris[$_] has correct second",
+	);
+}
+
+foreach (6 .. 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_time_zone,
+		"$uris[$_] has time zone",
+	);
+	ok(
+		$uris[$_]->datetime->time_zone->is_utc,
+		"$uris[$_] has correct time zone",
+	);
+}
+
+foreach (0 .. 9)
+{
+	my $emb = $uris[$_]->embedded_uri;
+	isa_ok $emb => 'URI';
+	is("$emb", "http://example.com/");
+}
+
+my $complex = URI->new('duri:2000-01-01T12:34:56.789+00:00:urn:example.com:foo');
+
+is(
+	$complex->datetime->year,
+	'2000',
+);
+is(
+	$complex->datetime->second,
+	'56',
+);
+is(
+	$complex->embedded_uri,
+	'urn:example.com:foo',
+);
+
+done_testing();
+use lib '../lib';
+use Data::Dumper;
+use Test::More tests => 115;
+use URI;
+use URI::tdb;
+
+my @uris = (
+	URI->new('tdb:2000:http://example.com/'), ####################### 0
+	URI->new('tdb:2000-01:http://example.com/'),                    # 1
+	URI->new('tdb:2000-01-01:http://example.com/'), ################# 2
+	URI->new('tdb:2000-01-01T12:34:http://example.com/'),           # 3
+	URI->new('tdb:2000-01-01T12:34:56:http://example.com/'), ######## 4
+	URI->new('tdb:2000-01-01T12:34:56.789:http://example.com/'),    # 5
+	URI->new('tdb:2000-01-01Z:http://example.com/'), ################ 6
+	URI->new('tdb:2000-01-01T12:34Z:http://example.com/'),          # 7
+	URI->new('tdb:2000-01-01T12:34:56Z:http://example.com/'), ####### 8
+	URI->new('tdb:2000-01-01T12:34:56.789Z:http://example.com/'),   # 9
+);
+
+foreach (0 .. 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_year,
+		"$uris[$_] has year",
+	);
+	is(
+		$uris[$_]->datetime->year,
+		'2000',
+		"$uris[$_] has correct year",
+	);
+}
+
+foreach (1 .. 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_month,
+		"$uris[$_] has month",
+	);
+	is(
+		$uris[$_]->datetime->month,
+		'01',
+		"$uris[$_] has correct month",
+	);
+}
+
+foreach (2 .. 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_day,
+		"$uris[$_] has day",
+	);
+	is(
+		$uris[$_]->datetime->day,
+		'01',
+		"$uris[$_] has correct day",
+	);
+}
+
+
+foreach (3, 4, 5, 7, 8, 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_hour &&
+		$uris[$_]->datetime->has_minute,
+		"$uris[$_] has hour and minute",
+	);
+	is(
+		$uris[$_]->datetime->hour,
+		'12',
+		"$uris[$_] has correct hour",
+	);
+	is(
+		$uris[$_]->datetime->minute,
+		'34',
+		"$uris[$_] has correct minute",
+	);
+}
+
+foreach (4, 5, 8, 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_second,
+		"$uris[$_] has second",
+	);
+	is(
+		$uris[$_]->datetime->second,
+		'56',
+		"$uris[$_] has correct second",
+	);
+}
+
+foreach (5, 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_nanosecond,
+		"$uris[$_] has nanosecond",
+	);
+	ok(
+		abs($uris[$_]->datetime->nanosecond - 789_000_000) < 5,
+		"$uris[$_] has correct second",
+	);
+}
+
+foreach (6 .. 9)
+{	
+	ok(
+		$uris[$_]->datetime->has_time_zone,
+		"$uris[$_] has time zone",
+	);
+	ok(
+		$uris[$_]->datetime->time_zone->is_utc,
+		"$uris[$_] has correct time zone",
+	);
+}
+
+foreach (0 .. 9)
+{
+	my $emb = $uris[$_]->embedded_uri;
+	isa_ok $emb => 'URI';
+	is("$emb", "http://example.com/");
+}
+
+my $complex = URI->new('tdb:2000-01-01T12:34:56.789+00:00:urn:example.com:foo');
+
+is(
+	$complex->datetime->year,
+	'2000',
+);
+is(
+	$complex->datetime->second,
+	'56',
+);
+is(
+	$complex->embedded_uri,
+	'urn:example.com:foo',
+);
+
+done_testing();
+use Test::More;
+eval "use Test::Pod 1.00";
+plan skip_all => "Test::Pod 1.00 required for testing POD" if $@;
+all_pod_files_ok();
+

File xt/02pod_coverage.t

+use Test::More;
+use Test::Pod::Coverage;
+
+my @modules = qw(URI::duri);
+pod_coverage_ok($_, "$_ is covered")
+	foreach @modules;
+done_testing(scalar @modules);
+

File xt/03meta_uptodate.t

+use Test::More tests => 1;
+use Test::RDF::DOAP::Version;
+doap_version_ok('URI-duri', 'URI::duri');
+
+use Test::EOL;
+all_perl_files_ok();