开发者

Calculating time until 1st or 15th of the month in python

开发者 https://www.devze.com 2022-12-29 04:43 出处:网络
I\'m trying to write a little budget program in python. This is my first program I\'m writing to learn python. The first step is to calcula开发者_Python百科te how many days until either the 1st or 15t

I'm trying to write a little budget program in python. This is my first program I'm writing to learn python. The first step is to calcula开发者_Python百科te how many days until either the 1st or 15th (paydays) depending on today's date. Can someone help me out a little?


Interesting question, and here's a complete solution. I'll start with my function definition, I've put this in a file named payday.py:

def nexypayday(fromdate=None):
    """
    @param fromdate: An instance of datetime.date that is the day to go from. If
                     not specified, todays date is used.
    @return: The first payday on or after the date specified.
    """

Next we need some tests. This is to clearly define the behaviour of our method. Because you're new to python, I'm going to go all out and give you an example of using unittests.

from unittest import TestCase, main
import payday
import datetime

class TestPayday(TestCase):
    def test_first_jan(self):
        self.assertEqual(payday.nextpayday(datetime.date(2010, 1, 1)),
                         datetime.date(2010, 1, 1))

    def test_second_jan(self):
        self.assertEqual(payday.nextpayday(datetime.date(2010, 1, 2)),
                         datetime.date(2010, 1, 15))

    def test_fifteenth_jan(self):
        self.assertEqual(payday.nextpayday(datetime.date(2010, 1, 15)),
                         datetime.date(2010, 1, 15))

    def test_thirty_one_jan(self):
        self.assertEqual(payday.nextpayday(datetime.date(2010, 1, 31)),
                         datetime.date(2010, 2, 1))

    def test_today(self):
        self.assertTrue(payday.nextpayday() >= datetime.date.today())

if __name__ == '__main__':
    main()

This is a runnable python module. You can go ahead and name that test_payday.py and run it with python test_payday.py. This should immediately fail with various error messages because we've not got the right code written yet.

After some fiddling with how datetime.date works, I've worked out: mydatetime.day is the day of the month, mydatetime + datetime.timedelta(days=1) will create a new datetime one day on in the year. Thus I can throw together this in payday.py.

import datetime

def nextpayday(fromdate=None):
    """
    @param fromdate: An instance of datetime.date that is the day to go from. If
                     not specified, todays date is used.
    @return: The first payday on or after the date specified.
    """
    if fromdate is None:
        fromdate = datetime.date.today()

    # while the day of the month isn't 1 or 15, increase the day by 1
    while fromdate.day not in (1, 15):
        fromdate = fromdate + datetime.timedelta(days=1)

    return fromdate

Run the unit tests and it should be all golden. Note that in my tests I've made it that if I check what the 'next' payday is from a payday, it returns its own day. Changing that to returning the 'next' payday is left as an exercise for the reader.


I don't want to entirely spoil your learning experience by just typing the answer, but the Python library makes this quite easy. Have a look at the datetime module, particularly the date and timedelta classes.


The classes in the datetime module will help.

You just need to check whether it's after the 15th of the month. If it is, find the 1st of the next month. If it isn't, find the 15th of the current month.


This will work with any reasonable paydays ... sorted, and all(1 <= payday <= 28 for payday in paydays) # otherwise workers strike every February.

from datetime import date

def calculate_days_to_payday(start_date=None, paydays=(1, 15)):
    if start_date is None:
        start_date = date.today()
    day = start_date.day
    for payday in paydays:
        if payday >= day:
            return payday - day
    # next payday is in next month
    month = start_date.month + 1
    year = start_date.year
    if month == 13:
        month = 1
        year += 1
    return (date(year, month, paydays[0]) - start_date).days

if __name__ == "__main__":
    tests = (
        (1, 12, 0),
        (2, 12, 13),
        (14, 12, 1),
        (15, 12, 0),
        (16, 12, 16),
        (31, 12, 1),
        )
    for d, m, expected in tests:
        actual = calculate_days_to_payday(date(2009, m, d))
        print "%5s" * 5 % (d, m, expected, actual, actual == expected)


Ugly with no docstrings, but quite a bit lighter than brute-forcing with timedelta in a while loop at the end of the month...

import datetime
from itertools import cycle

PAYDAYS = (1, 8, 15, 22, 20, 25, 30)

def calculate_days_difference(date_, day=None, next_month=False):
    day = day or date_.day
    year = date_.year
    month = date_.month
    if next_month:
        if month == 12:
            year = year + 1
            month = 1
        else:
            month = month + 1    
    return (datetime.date(year, month, day) - date_).days

def calculate_days_to_payday(date_, paydays=PAYDAYS):
    day = date_.day
    if day in paydays:
        return 0
    if day > max(paydays):
        return calculate_days_difference(date_, paydays[0], True)
    for payday in cycle(paydays):
        if (day > payday):
            continue
        return calculate_days_difference(date_, payday)

Usage:

test_data = (
    ((2010,04,28), 2),
    ((2010,04,01), 0),
    ((2010,04,30), 0),
)

for _input, _output in test_data:
    d = datetime.date(*input)
    for i in xrange(1000000):
        assert calculate_days_to_payday(d) == _output
0

精彩评论

暂无评论...
验证码 换一张
取 消