|
Date::EzDate - Date and time manipulation made easy |
clone()set_warnings($warning_level)next_month([integer])zero_hour_ampm(1|0)date_range_string($end_date)time_range_string($end_time)day_lumps(@dates)
Date::EzDate - Date and time manipulation made easy
An EzDate object represents a single point in time and exposes all properties of that point. EzDate has many features, here are a few:
use Date::EzDate; my $mydate = Date::EzDate->new();
# output some date information print $mydate, "\n"; # e.g. output: Wed Apr 11, 2001 09:06:26
# go to next day
$mydate->{'epochday'}++;
# determine if the date is before some other date
if ($mydate < 'June 21, 2003')
{...}
# output some other date and time information
# e.g. output: Thursday April 12, 2001 09:06 am
print
$mydate->{'weekday long'}, ' ',
$mydate->{'month long'}, ' ',
$mydate->{'day of month'}, ', ',
$mydate->{'year'}, ' ',
$mydate->{'ampm hour no zero'}, ':',
$mydate->{'min'}, ' ',
$mydate->{'am pm'}, "\n";
# go to Monday of same week, but be lazy and don't spell out
# the whole day or case it correctly
$mydate->{'weekday long'} = 'MON';
print $mydate, "\n"; # e.g. output: Mon Apr 09, 2001 09:06:26
# go to previous year
$mydate->{'year'}--;
print $mydate, "\n"; # e.g. output: Sun Apr 09, 2000 09:06:26
Date::EzDate can be installed with the usual routine:
perl Makefile.PL
make
make test
make install
You can also just copy EzDate.pm into the Date/ directory of one of your library trees.
Date::EzDate was motivated by the simple fact that I hate dealing with date and time calculations, so I put all of them into a single easy-to-use object. The main idea of EzDate is that the object represents a specific date and time. A variety of properties tell you information about that date and time such as hour, minute, day of month, weekday, etc.
The real power of EzDate is that you can assign to (almost) any of those properties and EzDate will automatically rework the other properties to produce a new valid date with the property you just assigned. Properties that can be kept the same with the new value aren't changed, while those that logically must change to accomodate the new value are recalculated. For example, incrementing epochday by one (i.e. moving the date forward one day) does not change the hour or minute but does change the day of week.
So, for example, suppose you want to get information about today, then get information about tomorrow. That can be done using the epochday property which is used for day-granularity calculations. Let's walk through the steps:
use Date::EzDate; my $mydate = Date::EzDate->new(); # the object defaults to the current date and time
# e.g. outputs: 11:11:40 Wed Apr 11, 2001
print $mydate->{'full'}, "\n";
$mydate->{'epochday'}++;
# outputs: 11:11:40 Thu Apr 12, 2001
print $mydate->{'full'}, "\n";
This demonstrates the basic concept: almost any of the properties can be set as well as read and EzDate will take care of resetting all other properties as needed.
In addition to initializing the EzDate object with either nothing (i.e. the current day)
or with a string representing a date/time, you can initialize the object with the strings
YESTERDAY or TOMORROW. For example, the following code creates an EzDate object
with tomorrow's date:
$date = Date::EzDate->new('tomorrow');
EzDate objects stringify to a full representation of the date. So, for example, the following
code outputs a string like Tue Sep 3, 2002 14:01:02:
$date = Date::EzDate->new();
print $date, "\n";
The object stringifies to its default format, so if you want to change how it
stringifies simply change the default format. For example, the following
code outputs a string like September 3, 2002:
$date->{'default'} = '{month long} {day of month no zero} {year}';
print $date, "\n";
There are two main ways to compare EzDate objects: by comparing the object directly using the numeric comparison operators, or by comparing their properties.
EzDate overloads the numeric comparison operators. The epochday properties of two EzDate objects can be compared
using the ==,
>,
>=,
<,
<= , and
<=>,
operators. For example, the following code creates
two EzDate objects, then determines if the first object is less than the second:
$mybday = Date::EzDate->new();
$yourbday = Date::EzDate->new('tomorrow');
if ($mybday < $yourbday) {
....
}
Only one of the two items being compared needs be an EzDate object. The other can be a string representation of a date. For example, the following code correctly determines if the given EzDate object is before June 25, 2003:
if ($date < 'June 25, 2003') {
...
}
By default, the comparison is done on the epochday property, so two EzDate objects that have the same date
but different times will be considered the same. If you want to compare based on some other property, set
$Date::EzDate::overload to the name of the property to compare. For example, the following code sets
the comparison property to epoch hour, meaning that two date/times are considered the same only if
they are identical down to the hour:
my ($start, $finish);
$start = Date::EzDate->new('Oct 18, 2006 4pm');
$finish = Date::EzDate->new('Oct 18, 2006 6pm');
# outputs false, because both epochdays are the same print 'finish is greater than start: ', $finish > $start, "\n";
# change $Date::EzDate::overload to epochhour $Date::EzDate::overload = 'epochhour';
# output true, because Oct 18, 2006 6pm is # greater than Oct 18, 2006 4pm print 'finish is greater than start: ', $finish > $start, "\n";
PLEASE NOTE: $Date::EzDate::overload used to be named $Date::EzDate::compare. I made a non-backwards compatible change to ``overload'' because the same variable for indicating default overload is now being used for non-comparison overloads like addition and subtraction.
The other way to compare dates is to compare their properties. For example,
you can simple determine if two dates are on the same day of week by using
their day of week properties:
$date = Date::EzDate->new('January 3, 2001');
$otherdate = Date::EzDate->new('January 10, 2001');
if ($date->{'day of week'} eq $otherdate->{'day of week'}) {
...
}
You can do basic addition and subtraction on EzDate objects to adjust the
epoch day property (or whatever property is indicated by the
$Date::EzDate::overload variable). For example, to increment the day of the
object, simply increment it with ++ like a number. For example, the
following code moves the day from Jan 31, 2003 to Feb 1, 2003:
my $date = Date::EzDate->new('Jan 31, 2003');
print $date, "\n"; # outputs Fri Jan 31, 2003 16:05:27
$date++;
print $date; # outputs Sat Feb 1, 2003 16:05:27
You can also move by more than one day with + or +=. These two commands do the same thing:
$date = $date + 3; $date += 3;
Subtraction works the same way. All of these commands move the object one day backwards:
$date = $date - 1; $date -= 1; $date--;
Currently, EzDate only accepts a single optional argument when instantiated. You may pass in either a Perl time integer or a string formatted as DDMMMYYYY. If you don't pass in any argument then the returned object represents the time and day at the moment it was created.
The following are valid ways to instantiate an EzDate object:
# current date and time my $date = Date::EzDate->new();
# a specific date and time
my $date = Date::EzDate->new('Jan 31, 2001');
# a date in DDMMMYYYY format
my $date = Date::EzDate->new('14JAN2003');
# a little forgiveness is built in (notice oddly place comma)
my $date = Date::EzDate->new('14 January, 2003');
# epoch second (23:27:39, Tue Apr 10, 2001 if you're curious) my $date = Date::EzDate->new(986959659);
# yesterday
my $date = Date::EzDate->new('yesterday');
# tomorrow
my $date = Date::EzDate->new('tomorrow');
set_format allows you to specify a custom format for use later on.
For example, suppose you want a format of the form Monday, June 10, 2002.
You can specify that format using set_format like this:
$date->set_format('myformat', '{weekday long}, {month long} {day of month}, {year}');
print $date->{'myformat'}, "\n";
You can also create a custom format by simply assigning the format to its name.
If EzDate sees a { in the value being assigned, it knows that you are
assigning a format, not a date. The set_format line above could be written
like this:
$date->{'myformat'} = '{weekday long}, {month long} {day of month}, {year}';
Note that it's not necessary to store a custom format if you're only going to use it once. If you wanted the format above, but just once, you could output it like this:
print $date->{'{weekday long}, {month long} {day of month}, {year}'};
To delete a custom format, $mydate-del_format($name)>. To get the format
string itself, use $mydate-get_format($name)>.
If you use the same custom format in a lot of different places in your project, you might find it easier to create your own customer super-class of Date::EzDate so that you can set the custom formats in one place. See ``Super-classing Date::EzDate'' below.
clone()This method returns an EzDate object exactly like the object it was called
from. clone is much cheaper than creating a new EzDate object and then
setting the new object to have the same properties as another EzDate object.
set_warnings($warning_level)When EzDate receives invalid instructions, by default it outputs a warning and continues. For example, if you use a time/date format that EzDate doesn't recognize, it outputs a warning to STDERR and ens the attempt to set the date/time. There are two other ways that EzDate could handle the error: it could ignore the error completely, or it could end the entire program.
You can set which error handling you prefer with the set_warnings method.
The first and only argument indicates how to handle errors. There are three
possible values:
0 Do not handle error in any way
1 Output error to STDERR (default)
2 Output to STDERR and exit program
So, for example, the following code sets the warnings to level 2:
$date->set_warnings(2);
You can set the global default warning level by setting $Date::EzDate::default_warning. For example, the following code sets the global default level to 2:
$Date::EzDate::default_warning = 2;
next_month([integer])EzDate lacks an epochmonth month property (because months aren't all the
same length) so it needed a way to say ``same day, next month''. Calling
next_month w/o any argument moves the object to the same day in the next
month. If the day doesn't exist in the next month, such as if you move from
Jan 31 to Feb, then the date is moved back to the last day of the next month.
The only argument, which defaults to 1, allows you to move backward or forward any number of months. For example, the following command moves the date forward two months:
$mydate->next_month(2);
This command moves the date backward three months:
$mydate->next_month(-3);
next_month() handles year boundaries without problem. Calling
next_month() for a date in December moves the date to January of the next
year.
zero_hour_ampm(1|0)In general, EzDate operates on the principal that only date/time properties
that are explicitly changed are changed. However, this rule was confusing
people in one manner, so I changed the default behavior. If you set the hour
using the format hour am|pm (e.g. 4 am without specifying the minute or
second, then EzDate assumes you meant to set the minute and second to 0. So,
the following string sets the object to exactly 4:00:00 pm:
$date = Date::EzDate->new('4 pm');
If you would prefer the old behavior where the time would be set to whatever
the current minute and second are, then call zero_hour_ampm with an argument
of zero:
$date->zero_hour_ampm(0);
You can also pass zero_hour_ampm as an initial argument for new:
$date = Date::EzDate->new('January 31, 2002 1 am', zero_hour_ampm=>0);
after_create is intended for use when you are super-classing EzDate. By
default, after_create does nothing. See ``Super-classing Date::EzDate''
below for more details.
date_range_string($end_date)date_range_string outputs a string representing the range of days
from the EzDate date to some other date. The routine tries to make
the string as concise as possible, so that months and years are not
repeated if they are the same in both days. The single argument
to date_range_string is another EzDate object.
# same month and year
# outputs Mar 5-7, 2004
$start = Date::EzDate->new('Mar 5, 2004');
$end = Date::EzDate->new('Mar 7, 2004');
print $start->date_range_string($end);
# same year, different months
# outputs Feb 20-Mar 3, 2004
$start = Date::EzDate->new('feb 20, 2004');
$end = Date::EzDate->new('mar 3, 2004');
print $start->date_range_string($end);
# different years
# outputs Dec 23, 2004-Jan 3, 2005
$start = Date::EzDate->new('Dec 23, 2004');
$end = Date::EzDate->new('Jan 3, 2005');
print $start->date_range_string($end);
It does not matter if the EzDate object is earlier or later than the second date. The function will always return them with the earlier date first.
You can pass either an EzDate object or a string. So, for example, the following blocks of code output the same thing:
# outputs Mar 5-7, 2004
$start = Date::EzDate->new('Mar 5, 2004');
$end = Date::EzDate->new('Mar 7, 2004');
print $start->date_range_string($end);
# outputs Mar 5-7, 2004
$start = Date::EzDate->new('Mar 5, 2004');
print $start->date_range_string('Mar 7, 2004');
If both dates are the same day, then just that date will be returned:
# same day
# outputs Dec 23, 2004
$start = Date::EzDate->new('Dec 23, 2004');
print $start->date_range_string('Dec 23, 2004');
date_range_string can also be called a static method, i.e., without
ever explicitly defining an EzDate object:
# outputs Mar 5-7, 2004
print Date::EzDate::date_range_string('Mar 5, 2004', 'Mar 7, 2004');
If you load EzDate using the ':all' param, the function call is even simpler:
# note use of ':all'
use Date::EzDate ':all';
# outputs Mar 5-7, 2004
print date_range_string('Mar 5, 2004', 'Mar 7, 2004');
Array references in the argument list are expanded. So, for example, the following two lines of code produce the same thing:
print date_range_string('May 3, 2005', 'May 5, 2005');
print date_range_string( ['May 3, 2005', 'May 5, 2005'] );
This behavior was added to accomodate the output from day_lumps.
See the documentation of day_lumps for a practical example of
this feature.
time_range_string($end_time)time_range_string returns a string representation of a range of minutes.
For example, the following code outputs the range from 10:00 am to 2:00 pm:
# outputs 10:00am-2:00pm
$start = Date::EzDate->new('10:00am');
$end = Date::EzDate->new('2:00pm');
print $start->time_range_string($end);
time_range_string always tries to return the string as concisely as possible,
so if the two times have the same am/pm designation then am/pm is only output once:
# outputs 10:00-11:00am
$start = Date::EzDate->new('10:00am');
$end = Date::EzDate->new('11:00am');
print $start->time_range_string($end);
time_range_string can also be called as a static method, i.e. without
actually creating any EzDate objects:
# outputs 8:00-9:00pm
print Date::EzDate::time_range_string('8pm', '9pm');
If you load EzDate using the ':all' param, the function call is even simpler:
# note use of ':all'
use Date::EzDate ':all';
# outputs 8:00-9:00pm
print time_range_string('8pm', '9pm');
The earlier time is always output first. If you only pass times, not dates, then EzDate assumes that both times are on the same day and outputs the earlier time first:
# outputs 8:00-9:00pm
print time_range_string('8pm', '9pm');
If the time range crosses over midnight, you should explicitly indicate both dates:
# output jan 21, 2005 8pm
print time_range_string('jan 21, 2005 9pm', 'jan 22, 2005 5am');
day_lumps(@dates)day_lumps groups an array of dates into ``lumps'' of contiguous dates.
For example, consider the following dates:
Jan 3, 2005
Jan 4, 2005
Jan 5, 2005
Jan 6, 2005
Jan 10, 2005
Jan 15, 2005
Jan 16, 2005
Jan 17, 2005
That list of dates could be more concisely expressed like this:
Jan 3-6, 2005
Jan 10, 2005
Jan 15-17, 2005
day_lumps produces an array of day spans, each span containing
the start and end date of a single ``lump''. Here's the code to
produce the output from the example above:
# note use of ':all'
use Date::EzDate ':all';
my (@dates, @lumps);
@dates = (
'Jan 3, 2005',
'Jan 4, 2005',
'Jan 5, 2005',
'Jan 6, 2005',
'Jan 10, 2005',
'Jan 15, 2005',
'Jan 16, 2005',
'Jan 17, 2005',
);
@lumps = day_lumps(@dates);
foreach my $lump (@lumps)
{ print date_range_string($lump), "\n" }
This section lists the properties of an EzDate object.
Properties are case and space insensitive. Properties can be in upper or lower case, and you can put spaces anywhere to make them more readable. For example, the following properties are all the same:
weekdaylong
WEEKDAYLONG
WeekDay Long
Wee Kdaylong # ugly but works
Also, certain words can always be abbreviated.
minute == min
second == sec
number == num
ordinal == num
So, for example, the following two properties are the same:
$mydate->{'minute of day'};
$mydate->{'min of day'};
All of these properties are both readable and writable. Where there might be some confusion about what happens if you assign to the property more detail is given.
ampm uc returns AM or PM uppercased. ampm lc returns am or pm lowercased.
$mydate->{'weekday number'} = 1;
Then the object will adjust to Monday Apr 9, 2001.
$mydate->{'month number'} = 1;
Then day of month will be set to 28.
yearday does the same thing.
yeardaybase1 does the same thing.
04:48:01 pm, Tue Apr 10, 2001.
You can assign just about any common date and/or time format to this property.
Please take the previous statement as a challenge. I've aggressively tried to find formats that EzDate can't understand. When I've found one, I've modified the code to accomodate it. If you have some reasonably unambiguous date format that EzDate is unable to parse correctly, please send it to me. -Miko
When assigning a full date/time string, you can use 'noon' and 'midnight' to indicate specific times. So, for example, this string indicates July 25, 2003 and noon:
$mydate = Date::EzDate->new('July 23 2003 noon');
print $mydate->{'full'}; # outputs Wed Jul 23, 2003 12:00:00
$mydate = Date::EzDate->new('December 23 2003 17:45');
print $mydate->{'dayandtime'}; # outputs Dec 23 2003, 5:45pm
dayandtime is the default output format.
03JUN2004.
dayparam is a convenient string for passing as a parameter to scripts.
It consists of just alphanumerics (so it need not be escaped in any way), it
is easily human readable, and is completely unambiguous.
Actually, dayparam looks about the same as dmy, but it lowercased.
$date = Date::EzDate->new('Dec 1, 2004 12:54:15');
print $date->{'dayparam'}; # outputs 01dec2004
The following properties allow you to do date calculations at different granularities. All of these properties are both readable and writable.
The following properties are read-only and will crash if you try to assign to them.
You'll probably often want to retrieve more than one piece of information about a date/time at once. You could, of course, do this by getting each property individually and concatenating them together. For example, you might want to get the date in the format Monday, June 10, 2002. You could build that string like this:
$str =
$date->{'weekday long'} . ', ' .
$date->{'month long'} . ' ' .
$date->{'day of month'} . ', ' .
$date->{'year'};
That's a lot of typing, however, and it's difficult to tell from the code what the final string will look like. To make life EZ, EzDate allows you embed several date properties in a single call. Just surround each property with braces:
$str = $date->{'{weekday long}, {month long} {day of month}, {year}'};
EzDate allows you to store your custom date formats for repeated calls. This comes in handy for
formats that are needed in several places throughout a project. For example, suppose you want all
your dates in the format Monday, June 10, 2002. Of course, you could output them using a
format string like in the example above, but even that will get tiring if you need to output the
same format in several places. Much easier would be to set the format once. To do so, just call the
set_format method with the name of the format and the format itself:
$date->set_format('myformat', '{weekday long}, {month long} {day of month}, {year}');
print $date->{'myformat'}, "\n";
You can also create a custom format by simply assigning the format to its name.
If EzDate sees a { in the value being assigned, it knows that you are
assigning a format, not a date. The set_format line above could be written like this:
$date->{'myformat'} = '{weekday long}, {month long} {day of month}, {year}';
To make the Unix types happy you can format your dates using standard Un*x date codes. The format string must contain at least one % or EzDate won't know it's a format string. For example, you could output a date like this:
print $mydate->{'%h %d, %Y %k:%M %p'}, "\n";
which would give you something like this:
Oct 31, 2001 02:43 pm
Following is a list of codes. * indicates that the code acts differently than
standard Unix codes. x indicates that the code does not exists in standard Unix
codes.
%a weekday, short Mon %A weekday, long Monday %b * hour, 12 hour format, no leading zero 2 %B * hour, 24 hour format, no leading zero 2 %c full date Mon Aug 10 14:40:38 %d numeric day of the month 10 %D date as month/date/year 08/10/98 %e x numeric month, 1 to 12, no leading zero 8 %f x numeric day of month, no leading zero 3 %h short month Aug %H hour 00 to 23 14 %j day of the year, 001 to 366 222 %k hour, 12 hour format 14 %m numeric month, 01 to 12 08 %M minutes 40 %n newline %P x AM/PM PM %p * am/pm pm %r hour:minute:second AM/PM 02:40:38 PM %s number of seconds since start of 1970 902774438 %S seconds 38 %t tab %T hour:minute:second (24 hour format) 14:40:38 %w numeric day of the week, 0 to 6, Sun is 0 1 %y last two digits of the year 98 %Y four digit year 1998 %% percent sign %
If you plan on using the same custom formats in several different places in your project, you might find it easier to super-class EzDate so that your formats are loaded automatically whenever an object is created.
To super-class EzDate, it is actually necessary to super-class two classes:
Date::EzDate and Date::EzDate::Tie. For example, suppose you want to create
a class called MyDateClass. To do that, create a file called MyDateClass.pm,
store it in the root of one of the directories in your @INC path. Then put
both MyDateClass and MyDateClass::Tie packages in that file. The following
code can be used as a working template for super-classing EzDate. Notice that
we override the after_create() method in order to add a custom format.
after_create() is called by the new method after the new object has
been created but before it is returned.
package MyDateClass;
use strict;
use Date::EzDate;
use vars qw(@ISA);
@ISA = ('Date::EzDate');
# override after_create
sub after_create {
my ($self) = @_;
$self->set_format('myformat', '{weekdaylong}, {monthlong} {dayofmonth}, {year}');
}
##############################################################
package MyDateClass::Tie;
use strict;
use vars qw(@ISA);
@ISA = ('Date::EzDate::Tie');
# return true 1;
You can then load your class with code like this:
use MyDateClass; my ($date, $str);
$date = MyDateClass->new();
print $date->{'myformat'}, "\n";
EzDate is really two packages in one: the public object, and the private tied hash (which is where all the date info is stored). If you want to add a public method, add it in the main class (e.g. MyDateClass, not MyDateClass::Tie). Usually in those situations you'll need to use the private tied hash object (i.e. the object used internally by the tying mechanism). To get to that tied object, used the tied method, like this:
sub my_method {
my ($self) = @_;
my $ob = tied(%{$self});
# do stuff with $self and $ob
}
The routine for setting the year has an off-by-one problem which is kludgly fixed but which I haven't been able to properly solve.
EzDate is entirely based on the localtime() and timelocal() functions, so
it inherits their limitations. On my computer that means it can't handle dates
before Jan 1, 1902 or after Dec 31, 2037. Your mileage may vary.
The following list itemizes features I'd like to add to EzDate.
$mydate->{'%h %d %Y'} = 'Nov 1 2001';
This would come in handy when dealing with weirdly formatted dates. However, EzDate is already quite robust about handling weirdly formatted dates, so this feature is not as pressingly needed as it might seem.
I'm currently working on this feature.
Copyright (c) 2001-2003 by Miko O'Sullivan. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. This software comes with NO WARRANTY of any kind.
Miko O'Sullivan miko@idocs.com
DST patch submitted by Greg Estep.
Added a lotta functionality:
- Space insensitive property names
- Custom formats using braced property names
- Stored custom formats
- More supportive of super-classing
- All that and yet actually decreased the volume of code
- Decided this sucker's ready for 1.00 release
Also made a few minor not-so-backward-compatible changes:
- Got rid of the printabledate and printabletime properties, which
were just relics from an early project that used EzDate.
- Changed nextmonth to next_month to stay compatible with other
methods that were added and will be added.
- Tightened up the code a little
- Added warnings to situations where the IM in DWIM isn't always clear.
- Added stringification of EzDate object
- Added overloaded comparison operators
- Made not-backward-compatible change to the full format.
- Improved efficiency of custom formats
- Added ``noon'' and ``midnight'' keywords.
- Added feature that if Date::EzDate->new() is called with an unrecognized format, then
| |
| undef is returned. This allows you to check formats for validity. |
- Fixed off-by-one problem that occurred when, for example, moving Jan 1, 2003 back one year to 2002
- Non-backwards compatible change: EzDate objects now stringify to the ``default'' format instead of the ``full'' format.
- Added overloading of addition and subtraction.
- Added recognition of the following time format, which is used by PostGreSql: 2003-02-13 12:35:49.480975-05
- Fixed bug in which days of DST changeover produced off-by-one problem when setting hours.
- Fixed bug in which the epochday value for dates before the epoch are off-by-one.
- Added dayparam as built-in format.
- Fixed bug: system did not recognize ``day of week short'' in all the places it should.
- Added some default formats: fullday, fulldate, dayandtime
- changed default format to dayandtime
- Fixed some bogus documentation.
- Fixed bug such that new method dies on invalid date format even when it is supposed to just give a warning.
- Added date_range_string, time_range_string, methods
- Removing MANY tests from test.pl. I discovered that the tests that were failing were not proper tests for the module.
- Fixed monthnum issue. Now you can set monthnum to any integer, and it will roll the month forward or backwards that many times. The resulting monthnum will still always be from 0..11.
- Clarified example of using $Date::EzDate::overload
|
Date::EzDate - Date and time manipulation made easy |