分析日期并每月打印下一个日期

时间:2018-04-10 13:18:57

标签: python python-3.x date datetime

对于我目前正在处理的程序的功能,我需要能够分析日期,然后每月返回所有即将到来的日期。

e.g。 插入:13/04/2018 - >这是星期五四月的第二个星期。 返回:即将到来的几个月的第二个星期五(11/05/201,8/06/201,18/07/201,...)

另一个限制是,如果即将到来的一个月的第一天是在星期六或星期日,那么这不算作第一周。第一周从第一周算起,有更多的日期而不是周末。 例如:4月1日是星期日,所以4月的第一周从2日开始到8日。或者6月1日是星期五,所以6月的第一周是5月28日到6月3日。

我尝试过做一些事情,但似乎没有回复正确的日期。

startdate = input("Enter a startdate: ")
startdate = datetime.strptime(startdate, "%Y-%m-%d").date()
dayOfTheWeek = startdate.weekday()
first_day = startdate.replace(day=1)
cal = calendar.Calendar()
weeks = cal.monthdayscalendar(startdate.year, startdate.month)
if(first_day.isoweekday() in (6,7)): adjustRange = 1
else: adjustRange = 0
for x in range(len(weeks)):
    if startdate.day in weeks[x]:
        weekNumb = x-adjustRange

for x in range(0, 5):
    tempWeek = weekNumb
    print("Date: ", startdate)
    if(startdate.month + 1 > 12): added_months = 1
    else: added_months = startdate.month + 1
    weeks = cal.monthdayscalendar(startdate.year, added_months)

    first_day = startdate.replace(day=1)
    if(first_day.isoweekday() in (6,7)): tempWeek += 1

    plannedDay = weeks[tempWeek][dayOfTheWeek]

    if(added_months < 10): added_months = str(added_months).zfill(2)
    datestr = "{}-{}-{}".format(startdate.year, added_months, plannedDay)
    startdate = datetime.strptime(datestr, "%Y-%m-%d").date()

输出:

Enter a startdate: 2018-04-13
Date:  2018-04-13
Date:  2018-05-18 --> must be 2018-05-11
Date:  2018-06-08 --> OK 
Date:  2018-07-06 --> must be 2018-07-13
Date:  2018-08-17 --> must be 2018-08-10

看起来他们已经休息了一周,无论是太多还是短暂。

基本上,我得到星期几(0-6),然后询问当月的第一天。然后,使用日历模块,我询问该月的所有周和日期,并检查插入日期所在的周。如果一个月的第一天是在星期六或星期日(6或7使用.isoweekday()),那么我们必须从周数中减去一个来获得实际的周数。

然后,我分析所有进一步的日期,如果他们的first_day也在星期六或星期日,我必须在tempWeek(其中包含原始的weekNumb)中添加一个,因为我试图从周元组中提取日期我收到了日历。

我希望这种方法有道理,而且我的代码有点可读。

3 个答案:

答案 0 :(得分:2)

如果你pip install python-dateutil

from dateutil.rrule import rrule, MONTHLY, weekdays
from dateutil.parser import parse
import math

def month_week(date):
    return math.ceil((date - date.replace(day=1)).days / 7 ) or 1

start_date = parse(input("Enter a startdate: "))
for day in rrule(freq=MONTHLY,
                 count=5,
                 dtstart=start_date,
                 byweekday=weekdays[start_date.weekday()](
                     month_week(start_date)),
                 ):
    print("Date: ", day.date())

将按照您的预期进行2018-04-13。

根据您的问题,我不确定您对2018 - 09 - 01(9月1日星期六)或2018 - 01 - 29(1月5日星期五)的预期产量是多少。因此,您可能希望更改month_week函数以返回您期望的月份的周数。

答案 1 :(得分:1)

当你输出接下来的4天兴趣时,你的for循环就出现了大问题。你有一行,first_day = startdate.replace(day=1),你正在通过该结果的isoweek设置tempWeek。问题是,您没有更新月份以反映added_months。这会抛弃一切。因此,将行更改为

first_day = startdate.replace(day=1, month=added_months)

但是你还需要跟踪岁月 - 将月份从12推到1可能很棒,但是当你这样做时,你也需要更新年份。这不会影响您的即时测试,但是您在年底前尝试的任何其他测试都会受到影响。

for x in range(0, 5):
    tempWeek = weekNumb
    print("Date: ", startdate)
    if(startdate.month + 1 > 12): 
        added_months = 1
        added_years = startdate.year + 1
    else: 
        added_months = startdate.month + 1
        added_years = startdate.year
    weeks = cal.monthdayscalendar(added_years, added_months)

    first_day = startdate.replace(day=1, month=added_months, year=added_years)
    if(first_day.isoweekday() in (6,7)): tempWeek += 1

    plannedDay = weeks[tempWeek][dayOfTheWeek]

    if(added_months < 10): added_months = str(added_months).zfill(2)
    datestr = "{}-{}-{}".format(startdate.year, added_months, plannedDay)
    startdate = datetime.strptime(datestr, "%Y-%m-%d").date()

这样做可以解决您的问题。

