Câu hỏi Tạo danh sách các tháng giữa khoảng thời gian trong python


Tôi muốn tạo một danh sách python chứa tất cả các tháng xảy ra giữa hai ngày, với đầu vào và đầu ra được định dạng như sau:

date1 = "2014-10-10"  # input start date
date2 = "2016-01-07"  # input end date
month_list = ['Oct-14', 'Nov-14', 'Dec-14', 'Jan-15', 'Feb-15', 'Mar-15', 'Apr-15', 'May-15', 'Jun-15', 'Jul-15', 'Aug-15', 'Sep-15', 'Oct-15', 'Nov-15', 'Dec-15', 'Jan-16']  # output

13
2018-01-20 11:16


gốc


Xem câu trả lời của tôi đây cho một ý tưởng. Bạn có thể sửa đổi chức năng đó cho phù hợp với nhu cầu của bạn. - Manhattan


Các câu trả lời:


>>> from datetime import datetime, timedelta
>>> from collections import OrderedDict
>>> dates = ["2014-10-10", "2016-01-07"]
>>> start, end = [datetime.strptime(_, "%Y-%m-%d") for _ in dates]
>>> OrderedDict(((start + timedelta(_)).strftime(r"%b-%y"), None) for _ in xrange((end - start).days)).keys()
['Oct-14', 'Nov-14', 'Dec-14', 'Jan-15', 'Feb-15', 'Mar-15', 'Apr-15', 'May-15', 'Jun-15', 'Jul-15', 'Aug-15', 'Sep-15', 'Oct-15', 'Nov-15', 'Dec-15', 'Jan-16']

Cập nhật: một chút giải thích, như được yêu cầu trong một bình luận. Có ba vấn đề ở đây: phân tích các ngày tháng thành các cấu trúc dữ liệu thích hợp (strptime); nhận được phạm vi ngày cho hai thái cực và bước (một tháng); định dạng ngày đầu ra (strftime). Các datetime loại quá tải toán tử trừ, để end - start có ý nghĩa. Kết quả là một timedelta đối tượng đại diện cho sự khác biệt giữa hai ngày và .days thuộc tính được sự khác biệt này được thể hiện trong ngày. Không có .months thuộc tính, vì vậy chúng tôi lặp lại một ngày tại một thời điểm và chuyển đổi ngày sang định dạng đầu ra mong muốn. Điều này mang lại rất nhiều bản sao, mà OrderedDict loại bỏ trong khi vẫn giữ các mục theo đúng thứ tự.

Bây giờ điều này là đơn giản và súc tích vì nó cho phép mô-đun datetime làm tất cả công việc, nhưng nó cũng vô cùng khủng khiếp. Chúng tôi đang gọi rất nhiều phương pháp cho mỗi ngày trong khi chúng tôi chỉ cần xuất tháng. Nếu hiệu suất không phải là một vấn đề, mã trên sẽ chỉ là tốt. Nếu không, chúng tôi sẽ phải làm việc nhiều hơn một chút. Hãy so sánh việc thực hiện ở trên với cách thực hiện hiệu quả hơn:

from datetime import datetime, timedelta
from collections import OrderedDict

dates = ["2014-10-10", "2016-01-07"]

def monthlist_short(dates):
    start, end = [datetime.strptime(_, "%Y-%m-%d") for _ in dates]
    return OrderedDict(((start + timedelta(_)).strftime(r"%b-%y"), None) for _ in xrange((end - start).days)).keys()

def monthlist_fast(dates):
    start, end = [datetime.strptime(_, "%Y-%m-%d") for _ in dates]
    total_months = lambda dt: dt.month + 12 * dt.year
    mlist = []
    for tot_m in xrange(total_months(start)-1, total_months(end)):
        y, m = divmod(tot_m, 12)
        mlist.append(datetime(y, m+1, 1).strftime("%b-%y"))
    return mlist

assert monthlist_fast(dates) == monthlist_short(dates)

if __name__ == "__main__":
    from timeit import Timer
    for func in "monthlist_short", "monthlist_fast":
        print func, Timer("%s(dates)" % func, "from __main__ import dates, %s" % func).timeit(1000)

Trên máy tính xách tay của tôi, tôi nhận được kết quả sau:

