C#
date manipulation
programming
code tutorial
datetime functions

Get the previous month's first and last day dates in c

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Finding the first and last day of the previous month is a common reporting task in C#. The cleanest solution is to anchor on the first day of the current month, then step backward in a way that avoids hard-coded month lengths.

A reliable DateTime approach

The easiest pattern is:

  1. build the first day of the current month
  2. subtract one day to get the last day of the previous month
  3. build the first day of that previous month
csharp
1using System;
2
3DateTime today = DateTime.Today;
4DateTime firstDayOfCurrentMonth = new DateTime(today.Year, today.Month, 1);
5DateTime lastDayOfPreviousMonth = firstDayOfCurrentMonth.AddDays(-1);
6DateTime firstDayOfPreviousMonth = new DateTime(
7    lastDayOfPreviousMonth.Year,
8    lastDayOfPreviousMonth.Month,
9    1);
10
11Console.WriteLine(firstDayOfPreviousMonth.ToString("yyyy-MM-dd"));
12Console.WriteLine(lastDayOfPreviousMonth.ToString("yyyy-MM-dd"));

This works for January as well. If today is in January, subtracting one day moves into December of the previous year automatically.

Wrap the logic in a reusable method

If you need the same calculation in reports, billing jobs, or API filters, move it into a method that returns both dates.

csharp
1using System;
2
3public static class DateRangeHelper
4{
5    public static (DateTime Start, DateTime End) GetPreviousMonthRange(DateTime referenceDate)
6    {
7        DateTime firstDayOfCurrentMonth = new DateTime(referenceDate.Year, referenceDate.Month, 1);
8        DateTime end = firstDayOfCurrentMonth.AddDays(-1);
9        DateTime start = new DateTime(end.Year, end.Month, 1);
10        return (start, end);
11    }
12}
13
14var range = DateRangeHelper.GetPreviousMonthRange(DateTime.Today);
15Console.WriteLine($"{range.Start:yyyy-MM-dd} to {range.End:yyyy-MM-dd}");

Passing a reference date instead of calling DateTime.Today inside the method makes the code easier to test and reuse.

An alternative using AddMonths

You can also start from the first day of the current month and step back exactly one month.

csharp
1using System;
2
3DateTime reference = DateTime.Today;
4DateTime firstDayOfCurrentMonth = new DateTime(reference.Year, reference.Month, 1);
5DateTime firstDayOfPreviousMonth = firstDayOfCurrentMonth.AddMonths(-1);
6DateTime lastDayOfPreviousMonth = firstDayOfCurrentMonth.AddDays(-1);

This version is compact and still safe because it never tries to guess how many days were in the previous month.

Include time boundaries when needed

Sometimes a report needs a full timestamp range rather than just dates. In that case, define clearly whether the end value is inclusive or exclusive.

An inclusive end:

csharp
DateTime start = firstDayOfPreviousMonth;
DateTime inclusiveEnd = lastDayOfPreviousMonth.Date.AddDays(1).AddTicks(-1);

A safer query boundary for databases is often an exclusive end:

csharp
DateTime start = firstDayOfPreviousMonth;
DateTime exclusiveEnd = firstDayOfCurrentMonth;

The exclusive-end pattern avoids precision problems and usually maps better to SQL predicates such as created_at >= start AND created_at < exclusiveEnd.

Prefer DateTimeOffset when timezone matters

If the range is used in a distributed system, think about timezone semantics early. DateTime.Today depends on the local machine timezone. For local desktop software that may be fine, but services often need an explicit offset or a UTC-based strategy.

csharp
1using System;
2
3DateTimeOffset now = DateTimeOffset.UtcNow;
4DateTime firstDayOfCurrentMonthUtc = new DateTime(now.Year, now.Month, 1, 0, 0, 0, DateTimeKind.Utc);
5DateTime lastDayOfPreviousMonthUtc = firstDayOfCurrentMonthUtc.AddDays(-1);

This is especially important when month boundaries drive billing or compliance logic.

Common Pitfalls

The biggest mistake is trying to compute the last day of the previous month by hard-coding month lengths or writing special cases for leap years. DateTime already handles those transitions correctly, so manual logic only creates bugs.

Another issue is mixing dates and datetimes without defining the boundary semantics. If your query says "up to the end of the previous month," decide whether that means midnight at the start of the last day, the final representable tick of that day, or an exclusive boundary at the next month.

Developers also run into timezone problems when a server in UTC and a user in a local timezone both talk about "previous month." The answer may differ near midnight on the first day of a month. Use a clear reference timezone if the result matters across systems.

Finally, avoid hiding the reference date inside the helper unless you truly want the current machine date every time. Explicit input makes tests deterministic and business rules easier to audit.

Summary

  • Build from the first day of the current month, then step backward.
  • 'AddDays(-1) and AddMonths(-1) handle year and month boundaries safely.'
  • Use a reusable helper that accepts a reference date for easier testing.
  • Define whether the end of the range is inclusive or exclusive.
  • Prefer explicit timezone handling when the date range is used across systems.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.