Lặp lại qua một loạt các ngày trong Python


368

Tôi có đoạn mã sau để làm điều này, nhưng làm thế nào tôi có thể làm nó tốt hơn? Ngay bây giờ tôi nghĩ rằng nó tốt hơn các vòng lặp lồng nhau, nhưng nó bắt đầu nhận được Perl-one-linerish khi bạn có một trình tạo trong danh sách hiểu.

day_count = (end_date - start_date).days + 1
for single_date in [d for d in (start_date + timedelta(n) for n in range(day_count)) if d <= end_date]:
    print strftime("%Y-%m-%d", single_date.timetuple())

Ghi chú

  • Tôi thực sự không sử dụng điều này để in. Đó chỉ là mục đích demo.
  • Các biến start_dateend_datedatetime.datecác đối tượng vì tôi không cần dấu thời gian. (Chúng sẽ được sử dụng để tạo báo cáo).

Đầu ra mẫu

Đối với ngày bắt đầu 2009-05-30và ngày kết thúc 2009-06-09:

2009-05-30
2009-05-31
2009-06-01
2009-06-02
2009-06-03
2009-06-04
2009-06-05
2009-06-06
2009-06-07
2009-06-08
2009-06-09

3
Chỉ cần chỉ ra: Tôi không nghĩ có bất kỳ sự khác biệt nào giữa 'time.strftime ("% Y-% m-% d", single_date.timetuple ())' và ngắn hơn 'single_date.strftime ("% Y-% m-% d ") '. Hầu hết các câu trả lời dường như được sao chép phong cách dài hơn.
Mu Mind

8
Wow, những câu trả lời này quá phức tạp. Hãy thử điều này: stackoverflow.com/questions/7274267/ khăn
Gringo Suave

@GringoSuave: câu trả lời của Sean Cavanagh có gì phức tạp ?
jfs

Ứng dụng: gian lận trên các vệt GitHub: stackoverflow.com/questions/20099235/ory
Ciro Santilli 冠状 病 六四 6/12/15

1
Sao y hoặc không, bạn sẽ nhận được câu trả lời đơn giản hơn ở trang khác.
Gringo Suave

Câu trả lời:


553

Tại sao có hai lần lặp lồng nhau? Đối với tôi, nó tạo ra cùng một danh sách dữ liệu chỉ với một lần lặp:

for single_date in (start_date + timedelta(n) for n in range(day_count)):
    print ...

Và không có danh sách nào được lưu trữ, chỉ có một trình tạo được lặp lại. Ngoài ra, "nếu" trong trình tạo dường như là không cần thiết.

Rốt cuộc, một chuỗi tuyến tính chỉ cần một vòng lặp, không phải hai.

Cập nhật sau khi thảo luận với John Machin:

Có lẽ giải pháp tao nhã nhất là sử dụng hàm tạo để ẩn / tóm tắt hoàn toàn việc lặp lại trong phạm vi ngày:

from datetime import timedelta, date

def daterange(start_date, end_date):
    for n in range(int ((end_date - start_date).days)):
        yield start_date + timedelta(n)

start_date = date(2013, 1, 1)
end_date = date(2015, 6, 2)
for single_date in daterange(start_date, end_date):
    print(single_date.strftime("%Y-%m-%d"))

NB: Để thống nhất với range()chức năng tích hợp, phép lặp này dừng trước khi đến end_date. Vì vậy, để lặp lại bao gồm sử dụng vào ngày hôm sau, như bạn sẽ làm với range().


4
-1 ... có một tính toán sơ bộ về day_count và sử dụng phạm vi không phải là tuyệt vời khi một vòng lặp while đơn giản sẽ đủ.
John Machin

7
@ John Machin: Được rồi. Tuy nhiên, tôi thực hiện trước một vòng lặp trong khi các vòng lặp với sự gia tăng rõ ràng của một số bộ đếm hoặc giá trị. Mẫu xen kẽ là pythonic hơn (ít nhất là theo quan điểm cá nhân của tôi) và cũng tổng quát hơn, vì nó cho phép thể hiện một phép lặp trong khi ẩn các chi tiết về cách lặp đó được thực hiện.
Ber