monthlist_short 2.3209939003
monthlist_fast 0.0774540901184

Việc triển khai ngắn gọn chậm hơn khoảng 30 lần, vì vậy tôi sẽ không đề xuất nó trong các ứng dụng thời gian quan trọng :)


18
2018-01-20 11:27



nhìn vào sản lượng dự kiến ​​của OP - ᴀʀᴍᴀɴ
Xin lỗi, lần đầu tiên tôi đọc nhanh. Tôi đã sửa đổi câu trả lời để thực sự giải quyết câu hỏi :) - simleo
Tôi rất ấn tượng! câu trả lời rất hay - Luis González
Đơn giản chỉ cần Awesome .. Chính xác những gì tôi đang tìm kiếm. Sẽ tốt hơn nếu bạn giải thích các nội dung cũng như để những người xem khác cũng có thể hiểu rõ nó @simleo - Birlla
Giải pháp tuyệt vời. Tôi đã tìm kiếm một cái gì đó như thế này một thời gian. Rất nhiều giải pháp khác đã không cho tôi kết quả mong muốn. Tnx! - toringe


Tôi tìm thấy một cách rất ngắn gọn để làm điều này với Pandas, chia sẻ trong trường hợp nó giúp bất cứ ai:

daterange = pd.date_range('2014-10-10','2016-01-07' , freq='1M') 
daterange = daterange.union([daterange[-1] + 1])  
daterange = [d.strftime('%y-%b') for d in daterange]

Dòng thứ hai ngăn không cho ngày cuối cùng bị cắt bớt khỏi danh sách.


17
2018-03-17 12:27



Như bạn đã đề cập, phương pháp này với Pandas là gọn gàng! Cảm ơn bạn! - Arun


Bạn phải sử dụng Lịch và Ngày giờ 

import calendar
from datetime import *
date1 = datetime.strptime("2014-10-10", "%Y-%m-%d")
date2 = datetime.strptime("2016-01-07", "%Y-%m-%d")
months_str = calendar.month_name
months = []
while date1 < date2:
    month = date1.month
    year  = date1.year
    month_str = months_str[month][0:3]
    months.append("{0}-{1}".format(month_str,str(year)[-2:]))
    next_month = month+1 if month != 12 else 1
    next_year = year + 1 if next_month == 1 else year
    date1 = date1.replace( month = next_month, year= next_year)

print months

Mã này trả về

['Oct-14', 'Nov-14', 'Dec-14', 'Jan-14', 'Feb-15', 'Mar-15', 'Apr-15', 'May-15', 'Jun-15', 'Jul-15', 'Aug-15', 'Sep-15', 'Oct-15', 'Nov-15', 'Dec-15', 'Jan-15']

5
2018-01-20 11:44



Tôi nhận được điều này bằng cách chạy mã của bạn: ['Oct-1', 'Nov-1', 'Dec-1', 'Jan-1', 'Feb-1', 'Mar-1', 'Apr-1' , 'May-1', 'Jun-1', 'Jul-1', 'Aug-1', 'Sep-1', 'Oct-1', 'Nov-1', 'Dec-1', ' Jan-1 '] - simleo
Vâng!! Đó là một sai lầm nhỏ .. Tôi đã viết str(year)[-2] thay vì str(year)[-2:]. Đã sửa! :) - Luis González


Với gấu trúc, bạn có thể có một lớp lót như thế này:

import pandas as pd

date1 = "2014-10-10"  # input start date
date2 = "2016-01-07"  # input end date

month_list = [i.strftime("%b-%y") for i in pd.date_range(start=date1, end=date2, freq='MS')]

2
2018-03-15 16:58





Tìm bên dưới cách tiếp cận của tôi cho vấn đề này bằng cách sử dụng chia ra và đơn giản dựa trên modulo lặp lại mà không cần nhập bất kỳ mô-đun đặc biệt nào.

date1 = "2014-10-10"
date2 = "2016-01-07"

y0 = int( date1.split('-')[0] ) # 2014
y1 = int( date2.split('-')[0] ) # 2016

m0 = int( date1.split('-')[1] ) - 1 # 10-1 --> 9 because will be used for indexing
m1 = int( date2.split('-')[1] ) - 1 # 01-1 --> 0 because will be used for indexing

