/
Units.pm
256 lines (174 loc) · 6.51 KB
/
Units.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
package Date::Easy::Units;
use strict;
use warnings;
use autodie;
# VERSION
use Carp;
use Exporter;
use parent 'Exporter';
my @date_units = qw< days weeks months years >;
my @datetime_units = (qw< seconds minutes hours >, @date_units);
our @EXPORT_OK = @datetime_units;
our %EXPORT_TAGS = ( date => \@date_units, datetime => \@datetime_units, all => \@EXPORT_OK );
##############################
# FUNCTIONS (*NOT* METHODS!) #
##############################
sub seconds () { __PACKAGE__->new("seconds") }
sub minutes () { __PACKAGE__->new("minutes") }
sub hours () { __PACKAGE__->new("hours") }
sub days () { __PACKAGE__->new("days") }
sub weeks () { __PACKAGE__->new("weeks") }
sub months () { __PACKAGE__->new("months") }
sub years () { __PACKAGE__->new("years") }
#######################
# REGULAR CLASS STUFF #
#######################
sub new
{
my ($class, $unit, $qty) = @_;
if (defined $qty)
{
croak("can only do integer math") unless $qty == int($qty);
}
else
{
$qty = 1;
}
return bless { qty => $qty, unit => $unit }, $class;
}
sub to_string
{
my $self = shift;
my $qty = $self->{qty};
my $unit = $self->{unit};
$unit =~ s/s$// if $qty == 1;
return "$qty $unit";
}
########################
# OVERLOADED OPERATORS #
########################
sub _add_to
{
my ($self, $operand) = @_;
my $unit = $self->{unit};
my $qty = $self->{qty};
my $method = "add_$unit";
return $operand->$method($qty);
}
sub _subtract_from
{
my ($self, $operand) = @_;
my $unit = $self->{unit};
my $qty = $self->{qty};
my $method = "subtract_$unit";
return $operand->$method($qty);
}
use overload
'""' => sub { shift->to_string },
'cmp' => sub { $_[0]->to_string cmp $_[1] },
'+' => sub { shift->_add_to( @_ ) },
'-' => sub { $_[2] ? shift->_subtract_from( @_ ) : croak("can't subtract from a unit") },
'*' => sub { __PACKAGE__->new( $_[0]->{unit}, $_[0]->{qty} * $_[1] ) },
;
1;
# ABSTRACT: units objects for easy date/datetime math
# COPYRIGHT
=head1 SYNOPSIS
use Date::Easy::Units ':all';
my @units = (seconds, minutes, hours, days, weeks, months, years);
my $quarters = 3 * months; # set the quantity (default: 1)
my $year1 = 4 * $quarters; # a year is 12 months
my $year2 = 1 * years; # `1 *' is redudant, but clearer
# units stringify as you'd expect
say 3 * weeks;
# prints "3 weeks"
say 1 * hours;
# prints "1 hour"
# $year1 and $year2 are _conceptually_ equal
today + $year1 == today + $year2; # true
# but not _actually_ equal
$year1 ne $year2; # because "12 months" ne "1 year"
=head1 DESCRIPTION
Date::Easy::Units objects represent a quantity and a unit: seconds, minutes, hours, days, weeks,
months, or years. The default quantity is 1, but you can multiply that by an integer (positive or
negative) to get a different quantity. Multiplying a quantity other than 1 by an integer does what
you expect.
You I<could> create a units object the "normal" OOP way:
my $s1 = Date::Easy::Units->new("seconds");
my $m4 = Date::Easy::Units->new("minutes", 4);
But you will rarely do it that way. More likely you'll just use the exportable (but not exported by
default) functions:
my $s1 = seconds;
my $m4 = 4 * minutes;
Arithmetic operators (plus and minus) can be used to add or subtract some amount of time to or from
a date or datetime. Multiplcation can be used to change the quantity of a units object (as shown
above).
Units objects are immutable.
See L<Date::Easy> for more general usage notes.
=head1 USAGE
=head2 Import Parameters
You can request only certain of the constructors below be exported, or use C<:all> to get them all.
=head2 Constructors
=head3 seconds
=head3 minutes
=head3 hours
=head3 days
=head3 weeks
=head3 months
=head3 years
Each constructor returns a units object with the specified unit and a quantity of 1. Multiply them
by an integer to get a different quantity.
=head3 new
Takes either one or two arguments: unit name (required) and quantity (optional). Most of the time
you will not use this.
(Technically speaking, you can use C<new> to create any unit you want. So you might:
my $u = Date::Easy::Units->new("bmoogles");
say $u; # prints "1 bmoogle"
say $u * 4; # prints "4 bmoogles"
my $d = my_date_thing();
$d + $u * 4; # calls $d->add_bmoogles(4)
and, assuming C<my_date_thing> returns an object with an C<add_bmoogles> method (and a
C<subtract_bmoogles> method), that will actually work. Then you could even:
sub bmoogles () { Date::Easy::Units->new("bmoogles") }
say my_date_thing() + 4*bmoogles;
Not saying that any of that is a good idea, of course ... just that you I<could>, if you were so
inclined.)
=head2 Other Methods
=head3 to_string
Does the work of the overloaded stringification, in case you ever want to call it explicitly.
=head2 Overloaded Operators
=head3 Multiplication
Returns a new units object with the same unit and a quantity equal to the existing quantity times
the operand. Thus, the operand should be an integer. Multiplying by a floating point causes an
exception. Multiplying by a string likely just gets you 0 (depending on whether your string can be
interpreted as a number), and probably a warning (if you have warnings turned on (which you
should)).
=head3 Addition
When you add a units object to a date (L<Date::Easy::Date>) or a datetime (L<Date::Easy::Datetime>),
it adds the appropriate number of the appropriate units to that date or datetime. Thus:
use Date::Easy;
say datetime("2010-01-01 noon") + 3*hours + 39*minutes;
# prints "Fri Jan 1 15:39:00 2010"
say 14*weeks + date("2015-07-17");
# addition is transitive: prints "Fri Oct 23 00:00:00 2015"
say today + 14*hours;
# throws exception: you can't add fractional days to a date
say 3*months + 4*days;
# throws exception: you can't add units to each other
say now + 3*months + 4*days;
# fine: addition is evaluated left-to-right
=head3 Subtraction
Subtracting a units object is just like adding the same units object times -1, except that of course
subtraction is not transitive. So:
use Date::Easy;
say now - 14*hours;
# fine
say 14*hours - now;
# throws exception: nonsensical
=head1 BUGS, CAVEATS and NOTES
This:
use Date::Easy::Units ':all';
say -1 * seconds;
prints "-1 seconds" as opposed to "-1 second." Whether or not you consider that a bug depends on
where your concepts of arithmetic and grammar intersect.
See also L<Date::Easy/"Limitations">.