10
@Ber: Tôi hoàn toàn không thích nó; Thật tồi tệ. Bạn đã có một lần lặp lại! Bằng cách gói các cấu trúc bị phàn nàn trong một trình tạo, bạn đã thêm nhiều chi phí thực thi hơn cộng với chuyển hướng sự chú ý của người dùng sang một nơi khác để đọc mã và / hoặc tài liệu của 3 người. -2
John Machin

8
@ John Machin: Tôi không đồng ý. Vấn đề không phải là giảm số lượng dòng xuống mức tối thiểu. Rốt cuộc, chúng ta không nói chuyện Perl ở đây. Ngoài ra, mã của tôi chỉ thực hiện một lần lặp (đó là cách trình tạo hoạt động, nhưng tôi đoán bạn biết điều đó). *** Quan điểm của tôi là về khái niệm trừu tượng hóa để sử dụng lại và tự giải thích mã. Tôi duy trì rằng điều này đáng giá hơn nhiều so với việc có mã ngắn nhất có thể.
Ber

9
Nếu bạn đang căng thẳng, bạn có thể sử dụng biểu thức trình tạo:(start_date + datetime.timedelta(n) for n in range((end_date - start_date).days))
Đánh dấu tiền chuộc

219

Điều này có thể rõ ràng hơn:

from datetime import date, timedelta

start_date = date(2019, 1, 1)
end_date = date(2020, 1, 1)
delta = timedelta(days=1)
while start_date <= end_date:
    print (start_date.strftime("%Y-%m-%d"))
    start_date += delta

3
Rất rõ ràng và ngắn gọn, nhưng không hoạt động tốt nếu bạn muốn sử dụng tiếp tục
rslite

hoạt động đáng yêu cho trường hợp sử dụng của tôi
doomdaam

169

Sử dụng dateutilthư viện:

from datetime import date
from dateutil.rrule import rrule, DAILY

a = date(2009, 5, 30)
b = date(2009, 6, 9)

for dt in rrule(DAILY, dtstart=a, until=b):
    print dt.strftime("%Y-%m-%d")

Thư viện python này có nhiều tính năng nâng cao hơn, một số tính năng rất hữu ích, như relative deltas, và được triển khai dưới dạng một tệp (mô-đun) dễ dàng đưa vào một dự án.


3
Lưu ý rằng ngày cuối cùng trong vòng lặp for ở đây là bao gồm các untiltrong khi ngày cuối cùng của daterangephương pháp trong câu trả lời của Berđộc quyền của end_date.
Ninjakannon

tài liệu hiện đại hơn dateutil.readthedocs.io/en/stable/rrule.html
qwr

77

Pandas là tuyệt vời cho chuỗi thời gian nói chung, và có hỗ trợ trực tiếp cho phạm vi ngày.

import pandas as pd
daterange = pd.date_range(start_date, end_date)

Sau đó, bạn có thể lặp qua daterange để in ngày:

for single_date in daterange:
    print (single_date.strftime("%Y-%m-%d"))

Nó cũng có rất nhiều lựa chọn để làm cho cuộc sống dễ dàng hơn. Ví dụ: nếu bạn chỉ muốn các ngày trong tuần, bạn sẽ chỉ trao đổi trong bdate_range. Xem http://pandas.pydata.org/pandas-docs/urdy/timeseries.html#generating-ranges-of-timestamp

Sức mạnh của Pandas thực sự là các dataframes của nó, hỗ trợ các hoạt động được vector hóa (giống như numpy) giúp cho các hoạt động trên một lượng lớn dữ liệu rất nhanh và dễ dàng.

EDIT: Bạn cũng có thể hoàn toàn bỏ qua vòng lặp for và chỉ cần in trực tiếp, dễ dàng và hiệu quả hơn:

