Billing Period
May 18, 2018
I’m not sure of the origin of today’s exercise, but given the contrived nature of the calculation, I suspect it’s a programming exercise for beginning programming students:
Our merchants receive “weekly” invoices, following these rules:
- Each Saturday marks the beginning of a new billing period.
- Each 1st of a month marks the begining of a new billing period.
- Within a year, billing periods are numbered consecutively, starting with billing period 1 on January 1st.
Thus, a billing period can be referenced by a year and period number.
Your task is to write a program that calculates the billing period for a given date. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.
Mumps/Cache version
billingperiod ; q ; go(dt) ; n adt,dti,dts,dtsi,i,period s dti=$zdh(dt),dts="01/01/"_$p(dt,"/",3),dtsi=$zdh(dts) s period=0 f i=dtsi:1:dti d . s adt=$zd(i) . i $p(adt,"/",2)="01" s period=period+1 . e i i#7=2 s period=period+1 ; Saturday q period --- f dt="01/01/2018","01/05/2018","01/06/2018","01/12/2018","01/13/2018","01/19/2018","01/20/2018","01/26/2018","01/27/2018","01/31/2018","02/01/2018","08/31/2018","09/01/2018","09/02/2018" w !,dt," -- ",$$go^billingperiod(dt) 01/01/2018 -- 1 01/05/2018 -- 1 01/06/2018 -- 2 01/12/2018 -- 2 01/13/2018 -- 3 01/19/2018 -- 3 01/20/2018 -- 4 01/26/2018 -- 4 01/27/2018 -- 5 01/31/2018 -- 5 02/01/2018 -- 6 08/31/2018 -- 42 09/01/2018 -- 43 09/02/2018 -- 43What ?
Mumps/Cache version, with comments and expanded commands, etc
billingperiod ; quit ; go(dt) ; new adt,dti,dts,dtsi,i,period ; initialize variables set dti=$zdh(dt),dts="01/01/"_$p(dt,"/",3),dtsi=$zdh(dts) ; get internal date,external Jan 1 date,internal Jan 1 date set period=0 ; initialize period count for i=dtsi:1:dti do ; loop through dates from Jan 1 to target date . set adt=$zd(i) ; get external form of date from loop . if $piece(adt,"/",2)="01" set period=period+1 ; if loop date is first of the month, increment counter . else if i#7=2 set period=period+1 ; Otherwise, if loop date is Saturday, increment counter q period --- ; For each of these dates, print date and calculated period ($$go^billingperiod is the called function) for dt="01/01/2018","01/05/2018","01/06/2018","01/12/2018","01/13/2018","01/19/2018","01/20/2018","01/26/2018","01/27/2018","01/31/2018","02/01/2018","08/31/2018","09/01/2018","09/02/2018" write !,dt," -- ",$$go^billingperiod(dt) 01/01/2018 -- 1 01/05/2018 -- 1 01/06/2018 -- 2 01/12/2018 -- 2 01/13/2018 -- 3 01/19/2018 -- 3 01/20/2018 -- 4 01/26/2018 -- 4 01/27/2018 -- 5 01/31/2018 -- 5 02/01/2018 -- 6 08/31/2018 -- 42 09/01/2018 -- 43 09/02/2018 -- 43Here’s a solution in C.
#include <stdbool.h> #include <stdio.h> #include <stdlib.h> int calc_julian(int year, int month, int day) { int m1 = (month-14)/12; int y1 = year+4800; int julian = 1461*(y1+m1)/4; julian += (367*(month-2-12*m1))/12; julian += (-3*((y1+m1+100)/100))/4; julian += day-32075; return julian; } int calc_billing_period(int year, int month, int day) { int billing_period = 0; int start = calc_julian(year, 1, 1); int end = calc_julian(year, month, day); // Add the number of full weeks (each of these includes a Saturday). int n_days = end - start + 1; int n_full_weeks = n_days / 7; billing_period += n_full_weeks; // Add 1 if there is a Saturday in the partial week. int remainder = n_days % 7; int start_day = start % 7; bool another_saturday = (remainder > 0) && (start_day != 6) && (start_day + remainder - 1 >= 5); billing_period += another_saturday; // Add the months that didn't start on Saturday. for (int i = 1; i <= month; ++i) { billing_period += (calc_julian(year, i, 1) % 7) != 5; } return billing_period; } int main(int argc, char* argv[]) { if (argc != 4) { fprintf(stderr, "Usage: %s <YEAR> <MONTH> <DAY>\n", argv[0]); return EXIT_FAILURE; } int year = atoi(argv[1]); int month = atoi(argv[2]); int day = atoi(argv[3]); int billing_period = calc_billing_period(year, month, day); printf("%d\n", billing_period); return EXIT_SUCCESS; }Example:
In Ruby.
require 'date' def billing_period(date_str) target_date = Date.parse(date_str) jan_first = Date.new(target_date.year, 1,1) (jan_first..target_date).reduce(0) do |period, date| date.day == 1 || date.saturday? ? period + 1 : period end end [ '2018-01-01', '2018-01-03', '2018-01-06', '2018-01-10', '2018-01-26', '2018-02-01', '2018-05-18' ].each do |date| puts "#{date} -> #{billing_period(date)}" endOutput:
2018-01-01 -> 1
2018-01-03 -> 1
2018-01-06 -> 2
2018-01-10 -> 2
2018-01-26 -> 4
2018-02-01 -> 6
2018-05-18 -> 24