pythonpython-datetime

What's the most elegant way to get the end of the day (datetime)?


I'm currently writing some reporting code that allows users to optionally specify a date range. The way it works (simplified), is:

Here's a code snippet, along with comments describing what I'd like to do:

from datetime import datetime, timedelta

# ...

now = datetime.now()
start_time = now.replace(hour=0, minute=0, second=0, microsecond=0)
stop_time = now
# If the user enters no year, month, or day--then we'll simply run a
# report that only spans the current day (from the start of today to now).

if options['year']:
    start_time = start_time.replace(year=options['year'], month=0, day=0)
    stop_time = stop_time.replace(year=options['year'])
    # If the user specifies a year value, we should set stop_time to the last
    # day / minute / hour / second / microsecond of the year, that way we'll
    # only generate reports from the start of the specified year, to the end
    # of the specified year.

if options['month']:
    start_time = start_time.replace(month=options['month'], day=0)
    stop_time = stop_time.replace(month=options['month'])
    # If the user specifies a month value, then set stop_time to the last
    # day / minute / hour / second / microsecond of the specified month, that
    # way we'll only generate reports for the specified month.

if options['day']:
    start_time = start_time.replace(day=options['day'])
    stop_time = stop_time.replace(day=options['day'])
    # If the user specifies a day value, then set stop_time to the last moment of
    # the current day, so that reports ONLY run on the current day.

I'm trying to find the most elegant way to write the code above--I've been trying to find a way to do it with timedelta, but can't seem to figure it out. Any advice would be appreciated.


Solution

  • After looking at some of the answers here, and not really finding anything extremely elegant, I did some poking around the standard library and found my current solution (which I like quite well): dateutil.

    Here's how I implemented it:

    from datetime import date
    from dateutil.relativedelta import relativedelta
    
    now = date.today()
    stop_time = now + relativedelta(days=1)
    start_time = date(
        # NOTE: I'm not doing dict.get() since in my implementation, these dict
        # keys are guaranteed to exist.
        year = options['year'] or now.year,
        month = options['month'] or now.month,
        day = options['day'] or now.day
    )
    
    if options['year']:
        start_time = date(year=options['year'] or now.year, month=1, day=1)
        stop_time = start_time + relativedelta(years=1)
    
    if options['month']:
        start_time = date(
            year = options['year'] or now.year,
            month = options['month'] or now.month,
            day = 1
        )
        stop_time = start_time + relativedelta(months=1)
    
    if options['day']:
        start_time = date(
            year = options['year'] or now.year,
            month = options['month'] or now.month,
            day = options['day'] or now.day,
        )
        stop_time = start_time + relativedelta(days=1)
    
    # ... do stuff with start_time and stop_time here ...
    

    What I like about this implementation, is that python's dateutil.relativedata.relativedata works really well on edge cases. It gets the days/months/years correct. If I have month=12, and do relativedata(months=1), it'll increment the year and set the month to 1 (works nicely).

    Also: in the above implementation, if the user specifies none of the optional dates (year, month, or day)--we'll fallback to a nice default (start_time = this morning, stop_time = tonight), that way we'll default to doing stuff for the current day only.

    Thanks to everyone for their answers--they were helpful in my research.