Billing Period
May 18, 2018
We provide a function period that takes a year, month and day and returns the billing period within the requested year:
(define (period year month day)
(define (first-of-month? julian)
(call-with-values
(lambda () (gregorian julian))
(lambda (year month day) (= day 1))))
(do ((j (julian year 1 1) (+ j 1))
(p 0 (+ p (if (or (= (modulo j 7) 5) (first-of-month? j)) 1 0))))
((< (julian year month day) j) p)))
Here’s an example:
> (period 2018 5 18)
24
The 24 days on which billing periods start are:
1/1, 1/6, 1/13, 1/20, 1/27
2/1, 2/3, 2/10, 2/17, 2/24
3/1, 3/3, 3/10, 3/17, 3/24, 3/31
4/1, 4/7, 4/14, 4/21, 4/28
5/1, 5/5, 5/12
It is easy to see that the program is correct; it examines each day from January 1st to the requested date and increments a counter if the day is the 1st of a month or a Saturday. There’s another algorithm that divides the number of days from January 1st to the requested date by 7, then adjusts for Saturdays at the ends of the range, but after several tries I was unable to get it working, so I gave up. The moral of the story is that you want to write clear, simple code.
You can run the program at https://ideone.com/UhtqXu.
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