diff --git a/Build.PL b/Build.PL new file mode 100644 index 0000000..859f15e --- /dev/null +++ b/Build.PL @@ -0,0 +1,18 @@ +use Module::Build; + +Module::Build->new( + module_name => 'DateTime::Format::ISO8601', + dist_version_from => 'lib/DateTime/Format/ISO8601.pm', + author => 'Joshua Hoblitt ', + license => 'perl', + create_makefile_pl => 'passthrough', + requires => { + DateTime => '0.18', + DateTime::Format::Builder => '0.77', + }, + recommends => { + 'Test::Pod' => '0.95', + 'File::Find::Rule' => '0.24', + 'Test::Distribution' => '1.22', + }, +)->create_build_script; diff --git a/Changes b/Changes new file mode 100644 index 0000000..83f07bb --- /dev/null +++ b/Changes @@ -0,0 +1,69 @@ +Revision history for Perl module DateTime::Format::ISO8601 + +0.08 Sat Feb 11 23:40:43 MST 2012 + - rt.cpan.org #52645 : UTC offsets must be in the same format + (basic|extended) as the time as to which it is attached. + +0.07 Sun Jan 17 23:35:40 MST 2010 + - fix test failures caused by tests using DateTime->now(), all tests should + now pass reguardless of the "wallclock" when they are being run + +0.06 Tue Apr 10 15:17:34 HST 2007 + - add Test::Distribution tests + - merge rel_0_04_FIXES branch -- fixing the major regressions introduced + in 0.05 + +0.05 Tue Mar 27 11:25:42 HST 2007 + - disable a test that may fail depending on the localtime date + +0.0403 Sun Aug 7 13:18:48 HST 2005 + - update doc format + - tidy Build.PL + - auto-generate Makefile.PL + - change set_base_datetime() to use DT's overloaded <=> instead of ->compare() + - tidy test sources and reduce runtime + +0.0402 Thu Oct 28 20:25:50 HST 2004 + - add 8 missing formats, patch by Kelly McCauley + +0.0401 Sun Feb 8 13:23:19 HST 2004 + - fix test for -DDD format as reported by Jonathan Leffler + +0.04 Sat Nov 15 14:29:58 HST 2003 + - require DT 0.18 and DT::F::B 0.77 + - recommend Test::Pod 0.95 and File::Find::Rule 0.24 + - doc update + - test update + - fix bug in -YY spec + - default handling of 2-digit years is now 0-49 as 20xx and 50-99 as 19xx + - add DefaultCutOffYear() + - add DefaultLegacyYear() + - add base_datetime() + - add clone() + - add cut_off_year() + - add legacy_year() + - add new() + - add set_base_datetime() + - add set_cut_off_year() + - add set_legacy_year() + +0.03 Thu Jul 3 19:57:18 HST 2003 + - require DT::F::B 0.75 + - 'constructor' in specrefs instead of _normalize_day() + - removed _normalize_day() + - speed enhancement from internals cleanup + - fixed _normalize_week to properly handle week specified without a day of week + - fixed ISO week day tests (count from zero instead of 1 error) + - ISO8601 sections 5.3.1.1 - 5.3.1.3 and 5.3.3 - 5.3.4.2 maybe prefixed with 'T' + - doc update + +0.02 Thu Jun 26 22:44:38 HST 2003 + - require DT 0.13 and DT::F::B 0.74 + - dropped Date::ISO dep + - length based string matching where possible + - support '.' as a fractional time separator + - slightly better docs + - bug fix for _add_minute() + +0.01 Sat Jun 21 22:35:19 HST 2003 + - support ISO8601 date/time formats diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9d0305b --- /dev/null +++ b/LICENSE @@ -0,0 +1,383 @@ +Terms of Perl itself + +a) the GNU General Public License as published by the Free + Software Foundation; either version 1, or (at your option) any + later version, or +b) the "Artistic License" + +--------------------------------------------------------------------------- + +The General Public License (GPL) +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, +Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute +verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to most of +the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software is +covered by the GNU Library General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you wish), that +you receive source code or can get it if you want it, that you can change the +software or use pieces of it in new free programs; and that you know you can do +these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny +you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a +fee, you must give the recipients all the rights that you have. You must make +sure that they, too, receive or can get the source code. And you must show +them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer +you this license which gives you legal permission to copy, distribute and/or +modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients to +know that what they have is not the original, so that any problems introduced by +others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish +to avoid the danger that redistributors of a free program will individually obtain +patent licenses, in effect making the program proprietary. To prevent this, we +have made it clear that any patent must be licensed for everyone's free use or +not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +GNU GENERAL PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND +MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms of +this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or translated +into another language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by +this License; they are outside its scope. The act of running the Program is not +restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made by +running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as +you receive it, in any medium, provided that you conspicuously and appropriately +publish on each copy an appropriate copyright notice and disclaimer of warranty; +keep intact all the notices that refer to this License and to the absence of any +warranty; and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at +your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus +forming a work based on the Program, and copy and distribute such +modifications or work under the terms of Section 1 above, provided that you also +meet all of these conditions: + +a) You must cause the modified files to carry prominent notices stating that you +changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or in +part contains or is derived from the Program or any part thereof, to be licensed +as a whole at no charge to all third parties under the terms of this License. + +c) If the modified program normally reads commands interactively when run, you +must cause it, when started running for such interactive use in the most ordinary +way, to print or display an announcement including an appropriate copyright +notice and a notice that there is no warranty (or else, saying that you provide a +warranty) and that users may redistribute the program under these conditions, +and telling the user how to view a copy of this License. (Exception: if the +Program itself is interactive but does not normally print such an announcement, +your work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Program, the distribution of the whole must be on +the terms of this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to +work written entirely by you; rather, the intent is to exercise the right to control +the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the +Program (or with a work based on the Program) on a volume of a storage or +distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 and 2 +above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source +code, which must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give any +third party, for a charge no more than your cost of physically performing source +distribution, a complete machine-readable copy of the corresponding source +code, to be distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for noncommercial +distribution and only if you received the program in object code or executable +form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all the +source code for all modules it contains, plus any associated interface definition +files, plus the scripts used to control compilation and installation of the +executable. However, as a special exception, the source code distributed need +not include anything that is normally distributed (in either source or binary form) +with the major components (compiler, kernel, and so on) of the operating system +on which the executable runs, unless that component itself accompanies the +executable. + +If distribution of executable or object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the source +code from the same place counts as distribution of the source code, even though +third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, or +rights, from you under this License will not have their licenses terminated so long +as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the Program +or its derivative works. These actions are prohibited by law if you do not accept +this License. Therefore, by modifying or distributing the Program (or any work +based on the Program), you indicate your acceptance of this License to do so, +and all its terms and conditions for copying, distributing or modifying the +Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor to copy, +distribute or modify the Program subject to these terms and conditions. You +may not impose any further restrictions on the recipients' exercise of the rights +granted herein. You are not responsible for enforcing compliance by third parties +to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), conditions are imposed on +you (whether by court order, agreement or otherwise) that contradict the +conditions of this License, they do not excuse you from the conditions of this +License. If you cannot distribute so as to satisfy simultaneously your obligations +under this License and any other pertinent obligations, then as a consequence +you may not distribute the Program at all. For example, if a patent license would +not permit royalty-free redistribution of the Program by all those who receive +copies directly or indirectly through you, then the only way you could satisfy +both it and this License would be to refrain entirely from distribution of the +Program. + +If any portion of this section is held invalid or unenforceable under any particular +circumstance, the balance of the section is intended to apply and the section as +a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other +property right claims or to contest validity of any such claims; this section has +the sole purpose of protecting the integrity of the free software distribution +system, which is implemented by public license practices. Many people have +made generous contributions to the wide range of software distributed through +that system in reliance on consistent application of that system; it is up to the +author/donor to decide if he or she is willing to distribute software through any +other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries +either by patents or by copyrighted interfaces, the original copyright holder who +places the Program under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is permitted +only in or among countries not thus excluded. In such case, this License +incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the +General Public License from time to time. Such new versions will be similar in +spirit to the present version, but may differ in detail to address new problems or +concerns. + +Each version is given a distinguishing version number. If the Program specifies a +version number of this License which applies to it and "any later version", you +have the option of following the terms and conditions either of that version or of +any later version published by the Free Software Foundation. If the Program does +not specify a version number of this License, you may choose any version ever +published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status of all +derivatives of our free software and of promoting the sharing and reuse of +software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS +NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE +COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR +IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, +YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED +TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY +WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS +PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM +(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY +OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + + +--------------------------------------------------------------------------- + +The Artistic License + +Preamble + +The intent of this document is to state the conditions under which a Package +may be copied, such that the Copyright Holder maintains some semblance of +artistic control over the development of the package, while giving the users of the +package the right to use and distribute the Package in a more-or-less customary +fashion, plus the right to make reasonable modifications. + +Definitions: + +- "Package" refers to the collection of files distributed by the Copyright + Holder, and derivatives of that collection of files created through textual + modification. +- "Standard Version" refers to such a Package if it has not been modified, + or has been modified in accordance with the wishes of the Copyright + Holder. +- "Copyright Holder" is whoever is named in the copyright or copyrights for + the package. +- "You" is you, if you're thinking about copying or distributing this Package. +- "Reasonable copying fee" is whatever you can justify on the basis of + media cost, duplication charges, time of people involved, and so on. (You + will not be required to justify it to the Copyright Holder, but only to the + computing community at large as a market that must bear the fee.) +- "Freely Available" means that no fee is charged for the item itself, though + there may be fees involved in handling the item. It also means that + recipients of the item may redistribute it under the same conditions they + received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you duplicate +all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications derived from +the Public Domain or from the Copyright Holder. A Package modified in such a +way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided +that you insert a prominent notice in each changed file stating how and when +you changed that file, and provided that you do at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said modifications + to Usenet or an equivalent medium, or placing the modifications on + a major archive site such as ftp.uu.net, or by allowing the + Copyright Holder to include your modifications in the Standard + Version of the Package. + + b) use the modified Package only within your corporation or + organization. + + c) rename any non-standard executables so the names do not + conflict with standard executables, which must also be provided, + and provide a separate manual page for each non-standard + executable that clearly documents how it differs from the Standard + Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or executable +form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library + files, together with instructions (in the manual page or equivalent) + on where to get the Standard Version. + + b) accompany the distribution with the machine-readable source of + the Package with your modifications. + + c) accompany any non-standard executables with their + corresponding Standard Version executables, giving the + non-standard executables non-standard names, and clearly + documenting the differences in manual pages (or equivalent), + together with instructions on where to get the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this Package. +You may charge any fee you choose for support of this Package. You may not +charge a fee for this Package itself. However, you may distribute this Package in +aggregate with other (possibly commercial) programs as part of a larger +(possibly commercial) software distribution provided that you do not advertise +this Package as a product of your own. + +6. The scripts and library files supplied as input to or produced as output from +the programs of this Package do not automatically fall under the copyright of this +Package, but belong to whomever generated them, and may be sold +commercially, and may be aggregated with this Package. + +7. C or perl subroutines supplied by you and linked into this Package shall not +be considered part of this Package. + +8. Aggregation of this Package with a commercial distribution is always permitted +provided that the use of this Package is embedded; that is, when no overt attempt +is made to make this Package's interfaces visible to the end user of the +commercial distribution. Such use shall not be construed as a distribution of +this Package. + +9. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +10. 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. + +The End + + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..03c99b4 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,19 @@ +Build.PL +Changes +LICENSE +MANIFEST +META.yml +Makefile.PL +README +Todo +lib/DateTime/Format/ISO8601.pm +lib/DateTime/Format/ISO8601.pod +t/00_distribution.t +t/01_load.t +t/02_examples.t +t/03_base_datetime.t +t/04_legacy_year.t +t/05_cut_off_year.t +t/06_bad_formats.t +t/99_pod.t +META.json diff --git a/META.json b/META.json new file mode 100644 index 0000000..ed996d6 --- /dev/null +++ b/META.json @@ -0,0 +1,47 @@ +{ + "abstract" : "Parses ISO8601 formats", + "author" : [ + "Joshua Hoblitt " + ], + "dynamic_config" : 1, + "generated_by" : "Module::Build version 0.38, CPAN::Meta::Converter version 2.112150", + "license" : [ + "perl_5" + ], + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : "2" + }, + "name" : "DateTime-Format-ISO8601", + "prereqs" : { + "configure" : { + "requires" : { + "Module::Build" : "0.38" + } + }, + "runtime" : { + "recommends" : { + "File::Find::Rule" : "0.24", + "Test::Distribution" : "1.22", + "Test::Pod" : "0.95" + }, + "requires" : { + "DateTime" : "0.18", + "DateTime::Format::Builder" : "0.77" + } + } + }, + "provides" : { + "DateTime::Format::ISO8601" : { + "file" : "lib/DateTime/Format/ISO8601.pm", + "version" : "0.08" + } + }, + "release_status" : "stable", + "resources" : { + "license" : [ + "http://dev.perl.org/licenses/" + ] + }, + "version" : "0.08" +} diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..7317b23 --- /dev/null +++ b/META.yml @@ -0,0 +1,28 @@ +--- +abstract: 'Parses ISO8601 formats' +author: + - 'Joshua Hoblitt ' +build_requires: {} +configure_requires: + Module::Build: 0.38 +dynamic_config: 1 +generated_by: 'Module::Build version 0.38, CPAN::Meta::Converter version 2.112150' +license: perl +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html + version: 1.4 +name: DateTime-Format-ISO8601 +provides: + DateTime::Format::ISO8601: + file: lib/DateTime/Format/ISO8601.pm + version: 0.08 +recommends: + File::Find::Rule: 0.24 + Test::Distribution: 1.22 + Test::Pod: 0.95 +requires: + DateTime: 0.18 + DateTime::Format::Builder: 0.77 +resources: + license: http://dev.perl.org/licenses/ +version: 0.08 diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..ff5123e --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,34 @@ +# Note: this file was auto-generated by Module::Build::Compat version 0.3800 + + unless (eval "use Module::Build::Compat 0.02; 1" ) { + print "This module requires Module::Build to install itself.\n"; + + require ExtUtils::MakeMaker; + my $yn = ExtUtils::MakeMaker::prompt + (' Install Module::Build now from CPAN?', 'y'); + + unless ($yn =~ /^y/i) { + die " *** Cannot install without Module::Build. Exiting ...\n"; + } + + require Cwd; + require File::Spec; + require CPAN; + + # Save this 'cause CPAN will chdir all over the place. + my $cwd = Cwd::cwd(); + + CPAN::Shell->install('Module::Build::Compat'); + CPAN::Shell->expand("Module", "Module::Build::Compat")->uptodate + or die "Couldn't install Module::Build, giving up.\n"; + + chdir $cwd or die "Cannot chdir() back to $cwd: $!"; + } + eval "use Module::Build::Compat 0.02; 1" or die $@; + + Module::Build::Compat->run_build_pl(args => \@ARGV); + my $build_script = 'Build'; + $build_script .= '.com' if $^O eq 'VMS'; + exit(0) unless(-e $build_script); # cpantesters convention + require Module::Build; + Module::Build::Compat->write_makefile(build_class => 'Module::Build'); diff --git a/README b/README new file mode 100644 index 0000000..90f841a --- /dev/null +++ b/README @@ -0,0 +1,14 @@ +DateTime::Format::ISO8601 +======================== + +Parses almost all ISO8601 date and time formats. +ISO8601 time-intervals will be supported in a later release. + +INSTALLATION + +To install this module type the following: + +perl Build.PL +./Build +./Build test +./Build install diff --git a/Todo b/Todo new file mode 100644 index 0000000..9c3fa17 --- /dev/null +++ b/Todo @@ -0,0 +1,14 @@ +TODO list for Perl module DateTime::Format::ISO8601 + +- more docs +- full time only and date only parsers +- group tags +- better expanded format support +- complete 5.4.2 support + +Roadmap: + +0.05 - more tests + - complete 5.4.2 support (recursive DT::F::Builder) +0.06 - start ISO8601 section 5.5 +0.07 - finish section 5.5 when DT::Duration::Set is finished diff --git a/lib/DateTime/Format/ISO8601.pm b/lib/DateTime/Format/ISO8601.pm new file mode 100644 index 0000000..6b469cd --- /dev/null +++ b/lib/DateTime/Format/ISO8601.pm @@ -0,0 +1,992 @@ +# Copyright (C) 2003-2005 Joshua Hoblitt + +package DateTime::Format::ISO8601; + +use strict; +use warnings; + +use vars qw( $VERSION ); +$VERSION = '0.08'; + +use Carp qw( croak ); +use DateTime; +use DateTime::Format::Builder; +use Params::Validate qw( validate validate_pos BOOLEAN OBJECT SCALAR ); + +{ + my $default_legacy_year; + sub DefaultLegacyYear { + my $class = shift; + + ( $default_legacy_year ) = validate_pos( @_, + { + type => BOOLEAN, + callbacks => { + 'is 0, 1, or undef' => + sub { ! defined( $_[0] ) || $_[0] == 0 || $_[0] == 1 }, + }, + } + ) if @_; + + return $default_legacy_year; + } +} +__PACKAGE__->DefaultLegacyYear( 1 ); + +{ + my $default_cut_off_year; + sub DefaultCutOffYear { + my $class = shift; + + ( $default_cut_off_year ) = validate_pos( @_, + { + type => SCALAR, + callbacks => { + 'is between 0 and 99' => + sub { $_[0] >= 0 && $_[0] <= 99 }, + }, + } + ) if @_; + + return $default_cut_off_year; + } +} +# the same default value as DT::F::Mail +__PACKAGE__->DefaultCutOffYear( 49 ); + +sub new { + my( $class ) = shift; + + my %args = validate( @_, + { + base_datetime => { + type => OBJECT, + can => 'utc_rd_values', + optional => 1, + }, + legacy_year => { + type => BOOLEAN, + default => $class->DefaultLegacyYear, + callbacks => { + 'is 0, 1, or undef' => + sub { ! defined( $_[0] ) || $_[0] == 0 || $_[0] == 1 }, + }, + }, + cut_off_year => { + type => SCALAR, + default => $class->DefaultCutOffYear, + callbacks => { + 'is between 0 and 99' => + sub { $_[0] >= 0 && $_[0] <= 99 }, + }, + }, + } + ); + + $class = ref( $class ) || $class; + + my $self = bless( \%args, $class ); + + if ( $args{ base_datetime } ) { + $self->set_base_datetime( object => $args{ base_datetime } ); + } + + return( $self ); +} + +# lifted from DateTime +sub clone { bless { %{ $_[0] } }, ref $_[0] } + +sub base_datetime { $_[0]->{ base_datetime } } + +sub set_base_datetime { + my $self = shift; + + my %args = validate( @_, + { + object => { + type => OBJECT, + can => 'utc_rd_values', + }, + } + ); + + # ISO8601 only allows years 0 to 9999 + # this implimentation ignores the needs of expanded formats + my $dt = DateTime->from_object( object => $args{ object } ); + my $lower_bound = DateTime->new( year => 0 ); + my $upper_bound = DateTime->new( year => 10000 ); + + if ( $dt < $lower_bound ) { + croak "base_datetime must be greater then or equal to ", + $lower_bound->iso8601; + } + if ( $dt >= $upper_bound ) { + croak "base_datetime must be less then ", $upper_bound->iso8601; + } + + $self->{ base_datetime } = $dt; + + return $self; +} + +sub legacy_year { $_[0]->{ legacy_year } } + +sub set_legacy_year { + my $self = shift; + + my @args = validate_pos( @_, + { + type => BOOLEAN, + callbacks => { + 'is 0, 1, or undef' => + sub { ! defined( $_[0] ) || $_[0] == 0 || $_[0] == 1 }, + }, + } + ); + + $self->{ legacy_year } = $args[0]; + + return $self; +} + +sub cut_off_year { $_[0]->{ cut_off_year } } + +sub set_cut_off_year { + my $self = shift; + + my @args = validate_pos( @_, + { + type => SCALAR, + callbacks => { + 'is between 0 and 99' => + sub { $_[0] >= 0 && $_[0] <= 99 }, + }, + } + ); + + $self->{ cut_off_year } = $args[0]; + + return $self; +} + +DateTime::Format::Builder->create_class( + parsers => { + parse_datetime => [ + { + #YYYYMMDD 19850412 + length => 8, + regex => qr/^ (\d{4}) (\d\d) (\d\d) $/x, + params => [ qw( year month day ) ], + }, + { + # uncombined with above because + #regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d) $/x, + # was matching 152746-05 + + #YYYY-MM-DD 1985-04-12 + length => 10, + regex => qr/^ (\d{4}) - (\d\d) - (\d\d) $/x, + params => [ qw( year month day ) ], + }, + { + #YYYY-MM 1985-04 + length => 7, + regex => qr/^ (\d{4}) - (\d\d) $/x, + params => [ qw( year month ) ], + }, + { + #YYYY 1985 + length => 4, + regex => qr/^ (\d{4}) $/x, + params => [ qw( year ) ], + }, + { + #YY 19 (century) + length => 2, + regex => qr/^ (\d\d) $/x, + params => [ qw( year ) ], + postprocess => \&_normalize_century, + }, + { + #YYMMDD 850412 + #YY-MM-DD 85-04-12 + length => [ qw( 6 8 ) ], + regex => qr/^ (\d\d) -?? (\d\d) -?? (\d\d) $/x, + params => [ qw( year month day ) ], + postprocess => \&_fix_2_digit_year, + }, + { + #-YYMM -8504 + #-YY-MM -85-04 + length => [ qw( 5 6 ) ], + regex => qr/^ - (\d\d) -?? (\d\d) $/x, + params => [ qw( year month ) ], + postprocess => \&_fix_2_digit_year, + }, + { + #-YY -85 + length => 3, + regex => qr/^ - (\d\d) $/x, + params => [ qw( year ) ], + postprocess => \&_fix_2_digit_year, + }, + { + #--MMDD --0412 + #--MM-DD --04-12 + length => [ qw( 6 7 ) ], + regex => qr/^ -- (\d\d) -?? (\d\d) $/x, + params => [ qw( month day ) ], + postprocess => \&_add_year, + }, + { + #--MM --04 + length => 4, + regex => qr/^ -- (\d\d) $/x, + params => [ qw( month ) ], + postprocess => \&_add_year, + }, + { + #---DD ---12 + length => 5, + regex => qr/^ --- (\d\d) $/x, + params => [ qw( day ) ], + postprocess => [ \&_add_year, \&_add_month ], + }, + { + #+[YY]YYYYMMDD +0019850412 + #+[YY]YYYY-MM-DD +001985-04-12 + length => [ qw( 11 13 ) ], + regex => qr/^ \+ (\d{6}) -?? (\d\d) -?? (\d\d) $/x, + params => [ qw( year month day ) ], + }, + { + #+[YY]YYYY-MM +001985-04 + length => 10, + regex => qr/^ \+ (\d{6}) - (\d\d) $/x, + params => [ qw( year month ) ], + }, + { + #+[YY]YYYY +001985 + length => 7, + regex => qr/^ \+ (\d{6}) $/x, + params => [ qw( year ) ], + }, + { + #+[YY]YY +0019 (century) + length => 5, + regex => qr/^ \+ (\d{4}) $/x, + params => [ qw( year ) ], + postprocess => \&_normalize_century, + }, + { + #YYYYDDD 1985102 + #YYYY-DDD 1985-102 + length => [ qw( 7 8 ) ], + regex => qr/^ (\d{4}) -?? (\d{3}) $/x, + params => [ qw( year day_of_year ) ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #YYDDD 85102 + #YY-DDD 85-102 + length => [ qw( 5 6 ) ], + regex => qr/^ (\d\d) -?? (\d{3}) $/x, + params => [ qw( year day_of_year ) ], + postprocess => [ \&_fix_2_digit_year ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #-DDD -102 + length => 4, + regex => qr/^ - (\d{3}) $/x, + params => [ qw( day_of_year ) ], + postprocess => [ \&_add_year ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #+[YY]YYYYDDD +001985102 + #+[YY]YYYY-DDD +001985-102 + length => [ qw( 10 11 ) ], + regex => qr/^ \+ (\d{6}) -?? (\d{3}) $/x, + params => [ qw( year day_of_year ) ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #YYYYWwwD 1985W155 + #YYYY-Www-D 1985-W15-5 + length => [ qw( 8 10 ) ], + regex => qr/^ (\d{4}) -?? W (\d\d) -?? (\d) $/x, + params => [ qw( year week day_of_year ) ], + postprocess => [ \&_normalize_week ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #YYYYWww 1985W15 + #YYYY-Www 1985-W15 + length => [ qw( 7 8 ) ], + regex => qr/^ (\d{4}) -?? W (\d\d) $/x, + params => [ qw( year week ) ], + postprocess => [ \&_normalize_week ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #YYWwwD 85W155 + #YY-Www-D 85-W15-5 + length => [ qw( 6 8 ) ], + regex => qr/^ (\d\d) -?? W (\d\d) -?? (\d) $/x, + params => [ qw( year week day_of_year ) ], + postprocess => [ \&_fix_2_digit_year, \&_normalize_week ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #YYWww 85W15 + #YY-Www 85-W15 + length => [ qw( 5 6 ) ], + regex => qr/^ (\d\d) -?? W (\d\d) $/x, + params => [ qw( year week ) ], + postprocess => [ \&_fix_2_digit_year, \&_normalize_week ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #-YWwwD -5W155 + #-Y-Www-D -5-W15-5 + length => [ qw( 6 8 ) ], + regex => qr/^ - (\d) -?? W (\d\d) -?? (\d) $/x, + params => [ qw( year week day_of_year ) ], + postprocess => [ \&_fix_1_digit_year, \&_normalize_week ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #-YWww -5W15 + #-Y-Www -5-W15 + length => [ qw( 5 6 ) ], + regex => qr/^ - (\d) -?? W (\d\d) $/x, + params => [ qw( year week ) ], + postprocess => [ \&_fix_1_digit_year, \&_normalize_week ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #-WwwD -W155 + #-Www-D -W15-5 + length => [ qw( 5 6 ) ], + regex => qr/^ - W (\d\d) -?? (\d) $/x, + params => [ qw( week day_of_year ) ], + postprocess => [ \&_add_year, \&_normalize_week ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #-Www -W15 + length => 4, + regex => qr/^ - W (\d\d) $/x, + params => [ qw( week ) ], + postprocess => [ \&_add_year, \&_normalize_week ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #-W-D -W-5 + length => 4, + regex => qr/^ - W - (\d) $/x, + params => [ qw( day_of_year ) ], + postprocess => [ + \&_add_year, + \&_add_week, + \&_normalize_week, + ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #+[YY]YYYYWwwD +001985W155 + #+[YY]YYYY-Www-D +001985-W15-5 + length => [ qw( 11 13 ) ], + regex => qr/^ \+ (\d{6}) -?? W (\d\d) -?? (\d) $/x, + params => [ qw( year week day_of_year ) ], + postprocess => [ \&_normalize_week ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #+[YY]YYYYWww +001985W15 + #+[YY]YYYY-Www +001985-W15 + length => [ qw( 10 11 ) ], + regex => qr/^ \+ (\d{6}) -?? W (\d\d) $/x, + params => [ qw( year week ) ], + postprocess => [ \&_normalize_week ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + { + #hhmmss 232050 - skipped + #hh:mm:ss 23:20:50 + length => [ qw( 8 9 ) ], + regex => qr/^ T?? (\d\d) : (\d\d) : (\d\d) $/x, + params => [ qw( hour minute second) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day + ], + }, + #hhmm 2320 - skipped + #hh 23 -skipped + { + #hh:mm 23:20 + length => [ qw( 4 5 6 ) ], + regex => qr/^ T?? (\d\d) :?? (\d\d) $/x, + params => [ qw( hour minute ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day + ], + }, + { + #hhmmss,ss 232050,5 + #hh:mm:ss,ss 23:20:50,5 + regex => qr/^ T?? (\d\d) :?? (\d\d) :?? (\d\d) [\.,] (\d+) $/x, + params => [ qw( hour minute second nanosecond) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_fractional_second + ], + }, + { + #hhmm,mm 2320,8 + #hh:mm,mm 23:20,8 + regex => qr/^ T?? (\d\d) :?? (\d\d) [\.,] (\d+) $/x, + params => [ qw( hour minute second ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_fractional_minute + ], + }, + { + #hh,hh 23,3 + regex => qr/^ T?? (\d\d) [\.,] (\d+) $/x, + params => [ qw( hour minute ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_fractional_hour + ], + }, + { + #-mmss -2050 - skipped + #-mm:ss -20:50 + length => 6, + regex => qr/^ - (\d\d) : (\d\d) $/x, + params => [ qw( minute second ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_add_hour + ], + }, + #-mm -20 - skipped + #--ss --50 - skipped + { + #-mmss,s -2050,5 + #-mm:ss,s -20:50,5 + regex => qr/^ - (\d\d) :?? (\d\d) [\.,] (\d+) $/x, + params => [ qw( minute second nanosecond ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_add_hour, + \&_fractional_second + ], + }, + { + #-mm,m -20,8 + regex => qr/^ - (\d\d) [\.,] (\d+) $/x, + params => [ qw( minute second ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_add_hour, + \&_fractional_minute + ], + }, + { + #--ss,s --50,5 + regex => qr/^ -- (\d\d) [\.,] (\d+) $/x, + params => [ qw( second nanosecond) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_add_hour, + \&_add_minute, + \&_fractional_second, + ], + }, + { + #hhmmssZ 232030Z + #hh:mm:ssZ 23:20:30Z + length => [ qw( 7 8 9 10 ) ], + regex => qr/^ T?? (\d\d) :?? (\d\d) :?? (\d\d) Z $/x, + params => [ qw( hour minute second ) ], + extra => { time_zone => 'UTC' }, + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + ], + }, + + { + #hhmmss.ssZ 232030.5Z + #hh:mm:ss.ssZ 23:20:30.5Z + regex => qr/^ T?? (\d\d) :?? (\d\d) :?? (\d\d) [\.,] (\d+) Z $/x, + params => [ qw( hour minute second nanosecond) ], + extra => { time_zone => 'UTC' }, + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_fractional_second + ], + }, + + { + #hhmmZ 2320Z + #hh:mmZ 23:20Z + length => [ qw( 5 6 7 ) ], + regex => qr/^ T?? (\d\d) :?? (\d\d) Z $/x, + params => [ qw( hour minute ) ], + extra => { time_zone => 'UTC' }, + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + ], + }, + { + #hhZ 23Z + length => [ qw( 3 4 ) ], + regex => qr/^ T?? (\d\d) Z $/x, + params => [ qw( hour ) ], + extra => { time_zone => 'UTC' }, + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + ], + }, + { + #hhmmss[+-]hhmm 152746+0100 152746-0500 + #hh:mm:ss[+-]hh:mm 15:27:46+01:00 15:27:46-05:00 + length => [ qw( 11 12 14 15 ) ], + regex => qr/^ T?? (\d\d) :?? (\d\d) :?? (\d\d) + ([+-] \d\d :?? \d\d) $/x, + params => [ qw( hour minute second time_zone ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_normalize_offset, + ], + }, + { + #hhmmss.ss[+-]hhmm 152746.5+0100 152746.5-0500 + #hh:mm:ss.ss[+-]hh:mm 15:27:46.5+01:00 15:27:46.5-05:00 + regex => qr/^ T?? (\d\d) :?? (\d\d) :?? (\d\d) [\.,] (\d+) + ([+-] \d\d :?? \d\d) $/x, + params => [ qw( hour minute second nanosecond time_zone ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_fractional_second, + \&_normalize_offset, + ], + }, + + { + #hhmmss[+-]hh 152746+01 152746-05 + #hh:mm:ss[+-]hh 15:27:46+01 15:27:46-05 + length => [ qw( 9 10 11 12 ) ], + regex => qr/^ T?? (\d\d) :?? (\d\d) :?? (\d\d) + ([+-] \d\d) $/x, + params => [ qw( hour minute second time_zone ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_normalize_offset, + ], + }, + { + #YYYYMMDDThhmmss 19850412T101530 + #YYYY-MM-DDThh:mm:ss 1985-04-12T10:15:30 + length => [ qw( 15 19 ) ], + regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d) + T (\d\d) :?? (\d\d) :?? (\d\d) $/x, + params => [ qw( year month day hour minute second ) ], + extra => { time_zone => 'floating' }, + }, + { + #YYYYMMDDThhmmss.ss 19850412T101530.123 + #YYYY-MM-DDThh:mm:ss.ss 1985-04-12T10:15:30.123 + regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d) + T (\d\d) :?? (\d\d) :?? (\d\d) [\.,] (\d+) $/x, + params => [ qw( year month day hour minute second nanosecond ) ], + extra => { time_zone => 'floating' }, + postprocess => [ + \&_fractional_second, + ], + }, + { + #YYYYMMDDThhmmssZ 19850412T101530Z + #YYYY-MM-DDThh:mm:ssZ 1985-04-12T10:15:30Z + length => [ qw( 16 20 ) ], + regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d) + T (\d\d) :?? (\d\d) :?? (\d\d) Z $/x, + params => [ qw( year month day hour minute second ) ], + extra => { time_zone => 'UTC' }, + }, + { + #YYYYMMDDThhmmss.ssZ 19850412T101530.5Z 20041020T101530.5Z + #YYYY-MM-DDThh:mm:ss.ssZ 1985-04-12T10:15:30.5Z 1985-04-12T10:15:30.5Z + regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d) + T?? (\d\d) :?? (\d\d) :?? (\d\d) [\.,] (\d+) + Z$/x, + params => [ qw( year month day hour minute second nanosecond ) ], + extra => { time_zone => 'UTC' }, + postprocess => [ + \&_fractional_second, + ], + }, + + { + #YYYYMMDDThhmmss[+-]hhmm 19850412T101530+0400 + #YYYY-MM-DDThh:mm:ss[+-]hh:mm 1985-04-12T10:15:30+04:00 + length => [ qw( 20 25 ) ], + regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d) + T (\d\d) :?? (\d\d) :?? (\d\d) ([+-] \d\d :?? \d\d) $/x, + params => [ qw( year month day hour minute second time_zone ) ], + postprocess => \&_normalize_offset, + }, + { + #YYYYMMDDThhmmss.ss[+-]hhmm 19850412T101530.5+0100 20041020T101530.5-0500 + regex => qr/^ (\d{4}) (\d\d) (\d\d) + T?? (\d\d) (\d\d) (\d\d) [\.,] (\d+) + ([+-] \d\d \d\d) $/x, + params => [ qw( year month day hour minute second nanosecond time_zone ) ], + postprocess => [ + \&_fractional_second, + \&_normalize_offset, + ], + }, + { + #YYYY-MM-DDThh:mm:ss.ss[+-]hh:mm 1985-04-12T10:15:30.5+01:00 1985-04-12T10:15:30.5-05:00 + regex => qr/^ (\d{4}) - (\d\d) - (\d\d) + T?? (\d\d) : (\d\d) : (\d\d) [\.,] (\d+) + ([+-] \d\d : \d\d) $/x, + params => [ qw( year month day hour minute second nanosecond time_zone ) ], + postprocess => [ + \&_fractional_second, + \&_normalize_offset, + ], + }, + + { + #YYYYMMDDThhmmss[+-]hh 19850412T101530+04 + #YYYY-MM-DDThh:mm:ss[+-]hh 1985-04-12T10:15:30+04 + length => [ qw( 18 22 ) ], + regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d) + T (\d\d) :?? (\d\d) :?? (\d\d) ([+-] \d\d) $/x, + params => [ qw( year month day hour minute second time_zone ) ], + postprocess => \&_normalize_offset, + }, + { + #YYYYMMDDThhmm 19850412T1015 + #YYYY-MM-DDThh:mm 1985-04-12T10:15 + length => [ qw( 13 16 ) ], + regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d) + T (\d\d) :?? (\d\d) $/x, + params => [ qw( year month day hour minute ) ], + extra => { time_zone => 'floating' }, + }, + { + #YYYYDDDThhmmZ 1985102T1015Z + #YYYY-DDDThh:mmZ 1985-102T10:15Z + length => [ qw( 13 15 ) ], + regex => qr/^ (\d{4}) -?? (\d{3}) T + (\d\d) :?? (\d\d) Z $/x, + params => [ qw( year day_of_year hour minute ) ], + extra => { time_zone => 'UTC' }, + constructor => [ 'DateTime', 'from_day_of_year' ], + + }, + { + #YYYYWwwDThhmm[+-]hhmm 1985W155T1015+0400 + #YYYY-Www-DThh:mm[+-]hh 1985-W15-5T10:15+04 + length => [ qw( 18 19 ) ], + regex => qr/^ (\d{4}) -?? W (\d\d) -?? (\d) + T (\d\d) :?? (\d\d) ([+-] \d{2,4}) $/x, + params => [ qw( year week day_of_year hour minute time_zone) ], + postprocess => [ \&_normalize_week, \&_normalize_offset ], + constructor => [ 'DateTime', 'from_day_of_year' ], + }, + ], + parse_time => [ + { + #hhmmss 232050 + length => [ qw( 6 7 ) ], + regex => qr/^ T?? (\d\d) (\d\d) (\d\d) $/x, + params => [ qw( hour minute second ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + ], + }, + { + #hhmm 2320 + length => [ qw( 4 5 ) ], + regex => qr/^ T?? (\d\d) (\d\d) $/x, + params => [ qw( hour minute ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + ], + }, + { + #hh 23 + length => [ qw( 2 3 ) ], + regex => qr/^ T?? (\d\d) $/x, + params => [ qw( hour ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + ], + }, + { + #-mmss -2050 + length => 5, + regex => qr/^ - (\d\d) (\d\d) $/x, + params => [ qw( minute second ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_add_hour, + ], + }, + { + #-mm -20 + length => 3, + regex => qr/^ - (\d\d) $/x, + params => [ qw( minute ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_add_hour, + ], + }, + { + #--ss --50 + length => 4, + regex => qr/^ -- (\d\d) $/x, + params => [ qw( second ) ], + postprocess => [ + \&_add_year, + \&_add_month, + \&_add_day, + \&_add_hour, + \&_add_minute, + ], + }, + ], + } +); + +sub _fix_1_digit_year { + my %p = @_; + + no strict 'refs'; + my $year = ( $p{ self }{ base_datetime } || DateTime->now )->year; + use strict; + + $year =~ s/.$//; + $p{ parsed }{ year } = $year . $p{ parsed }{ year }; + + return 1; +} + +sub _fix_2_digit_year { + my %p = @_; + + # this is a mess because of the need to support parse_* being called + # as a class method + no strict 'refs'; + if ( exists $p{ self }{ legacy_year } ) { + if ( $p{ self }{ legacy_year } ) { + my $cutoff = exists $p{ self }{ cut_off_year } + ? $p{ self }{ cut_off_year } : $p{ self }->DefaultCutOffYear; + $p{ parsed }{ year } += $p{ parsed }{ year } > $cutoff ? 1900 : 2000; + } else { + my $century = ( $p{ self }{ base_datetime } || DateTime->now )->strftime( '%C' ); + $p{ parsed }{ year } += $century * 100; + } + } else { + my $cutoff = exists $p{ self }{ cut_off_year } + ? $p{ self }{ cut_off_year } : $p{ self }->DefaultCutOffYear; + $p{ parsed }{ year } += $p{ parsed }{ year } > $cutoff ? 1900 : 2000; + } + use strict; + + return 1; +} + +sub _add_minute { + my %p = @_; + + no strict 'refs'; + $p{ parsed }{ minute } = ( $p{ self }{ base_datetime } || DateTime->now )->minute; + use strict; + + return 1; +} + +sub _add_hour { + my %p = @_; + + no strict 'refs'; + $p{ parsed }{ hour } = ( $p{ self }{ base_datetime } || DateTime->now )->hour; + use strict; + + return 1; +} + +sub _add_day { + my %p = @_; + + no strict 'refs'; + $p{ parsed }{ day } = ( $p{ self }{ base_datetime } || DateTime->now )->day; + use strict; + + return 1; +} + +sub _add_week { + my %p = @_; + + no strict 'refs'; + $p{ parsed }{ week } = ( $p{ self }{ base_datetime } || DateTime->now )->week; + use strict; + + return 1; +} + +sub _add_month { + my %p = @_; + + no strict 'refs'; + $p{ parsed }{ month } = ( $p{ self }{ base_datetime } || DateTime->now )->month; + use strict; + + return 1; +} + +sub _add_year { + my %p = @_; + + no strict 'refs'; + $p{ parsed }{ year } = ( $p{ self }{ base_datetime } || DateTime->now )->year; + use strict; + + return 1; +} + +sub _fractional_second { + my %p = @_; + + $p{ parsed }{ nanosecond } = ".$p{ parsed }{ nanosecond }" * 10**9; + + return 1; +} + +sub _fractional_minute { + my %p = @_; + + $p{ parsed }{ second } = ".$p{ parsed }{ second }" * 60; + + return 1; +} + +sub _fractional_hour { + my %p = @_; + + $p{ parsed }{ minute } = ".$p{ parsed }{ minute }" * 60; + + return 1; +} + +sub _normalize_offset { + my %p = @_; + + $p{ parsed }{ time_zone } =~ s/://; + + if( length $p{ parsed }{ time_zone } == 3 ) { + $p{ parsed }{ time_zone } .= '00'; + } + + return 1; +} + +sub _normalize_week { + my %p = @_; + + # from section 4.3.2.2 + # "A calendar week is identified within a calendar year by the calendar + # week number. This is its ordinal position within the year, applying the + # rule that the first calendar week of a year is the one that includes the + # first Thursday of that year and that the last calendar week of a + # calendar year is the week immediately preceding the first calendar week + # of the next calendar year." + + # this make it oh so fun to covert an ISO week number to a count of days + + my $dt = DateTime->new( + year => $p{ parsed }{ year }, + ); + + if ( $dt->week_number == 1 ) { + $p{ parsed }{ week } -= 1; + } + + $p{ parsed }{ week } *= 7; + + if( defined $p{ parsed }{ day_of_year } ) { + $p{ parsed }{ week } -= $dt->day_of_week -1; + } + + $p{ parsed }{ day_of_year } += $p{ parsed }{ week }; + + delete $p{ parsed }{ week }; + + return 1; +} + +sub _normalize_century { + my %p = @_; + + $p{ parsed }{ year } .= '01'; + + return 1; +} + +1; + +__END__ diff --git a/lib/DateTime/Format/ISO8601.pod b/lib/DateTime/Format/ISO8601.pod new file mode 100644 index 0000000..8cd3c52 --- /dev/null +++ b/lib/DateTime/Format/ISO8601.pod @@ -0,0 +1,431 @@ +=pod + +=head1 NAME + +DateTime::Format::ISO8601 - Parses ISO8601 formats + +=head1 SYNOPSIS + + use DateTime::Format::ISO8601; + + my $dt = DateTime::Format::ISO8601->parse_datetime( $str ); + my $dt = DateTime::Format::ISO8601->parse_time( $str ); + + or + + my $iso8601 = DateTime::Format::ISO8601->new; + my $dt = $iso8601->parse_datetime( $str ); + my $dt = $iso8601->parse_time( $str ); + +=head1 DESCRIPTION + +Parses almost all ISO8601 date and time formats. +ISO8601 time-intervals will be supported in a later release. + +=head1 USAGE + +=head2 Import Parameters + +This module accepts no arguments to it's C method. + +=head2 Methods + +=head3 Constructors + +=over 4 + +=item * new( ... ) + +Accepts an optional hash. + + my $iso8601 = DateTime::Format::ISO8601->new( + base_datetime => $dt, + cut_off_year => 42, + legacy_year => 1, + ); + +=over 4 + +=item * base_datetime + +A C object that will be used to fill in missing information from +incomplete date/time formats. + +This key is optional. + +=item * cut_off_year + +A integer representing the cut-off point between interpreting 2-digits years +as 19xx or 20xx. + + 2-digit years < legacy_year will be interpreted as 20xx + 2-digit years >= legacy_year will be untreated as 19xx + +This key defaults to the value of C. + +=item * legacy_year + +A boolean value controlling if a 2-digit year is interpreted as being in the +current century (unless a C is set) or if C +should be used to place the year in either 20xx or 19xx. + +This key defaults to the value of C. + +=back + +=item * clone + +Returns a replica of the given object. + +=back + +=head3 Object Methods + +=over 4 + +=item * base_datetime + +Returns a C object if a C has been set. + +=item * set_base_datetime( object => $object ) + +Accepts a C object that will be used to fill in missing information +from incomplete date/time formats. + +=item * cut_off_year + +Returns a integer representing the cut-off point between interpreting +2-digits years as 19xx or 20xx. + +=item * set_cut_off_year( $int ) + +Accepts a integer representing the cut-off point between interpreting +2-digits years as 19xx or 20xx. + + 2-digit years < legacy_year will be interpreted as 20xx + 2-digit years >= legacy_year will be interpreted as 19xx + +=item * legacy_year + +Returns a boolean value indicating the 2-digit year handling behavior. + +=item * set_legacy_year( $bool ) + +Accepts a boolean value controlling if a 2-digit year is interpreted as being +in the current century (unless a C is set) or if +C should be used to place the year in either 20xx or 19xx. + +=back + +=head3 Class Methods + +=over 4 + +=item * DefaultCutOffYear( $int ) + +Accepts a integer representing the cut-off point for 2-digit years when +calling C as class methods and the default value for C +when creating objects. If called with no parameters this method will return +the default value for C. + +=item * DefaultLegacyYear( $bool ) + +Accepts a boolean value controlling the legacy year behavior when calling +C as class methods and the default value for C when +creating objects. If called with no parameters this method will return the +default value for C. + +=back + +=head3 Parser(s) + +These may be called as either class or object methods. + +=over 4 + +=item * parse_datetime + +=item * parse_time + +Please see the L section. + +=back + +=head1 FORMATS + +There are 6 string that can match against date only or time only formats. +The C method will attempt to match these ambiguous strings +against date only formats. If you want to match against the time only +formats see the C method. + +=head2 Conventions + +=over 4 + +=item * Expanded ISO8601 + +These formats are supported with exactly 6 digits for the year. +Support for a variable number of digits will be in a later release. + +=item * Precision + +If a format doesn't include a year all larger time unit up to and including +the year are filled in using the current date/time or [if set] the +C object. + +=item * Fractional time + +There is no limit on the expressed precision. + +=back + +=head2 Supported via parse_datetime + +The supported formats are listed by the section of ISO 8601:2000(E) in +which they appear. + +=head3 5.2 Dates + +=head3 5.2.1.1 + + YYYYMMDD + YYYY-MM-DD + +=head3 5.2.1.2 + + YYYY-MM + YYYY + YY + +=head3 5.2.1.3 + + YYMMDD + YY-MM-DD + -YYMM + -YY-MM + -YY + --MMDD + --MM-DD + --MM + ---DD + +=head3 5.2.1.4 + + +[YY]YYYYMMDD + +[YY]YYYY-MM-DD + +[YY]YYYY-MM + +[YY]YYYY + +[YY]YY + +=head3 5.2.2.1 + + YYYYDDD + YYYY-DDD + +=head3 5.2.2.2 + + YYDDD + YY-DDD + -DDD + +=head3 5.2.2.3 + + +[YY]YYYYDDD + +[YY]YYYY-DDD + +=head3 5.3.2.1 + + YYYYWwwD + YYYY-Www-D + +=head3 5.2.3.2 + + YYYYWww + YYYY-Www + YYWwwD + YY-Www-D + YYWww + YY-Www + -YWwwD + -Y-Www-D + -YWww + -Y-Www + -WwwD + -Www-D + -Www + -W-D + +=head3 5.2.3.4 + + +[YY]YYYYWwwD + +[YY]YYYY-Www-D + +[YY]YYYYWww + +[YY]YYYY-Www + +=head3 5.3 Time of Day + +=head3 5.3.1.1 - 5.3.1.3 + +optionally prefixed with 'T' + +=head3 5.3.1.1 + + hh:mm:ss + +=head3 5.3.1.2 + + hh:mm + +=head3 5.3.1.3 - 5.3.1.4 + +fractional (decimal) separator maybe either ',' or '.' + +=head3 5.3.1.3 + + hhmmss,ss + hh:mm:ss,ss + hhmm,mm + hh:mm,mm + hh,hh + +=head3 5.3.1.4 + + -mm:ss + -mmss,s + -mm:ss,s + -mm,m + --ss,s + +=head3 5.3.3 - 5.3.4.2 + +optionally prefixed with 'T' + +=head3 5.3.3 + + hhmmssZ + hh:mm:ssZ + hhmmZ + hh:mmZ + hhZ + hhmmss.ssZ + hh:mm:ss.ssZ + +=head3 5.3.4.2 + + hhmmss[+-]hhmm + hh:mm:ss[+-]hh:mm + hhmmss[+-]hh + hh:mm:ss[+-]hh + hhmmss.ss[+-]hhmm + hh:mm:ss.ss[+-]hh:mm + +=head3 5.4 Combinations of date and time of day + +=head3 5.4.1 + + YYYYMMDDThhmmss + YYYY-MM-DDThh:mm:ss + YYYYMMDDThhmmssZ + YYYY-MM-DDThh:mm:ssZ + YYYYMMDDThhmmss[+-]hhmm + YYYY-MM-DDThh:mm:ss[+-]hh:mm + YYYYMMDDThhmmss[+-]hh + YYYY-MM-DDThh:mm:ss[+-]hh + +=head3 5.4.2 + + YYYYMMDDThhmmss.ss + YYYY-MM-DDThh:mm:ss.ss + YYYYMMDDThhmmss.ss[+-]hhmm + YYYY-MM-DDThh:mm:ss.ss[+-]hh:mm + +Support for this section is not complete. + + YYYYMMDDThhmm + YYYY-MM-DDThh:mm + YYYYDDDThhmmZ + YYYY-DDDThh:mmZ + YYYYWwwDThhmm[+-]hhmm + YYYY-Www-DThh:mm[+-]hh + +=head3 5.5 Time-Intervals + +Will be supported in a later release. + +=head2 Supported via parse_time + +=head3 5.3.1.1 - 5.3.1.3 + +optionally prefixed with 'T' + +=head3 5.3.1.1 + + hhmmss + +=head3 5.3.1.2 + + hhmm + hh + +=head3 5.3.1.4 + + -mmss + -mm + --ss + +=head1 STANDARDS DOCUMENT + +=head2 Title + + ISO8601:2000(E) + Data elements and interchange formats - information exchange - + Representation of dates and times + Second edition 2000-12-15 + +=head2 Reference Number + + ISO/TC 154 N 362 + +=head1 CREDITS + +Iain 'Spoon' Truskett (SPOON) who wrote L. +That has grown into I Katana> +of date and time parsing. This module was inspired by and conceived +in honor of Iain's work. + +Tom Phoenix (PHOENIX) and PDX.pm for helping me solve the ISO week conversion +bug. Not by fixing the code but motivation me to fix it so I could +participate in a game of C. + +Jonathan Leffler (JOHNL) for reporting a test bug. + +Kelly McCauley for a patch to add 8 missing formats. + +Alasdair Allan (AALLAN) for complaining about excessive test execution time. + +Everyone at the DateTime C. + +=head1 SUPPORT + +Support for this module is provided via the email list. +See L for more details. + +=head1 AUTHOR + +Joshua Hoblitt + +=head1 COPYRIGHT + +Copyright (c) 2003-2005 Joshua Hoblitt. All rights reserved. This program is +free software; you can redistribute it and/or modify it under the same terms as +Perl itself. + +The full text of the licenses can be found in the I file included with +this module, or in L and L as supplied with Perl 5.8.1 +and later. + +=head1 SEE ALSO + +L, L, L + +=cut diff --git a/t/00_distribution.t b/t/00_distribution.t new file mode 100644 index 0000000..6e68e74 --- /dev/null +++ b/t/00_distribution.t @@ -0,0 +1,26 @@ +#!/usr/bin/env perl + +# Copyright (C) 2005 Joshua Hoblitt + +use strict; +use warnings FATAL => qw( all ); + +use lib qw( ./lib ./t ); + +use Test::More; + +# example taken from Test::Distribution Pod + +BEGIN { + eval { + require Test::Distribution; + }; + if($@) { + plan skip_all => 'Test::Distribution not installed'; + } else { + # pod tests have to be disabled because of the Pod test strings in + # Test::Pod::Tidy +#import Test::Distribution not => qw( pod ); + import Test::Distribution; + } +} diff --git a/t/01_load.t b/t/01_load.t new file mode 100644 index 0000000..0330eb2 --- /dev/null +++ b/t/01_load.t @@ -0,0 +1,15 @@ +#!/usr/bin/perl + +# Copyright (C) 2003-2005 Joshua Hoblitt + +use strict; +use warnings; + +use lib qw( ./lib ); + +use Test::More tests => 2; + +BEGIN { use_ok( 'DateTime::Format::ISO8601' ); } + +my $object = DateTime::Format::ISO8601->new; +isa_ok( $object, 'DateTime::Format::ISO8601' ); diff --git a/t/02_examples.t b/t/02_examples.t new file mode 100644 index 0000000..756c829 --- /dev/null +++ b/t/02_examples.t @@ -0,0 +1,722 @@ +#!/usr/bin/perl + +# Copyright (C) 2003-2005 Joshua Hoblitt + +use strict; +use warnings; + +use lib qw( ./lib ); + +use Test::More tests => 174; + +use DateTime::Format::ISO8601; + +# parse_datetime +my $base_year = 2000; +my $base_month = "01"; +my $iso8601 = DateTime::Format::ISO8601->new( + base_datetime => DateTime->new( year => $base_year, month => $base_month ), +); + +{ + #YYYYMMDD 19850412 + my $dt = $iso8601->parse_datetime( '19850412' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #YYYY-MM-DD 1985-04-12 + my $dt = $iso8601->parse_datetime( '1985-04-12' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #YYYY-MM 1985-04 + my $dt = $iso8601->parse_datetime( '1985-04' ); + is( $dt->ymd, '1985-04-01' ); +} + +{ + #YYYY 1985 + my $dt = $iso8601->parse_datetime( '1985' ); + is( $dt->ymd, '1985-01-01' ); +} + +{ + #YY 19 (century) + my $dt = $iso8601->parse_datetime( '19' ); + is( $dt->ymd, '1901-01-01' ); +} + +{ + #YYMMDD 850412 + my $dt = $iso8601->parse_datetime( '850412' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #YY-MM-DD 85-04-12 + my $dt = $iso8601->parse_datetime( '85-04-12' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #-YYMM -8504 + my $dt = $iso8601->parse_datetime( '-8504' ); + is( $dt->ymd, '1985-04-01' ); +} + +{ + #-YY-MM -85-04 + my $dt = $iso8601->parse_datetime( '-85-04' ); + is( $dt->ymd, '1985-04-01' ); +} + +{ + #-YY -85 + my $dt = $iso8601->parse_datetime( '-85' ); + is( $dt->year, '1985' ); +} + +{ + #--MMDD --0412 + my $dt = $iso8601->parse_datetime( '--0412' ); + is( $dt->ymd, "${base_year}-04-12" ); +} + +{ + #--MM-DD --04-12 + my $dt = $iso8601->parse_datetime( '--04-12' ); + is( $dt->ymd, "${base_year}-04-12" ); +} + +{ + #--MM --04 + my $dt = $iso8601->parse_datetime( '--04' ); + is( $dt->ymd, "${base_year}-04-01" ); +} + +{ + #---DD ---12 + my $dt = $iso8601->parse_datetime( '---12' ); + is( $dt->ymd, "${base_year}-${base_month}-12" ); +} + +{ + #+[YY]YYYYMMDD +0019850412 + my $dt = $iso8601->parse_datetime( '+0019850412' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #+[YY]YYYY-MM-DD +001985-04-12 + my $dt = $iso8601->parse_datetime( '+001985-04-12' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #+[YY]YYYY-MM +001985-04 + my $dt = $iso8601->parse_datetime( '+001985-04' ); + is( $dt->ymd, '1985-04-01' ); +} + +{ + #+[YY]YYYY +001985 + my $dt = $iso8601->parse_datetime( '+001985' ); + is( $dt->ymd, '1985-01-01' ); +} + +{ + #+[YY]YY +0019 (century) + my $dt = $iso8601->parse_datetime( '+0019' ); + is( $dt->ymd, '1901-01-01' ); +} + +{ + #YYYYDDD 1985102 + my $dt = $iso8601->parse_datetime( '1985102' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #YYYY-DDD 1985-102 + my $dt = $iso8601->parse_datetime( '1985-102' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #YYDDD 85102 + my $dt = $iso8601->parse_datetime( '85102' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #YY-DDD 85-102 + my $dt = $iso8601->parse_datetime( '85-102' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #-DDD -102 + my $dt = $iso8601->parse_datetime( '-102' ); + my $year = sprintf( "%04i", $base_year ); + is( $dt->strftime( "%j" ), 102 ); +} + +{ + #+[YY]YYYYDDD +001985102 + my $dt = $iso8601->parse_datetime( '+001985102' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #+[YY]YYYY-DDD +001985-102 + my $dt = $iso8601->parse_datetime( '+001985-102' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #YYYYWwwD 1985W155 + my $dt = $iso8601->parse_datetime( '1985W155' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #YYYY-Www-D 1985-W15-5 + my $dt = $iso8601->parse_datetime( '1985-W15-5' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #YYYYWww 1985W15 + my $dt = $iso8601->parse_datetime( '1985W15' ); + is( $dt->ymd, '1985-04-08' ); +} + +{ + #YYYY-Www 1985-W15 + my $dt = $iso8601->parse_datetime( '1985-W15' ); + is( $dt->ymd, '1985-04-08' ); +} + +{ + #YYWwwD 85W155 + my $dt = $iso8601->parse_datetime( '85W155' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #YY-Www-D 85-W15-5 + my $dt = $iso8601->parse_datetime( '85-W15-5' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #YYWww 85W15 + my $dt = $iso8601->parse_datetime( '85W15' ); + is( $dt->ymd, '1985-04-08' ); +} + +{ + #YY-Www 85-W15 + my $dt = $iso8601->parse_datetime( '85-W15' ); + is( $dt->ymd, '1985-04-08' ); +} + +{ + #-YWwwD -5W155 + my $dt = $iso8601->parse_datetime( '-5W155' ); + is( $dt->year, '2005' ); + is( $dt->week_number, '15' ); + is( $dt->day_of_week, '5' ); +} + +{ + #-Y-Www-D -5-W15-5 + my $dt = $iso8601->parse_datetime( '-5-W15-5' ); + is( $dt->year, '2005' ); + is( $dt->week_number, '15' ); + is( $dt->day_of_week, '5' ); +} + +{ + #-YWww -5W15 + my $dt = $iso8601->parse_datetime( '-5W15' ); + is( $dt->year, '2005' ); + is( $dt->week_number, '15' ); +} + +{ + #-Y-Www -5-W15 + my $dt = $iso8601->parse_datetime( '-5-W15' ); + is( $dt->year, '2005' ); + is( $dt->week_number, '15' ); +} + +{ + #-WwwD -W155 + my $dt = $iso8601->parse_datetime( '-W155' ); + is( $dt->week_number, '15' ); + is( $dt->day_of_week, '5' ); +} + +{ + #-Www-D -W15-5 + my $dt = $iso8601->parse_datetime( '-W15-5' ); + is( $dt->week_number, '15' ); + is( $dt->day_of_week, '5' ); +} + +# { +# #-Www -W15 +# my $dt = $iso8601->parse_datetime( '-W15' ); +# is( $dt->week_number, '15' ); +# } + +{ + #-W-D -W-5 + my $dt = $iso8601->parse_datetime( '-W-5' ); + is( $dt->day_of_week, '5' ); +} + +{ + #+[YY]YYYYWwwD +001985W155 + my $dt = $iso8601->parse_datetime( '+001985W155' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #+[YY]YYYY-Www-D +001985-W15-5 + my $dt = $iso8601->parse_datetime( '+001985-W15-5' ); + is( $dt->ymd, '1985-04-12' ); +} + +{ + #+[YY]YYYYWww +001985W15 + my $dt = $iso8601->parse_datetime( '+001985W15' ); + is( $dt->ymd, '1985-04-08' ); +} + +{ + #+[YY]YYYY-Www +001985-W15 + my $dt = $iso8601->parse_datetime( '+001985-W15' ); + is( $dt->ymd, '1985-04-08' ); +} + +{ + #hh:mm:ss 23:20:50 + my $dt = $iso8601->parse_datetime( '23:20:50' ); + is( $dt->hms, '23:20:50' ); +} + +{ + #hh:mm 23:20 + my $dt = $iso8601->parse_datetime( '23:20' ); + is( $dt->hms, '23:20:00' ); +} + +{ + #hhmmss,ss 232050,5 + my $dt = $iso8601->parse_datetime( '232050,5' ); + is( $dt->hms, '23:20:50' ); + is( $dt->nanosecond, 500_000_000 ); +} + +{ + #hh:mm:ss,ss 23:20:50,5 + my $dt = $iso8601->parse_datetime( '23:20:50,5' ); + is( $dt->hms, '23:20:50' ); + is( $dt->nanosecond, 500_000_000 ); +} + +{ + #hhmm,mm 2320,8 + my $dt = $iso8601->parse_datetime( '2320,8' ); + is( $dt->hms, '23:20:48' ); +} + +{ + #hh:mm,mm 23:20,8 + my $dt = $iso8601->parse_datetime( '23:20,8' ); + is( $dt->hms, '23:20:48' ); +} + +{ + #hh,hh 23,3 + my $dt = $iso8601->parse_datetime( '23,3' ); + is( $dt->hms, '23:18:00' ); +} + +{ + #-mm:ss -20:50 + my $dt = $iso8601->parse_datetime( '-20:50' ); + is( $dt->minute, '20' ); + is( $dt->second, '50' ); +} + +{ + #-mmss,s -2050,5 + my $dt = $iso8601->parse_datetime( '-2050,5' ); + is( $dt->minute, '20' ); + is( $dt->second, '50' ); + is( $dt->nanosecond, 500_000_000 ); +} + +{ + #-mm:ss,s -20:50,5 + my $dt = $iso8601->parse_datetime( '-20:50,5' ); + is( $dt->minute, '20' ); + is( $dt->second, '50' ); + is( $dt->nanosecond, 500_000_000 ); +} + +{ + #-mm,m -20,8 + my $dt = $iso8601->parse_datetime( '-20,8' ); + is( $dt->minute, '20' ); + is( $dt->second, '48' ); +} + +{ + #--ss,s --50,5 + my $dt = $iso8601->parse_datetime( '--50,5' ); + is( $dt->second, '50' ); + is( $dt->nanosecond, 500_000_000 ); +} + +{ + #hhmmssZ 232030Z + my $dt = $iso8601->parse_datetime( '232030Z' ); + is( $dt->hms, '23:20:30' ); + is( $dt->time_zone->name, 'UTC' ); +} + +{ + #hhmmss.ssZ 232030.5Z + my $dt = $iso8601->parse_datetime( '232030.5Z' ); + is( $dt->hms, '23:20:30' ); + is( $dt->nanosecond, 500_000_000 ); + is( $dt->time_zone->name, 'UTC' ); +} + + +{ + #hh:mm:ssZ 23:20:30Z + my $dt = $iso8601->parse_datetime( '23:20:30Z' ); + is( $dt->hms, '23:20:30' ); + is( $dt->time_zone->name, 'UTC' ); +} + +{ + #hhmmssZ 23:20:30.5Z + my $dt = $iso8601->parse_datetime( '23:20:30.5Z' ); + is( $dt->hms, '23:20:30' ); + is( $dt->nanosecond, 500_000_000 ); + is( $dt->time_zone->name, 'UTC' ); +} + + +{ + #hhmmZ 2320Z + my $dt = $iso8601->parse_datetime( '2320Z' ); + is( $dt->hms, '23:20:00' ); + is( $dt->time_zone->name, 'UTC' ); +} + +{ + #hh:mmZ 23:20Z + my $dt = $iso8601->parse_datetime( '23:20Z' ); + is( $dt->hms, '23:20:00' ); + is( $dt->time_zone->name, 'UTC' ); +} + +{ + #hhZ 23Z + my $dt = $iso8601->parse_datetime( '23Z' ); + is( $dt->hms, '23:00:00' ); + is( $dt->time_zone->name, 'UTC' ); +} + +{ + #hhmmss[+/-]hhmm 152746+0100 152746-0500 + my $dt = $iso8601->parse_datetime( '152746+0100' ); + is( $dt->hms, '15:27:46' ); + is( $dt->time_zone->name, '+0100' ); +} + +{ + #hhmmss[+/-]hhmm 152746+0100 152746-0500 + my $dt = $iso8601->parse_datetime( '152746-0500' ); + is( $dt->hms, '15:27:46' ); + is( $dt->time_zone->name, '-0500' ); +} + +{ + #hhmmss.ss[+/-]hhmm 152746.5+0100 152746.5-0500 + my $dt = $iso8601->parse_datetime( '152746.5+0100' ); + is( $dt->hms, '15:27:46' ); + is( $dt->nanosecond, 500_000_000 ); + is( $dt->time_zone->name, '+0100' ); +} + +{ + #hhmmss.ss[+/-]hh:mm 152746.5+01:00 152746.5-05:00 + my $dt = $iso8601->parse_datetime( '152746.5-05:00' ); + is( $dt->hms, '15:27:46' ); + is( $dt->nanosecond, 500_000_000 ); + is( $dt->time_zone->name, '-0500' ); +} + +{ + #hhmmss[+/-]hh:mm 152746.05+01:00 152746.05-05:00 + my $dt = $iso8601->parse_datetime( '152746.05-05:00' ); + is( $dt->hms, '15:27:46' ); + is( $dt->nanosecond, 50_000_000 ); + is( $dt->time_zone->name, '-0500' ); +} + + +{ + #hh:mm:ss[+/-]hh:mm 15:27:46+01:00 15:27:46-05:00 + my $dt = $iso8601->parse_datetime( '15:27:46+01:00' ); + is( $dt->hms, '15:27:46' ); + is( $dt->time_zone->name, '+0100' ); +} + +{ + #hh:mm:ss[+/-]hh:mm 15:27:46+01:00 15:27:46-05:00 + my $dt = $iso8601->parse_datetime( '15:27:46-05:00' ); + is( $dt->hms, '15:27:46' ); + is( $dt->time_zone->name, '-0500' ); +} + +{ + #hhmmss.ss[+/-]hhmm 15:27:46.5+0100 15:27:46.5-0500 + my $dt = $iso8601->parse_datetime( '15:27:46.5+0100' ); + is( $dt->hms, '15:27:46' ); + is( $dt->nanosecond, 500_000_000 ); + is( $dt->time_zone->name, '+0100' ); +} + +{ + #hh:mm:ss.ss[+/-]hh:mm 15:27:46.5+01:00 15:27:46.5-05:00 + my $dt = $iso8601->parse_datetime( '15:27:46.5-05:00' ); + is( $dt->hms, '15:27:46' ); + is( $dt->nanosecond, 500_000_000 ); + is( $dt->time_zone->name, '-0500' ); +} + +{ + #hhmmss[+/-]hh 152746+01 152746-05 + my $dt = $iso8601->parse_datetime( '152746+01' ); + is( $dt->hms, '15:27:46' ); + is( $dt->time_zone->name, '+0100' ); +} + +{ + #hhmmss[+/-]hh 152746+01 152746-05 + my $dt = $iso8601->parse_datetime( '152746-05' ); + is( $dt->hms, '15:27:46' ); + is( $dt->time_zone->name, '-0500' ); +} + +{ + #hh:mm:ss[+/-]hh 15:27:46+01 15:27:46-05 + my $dt = $iso8601->parse_datetime( '15:27:46+01' ); + is( $dt->hms, '15:27:46' ); + is( $dt->time_zone->name, '+0100' ); +} + +{ + #hh:mm:ss[+/-]hh 15:27:46+01 15:27:46-05 + my $dt = $iso8601->parse_datetime( '15:27:46-05' ); + is( $dt->hms, '15:27:46' ); + is( $dt->time_zone->name, '-0500' ); +} + +{ + #YYYYMMDDThhmmss 19850412T101530 + my $dt = $iso8601->parse_datetime( '19850412T101530' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->time_zone->name, 'floating' ); +} + +{ + #YYYY-MM-DDThh:mm:ss 1985-04-12T10:15:30 + my $dt = $iso8601->parse_datetime( '1985-04-12T10:15:30' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->time_zone->name, 'floating' ); +} + +{ + #YYYYMMDDThhmmss.ss 19850412T101530.5 + my $dt = $iso8601->parse_datetime( '19850412T101530.5' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->nanosecond, 500_000_000 ); + is( $dt->time_zone->name, 'floating' ); +} + +{ + #YYYY-MM-DDThh:mm:ss.ss 1985-04-12T10:15:30.5 + my $dt = $iso8601->parse_datetime( '1985-04-12T10:15:30.5' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->nanosecond, 500_000_000 ); + is( $dt->time_zone->name, 'floating' ); +} + +{ + #YYYYMMDDThhmmssZ 19850412T101530Z + my $dt = $iso8601->parse_datetime( '19850412T101530Z' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->time_zone->name, 'UTC' ); +} + +{ + #YYYY-MM-DDThh:mm:ssZ 1985-04-12T10:15:30Z + my $dt = $iso8601->parse_datetime( '1985-04-12T10:15:30Z' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->time_zone->name, 'UTC' ); +} + +{ + #YYYYMMDDThhmmss.ssZ 19850412T101530.5Z + my $dt = $iso8601->parse_datetime( '19850412T101530.5Z' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->nanosecond, 500_000_000 ); + is( $dt->time_zone->name, 'UTC' ); +} + +{ + #YYYY-MM-DDThh:mm:ss.ssZ 1985-04-12T10:15:30.5Z + my $dt = $iso8601->parse_datetime( '1985-04-12T10:15:30.5Z' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->nanosecond, 500_000_000 ); + is( $dt->time_zone->name, 'UTC' ); +} + + +{ + #YYYYMMDDThhmmss+hhmm 19850412T101530+0400 + my $dt = $iso8601->parse_datetime( '19850412T101530+0400' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->time_zone->name, '+0400' ); +} + +{ + #YYYY-MM-DDThh:mm:ss+hh:mm 1985-04-12T10:15:30+04:00 + my $dt = $iso8601->parse_datetime( '1985-04-12T10:15:30+04:00' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->time_zone->name, '+0400' ); +} + +{ + #YYYYMMDDThhmmss.ss+hhmm 19850412T101530.5+0400 + my $dt = $iso8601->parse_datetime( '19850412T101530.5+0400' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->nanosecond, 500_000_000 ); + is( $dt->time_zone->name, '+0400' ); +} + +{ + #YYYY-MM-DDThh:mm:ss.ss+hh:mm 1985-04-12T10:15:30.5+04:00 + my $dt = $iso8601->parse_datetime( '1985-04-12T10:15:30.5+04:00' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->nanosecond, 500_000_000 ); + is( $dt->time_zone->name, '+0400' ); +} + + +{ + #YYYYMMDDThhmmss+hh 19850412T101530+04 + my $dt = $iso8601->parse_datetime( '19850412T101530+04' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->time_zone->name, '+0400' ); +} + +{ + #YYYY-MM-DDThh:mm:ss+hh 1985-04-12T10:15:30+04 + my $dt = $iso8601->parse_datetime( '1985-04-12T10:15:30+04' ); + is( $dt->iso8601, '1985-04-12T10:15:30' ); + is( $dt->time_zone->name, '+0400' ); +} + +{ + #YYYYMMDDThhmm 19850412T1015 + my $dt = $iso8601->parse_datetime( '19850412T1015' ); + is( $dt->iso8601, '1985-04-12T10:15:00' ); + is( $dt->time_zone->name, 'floating' ); +} + +{ + #YYYY-MM-DDThh:mm 1985-04-12T10:15 + my $dt = $iso8601->parse_datetime( '1985-04-12T10:15' ); + is( $dt->iso8601, '1985-04-12T10:15:00' ); + is( $dt->time_zone->name, 'floating' ); +} + +{ + #YYYYDDDThhmmZ 1985102T1015Z + my $dt = $iso8601->parse_datetime( '1985102T1015Z' ); + is( $dt->iso8601, '1985-04-12T10:15:00' ); + is( $dt->time_zone->name, 'UTC' ); +} + +{ + #YYYY-DDDThh:mmZ 1985-102T10:15Z + my $dt = $iso8601->parse_datetime( '1985-102T10:15Z' ); + is( $dt->iso8601, '1985-04-12T10:15:00' ); + is( $dt->time_zone->name, 'UTC' ); +} + +{ + #YYYYWwwDThhmm+hhmm 1985W155T1015+0400 + my $dt = $iso8601->parse_datetime( '1985W155T1015+0400' ); + is( $dt->iso8601, '1985-04-12T10:15:00' ); + is( $dt->time_zone->name, '+0400' ); +} + +{ + #YYYY-Www-DThh:mm+hh 1985-W15-5T10:15+04 + my $dt = $iso8601->parse_datetime( '1985-W15-5T10:15+04' ); + is( $dt->iso8601, '1985-04-12T10:15:00' ); + is( $dt->time_zone->name, '+0400' ); +} + +# parse_time + +{ + #hhmmss 232050 + my $dt = $iso8601->parse_time( '232050' ); + is( $dt->hms, '23:20:50' ); +} + +{ + #hhmm 2320 + my $dt = $iso8601->parse_time( '2320' ); + is( $dt->hms, '23:20:00' ); +} + +{ + #hh 23 + my $dt = $iso8601->parse_time( '23' ); + is( $dt->hms, '23:00:00' ); +} + +{ + #-mmss -2050 + my $dt = $iso8601->parse_time( '-2050' ); + is( $dt->minute, '20' ); + is( $dt->second, '50' ); +} + +{ + #-mm -20 + my $dt = $iso8601->parse_time( '-20' ); + is( $dt->minute, '20' ); +} + +{ + #--ss --50 + my $dt = $iso8601->parse_time( '--50' ); + is( $dt->second, '50' ); +} diff --git a/t/03_base_datetime.t b/t/03_base_datetime.t new file mode 100644 index 0000000..6c88a12 --- /dev/null +++ b/t/03_base_datetime.t @@ -0,0 +1,121 @@ +#!/usr/bin/perl + +# Copyright (C) 2005 Joshua Hoblitt + +use strict; +use warnings; + +use lib qw( ./lib ); + +use Test::More tests => 36; + +use DateTime; +use DateTime::Format::ISO8601; + +my @good_bases = ( + { year => 0 }, + { year => 1 }, + { year => 2500 }, + { year => 5000 }, + { year => 7500 }, + { year => 9998 }, + { + year => 9999, + month => 12, + day => 12, + hour => 23, + minute => 59, + second => 59, + nanosecond => 999_999_999, + }, +); + +my @test_bases = ( + [ "1945-09-02T09:04", '1945-W35' ], + [ "1987-12-18T02:03", '1987-W51' ], + [ "1988-05-05T04:05", '1988-W18' ], + [ "1989-10-18T06:07", '1989-W42' ], + [ "1991-03-21T08:09", '1991-W12' ], +); + +my @bad_bases = ( + { year => -2 }, + { year => -1 }, + { + year => -1, + month => 12, + day => 31, + hour => 23, + minute => 59, + second => 59, + nanosecond => 999_999_999 + }, + { year => 10_000 }, + { year => 10_001 }, + { year => 10_002 }, +); + +foreach my $params ( @good_bases ) { + my $dt = DateTime->new( %$params ); + + { + my $iso_parser = DateTime::Format::ISO8601->new( + base_datetime => $dt, + ); + isa_ok( $iso_parser, 'DateTime::Format::ISO8601' ); + } + + { + my $iso_parser = DateTime::Format::ISO8601->new->set_base_datetime( + object => $dt, + ); + isa_ok( $iso_parser, 'DateTime::Format::ISO8601' ); + } +} + +foreach ( @test_bases ) { + my $iso_parser = DateTime::Format::ISO8601->new( + base_datetime => DateTime::Format::ISO8601->parse_datetime( $_->[0] ), + ); + + { + #tests... + #_add_minute + #_add_hour + #_add_day + #_add_month + #_add_year + + #--ss,s --50,5 + my $dt = $iso_parser->parse_datetime( '--50,5' ); + is( $dt->strftime( "%Y-%m-%dT%H:%M" ), $_->[0] ); + } + + { + #tests... + #_add_week + #_add_year + + #-W-D -W-5 + my $dt = $iso_parser->parse_datetime( '-W-5' ); + is( $dt->strftime( "%Y-W%V" ), $_->[1] ); + } +} + +foreach my $params ( @bad_bases ) { + my $dt = DateTime->new( %$params ); + + eval { + DateTime::Format::ISO8601->new( + base_datetime => $dt, + ); + }; + like( $@, qr/base_datetime must be (greater|less) then/ ); + + eval { + DateTime::Format::ISO8601->new->set_base_datetime( + object => $dt, + ); + }; + like( $@, qr/base_datetime must be (greater|less) then/ ); +} diff --git a/t/04_legacy_year.t b/t/04_legacy_year.t new file mode 100644 index 0000000..f4bf033 --- /dev/null +++ b/t/04_legacy_year.t @@ -0,0 +1,74 @@ +#!/usr/bin/perl + +# Copyright (C) 2005 Joshua Hoblitt + +use strict; +use warnings; + +use lib qw( ./lib ); + +use Test::More tests => 36; + +use DateTime::Format::ISO8601; + +{ + is( DateTime::Format::ISO8601->DefaultLegacyYear, 1 ); + my $iso_parser = DateTime::Format::ISO8601->new; + is( $iso_parser->legacy_year, 1 ); +} + +foreach my $n ( 0, 1, undef ) { + DateTime::Format::ISO8601->DefaultLegacyYear( $n ); + is( DateTime::Format::ISO8601->DefaultLegacyYear, $n ); + my $iso_parser = DateTime::Format::ISO8601->new; + is( $iso_parser->legacy_year, $n ); +} + +foreach my $n ( -3 .. -1, 2 .. 4 ) { + eval { DateTime::Format::ISO8601->DefaultLegacyYear( $n ) }; + like( $@, qr/did not pass the 'is 0, 1, or undef' callback/ ); +} + +# restore default legacy year behavior +DateTime::Format::ISO8601->DefaultLegacyYear( 1 ); + +foreach my $n ( 0, 1, undef ) { + my $iso_parser = DateTime::Format::ISO8601->new( legacy_year => $n ); + isa_ok( $iso_parser, 'DateTime::Format::ISO8601' ); + is( $iso_parser->legacy_year, $n ); + + { + my $iso_parser = DateTime::Format::ISO8601->new->set_legacy_year( $n ); + is( $iso_parser->legacy_year, $n ); + } +} + +foreach my $n ( -3 .. -1, 2 .. 4 ) { + eval { DateTime::Format::ISO8601->new( legacy_year => $n ) }; + like( $@, qr/did not pass the 'is 0, 1, or undef' callback/ ); + + eval { DateTime::Format::ISO8601->new->set_legacy_year( $n ) }; + like( $@, qr/did not pass the 'is 0, 1, or undef' callback/ ); +} + +{ + my $failed = 0; + + foreach my $year ( 0 .. 99 ) { + $year *= 100; # [0, 9900], step 100 + my $iso_parser = DateTime::Format::ISO8601->new( + legacy_year => 0, + base_datetime => DateTime->new( year => $year ), + ); + + foreach my $tdy ( 0 .. 9 ) { + $tdy *= 10; # [0, 90], step 10 + $tdy = sprintf( "%02d", $tdy ); + my $dt = $iso_parser->parse_datetime( "-$tdy" ); + $failed++ unless $dt->year eq sprintf( + "%d", $iso_parser->base_datetime->strftime( "%C" ) . $tdy ); + } + } + + is( $failed, 0, "parse_datetime() with a base_datetime" ); +} diff --git a/t/05_cut_off_year.t b/t/05_cut_off_year.t new file mode 100644 index 0000000..42524d5 --- /dev/null +++ b/t/05_cut_off_year.t @@ -0,0 +1,122 @@ +#!/usr/bin/perl + +# Copyright (C) 2005 Joshua Hoblitt + +use strict; +use warnings; + +use lib qw( ./lib ); + +use Test::More tests => 24; + +use DateTime::Format::ISO8601; + +# DefaultCutOffYear() + +{ + is( DateTime::Format::ISO8601->DefaultCutOffYear, 49, + "class default DefaultCutOffYear()" ); + is( DateTime::Format::ISO8601->new->cut_off_year, 49, + "object default DefaultCutOffYear()" ); +} + +{ + my $failed = 0; + + foreach my $n ( 0 .. 99 ) { + DateTime::Format::ISO8601->DefaultCutOffYear( $n ); + + $failed++ unless DateTime::Format::ISO8601->DefaultCutOffYear == $n; + $failed++ unless DateTime::Format::ISO8601->new->cut_off_year == $n; + } + + is( $failed, 0, "set default DefaultCutOffYear()" ); +} + +foreach my $n ( -3 .. -1, 100 .. 102 ) { + eval { DateTime::Format::ISO8601->DefaultCutOffYear( $n ) }; + like( $@, qr/did not pass the 'is between 0 and 99' callback/ ); +} + +# restore default cut off year behavior +DateTime::Format::ISO8601->DefaultCutOffYear( 49 ); + +# set_cut_off_year() + +{ + my $failed = 0; + + foreach my $n ( 0 .. 99 ) { + { + my $iso_parser = DateTime::Format::ISO8601->new( cut_off_year => $n ); + $failed++ unless UNIVERSAL::isa( $iso_parser, 'DateTime::Format::ISO8601' ); + $failed++ unless $iso_parser->cut_off_year == $n; + } + + { + my $iso_parser = DateTime::Format::ISO8601->new->set_cut_off_year( $n ); + $failed++ unless $iso_parser->cut_off_year == $n; + } + } + + is( $failed, 0, "set_cut_off_year()" ); +} + +foreach my $n ( -3 .. -1, 100 .. 102 ) { + eval { DateTime::Format::ISO8601->new( cut_off_year => $n ) }; + like( $@, qr/did not pass the 'is between 0 and 99' callback/, + "cut_off_year value out of range" ); + + eval { DateTime::Format::ISO8601->new->set_cut_off_year( $n ) }; + like( $@, qr/did not pass the 'is between 0 and 99' callback/, + "set_cut_off_year() value out of range" ); +} + +# parse_datetime() as a class method + +{ + my $failed = 0; + + foreach my $n ( 0 .. 99 ) { + DateTime::Format::ISO8601->DefaultCutOffYear( $n ); + + foreach my $i ( 0 .. DateTime::Format::ISO8601->DefaultCutOffYear ) { + my $tdy = sprintf( "%02d", $i ); + my $dt = DateTime::Format::ISO8601->parse_datetime( "-$tdy" ); + $failed++ unless ( $dt->year eq "20$tdy" ); + + } + + foreach my $i ( ( DateTime::Format::ISO8601->DefaultCutOffYear + 1 ) .. 99 ) { + my $tdy = sprintf( "%02d", $i ); + my $dt = DateTime::Format::ISO8601->parse_datetime( "-$tdy" ); + $failed++ unless ( $dt->year eq "19$tdy" ); + } + } + + is( $failed, 0, "parse_datetime() as a class method" ); +} + +# parse_datetime() as an object method + +{ + my $failed = 0; + + foreach my $n ( 0 .. 99 ) { + my $iso_parser = DateTime::Format::ISO8601->new( cut_off_year => $n ); + + foreach my $i ( 0 .. $iso_parser->cut_off_year ) { + my $tdy = sprintf( "%02d", $i ); + my $dt = $iso_parser->parse_datetime( "-$tdy" ); + $failed++ unless ( $dt->year eq "20$tdy" ); + } + + foreach my $i ( ( $iso_parser->cut_off_year + 1 ) .. 99 ) { + my $tdy = sprintf( "%02d", $i ); + my $dt = $iso_parser->parse_datetime( "-$tdy" ); + $failed++ unless ( $dt->year eq "19$tdy" ); + } + } + + is( $failed, 0, "parse_datetime() as an object method" ); +} diff --git a/t/06_bad_formats.t b/t/06_bad_formats.t new file mode 100644 index 0000000..c050683 --- /dev/null +++ b/t/06_bad_formats.t @@ -0,0 +1,48 @@ +#!/usr/bin/perl + +# Copyright (C) 2003-2012 Joshua Hoblitt + +use strict; +use warnings; + +use lib qw( ./lib ); + +use Test::More tests => 4; + +use DateTime::Format::ISO8601; + +# parse_datetime +my $base_year = 2000; +my $base_month = "01"; +my $iso8601 = DateTime::Format::ISO8601->new( + base_datetime => DateTime->new( year => $base_year, month => $base_month ), +); + +# examples from https://rt.cpan.org/Ticket/Update.html?id=5264 + +#Section 4.2.5.1 says "Expressions of the difference between local time and UTC +#of day are a component in the representations defined in 4.2.5.2; they shall +#not be used as self-standing expressions.". Which means the UTC offset is +#considered part of the time format so you get to use the extended formation +#(the ':') or not but you can't mix and match the two. + +eval { + my $dt = $iso8601->parse_datetime( '2009-12-10T09:00:00.00+0100' ); +}; +like( $@, qr/Invalid date format/ ); + +eval { + my $dt = $iso8601->parse_datetime( '2011-07-04T20:50:23+0200' ); +}; +like( $@, qr/Invalid date format/ ); + +# more "colon or not" coverage +eval { + my $dt = $iso8601->parse_datetime( '20091210T090000.00+01:00' ); +}; +like( $@, qr/Invalid date format/ ); + +eval { + my $dt = $iso8601->parse_datetime( '20110704T205023+02:00' ); +}; +like( $@, qr/Invalid date format/ ); diff --git a/t/99_pod.t b/t/99_pod.t new file mode 100644 index 0000000..cb0edce --- /dev/null +++ b/t/99_pod.t @@ -0,0 +1,24 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use vars qw( @files ); + +BEGIN { + eval "require File::Find::Rule"; + if ($@) + { + print "1..1\nok 1 # skip File::Find::Rule not installed\n"; + exit; + } + @files = File::Find::Rule->file()->name( '*.pm', '*.pod' )->in( 'blib/lib' ); +} + +use Test::More tests => scalar @files; + +eval "use Test::Pod 0.95"; +SKIP: { + skip "Test::Pod 0.95 not installed.", scalar @files if $@; + pod_file_ok( $_ ) for @files; +}