months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
result = []
start = m0
for y in range(y0, y1+1):
    for m in range(start,12):
        result.append( str( months[m  % 12])+'-'+str(y) )
        if y == y1 and (m % 12) == m1:
            break
    start = 0

print result

$ python dates.py 

['Oct-2014', 'Nov-2014', 'Dec-2014', 'Jan-2015', 'Feb-2015', 'Mar-2015', 'Apr-2015', 'May-2015', 'Jun-2015', 'Jul-2015', 'Aug-2015', 'Sep-2015', 'Oct-2015', 'Nov-2015', 'Dec-2015', 'Jan-2016']

1
2018-01-20 13:19





Đây là giải pháp của tôi với việc hiểu danh sách đơn giản, sử dụng range biết tháng nào bắt đầu và kết thúc

from datetime import datetime as dt
sd = dt.strptime('2014-10-10', "%Y-%m-%d") 
ed = dt.strptime('2016-01-07', "%Y-%m-%d") 

lst = [dt.strptime('%2.2d-%2.2d' % (y, m), '%Y-%m').strftime('%b-%y') \
       for y in xrange(sd.year, ed.year+1) \
       for m in xrange(sd.month if y==sd.year else 1, ed.month+1 if y == ed.year else 13)]

print lst

sản xuất

['Oct-14', 'Nov-14', 'Dec-14', 'Jan-15', 'Feb-15', 'Mar-15', 'Apr-15', 'May-15', 'Jun-15', 'Jul-15', 'Aug-15', 'Sep-15', 'Oct-15', 'Nov-15', 'Dec-15', 'Jan-16']

0
2018-01-20 12:55





Đã từng làm những thứ tương tự trước đây, tôi đã đâm vào việc giải quyết vấn đề này. Sử dụng các thành phần riêng biệt để làm điều này linh hoạt hơn và cho phép bạn trộn và kết hợp chúng với các trường hợp sử dụng khác nhau. Họ cũng có thể được thử nghiệm dễ dàng hơn theo cách này, như bạn có thể thấy bằng các tài liệu trong iterate_months.

Ngoài ra tôi đề nghị sử dụng datetime.date đối tượng cho đầu vào của bạn vì bạn chỉ có thể làm được nhiều hơn với những thứ đó. Để làm điều đó bạn sẽ phải phân tích đầu tiên chuỗi đầu vào của bạn, nhưng điều này là rất dễ dàng thực hiện.

Phân tích cú pháp chuỗi ngày tháng

def datify(date):
    if isinstance(date, datetime.date):
        return date
    elif isinstance(date, datetime.datetime):
        return date.date()
    else:
        # taken from simleo's answer
        return datetime.strptime(date, "%Y-%m-%d")

Đầu tiên, chúng tôi lặp lại qua các tháng

import datetime


def iterate_months(start_date, end_date):
    """Iterate monthly between two given dates.

    Emitted will be the first day of each month.

    >>> list(iterate_months(datetime.date(1999, 11, 1),
    ...                     datetime.date(2000, 2, 1)))
    [datetime.date(1999, 11, 1), datetime.date(1999, 12, 1),\
 datetime.date(2000, 1, 1), datetime.date(2000, 2, 1)]

    """
    assert isinstance(start_date, datetime.date)
    assert isinstance(end_date, datetime.date)
    assert start_date < end_date

    year = start_date.year
    month = start_date.month
    while True:
        current = datetime.date(year, month, 1)
        yield current
        if current.month == end_date.month and current.year == end_date.year:
            break
        else:
            month = ((month + 1) % 12) or 12
            if month == 1:
                year += 1


if __name__ == '__main__':
    import doctest
    doctest.testmod()

Để định dạng ngày của bạn, hãy sử dụng một cái gì đó như thế này

def format_month(date):
    return date.strftime(r"%b-%y")

Để tất cả chúng cùng nhau

start = datify("2014-10-10")
end = datify("2016-01-07")

for entry in iterate_months(start, end):
    print format_month(entry)

Hoặc lưu nó dưới dạng danh sách:

result = list(iterate_months(start, end))

0
2018-01-20 11:46