A Date and Time Package for Perl

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 ---