print(daterange)

"giống như numpy" - Pandas được xây dựng trên numpy: P
Zach Saucier

15
import datetime

def daterange(start, stop, step=datetime.timedelta(days=1), inclusive=False):
  # inclusive=False to behave like range by default
  if step.days > 0:
    while start < stop:
      yield start
      start = start + step
      # not +=! don't modify object passed in if it's mutable
      # since this function is not restricted to
      # only types from datetime module
  elif step.days < 0:
    while start > stop:
      yield start
      start = start + step
  if inclusive and start == stop:
    yield start

# ...

for date in daterange(start_date, end_date, inclusive=True):
  print strftime("%Y-%m-%d", date.timetuple())

Hàm này thực hiện nhiều hơn yêu cầu của bạn, bằng cách hỗ trợ bước phủ định, v.v ... Miễn là bạn tính ra logic phạm vi của mình, thì bạn không cần riêng biệt day_countvà quan trọng nhất là mã trở nên dễ đọc hơn khi bạn gọi hàm từ nhiều nơi.


Cảm ơn, được đổi tên thành các tham số phù hợp chặt chẽ hơn, quên thay đổi trong cơ thể.

+1 ... nhưng khi bạn cho phép bước là timedelta, bạn nên (a) gọi nó là dateTIMErange () và thực hiện các bước ví dụ: timedelta (hours = 12) và timedelta (hours = 36) hoạt động đúng hoặc ( b) các bước bẫy không phải là số ngày không thể thiếu hoặc (c) lưu rắc rối cho người gọi và thể hiện bước đó dưới dạng số ngày thay vì theo thời gian.
John Machin

Bất kỳ timedelta nào cũng đã hoạt động, nhưng tôi đã thêm datetime_range và date_range vào bộ sưu tập phế liệu cá nhân của mình sau khi viết bài này, vì (a). Không chắc chắn một chức năng khác đáng giá cho (c), trường hợp phổ biến nhất của ngày = 1 đã được xử lý và phải vượt qua một timedelta rõ ràng để tránh nhầm lẫn. Có lẽ tải lên nó ở đâu đó là tốt nhất: bitbucket.org/kniht/scraps/src/tip/python/gen_range.py

để thực hiện công việc này trên các mức tăng khác với số ngày bạn nên kiểm tra theo step.total_seconds () chứ không phải step.days
amohr

12

Đây là giải pháp dễ đọc nhất mà con người tôi có thể nghĩ ra.

import datetime

def daterange(start, end, step=datetime.timedelta(1)):
    curr = start
    while curr < end:
        yield curr
        curr += step

11

Sao không thử nhỉ:

import datetime as dt

start_date = dt.datetime(2012, 12,1)
end_date = dt.datetime(2012, 12,5)

total_days = (end_date - start_date).days + 1 #inclusive 5 days

for day_number in range(total_days):
    current_date = (start_date + dt.timedelta(days = day_number)).date()
    print current_date

7

arangeChức năng của Numpy có thể được áp dụng cho ngày:

import numpy as np
from datetime import datetime, timedelta
d0 = datetime(2009, 1,1)
d1 = datetime(2010, 1,1)
dt = timedelta(days = 1)
dates = np.arange(d0, d1, dt).astype(datetime)

Việc sử dụng astypelà để chuyển đổi từ numpy.datetime64một mảng các datetime.datetimeđối tượng.


Xây dựng siêu nạc! Dòng cuối cùng hoạt động với tôidates = np.arange(d0, d1, dt).astype(datetime.datetime)
pyano

+1 để đăng một giải pháp chung chung cho phép mọi thời gian, thay vì một bước làm tròn cố định, chẳng hạn như hàng giờ / phút / Lôi.
F.Raab

7

Hiển thị n ngày cuối cùng kể từ hôm nay:

import datetime
for i in range(0, 100):
    print((datetime.date.today() + datetime.timedelta(i)).isoformat())

Đầu ra:

