How many days to add for "semi-monthly"
Asked Answered
G

4

6

I have a enum type called PaymentFrequency whose values indicate how many payments per year are being made... So I have

public enum PaymentFrequency
{
    None             = 0,
    Annually         = 1,
    SemiAnnually     = 2,
    EveryFourthMonth = 3,
    Quarterly        = 4,
    BiMonthly        = 6,
    Monthly          = 12,
    EveryFourthWeek  = 13,
    SemiMonthly      = 24,
    BiWeekly         = 26,
    Weekly           = 52
}

Based on NumberOfPayments, PaymentFrequency, and FirstPaymentDate (of type DateTimeOffset) I want to calculate LastPaymentDate. But I am having issue figuring out how many time units (days, months) to add in case of SemiMonthly...

    switch (paymentFrequency)
    {
        // add years...
        case PaymentFrequency.Annually:
            LastPaymentDate = FirstPaymentDate.AddYears(NumberOfPayments - 1); 
            break;
        // add months...
        case PaymentFrequency.SemiAnnually:
            LastPaymentDate = FirstPaymentDate.AddMonths((NumberOfPayments - 1) * 6); // 6 months
            break;
        case PaymentFrequency.EveryFourthMonth:
            LastPaymentDate = FirstPaymentDate.AddMonths((NumberOfPayments - 1) * 4); // 4 months
            break;
        case PaymentFrequency.Quarterly:
            LastPaymentDate = FirstPaymentDate.AddMonths((NumberOfPayments - 1) * 3); // 3 months
            break;
        case PaymentFrequency.BiMonthly:
            LastPaymentDate = FirstPaymentDate.AddMonths((NumberOfPayments - 1) * 2); // 2 months
            break;
        case PaymentFrequency.Monthly:
            LastPaymentDate = FirstPaymentDate.AddMonths(NumberOfPayments - 1);
            break;
        // add days...
        case PaymentFrequency.EveryFourthWeek:
            LastPaymentDate = FirstPaymentDate.AddDays((NumberOfPayments - 1) * 4 * 7); // 4 weeks (1 week = 7 days)
            break;
        case PaymentFrequency.SemiMonthly:
            // NOTE: how many days in semi month? AddMonths (0.5) does not work :)
            LastPaymentDate = FirstPaymentDate.AddMonths((NumberOfPayments - 1) * 0.5); // 2 weeks (1 week = 7 days)
            break;
        case PaymentFrequency.BiWeekly:
            LastPaymentDate = FirstPaymentDate.AddDays((NumberOfPayments - 1) * 2 * 7); // 2 weeks (1 week = 7 days)
            break;
        case PaymentFrequency.Weekly:
            LastPaymentDate = FirstPaymentDate.AddDays((NumberOfPayments - 1) * 7); // 1 week (1 week = 7 days)
            break;
        case PaymentFrequency.None:
        default:
            throw new ArgumentException("Payment frequency is not initialized to valid value!", "paymentFrequency");
    }

So, how many days/months should I use when using SemiMonthly? Is this even possible without knowing exact # of days for each month in between? Or is this really simple, and I have just run out of caffeine and I am not seeing forest for the trees :)

Grijalva answered 12/12, 2011 at 22:3 Comment(6)
What's your business requirement for semi-monthly? Which two days every month? Are they fixed? Or just always 2 different days 15 days apart (i.e. 2 & 17, 3 & 18, 4 & 19, 5 & 20, etc)Trump
@James Michael Hare Let's say 1st and 16th for sake of question. I think I know where this is going...Grijalva
And do you know for a fact your first payment month will always correctly fall on 1st?Trump
What if the first payment is on Feb. 29?Strickler
They are always fixed, 15 days apart - for argument sake please use 1st and 16th of each month...Grijalva
@zam6ak: Try what I've got below and see if it meets your needs...Trump
T
7

For Semi-Monthly, if your first payment was always the 1st payment of the month as well (that is, anytime from the 1st to the 13th, starting after 13th is problematic as discussed in the comments), you could do as follows:

 // assuming first payment will be 1st of month, add month for every 2 payments
 // num payments / 2 (int division, remainder is chucked)
 // then add 15 days if this is even payment of the month
 LastPaymentDate = FirstPaymentDate.AddMonths((NumberOfPayments - 1) / 2)
     .AddDays((NumberOfPayments % 2) == 0 ? 15 : 0);

So for the 1st payment, this will add 0 months and 0 days so be 1st payment date. For 2nd payment, this will add 0 months (int dividision, remainder is chucked) and 15 days for 16th of month. For 3rd payment, this will add 1 month (1 / 3) and 0 days for 1st of next month, etc.

This is assuming that the FirstPaymentDate will be on the 1st of some given month. You can probably see where to go from here if you want to allow the 16th to be a starting date, etc.

Make sense?

So to illustrate, if we had:

DateTime LastPaymentDate, FirstPaymentDate = new DateTime(2011, 12, 5);

for(int numOfPayments=1; numOfPayments<=24; numOfPayments++)
{
    LastPaymentDate = FirstPaymentDate.AddMonths((numOfPayments - 1) / 2)
        .AddDays((numOfPayments % 2) == 0 ? 15 : 0);

    Console.WriteLine(LastPaymentDate);
}

This loop would give us:

12/5/2011 12:00:00 AM
12/20/2011 12:00:00 AM
1/5/2012 12:00:00 AM
// etc...
10/20/2012 12:00:00 AM
11/5/2012 12:00:00 AM
11/20/2012 12:00:00 AM
Trump answered 12/12, 2011 at 22:15 Comment(11)
Nice, thanks...If I wanted to have user specified semi monthly dates (1-15, 2-16, 3-17, etc, etc) would I just have to insure that 1) date diff = 15 days and 2) use the 2nd day in the 2nd part?Grijalva
@zam6ak: Yep, the 2nd date is always 15 days from the first in any semi-monthly scheme I've seen. The second part is always the literal 15. That is, the == 0 ? 15 : 0 part of the code is to add 15 days, it doesn't mean the 15th. Thus, this logic always works for any starting day that is < the 13th (greater than the 13th you start messing up february...)Trump
@zam6ak: Does that make sense? So even if FirstPaymentDate is 12/05/2011, this will correctly give you 12/05/2011 and 12/20/2011. The only place you'd run into problems is if the starting date is > 13, because then you'd have problems with february.Trump
So the above will work for any semi-monthly number of payments? (and as you mentioned, as long as FirstPaymentDate is < 13th (due to Feb)). I ask because code comment says "assuming first payment will be 1st of the month"Grijalva
@zam6ak: Yep, sorry, my first line about 1st strictly was incorrect, it works for any starting date up and including to 13th of month. You have to be a bit selective, because the 14th and 15th is no-man's land. You can't use 14th/15th as starter because 29th, 30th don't (always) exist in february. You could allow starts of 16th and beyond and subtract back 15, but then you'd have to avoid starts of 29th, 30th, 31st for same reason in reverse. Thus, I really think you're better off just allowing a start of 1st - 13th.Trump
@zam6ak: I clarified my opening line of the answer to suit our discussion.Trump
@zam6ak: Consider getting rid of this enum. You'll get into deep trouble in a few weeks/months. You'are on the straight way to get clumsy, unreadable code mate. Think OOP!Matheny
@James Michael Hare I think .AddDays part should be AddDays((numOfPayments-1 % 2) == 0 ? 15 : 0); perhaps?Grijalva
@Matheny I am using extension method on this enum...The compiler will compile it to static method anyways...But with extension method I can do simply something like: PaymentFrequency.SemiMonthly.GetPaymentDates(24, DateTimeOffset.Parse("1/1/2012")); and get a list of payment dates...Why is that not an OOP approach?Grijalva
Because it's not Object Oriented Programming :) it looks more like C-style procedural code. public static class with public static methods is nothing more than introducing C standards to the OOP language.Matheny
@zam6K: No, it's correct without the -1. You want the even payment (2nd, 4th, 6th, 8th, etc) to be the 15th. You can do -1 but then you'd check == 1 to get the odd payment instead.Trump
T
1

Because months have varying lengths, you can't just add a pre-defined number. You have to know which month you are dealing with, and go from there.

If you know that the 1st and the 16th of a month are due dates, then the last payment is December 16th (assuming you are calculating for a calendar year).

Theological answered 12/12, 2011 at 22:9 Comment(2)
Daniel - the LastPaymentDate depends on NumberOfPayments. So If I have SemiMonthly frequency with 48 payments, and with FirstPaymentDate of 01/01/1978 - what would be LastPaymentDate....I think, unlike in other cases, with SemiMonthly I will have to know the 2 dates and "walk" each payment ...Grijalva
There are only 4 cases: the first payment is on the 1st of the month and NumberOfPayments is odd; the first payment is on the 16th of the month and NumberOfPayments is odd; the first payment is on the 1st of the month and NumberOfPayments is even; the first payment is on the 16th of the month and the NumberOfPayments is event. In either case the last payment will be also either on the 1st, or the 16th. So, you can divide each month into 2 pieces: 1-15, 16-end of month. 2 periods per month, and you just need to calculate how many such periods are needed to finish the payment.Theological
E
1

The basic pairs for semi monthly payments are:

  • 1 and 16 (the 1st and 16th day of a month)
  • 15 and (2|3)? (the 15th and the last day of the month)

Peek and choose

Eveliaevelin answered 12/12, 2011 at 22:11 Comment(1)
@Eveliaevelin If we know it's 1st and 16th, how do we add them up?Reverie
E
0

I've recently had the same issue, but I needed to allow any date input. It's a bit of a mess and needs to be refactored, but this is what I came up with so far. February had some problems that I had to hack.

Date returnDate;

if (numberOfPayments % 2 == 0)
{
   returnDate = date.AddMonths(numberOfPayments / 2);

    if (date.Day == DateTime.DaysInMonth(date.Year, date.Month))//Last day of the month adjustment
    {
        returnDate = new Date(returnDate.Year, returnDate.Month, DateTime.DaysInMonth(returnDate.Year, returnDate.Month));
    }
}
else
{
    returnDate = date.Day <= 15 ? date.AddDays(15).AddMonths((numberOfPayments - 1) / 2) : date.AddDays(-15).AddMonths((numberOfPayments + 1) / 2);
    if (date.Day == DateTime.DaysInMonth(date.Year, date.Month))//Last day of the month adjustment
    {
        returnDate = new Date(returnDate.Year, returnDate.Month, 15);
    }
    else if (date.Month == 2 && date.Day == 14)
    {
        returnDate = returnDate.AddMonths(-1);
        returnDate = new Date(returnDate.Year, returnDate.Month, returnDate.Month == 2 ? 28 : 29);
    }
    else if (date.Month == 2 && date.Day == 15)
    {
        returnDate = returnDate.AddMonths(-1);
        returnDate = new Date(returnDate.Year, returnDateMonth, DateTime.DaysInMonth(returnDate.Year, returnDate.Month));
    }
}

return returnDate;
Elysha answered 22/3, 2017 at 18:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.