## 13.2. phonebill--Track Phone Usage

Contributed by Nick Holloway

The problem is to calculate the cost of phone calls made. In the United Kingdom, charges are made for the number of "units" used during the duration of the call (no free local calls). The length of time a "unit" lasts depends on the charge band (linked to distance) and the charge rate (linked to time of day). You get charged a whole unit as soon as the time period begins.

The input to the program is four fields. The first field is the date (not used). The second field is "band/rate" and is used to look up the length a unit will last. The third field is the length of the call. This can either be "ss," "mm:ss," or "hh:mm:ss". The fourth field is the name of the caller. We keep a stopwatch (old cheap digital), a book, and a pen. Come bill time this is fed through my awk script. This only deals with the cost of the calls, not the standing charge.

The aim of the program was to enable the minimum amount of information to be entered by the callers, and the program could be used to collect together the call costs for each user in one report. It is also written so that if British Telecom changes its costs, these can be done easily in the top of the source (this has been done once already). If more charge bands or rates are added, the table can be simply expanded (wonders of associative arrays). There are no real sanity checks done on the input data. The usage is:

>     phonebill [ file ... ]

Here is a (short) sample of input and output.

Input:

>     29/05   b/p      5:35   Nick
>     29/05   L/c   1:00:00   Dale
>     01/06   L/c     30:50   Nick

Output:

>     Summary for Dale:
>     	29/05   L/c  1:00:00  11 units
>     Total: 11 units @ 5.06 pence per unit = $0.56
>     Summary for Nick:
>     	29/05   b/p     5:35  19 units
>     	01/06   L/c    30:50   6 units
>     Total: 25 units @ 5.06 pence per unit = $1.26

The listing for phonebill follows:

>     #!/bin/awk -f
>     #------------------------------------------------------------------
>     #   Awk script to take in phone usage - and calculate cost for each
>     #   person
>     #------------------------------------------------------------------
>     #   Author: N.Holloway (alfie@cs.warwick.ac.uk)
>     #   Date  : 27 January 1989
>     #   Place : University of Warwick
>     #------------------------------------------------------------------
>     #   Entries are made in the form
>     #	Date   Type/Rate   Length  Name
>     #
>     #   Format:
>     #	Date		: "dd/mm"		- one word
>     #	Type/Rate	: "bb/rr"  (e.g. L/c)
>     #	Length		: "hh:mm:ss", "mm:ss", "ss"
>     #	Name		: "Fred"		- one word (unique)
>     #------------------------------------------------------------------
>     #   Charge information kept in array 'c', indexed by "type/rate",
>     #   and the cost of a unit is kept in the variable 'pence_per_unit'
>     #   The info is stored in two arrays, both indexed by the name. The
>     #   first 'summary' has the lines that hold input data, and number
>     #   of units, and 'units' has the cumulative total number of units
>     #   used by name.
>     #------------------------------------------------------------------

>     BEGIN \
>         {
>     	# --- Cost per unit
>     	pence_per_unit  = 4.40		# cost is 4.4 pence per unit
>     	pence_per_unit *= 1.15		# VAT is 15%

>     	# --- Table of seconds per unit for different bands/rates
>     	#     [ not applicable have 0 entered as value ]
>     	c ["L/c"] = 330 ;  c ["L/s"] = 85.0;  c ["L/p"] = 60.0;
>     	c ["a/c"] =  96 ;  c ["a/s"] = 34.3;  c ["a/p"] = 25.7;
>     	c ["b1/c"]= 60.0;  c ["b1/s"]= 30.0;  c ["b1/p"]= 22.5;
>     	c ["b/c"] = 45.0;  c ["b/s"] = 24.0;  c ["b/p"] = 18.0;
>     	c ["m/c"] = 12.0;  c ["m/s"] = 8.00;  c ["m/p"] = 8.00;
>     	c ["A/c"] = 9.00;  c ["A/s"] = 7.20;  c ["A/p"] = 0   ;
>     	c ["A2/c"]= 7.60;  c ["A2/s"]= 6.20;  c ["A2/p"]= 0   ;
>     	c ["B/c"] = 6.65;  c ["B/s"] = 5.45;  c ["B/p"] = 0   ;
>     	c ["C/c"] = 5.15;  c ["C/s"] = 4.35;  c ["C/p"] = 3.95;
>     	c ["D/c"] = 3.55;  c ["D/s"] = 2.90;  c ["D/p"] = 0   ;
>     	c ["E/c"] = 3.80;  c ["E/s"] = 3.05;  c ["E/p"] = 0   ;
>     	c ["F/c"] = 2.65;  c ["F/s"] = 2.25;  c ["F/p"] = 0   ;
>     	c ["G/c"] = 2.15;  c ["G/s"] = 2.15;  c ["G/p"] = 2.15;
>         }

>         {
>     	spu = c [ $2 ]				# look up charge band
>     	if ( spu == "" || spu == 0 ) {
>     	    summary [ $4 ] = summary [ $4 ] "\n\t" \
>     			    sprintf ( "%4s  %4s  %7s   ? units",\
>     	                          $1, $2, $3 ) \
>     			    " - Bad/Unknown Chargeband"
>     	} else {
>     	    n = split ( $3, t, ":" )  # calculate length in seconds
>     	    seconds = 0
>     	    for ( i = 1; i <= n; i++ )
>     		seconds = seconds*60 + t[i]
>     	    u = seconds / spu   # calculate number of seconds
>     	    if ( int( u ) == u )   # round up to next whole unit
>     		u = int( u )
>     	    else
>     		u = int( u ) + 1
>     	    units [ $4 ] += u   # store info to output at end
>     	    summary [ $4 ] = summary [ $4 ] "\n\t" \
>     			    sprintf ( "%4s  %4s  %7s %3d units",\
>     	                         $1, $2, $3, u )
>     	}
>         }

>     END \
>         {
>     	for ( i in units ) {		# for each person
>     	    printf ( "Summary for %s:", i ) # newline at start
>                                                 # of summary
>     	    print summary [ i ]			# print summary details
>     	    # calc cost
>     	    total = int ( units[i] * pence_per_unit + 0.5 )
>     	    printf ( \
>     		"Total: %d units @ %.2f pence per unit = $%d.%02d\n\n", \
>     			    units [i], pence_per_unit, total/100, \
>                                                    total%100 )
>     	}
>         }

### 13.2.1. Program Notes for phonebill

This program is another example of generating a report that consolidates information from a simple record structure.

This program also follows the three-part structure. The BEGIN procedure defines variables that are used throughout the program. This makes it easy to change the program, as phone companies are known to "upwardly revise" their rates. One of the variables is a large array named c in which each element is the number of seconds per unit, using the band over the rate as the index to the array.

The main procedure reads each line of the user log. It uses the second field, which identifies the band/rate, to get a value from the array c. It checks that a positive value was returned and then processes that value by the time specified in $3. The number of units for that call is then stored in an array named units, indexed by the name of the caller ($4). This value accumulates for each caller.

Finally, the END routine prints out the values in the units array, producing the report of units used per caller and the total cost of the calls.

Copyright © 2003 O'Reilly & Associates. All rights reserved.