额外提示:

  • 我只是通过在交互式python shell中输入每一行来解决这个问题,每当我设置一个变量时,我都会在之后查看它。随着你越来越好,你可能会学会开始使用调试器,这会给你带来同样的效果,但更容易。

  • 我建议不要踩踏startdate。你所做的是完全有效的,但是你保留一些变量并踩踏别人是很奇怪的。为了与您拥有的变量名称保持一致,我建议added_date,但在我的代码中,我将其更改为first_date

  • 使用字符串表示形式覆盖added_months(整数)也很奇怪(但允许)。我再次使用不同的变量,例如str_months。让一个变量做一件事通常可以使阅读更顺畅。在下面的示例代码中,我通过利用format的功能消除了对字符串的需求。

  • if语句的结果与if放在同一行,这样做通常很好。现在,空白非常便宜,它使阅读更容易。

  • 如果for循环的最终结果是在结尾而不是中途,那么事情会更清晰。

  • 此外,你应该渴望在内部与你的名字保持一致,无论是camelCase还是underscored_names,但理想情况下不是两者兼而有之。

  • 它是pythonic,以避免在任何可能的情况下使用for循环,通常如果你使用&#34;对于len中的x(...&#34;,那么更有效的方式正如评论中所提到的,枚举可以做到这一点。

  • python中一个不错的标准是将_用于你不关心的变量,就像在你的固定for循环中一样。

以上代码是让代码正常工作的极简主义变化。根据我刚才提到的提示,下面是我要做的修改。此外,我包含了import语句,因为没有它们,代码不可执行。我们总是尝试在Stack Overflow上发布可执行代码。

from datetime import datetime
import calendar

start_date = input("Enter a startdate: ")

start_date = datetime.strptime(start_date, "%Y-%m-%d").date()
day_of_the_week = start_date.weekday()
first_day = start_date.replace(day=1)
cal = calendar.Calendar()
weeks = cal.monthdayscalendar(start_date.year, start_date.month)
# This if statement could be a single line: 
# adjust_range = 1 if first_day.isoweekday() in (6,7) else 0
if(first_day.isoweekday() in (6,7)):
    adjust_range = 1
else: 
    adjust_range = 0

for x, week in enumerate(weeks):
    if start_date.day in week:
        week_numb = x - adjust_range
        # This will happen only once, so you can break out of the loop
        break

print("Date: ", start_date)

first_date = start_date.replace(day=1)
for _ in range(4):
    temp_week = week_numb
    if(first_date.month + 1 > 12): 
        new_months = 1
        new_years = first_date.year + 1
    else: 
        new_months = first_date.month + 1
        new_years = first_date.year

    weeks = cal.monthdayscalendar(new_years, new_months)
    first_date = first_date.replace(day=1, month=new_months, year=new_years)
    if(first_date.isoweekday() in (6,7)):
        temp_week += 1

    planned_day = weeks[temp_week][day_of_the_week]

    # The if wasn't needed; let format do the work for you.
    print("{}-{:02d}-{:02d}".format(new_years, new_months, planned_day))

    # You could set something like 
    # interested_date = datetime(added_years, added_months, planned_day), 
    # but you don't seem to use it.

答案 2 :(得分:1)

我一直在玩这个。给出的答案都很有帮助,并帮助我进一步了解问题,谢谢你。您可能会在这里看到我使用过的其他人的一些代码片段。在这种情况下,我决定使用Calendar模块来帮助我。

import calendar
from datetime import datetime
from datetime import timedelta
import math


cal = calendar.Calendar()

start_date = datetime.strptime(input("Enter a start date: "), "%Y-%m-%d").date()
end_date = datetime.strptime(input("Enter an end date: "), "%Y-%m-%d").date()
days_difference = (end_date - start_date).days
week_number = math.ceil((start_date - start_date.replace(day=1)).days / 7) - 1
weekday = start_date.weekday()

for _ in range(15):
    weeks = cal.monthdayscalendar(start_date.year, start_date.month)

    for x, week in enumerate(weeks):
        if(week[weekday] == 0):
            weeks.pop(x)
            break

    if(len(weeks) == 5 and (week_number+1) == 4):
        if(weeks[len(weeks)-1][weekday] != 0 and weeks[0][weekday] != 0):
            weeks.pop(0)

    new_weekday = weeks[week_number][weekday]

    start_date = datetime(start_date.year, start_date.month, new_weekday).date()
    print(start_date)

    for x in range(1, days_difference+1):
        start_date += timedelta(days=1)
        print(start_date)

    if(start_date.month + 1 > 12):
        new_year = start_date.year + 1
        new_month = 1
    else:
        new_month = start_date.month + 1
        new_year = start_date.year

    start_date = datetime(new_year, new_month, 1).date()

    print("")

输出:

Enter a start date: 2018-04-12
Enter an end date: 2018-04-13
2018-04-12
2018-04-13

2018-05-10
2018-05-11

2018-06-14
2018-06-15

2018-07-12
2018-07-13
...

我所做的是询问开始日期和结束日期(相互关注)。然后,我使用calendar.monthdayscalendar()询问周和他们的日期。我查看了它们,如果在其中找到了一个星期,其中不存在给定日期的工作日,则会将其从列表中删除,因为我们不需要它。

然后,这就是它变得棘手的地方:我正在收集数据的平台将第四个东西转换为第五个东西,当那个月那天发生了第五次。 示例:我插入2018-09-25,这是星期二的第四天,但下一个日期将是2018-10-30,这是第五个星期二,因为那个月有五个出现。我花了一段时间才发现这一点(以及为什么我在开始时说星期六或星期日开始的星期不计算,但这并不完全准确:我的道歉)。

因此,如果月份有五周且原始周数为4,那么我们会在第一周弹出,因为我们不需要它,并且周数的索引可以保持相同以收集正确的日期。对于结束日期,我只是继续添加一天,直到差异用完为止。

再次感谢大家的贡献。我试着看看dateutil,但是由于第四件事情变成了第五件事,如果那个月有五件事,这不是解决办法。