How to get the start and end times of a day

Viewed 128648

In my C# app, I pass a string variable that is of format yyyymmdd-yyyymmdd that represents a from and to date. I want to get the start and end times for these dates respectively. Currently I have the below code but was wondering if there was more of an elegant solution?

So for pdr = 20090521-20090523 would get "20090521 00:00:00" and "20090523 23:59:59"

private void ValidateDatePeriod(string pdr, out DateTime startDate, 
                                out DateTime endDate)
{
    string[] dates = pdr.Split('-');

    if (dates.Length != 2)
    {
        throw new Exception("Date period is of incorrect format");
    }

    if (dates[0].Length != 8 || dates[1].Length != 8)
    {
        throw new Exception("Split date periods are of incorrect format");
    }

    startDate = DateTime.ParseExact(dates[0] + " 00:00:00", 
        "yyyyMMdd HH:mm:ss", null);
    endDate = DateTime.ParseExact(dates[1] + "23:59:59", 
        "yyyyMMdd HH::mm:ss", null);
}
11 Answers

You could define two extension methods somewhere, in a utility class like so :

public static DateTime EndOfDay(this DateTime date)
{
    return new DateTime(date.Year, date.Month, date.Day, 23, 59, 59, 999);
}

public static DateTime StartOfDay(this DateTime date)
{
    return new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, 0);
}

And then use them in code like so :

public DoSomething()
{
    DateTime endOfThisDay = DateTime.Now.EndOfDay();
}

If you are only worried about .Net precision...

startDate = DateTime.ParseExact(dates[0], "yyyyMMdd");
endDate = DateTime.ParseExact(dates[1], "yyyyMMdd").AddTicks(-1).AddDays(1);

You really don't need to concatenate extra values onto the string for the time portion.


As an addendum, if you are using this for a query against, for example, a database...

startDate = DateTime.ParseExact(dates[0], "yyyyMMdd");
endDate = DateTime.ParseExact(dates[1], "yyyyMMdd").AddDays(1);

With a query of...

WHERE "startDate" >= @startDate AND "endDate" < @endDate

Then the precision issues noted in the comments won't really matter. The endDate in this case would not be part of the range, but the outside boundary.

public static class DateTimeExtension {        
    public static DateTime StartOfTheDay(this DateTime d) => new DateTime(d.Year, d.Month, d.Day, 0, 0,0);
    public static DateTime EndOfTheDay(this DateTime d) => new DateTime(d.Year, d.Month, d.Day, 23, 59,59);
}

The DateTime object has a property called Date which will return just the date portion. (The time portion is defaulted to 12:00 am).

I would recommend as a more elegant solution (IMHO) that if you want to allow any datetime on the last day, then you add 1 day to the date, and compare to allow times greater than or equal to the start date, but strictly less than the end date (plus 1 day).

// Calling code.  beginDateTime and endDateTime are already set.
// beginDateTime and endDateTime are inclusive.
// targetDateTime is the date you want to check.
beginDateTime = beginDateTime.Date;
endDateTime = endDateTime.Date.AddDays(1);

if ( beginDateTime <= targetDateTime &&
     targetDateTime < endDateTime )
   // Do something.

That's pretty much what I would do, with some small tweaks (really no big deal, just nitpicking):

  • The TryParse()/TryParseExact() methods should be used which return false instead of throwing exceptions.
  • FormatException is more specific than Exception
  • No need to check for Length == 8, because ParseExact()/TryParseExact() will do this
  • "00:00:00" and "23:59:59" are not needed
  • return true/false is you were able to parse, instead of throwing an exception (remember to check value returned from this method!)

Code:

private bool ValidateDatePeriod(string pdr, out DateTime startDate, 
                        out DateTime endDate)
{
   string[] dates = pdr.Split('-');

   if (dates.Length != 2)
   {
       return false;
   }

   // no need to check for Length == 8 because the following will do it anyway
   // no need for "00:00:00" or "23:59:59" either, I prefer AddDays(1)

   if(!DateTime.TryParseExact(dates[0], "yyyyMMdd", null, DateTimeStyles.None, out startDate))
      return false;

   if(!DateTime.TryParseExact(dates[1], "yyyyMMdd", null, DateTimeStyles.None, out endDate))
      return false;

   endDate = endDate.AddDays(1);
   return true;
}

The issue above regarding the few milliseconds can be resolved by querying the database with the next day's start date.

For example:

SELECT * FROM temp WHERE createdDate >= fromDate AND createdDate < toDate

Using the extension methods below you could set the from and to dates to:

DateTimeOffset fromDate = DateTimeOffset.UtcNow.StartOfDay();
DateTimeOffset toDate = DateTimeOffset.UtcNow.EndOfDay();
public static class DateExtentions
{
    public static DateTimeOffset StartOfDay(this DateTimeOffset dateTime)
    {
        return new DateTimeOffset(dateTime.Year, dateTime.Month, dateTime.Day, 0, 0, 0, 0, dateTime.Offset);
    }

    public static DateTimeOffset EndOfDay(this DateTimeOffset dateTime)
    {
        return dateTime.StartOfDay().AddDays(1);
    }

    public static DateTimeOffset StartOfMonth(this DateTimeOffset dateTime)
    {
        return new DateTimeOffset(dateTime.Year, dateTime.Month, 1, 0, 0, 0, 0, dateTime.Offset);
    }

    public static DateTimeOffset EndOfMonth(this DateTimeOffset dateTime)
    {
        return dateTime.StartOfMonth().AddMonths(1);
    }

    public static DateTimeOffset StartOfYear(this DateTimeOffset dateTime)
    {
        return new DateTimeOffset(dateTime.Year, 1, 1, 0, 0, 0, 0, dateTime.Offset);
    }

    public static DateTimeOffset EndOfYear(this DateTimeOffset dateTime)
    {
        return dateTime.StartOfYear().AddYears(1);
    }
}

In Java 8, you can do it using LocalDate as follows:

    LocalDate localDateStart = LocalDate.now();
    Date startDate = Date.from(localDateStart.atStartOfDay(ZoneId.systemDefault()).toInstant());

    LocalDate localDateEnd = localDateStart.plusDays(1);
    Date endDate = Date.from(localDateEnd.atStartOfDay(ZoneId.systemDefault()).toInstant());
Related