2016-06-29
2016-06-30
2016-07-01
2016-07-02
2016-07-03
2016-07-04

Vui lòng thêm dấu ngoặc tròn, nhưprint((datetime.date.today() + datetime.timedelta(i)).isoformat())
TitanFighter

@TitanFighter xin vui lòng chỉnh sửa, tôi sẽ chấp nhận chúng.
dùng1767754

2
Tôi đã thử. Chỉnh sửa yêu cầu tối thiểu 6 ký tự, nhưng trong trường hợp này, chỉ cần thêm 2 ký tự, "(" và ")"
TitanFighter

print((datetime.date.today() + datetime.timedelta(i)))không có .isoformat () cho chính xác cùng một đầu ra. Tôi cần tập lệnh của mình để in YYMMDD. Có ai biết làm như thế nào không?
mr.zog

Chỉ cần làm điều này trong vòng lặp for thay vì câu lệnh ind = datetime.date.today() + datetime.timedelta(i); d.strftime("%Y%m%d")
user1767754

5
import datetime

def daterange(start, stop, step_days=1):
    current = start
    step = datetime.timedelta(step_days)
    if step_days > 0:
        while current < stop:
            yield current
            current += step
    elif step_days < 0:
        while current > stop:
            yield current
            current += step
    else:
        raise ValueError("daterange() step_days argument must not be zero")

if __name__ == "__main__":
    from pprint import pprint as pp
    lo = datetime.date(2008, 12, 27)
    hi = datetime.date(2009, 1, 5)
    pp(list(daterange(lo, hi)))
    pp(list(daterange(hi, lo, -1)))
    pp(list(daterange(lo, hi, 7)))
    pp(list(daterange(hi, lo, -7))) 
    assert not list(daterange(lo, hi, -1))
    assert not list(daterange(hi, lo))
    assert not list(daterange(lo, hi, -7))
    assert not list(daterange(hi, lo, 7)) 


4

Để hoàn thiện, Pandas cũng có period_rangechức năng cho dấu thời gian nằm ngoài giới hạn:

import pandas as pd

pd.period_range(start='1/1/1626', end='1/08/1627', freq='D')

3

Tôi có một vấn đề tương tự, nhưng tôi cần lặp đi lặp lại hàng tháng thay vì hàng ngày.

Đây là giải pháp của tôi

import calendar
from datetime import datetime, timedelta

def days_in_month(dt):
    return calendar.monthrange(dt.year, dt.month)[1]

def monthly_range(dt_start, dt_end):
    forward = dt_end >= dt_start
    finish = False
    dt = dt_start

    while not finish:
        yield dt.date()
        if forward:
            days = days_in_month(dt)
            dt = dt + timedelta(days=days)            
            finish = dt > dt_end
        else:
            _tmp_dt = dt.replace(day=1) - timedelta(days=1)
            dt = (_tmp_dt.replace(day=dt.day))
            finish = dt < dt_end

Ví dụ 1

date_start = datetime(2016, 6, 1)
date_end = datetime(2017, 1, 1)

for p in monthly_range(date_start, date_end):
    print(p)

Đầu ra

2016-06-01
2016-07-01
2016-08-01
2016-09-01
2016-10-01
2016-11-01
2016-12-01
2017-01-01

Ví dụ # 2

date_start = datetime(2017, 1, 1)
date_end = datetime(2016, 6, 1)

for p in monthly_range(date_start, date_end):
    print(p)

Đầu ra

2017-01-01
2016-12-01
2016-11-01
2016-10-01
2016-09-01
2016-08-01
2016-07-01
2016-06-01

3

Có thể 't * tin rằng câu hỏi này đã tồn tại 9 năm mà không ai thấy một hàm đệ quy đơn giản:

from datetime import datetime, timedelta

def walk_days(start_date, end_date):
    if start_date <= end_date:
        print(start_date.strftime("%Y-%m-%d"))
        next_date = start_date + timedelta(days=1)
        walk_days(next_date, end_date)

