DateTime-Moonpig

 view release on metacpan or  search on metacpan

lib/DateTime/Moonpig.pm  view on Meta::CPAN

use strict;
use warnings;
package DateTime::Moonpig;
{
  $DateTime::Moonpig::VERSION = '1.03';
}
# ABSTRACT: a DateTime object with different math

use base 'DateTime';
use Carp qw(confess croak);
use overload
  '+' => \&plus,
  '-' => \&minus,
;
use Scalar::Util qw(blessed reftype);
use Sub::Install ();

use namespace::autoclean;

sub new {
  my ($base, @arg) = @_;
  my $class = ref($base) || $base;

  if (@arg == 1) { return $class->from_epoch( epoch => $arg[0] ) }

  my %arg = @arg;
  $arg{time_zone} = 'UTC' unless exists $arg{time_zone};
  bless $class->SUPER::new(%arg) => $class;
}

sub new_datetime {
  my ($class, $dt) = @_;
  bless $dt->clone => $class;
}

# $a is expected to be epoch seconds
sub plus {
  my ($self, $a) = @_;
  my $class = ref($self);
  my $a_sec = $class->_to_sec($a);
  return $class->from_epoch( epoch     => $self->epoch + $a_sec,
                             time_zone => $self->time_zone,
                           );
}

sub minus {
  my ($a, $b, $rev) = @_;
  # if $b is a datetime, the result is an interval
  # but if $b is an interval, the result is another datetime
  if (blessed($b)) {
    if ($b->can("as_seconds")) {
      croak "subtracting a date from a scalar object is forbidden"
        if $rev;
      return $a->plus( - $b->as_seconds );
    } elsif ($b->can("epoch")) {
      my $res = ( $a->epoch - $b->epoch ) * ($rev ? -1 : 1);
      return $a->interval_factory($res);
    } else {
      croak "Can't subtract X from $a when X has neither 'as_seconds' nor 'epoch' method";
    }
  } elsif (ref $b) {
    croak "Can't subtract unblessed " . reftype($b) . " reference from $a";
  } else { # $b is a number
    croak "subtracting a date from a number is forbidden"
      if $rev;
    return $a + (-$b);
  }
}

sub number_of_days_in_month {
  my ($self) = @_;
  return (ref $self)
          ->last_day_of_month(year => $self->year, month => $self->month)
          ->day;
}

for my $mutator (qw(
  add_duration subtract_duration
  truncate
  set
    _year _month _day _hour _minute _second _nanosecond
)) {
  (my $method = $mutator) =~ s/^_/set_/;
  Sub::Install::install_sub({
    code => sub { confess "Do not mutate DateTime objects! (http://rjbs.manxome.org/rubric/entry/1929)" },
    as   => $method,
  });
}

sub interval_factory { return $_[1] }

