0
votes

Assume Financial Quarters always start on the 1st of a month and they are always 3 calendar months long.

Different organisations start their Financial Year (FY) in different months - some may be 1st April , some may be 1st July or could be just 1st Jan (which will match normal Calendar Quarters).

Given a date and a month that the FY starts on how can you determine the start of the quarter that the date falls in.

E.g.

DateTime getStartOfFinancialQtr(DateTime date, int monthFinancialYearStartsOn)

15th Jan when FY starts Jan would = 1st Jan

getStartOfFinancialQtr(new DateTime(2013,1,15), 1) == new DateTime(2013,1,1)

15th August when FY starts April would be 1st July

getStartOfFinancialQtr(new DateTime(2013,8,15), 4) == new DateTime(2013,7,1)

BUT 15th Jan 2013 when FY starts February would be 1st November 2012

getStartOfFinancialQtr(new DateTime(2013,1,15), 2) == new DateTime(2012,11,1)
4
Haven't you tried anything? If not, why? If you did try anything, please post your code and explain your problems in detail. This is trivial, you basically just have to code the same steps you used manually to determine the expected values of the samples you posted.Daniel Hilgarth
This is still a possible duplicate of Nearest completed quarter You can easily modify the QuartersInYear method to handle your definition of quarter.jason
Erm - do you want my browser history of searches, scan of paper I've got in front of me? I've not really been on stack overflow in a few months, when did it become a bearpit? :/Ryan
@Jason - I don't think so. Try it with the last example in the question above.Ryan
@Ryan: It still works. I can't stress this enough. Change the definition of QuartersInYear to return new List<DateTime>() { new DateTime(year, 2, 1), new DateTime(year, 5, 1), new DateTime(year, 8, 1), new DateTime(year, 11, 1), }; in the case that the Fiscal Year starts in February. Then DateTime d = new DateTime(2013, 1, 15); Console.WriteLine(d.NearestQuarterEnd()); will print 11/1/2012 12:00:00 AM to the console.jason

4 Answers

6
votes

The following solution is the most simple implementation I could think of and works without any - unnecessary - loops:

DateTime getStartOfFinancialQtr(DateTime date, int monthFinancialYearStartsOn)
{
    var actualMonth = date.Month;
    var financialYear = date.Year;
    var difference = actualMonth - monthFinancialYearStartsOn;
    if(difference < 0)
    {
        --financialYear;
        difference += 12;
    }
    var quarter = difference / 3;

    return new DateTime(financialYear, monthFinancialYearStartsOn, 1).AddMonths(quarter * 3);
}
3
votes

Isn't it as simple as this? Am I missing something to this? A quarter is defined as a period of three months, so you just have to find where the given date is, and then compute where the quarter begins based off that given month of the date.

public DateTime GetStartOfFinancialQtr(DateTime dtGiven, int startMonth) {
    DateTime dtQuarter = new DateTime(dtGiven.Year, startMonth, 1);

    // Start Q is less than the given date
    if(startMonth > dtGiven.Month) {
        while(dtQuarter > dtGiven) {
            dtQuarter = dtQuarter.AddMonths(-3);
        }
    }
    // Start Q is larger than the given date
    else {
        while(dtQuarter.Month + 3 <= dtGiven.Month) {
            dtQuarter = dtQuarter.AddMonths(3);
        }
    }

    return dtQuarter;
}

Below is the testing I ran:

Console.WriteLine(GetStartOfFinancialQtr(new DateTime(2013, 1, 15), 1).ToString());
Console.WriteLine(GetStartOfFinancialQtr(new DateTime(2013, 8, 15), 4).ToString());
Console.WriteLine(GetStartOfFinancialQtr(new DateTime(2013, 1, 15), 2).ToString());

Console output:

01/01/2013 000000
07/01/2013 000000
11/01/2012 000000
3
votes

You can use the Year class of the Time Period Library for .NET:

// ----------------------------------------------------------------------
public void FiscalYearRange()
{
  // calendar
  TimeCalendar fiscalYearCalendar = new TimeCalendar(
    new TimeCalendarConfig
      {
        YearBaseMonth = YearMonth.April,
        YearType = YearType.FiscalYear
      } );

  // time range
  TimeRange timeRange = new TimeRange( new DateTime( 2007, 10, 1 ), new DateTime( 2012, 2, 25 ) );
  Console.WriteLine( "Time range: " + timeRange );
  Console.WriteLine();

  // fiscal quarter
  Console.WriteLine( "Start Quarter: " + new Quarter( timeRange.Start, fiscalYearCalendar ) );
  Console.WriteLine( "End Quarter: " + new Quarter( timeRange.End, fiscalYearCalendar ) );
  Console.WriteLine();

  // fiscal year
  Year year = new Year( timeRange.Start, fiscalYearCalendar );
  while ( year.Start < timeRange.End )
  {
    Console.WriteLine( "Fiscal Year: " + year );
    year = year.GetNextYear();
  }
} // FiscalYearRange
1
votes

As mentioned, you can easily obtain the answer from Nearest Completed quarter. Here's how you make the modification:

public static class DateTimeExtensions {
    public static DateTime NearestQuarterEnd(
        this DateTime date,
        int firstMonthOfFiscalYear
    ) {
        IEnumerable<DateTime> candidates =
            QuartersInYear(date.Year, firstMonthOfFiscalYear)
                .Concat(QuartersInYear(date.Year - 1, firstMonthOfFiscalYear));
        return candidates.SkipWhile(d => d > date).First();
    }

    static Dictionary<Tuple<int, int>, List<DateTime>> dict =
        new Dictionary<Tuple<int, int>, List<DateTime>>();
    static IEnumerable<DateTime> QuartersInYear(
        int year,
        int firstMonthOfFiscalYear
    ) {
        Contract.Requires(firstMonthOfFiscalYear >= 1 
            && firstMonthOfFiscalYear <= 12);
        var key = Tuple.Create(year, firstMonthOfFiscalYear);
        if(dict.ContainsKey(key)) {
            return dict[key];
        }
        else {
            var value =
                Enumerable
                  .Range(0, 4)
                  .Select(k => firstMonthOfFiscalYear + 3 * k)
                  .Select(m => m <= 12 ? m : m % 12)
                  .Select(m => new DateTime(year, m, 1))
                  .OrderByDescending(d => d)
                  .ToList();
            dict.Add(key, value);
            return value;
        }
    }
}

Usage:

 Console.WriteLine(new DateTime(2013, 1, 15).NearestQuarterEnd(1));
 Console.WriteLine(new DateTime(2013, 8, 15).NearestQuarterEnd(4));
 Console.WriteLine(new DateTime(2013, 1, 15).NearestQuarterEnd(2));

Output:

1/1/2013 12:00:00 AM
7/1/2013 12:00:00 AM
11/1/2012 12:00:00 AM

This passes all three of your test cases.