#demo
start_date = datetime(2009, 5, 30)
end_date   = datetime(2009, 6, 9)

walk_days(start_date, end_date)

Đầu ra:

2009-05-30
2009-05-31
2009-06-01
2009-06-02
2009-06-03
2009-06-04
2009-06-05
2009-06-06
2009-06-07
2009-06-08
2009-06-09

Chỉnh sửa: * Bây giờ tôi có thể tin điều đó - xem Python có tối ưu hóa đệ quy đuôi không? . Cảm ơn bạn Tim .


3
Tại sao bạn sẽ thay thế một vòng lặp đơn giản bằng đệ quy? Điều này phá vỡ cho các phạm vi dài hơn khoảng hai năm rưỡi.
Tim-Erwin

@ Tim-Erwin Thành thật tôi không biết CPython không tối ưu hóa đệ quy đuôi nên nhận xét của bạn rất có giá trị.
Pocketsand

2

Bạn có thể tạo một chuỗi ngày giữa hai ngày bằng thư viện gấu trúc một cách đơn giản và đáng tin cậy

import pandas as pd

print pd.date_range(start='1/1/2010', end='1/08/2018', freq='M')

Bạn có thể thay đổi tần suất tạo ngày bằng cách đặt freq là D, M, Q, Y (hàng ngày, hàng tháng, hàng quý, hàng năm)


Đã trả lời trong chủ đề này vào năm 2014
Alexey Vazhnov

2
> pip install DateTimeRange

from datetimerange import DateTimeRange

def dateRange(start, end, step):
        rangeList = []
        time_range = DateTimeRange(start, end)
        for value in time_range.range(datetime.timedelta(days=step)):
            rangeList.append(value.strftime('%m/%d/%Y'))
        return rangeList

    dateRange("2018-09-07", "2018-12-25", 7)  

    Out[92]: 
    ['09/07/2018',
     '09/14/2018',
     '09/21/2018',
     '09/28/2018',
     '10/05/2018',
     '10/12/2018',
     '10/19/2018',
     '10/26/2018',
     '11/02/2018',
     '11/09/2018',
     '11/16/2018',
     '11/23/2018',
     '11/30/2018',
     '12/07/2018',
     '12/14/2018',
     '12/21/2018']

1

Chức năng này có một số tính năng bổ sung:

  • có thể vượt qua một chuỗi khớp với DATE_FORMAT để bắt đầu hoặc kết thúc và nó được chuyển đổi thành một đối tượng ngày
  • có thể vượt qua một đối tượng ngày để bắt đầu hoặc kết thúc
  • kiểm tra lỗi trong trường hợp kết thúc cũ hơn bắt đầu

    import datetime
    from datetime import timedelta
    
    
    DATE_FORMAT = '%Y/%m/%d'
    
    def daterange(start, end):
          def convert(date):
                try:
                      date = datetime.datetime.strptime(date, DATE_FORMAT)
                      return date.date()
                except TypeError:
                      return date
    
          def get_date(n):
                return datetime.datetime.strftime(convert(start) + timedelta(days=n), DATE_FORMAT)
    
          days = (convert(end) - convert(start)).days
          if days <= 0:
                raise ValueError('The start date must be before the end date.')
          for n in range(0, days):
                yield get_date(n)
    
    
    start = '2014/12/1'
    end = '2014/12/31'
    print list(daterange(start, end))
    
    start_ = datetime.date.today()
    end = '2015/12/1'
    print list(daterange(start, end))

1

Đây là mã cho một hàm phạm vi ngày chung, tương tự như câu trả lời của Ber, nhưng linh hoạt hơn:

def count_timedelta(delta, step, seconds_in_interval):
    """Helper function for iterate.  Finds the number of intervals in the timedelta."""
    return int(delta.total_seconds() / (seconds_in_interval * step))


