I kept writing the same date formatting code over and over. Every CGI script, every log parser, every report generator needed dates in some particular format. So I finally sat down and wrote a proper package for it.
Date_Time.pm is an object-oriented Perl 5 package that handles date and time formatting. You create an instance, and then you can call methods on it to get dates in different formats. It supports today’s date, yesterday, tomorrow, and arbitrary offsets from today (any number of days forward or back).
The core data structures are straightforward. There is an array of month names:
@month_names = ("January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December");
And an array of days of the week:
@day_names = ("Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday");
The package uses these to give you human-readable dates instead of just numbers. Here is how you use it:
use Date_Time;
# Get today's date
my $today = Date_Time->today();
print $today->date_format_1(); # "June 2, 1996"
print $today->month_name(); # "June"
# Get yesterday
my $yesterday = Date_Time->yesterday();
print $yesterday->date_format_1();
# Get tomorrow
my $tomorrow = Date_Time->tomorrow();
# Arbitrary offset: 7 days from now
my $next_week = Date_Time->today(7);
The date_format_1 method returns dates like “June 2, 1996”, which is what I need most often for web pages. The month_name method gives you just the month. There are other methods for different formats, and adding new ones is simple because the object already has all the date components broken out.
It requires Perl 5.002 or later because it uses the object-oriented features that were added in Perl 5. If you are still running Perl 4, this will not work. But if you are doing web development in 1996, you should be on Perl 5 by now.
I wrote this at Philadelphia Newspapers where I build tools for our web sites. A lot of what we publish is date-sensitive. Stories need dates, archive pages are organized by date, and reports need timestamps. Having a clean, reusable package for this saves me from introducing bugs every time I rewrite the same logic in a new script.
The design is simple on purpose. I did not try to handle time zones or calendar arithmetic beyond day offsets. For what I need, which is formatting dates for display on web pages and in reports, it does the job well. If you need heavy date math, there are other tools for that. This is for the common case where you just need a nicely formatted date string.
The license is simple: feel free to use this without any obligations to me or anyone else. I have benefited from other people sharing their Perl code, and I want to do the same. If you find it useful, that is enough.
The package is available on my web site at http://rajiv.org/free/.
Here is the full source code for Date_Time.pm:
# file name: Date_Time.pm
# language: perl 5
# author: Rajiv Pant (Betul) [email protected] http://rajiv.org
# (c) 1996 Rajiv Pant (Betul)
# Feel free to use this without any obligations to me or anyone else.
# Version 1.02. 1996/May/28
# description:
# Date and Time package
package Date_Time ;
require 5.002 ;
use strict ;
local ($[) = 0 ; # start array index with 0 for this function
# just in case the program has it set to 1.
$Date_Time::author = <<'';
Rajiv Pant (Betul) [email protected] http://rajiv.org
$Date_Time::VERSION = 1.02 ; # 1.02 Fixed minor bug in yr method
@Date_Time::month_names = qw {
January February March April
May June July August
September October November December
} ;
# three letter abbriviations for the names of the months
@Date_Time::months = @Date_Time::month_names ;
grep (s/^(\w{3}).*$/$1/, @Date_Time::months) ;
@Date_Time::names_of_days_of_week = qw {
Sunday Monday Tuesday Wednesday
Thursday Friday Saturday
} ;
# three letter abbriviations for the names of the days of the week
@Date_Time::days_of_week = @Date_Time::names_of_days_of_week ;
grep (s/^(\w{3}).*$/$1/, @Date_Time::days_of_week) ;
1 ; # The modules needs to retun a true value
# ---------------- functions ----------------
sub new
{
my $self = {} ;
my $class = shift ;
my $arg = shift ;
# TO DO: consider allowing multiple instead of using elsif
# that will let people combine yesterday and last_year
if ($arg =~ /yesterday/i) { $self->{when} = -86400 ; }
elsif ($arg =~ /tomorrow/i) { $self->{when} = +86400 ; }
elsif ($arg =~ /other.day[^\-\+\d]?([\+\-\d]+)/i)
{
$self->{when} = $1 * 86400 ;
}
elsif ($arg =~ /other.timestamp[^\-\+\d]?([\+\-\d]+)/i)
{
$self->{when} = $1 ;
}
else
{
$self->{when} = 0 ;
}
bless $self, $class ;
} # end of sub new
# public methods
sub version { $Date_Time::VERSION }
# the following methods return the day, month, year, hour, etc.
# the following year_short method returns the current year minus 1900
# it is the same as yr for dates less than the year 2000.
# it is better to use yr to get the last two digits of the year
# NOTE: this version of this library was written to deal with dates within
# a few hunderd years around 1996. this interval is usually all that
# my programs using this library need. i may add support for a wider
# range in a later release if people request. don't worry about
# future additions to this library -- they will not affect any of
# the existing public methods that you use.
sub year_short
{
my $year_short = (&ctime)[5] ;
($year_short =~ /^\d\d(\d\d)$/) && ($year_short = $1) ;
$year_short ;
}
# returns all 4 digits of the year
sub year
{
my $year = &year_short + 1900 ;
}
# use this yr method to get the last two digits of the year
sub yr
{
my ($yr) = &year =~ /^\d\d(\d\d)$/ ;
$yr ;
}
# returns number of the month as one or two digits
# for eg. Sepetember is 9 October is 10
sub month_number_short
{
(&ctime)[4] + 1 ; # adding one because array subscripts start at 0 here
}
sub month_number
{
my $month_number_short = &month_number_short ;
my $month_number = length ($month_number_short) == 1 ?
'0'. $month_number_short : $month_number_short ;
}
# returns name of the month abbriviated to three letters with the
# first letter uppercase and next two in lowercase. eg. Sep
sub month
{
$Date_Time::months[&month_number - 1] ;
# subtracting 1 because array subscripts
# start at 0 here
}
# full name of the month
sub month_name
{
$Date_Time::month_names[&month_number - 1] ;
# subtracting 1 because array subscripts
# start at 0 here
}
# returns the nth day of the year.
# eg. January 1 is day 1, February 2 is the day 33
sub nth_day_of_year
{
(&ctime)[7] ;
}
# returns the day of the month. days before the 10th are returned as
# a single digit
sub day_of_month_short
{
(&ctime)[3] ;
}
# same as day_of_month_short
sub today_short
{
&day_of_month_short ;
}
# returns two digits for the day of the month.
# days before the 10th begin with a 0 as in 09
sub day_of_month
{
my $today_short = &today_short ;
my $today = length ($today_short) == 1 ? '0'. $today_short : $today_short ;
}
# same as day_of_month
sub day
{
&day_of_month ;
}
# same as day_of_month
sub today
{
&day_of_month ;
}
# returns the number of the day of the week.
# eg. Sunday = 1, Saturday = 7
sub nth_day_of_week
{
(&ctime)[6] + 1 ;
}
# returns name of the day of the week abbriviated to three letters with the
# first letter uppercase and next two in lowercase. eg. Mon
sub day_of_week
{
$Date_Time::days_of_week[&nth_day_of_week - 1] ;
# subtracting 1 because array subscripts
# start at 0 here
}
# full name of the day of the week
sub day_of_week_name
{
$Date_Time::names_of_days_of_week[&nth_day_of_week - 1] ;
# subtracting 1 because array subscripts
# start at 0 here
}
# same as day_of_week_name
sub full_day_of_week
{
&day_of_week_name ;
}
# times of the day
sub hour { (&ctime)[2] ; }
# returns the hours as two digits
sub hh
{
my $hour = &hour ;
my $hh = length ($hour) == 1 ? '0'. $hour : $hour ;
}
sub hours { &hh ; }
sub minute { (&ctime)[1] ; }
# returns the minutes as two digits
sub mm
{
my $minute = &minute ;
my $mm = length ($minute) == 1 ? '0'. $minute : $minute ;
}
sub minutes { &mm ; }
sub second { (&ctime)[0] ; }
# returns the seconds as two digits
sub ss
{
my $second = &second ;
my $ss = length ($second) == 1 ? '0'. $second : $second ;
}
sub seconds { &ss ; }
# some nicely formatted combinations
# eg. Saturday, March 16, 1996
sub date_format_1
{
my $self = shift ;
$self->day_of_week_name . ', ' .
$self->month_name . ' ' . $self->day_of_month_short . ', ' . $self->year ;
}
# eg. 14:42:49
sub time_format_1
{
my $self = shift ;
$self->hour . ':' . $self->mm . ':' . $self->second ;
}
# private methods
sub ctime
{
my $self = shift ;
my $time = time + $self->{when} ;
my
($seconds, $minutes, $hour, $day_of_month,
$month_number, $year_short, $nth_day_of_week, $nth_day_of_year,
$is_daylight_savings)
= localtime ($time) ;
($seconds, $minutes, $hour, $day_of_month,
$month_number, $year_short, $nth_day_of_week, $nth_day_of_year,
$is_daylight_savings) ;
} # --- end of sub ctime ---