sub _to_sec {
  my ($self, $a) = @_;

lib/DateTime/Moonpig.pm  view on Meta::CPAN

=item *

You can similarly subtract a scalar from a C<DateTime::Moonpig>. Subtracting a
C<DateTime::Moonpig> from a scalar is a fatal error.

=item *

You can subtract a C<DateTime::Moonpig> from another date object, such as another
C<DateTime::Moonpig>, or vice versa.  The result is the number of seconds between the
times represented by the two objects.

=item *

An object will be treated like a scalar if it implements an
C<as_seconds> method; it will be treated like a date object if it
implements an C<epoch> method.

=back

=head3 Full details

You can add a number to a C<DateTime::Moonpig> object, or subtract a number from a C<DateTime::Moonpig>
object; the number will be interpreted as a number of seconds to add
or subtract:

        # 1969-04-02 02:38:00
	$birthday = DateTime::Moonpig->new( year   => 1969,
                                            month  =>    4,
                                            day    =>    2,
                                            hour   =>    2,
                                            minute =>   38,
                                            second =>    0,
                                          );

	$x0    = $birthday + 10;         # 1969-04-02 02:38:10
	$x1    = $birthday - 10;         # 1969-04-02 02:37:50
	$x2    = $birthday + (-10);      # 1969-04-02 02:37:50

	$x3    = $birthday + 100;        # 1969-04-02 02:39:40
	$x4    = $birthday - 100;        # 1969-04-02 02:36:20

        # identical to $birthday + 100
	$x5    = 100 + $birthday;        # 1969-04-02 02:39:40

        # forbidden
	$x6    = 100 - $birthday;        # croaks

        # handy technique
        sub hours { $_[0} * 3600 }
	$x7    = $birthday + hours(12);  # 1969-04-02 14:38:00
	$x8    = $birthday - hours(12);  # 1969-04-01 14:38:00

C<$birthday> is I<never> modified by any of this.  The resulting objects will be in the same time zone as the original object, in this case UTC.

You can add any object to a C<DateTime::Moonpig> object if the other object supports an
C<as_seconds> method.  C<DateTime> and C<DateTime::Moonpig> objects do I<not> provide this method.

        package MyDaysInterval;                 # Silly example
	sub new {
	  my ($class, $days) = @_;
	  bless { days => $days } => $class;
        }

        sub as_seconds { $_[0]{days} * 86400 }

        package main;

        my $three_days = MyDaysInterval->new(3);

        $y0   = $birthday + $three_days;        # 1969-04-05 02:38:00

        # forbidden
        $y1   = $birthday + DateTime->new(...); # croaks
        $y2   = $birthday + $birthday;          # croaks

Again, C<$birthday> is not modified by any of this arithmetic.

You can subtract any object I<from> a C<DateTime::Moonpig> object, but
not vice versa, if that object provides an C<as_seconds> method.  It
will be interpreted as a time interval, and the result will be a new
C<DateTime::Moonpig> object:

        $z2   = $birthday - $three_days;     # 1969-03-30 02:38:00

	# forbidden
        $z3   = $three_days - $birthday;     # croaks

If you have another object that represents a time, and that implements
an C<epoch> method that returns its value as seconds since the Unix
epoch, you may subtract it from a C<DateTime::Moonpig> object or vice
versa. The result is the number of seconds between the second and the
first operands.  Since C<DateTime::Moonpig> implements C<epoch>, you
can subtract one C<DateTime::Moonpig> object from another to get the
number of seconds difference between them:

	$x0   = $birthday + 10;         # 1969-04-02 02:38:10

        $z4   = $x0 - $birthday;         # 10
        $z5   = $birthday - $x0;         # -10

        package Feb13;                  # Silly example
	sub new {
	  my ($class) = @_;
	  bless [ "DUMMY" ] => $class;
        }
        sub epoch { return 1234567890 } # Feb 13 23:31:30 2009 UTC

        package main;

        my $feb13 = Feb13->new();

        $feb13_dt = DateTime->new( year   => 2009,
                                   month  =>    2,
                                   day    =>   13,
                                   hour   =>   23,
                                   minute =>   31,
                                   second =>   30,
                                   time_zone => "UTC",
                                 );

        $z6   = $birthday - $feb13;     # -1258232010
        $z7   = $birthday - $feb13_dt;  # -1258232010
        $z8   = $feb13 - $birthday;     # 1258232010

        # WATCH OUT - will NOT return 1258232010
        $z9   = $feb13_dt - $birthday;  # returns a DateTime::Duration object

In this last example, C<DateTime>'s overloading is respected, rather than
C<DateTime::Moonpig>'s, and we get back a C<DateTime::Duration> object that represents
the elapsed difference of 40-some years.  Sorry, can't fix that; it's determined by Perl, which has to decide which of the two conflicting definitions of C<-> to honor, and chooses the other one.

None of these subtractions will modify any of the argument objects.

=head3 C<interval_factory>

When two time objects are subtracted, the result is normally a number.
However, the numeric difference is first passed to the target object's
C<interval_factory> method, which has the option to transform it and
return an object (or something else) instead.  The default
C<interval_factory> returns its argument unchanged.  So for example,

        $z0   = $x0 - $birthday;       # 10

is actually returning the result of C<< $x0->interval_factory(10) >>, which is 10.

=head3 Absolute time, not calendar time

C<DateTime::Moonpig> C<plus> and C<minus> always do real-time calculations, never civil
calendar calculations.  If your locality began observing daylight
savings on 2007-03-11, as most of the USA did, then:

        $a_day    = DateTime::Moonpig->new( year   => 2007,
                                            month  =>    3,
                                            day    =>   11,
                                            hour   =>    1,
                                            minute =>    0,
                                            second =>    0,
                                            time_zone => "America/New_York",
                                          );
	$next_day = $a_day->plus(24*3600);

At this point C<$next_day> is exactly 24E<middot>3600 seconds ahead
of C<$a_day>. Because the civil calendar day for 2007-03-11 in New
York was only 23 hours long, C<$next_day> represents represents



( run in 0.222 second using v1.01-cache-2.11-cpan-0d8aa00de5b )