def range_dt(start, end, step=1, interval='day'):
    """Iterate over datetimes or dates, similar to builtin range."""
    intervals = functools.partial(count_timedelta, (end - start), step)

    if interval == 'week':
        for i in range(intervals(3600 * 24 * 7)):
            yield start + datetime.timedelta(weeks=i) * step

    elif interval == 'day':
        for i in range(intervals(3600 * 24)):
            yield start + datetime.timedelta(days=i) * step

    elif interval == 'hour':
        for i in range(intervals(3600)):
            yield start + datetime.timedelta(hours=i) * step

    elif interval == 'minute':
        for i in range(intervals(60)):
            yield start + datetime.timedelta(minutes=i) * step

    elif interval == 'second':
        for i in range(intervals(1)):
            yield start + datetime.timedelta(seconds=i) * step

    elif interval == 'millisecond':
        for i in range(intervals(1 / 1000)):
            yield start + datetime.timedelta(milliseconds=i) * step

    elif interval == 'microsecond':
        for i in range(intervals(1e-6)):
            yield start + datetime.timedelta(microseconds=i) * step

    else:
        raise AttributeError("Interval must be 'week', 'day', 'hour' 'second', \
            'microsecond' or 'millisecond'.")

0

Điều gì về những điều sau đây để thực hiện một phạm vi tăng theo ngày:

for d in map( lambda x: startDate+datetime.timedelta(days=x), xrange( (stopDate-startDate).days ) ):
  # Do stuff here
  • startDate và stopDate là các đối tượng datetime.date

Đối với phiên bản chung:

for d in map( lambda x: startTime+x*stepTime, xrange( (stopTime-startTime).total_seconds() / stepTime.total_seconds() ) ):
  # Do stuff here
  • startTime và stopTime là đối tượng datetime.date hoặc datetime.datetime (cả hai nên cùng loại)
  • stepTime là một đối tượng timedelta

Lưu ý rằng .total_seconds () chỉ được hỗ trợ sau python 2.7 Nếu bạn bị kẹt với phiên bản cũ hơn, bạn có thể viết hàm của riêng mình:

def total_seconds( td ):
  return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6

0

Cách tiếp cận hơi khác nhau đối với các bước có thể đảo ngược bằng cách lưu trữ rangeargs trong một tuple.

def date_range(start, stop, step=1, inclusive=False):
    day_count = (stop - start).days
    if inclusive:
        day_count += 1

    if step > 0:
        range_args = (0, day_count, step)
    elif step < 0:
        range_args = (day_count - 1, -1, step)
    else:
        raise ValueError("date_range(): step arg must be non-zero")

    for i in range(*range_args):
        yield start + timedelta(days=i)

0
import datetime
from dateutil.rrule import DAILY,rrule

date=datetime.datetime(2019,1,10)

date1=datetime.datetime(2019,2,2)

for i in rrule(DAILY , dtstart=date,until=date1):
     print(i.strftime('%Y%b%d'),sep='\n')

ĐẦU RA:

2019Jan10
2019Jan11
2019Jan12
2019Jan13
2019Jan14
2019Jan15
2019Jan16
2019Jan17
2019Jan18
2019Jan19
2019Jan20
2019Jan21
2019Jan22
2019Jan23
2019Jan24
2019Jan25
2019Jan26
2019Jan27
2019Jan28
2019Jan29
2019Jan30
2019Jan31
2019Feb01
2019Feb02

Chào mừng bạn đến với Stack Overflow! Mặc dù mã này có thể giải quyết câu hỏi, bao gồm giải thích về cách thức và lý do giải quyết vấn đề này, đặc biệt là các câu hỏi có quá nhiều câu trả lời hay, thực sự sẽ giúp cải thiện chất lượng bài đăng của bạn và có thể dẫn đến nhiều lượt ủng hộ hơn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai, không chỉ người hỏi bây giờ. Vui lòng chỉnh sửa câu trả lời của bạn để thêm giải thích và đưa ra dấu hiệu về những hạn chế và giả định được áp dụng. Từ đánh giá
tiếng bíp đôi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.