2016-04-22 17 views
5

Ho costruito una funzione per ottenere il primo e l'ultimo giorno del trimestre corrente ma è un po 'prolisso. Mi stavo chiedendo, c'è un modo più succinto di realizzare questo?Python ottiene il primo e l'ultimo giorno del trimestre corrente

Capisco che pandas ha una funzione QuarterBegin(), ma non ho potuto implementarlo in modo più conciso.

import datetime as dt 
from dateutil.relativedelta import relativedelta  

def get_q(first=None,last=None): 

    today = dt.date.today() 

    qmonth = [1, 4, 7, 10] 

    if first: 
     for i,v in enumerate(qmonth): 
      if (today.month-1)//3 == i: 
       return dt.date(today.year,qmonth[i],1).strftime("%Y-%m-%d") 

    if last: 
     firstday = dt.datetime.strptime(get_q(first=True),"%Y-%m-%d") 
     lastday = firstday + relativedelta(months=3, days=-1) 
     return lastday.strftime("%Y-%m-%d") 

EDIT: Per favore fatemi sapere se questo sarebbe più adatto a Code Review

+1

Perché 'relativedelta (mesi = 3) - dt.timedelta (giorni = 1)' invece di 'relativedelta (mesi = 3, giorni = -1)'? – Paul

+0

@Paul - ah sì, è meglio. Grazie. Modifico il post – Charon

risposta

4

Si può fare in questo modo:

import bisect 
import datetime as dt 

def get_quarter_begin(): 
    today = dt.date.today() 

    qbegins = [dt.date(today.year, month, 1) for month in (1,4,7,10)] 

    idx = bisect.bisect(qbegins, today) 
    return str(qbegins[idx-1]) 

Questo risolve il caso "prima"; Lascio il caso "last" come esercizio ma suggerisco di mantenerlo come una funzione indipendente per chiarezza (con la tua versione originale è piuttosto strano cosa succede se non vengono passati argomenti!).

+0

renderebbe la funzione più generale se si restituisse restituire un oggetto data invece di una stringa. – jfs

+0

@ J.F.Sebastian: Sono assolutamente d'accordo: ho modellato questo dopo ciò che l'OP ha fatto. Ma sì, rimuovere la chiamata 'str()' nella dichiarazione di ritorno sarebbe preferibile anche a me. –

+0

non correlato: per un piccolo 'N' (come in questo caso) potrebbe essere utilizzata una ricerca lineare invece della ricerca binaria:' return next (date (today.year, month, 1) per mese, next_month in zip (quarter_months , quarter_months [1:]) if today.month jfs

2
  • evitare Yo-Yo code. Non convertire un oggetto data in una stringa solo per analizzarlo nuovamente in un oggetto data immediatamente come nel ramo if last nella domanda. Invece, restituire un oggetto data e convertirlo in stringa solo quando necessario (è più semplice accedere agli attributi dell'oggetto come .year, .month piuttosto che analizzare la rappresentazione della stringa per estrarre le stesse informazioni)
  • evitare argomenti di parole chiave booleane che si escludono a vicenda (first, last). È un'interfaccia soggetta a errori e genera la duplicazione del codice. È facile restituire entrambi i risultati qui e accedere agli attributi corrispondenti come .first_day in seguito. O se ci sono (improbabili) problemi di prestazioni; è possibile creare due funzioni come
  • per semplificare l'algoritmo, è possibile aggiungere una piccola ridondanza ai dati di input, ad esempio, vedere quarter_first_days nel seguente codice (1 è menzionato due volte nell'elenco di mesi) -it consentire l'uso i+1 incondizionatamente:
#!/usr/bin/env python 
from collections import namedtuple 
from datetime import MINYEAR, date, timedelta 

DAY = timedelta(1) 
quarter_first_days = [date(MINYEAR+1, month, 1) for month in [1, 4, 7, 10, 1]]  
Quarter = namedtuple('Quarter', 'first_day last_day') 

def get_current_quarter(): 
    today = date.today() 
    i = (today.month - 1) // 3 # get quarter index 
    days = quarter_first_days[i], quarter_first_days[i+1] - DAY 
    return Quarter(*[day.replace(year=today.year) for day in days]) 

MINYEAR+1 Serve per - DAY espressione (assume MINYEAR < MAXYEAR). La formula indice di quarto è da Is there a Python function to determine which quarter of the year a date is in?

Esempio:

>>> get_current_quarter() 
Quarter(first_day=datetime.date(2016, 4, 1), last_day=datetime.date(2016, 6, 30)) 
>>> str(get_current_quarter().last_day) 
'2016-06-30' 
+0

Grazie per aver postato questa risposta. Vedo che sarebbe meglio lavorare semplicemente con gli oggetti 'date' e poi convertirli in stringhe solo se e quando necessario. Penso che combinerò la tua risposta con quella sopra per rendere la mia funzione. – Charon

4

Perché così complicato :-)

from datetime import date 
from calendar import monthrange 

quarter = 2 
year = 2016 
first_month_of_quarter = 3 * quarter - 2 
last_month_of_quarter = 3 * quarter 
date_of_first_day_of_quarter = date(year, first_month_of_quarter, 1) 
date_of_last_day_of_quarter = date(year, last_month_of_quarter, monthrange(year, last_month_of_quarter)[1]) 
2

Perché rotolare il proprio?

import pandas as pd 

quarter_start = pd.to_datetime(pd.datetime.today() - pd.tseries.offsets.QuarterBegin(startingMonth=1)).date() 
+0

E come calcolare la data corrente di fine trimestre? –

+1

Cambia QuarterBegin a QuarterEnd e aggiungi invece di sottrarre –

+0

Grazie fatto una modifica anche nel mese di partenza = 3 –

0

Non è necessario utilizzare loop non necessari o librerie di grandi dimensioni come i panda per farlo. Puoi farlo con una semplice divisione intera/aritmetica e solo con la libreria datetime (anche se l'uso di dateutil produce un codice più pulito).

import datetime 

def getQuarterStart(dt=datetime.date.today()): 
    return datetime.date(dt.year, (dt.month - 1) // 3 * 3 + 1, 1) 

# using just datetime 
def getQuarterEnd1(dt=datetime.date.today()): 
    nextQtYr = dt.year + (1 if dt.month>9 else 0) 
    nextQtFirstMo = (dt.month - 1) // 3 * 3 + 4 
    nextQtFirstMo = 1 if nextQtFirstMo==13 else nextQtFirstMo 
    nextQtFirstDy = datetime.date(nextQtYr, nextQtFirstMo, 1) 
    return nextQtFirstDy - datetime.timedelta(days=1) 

# using dateutil 
from dateutil.relativedelta import relativedelta 

def getQuarterEnd2(dt=datetime.date.today()): 
    quarterStart = getQuarterStart(dt) 
    return quarterStart + relativedelta(months=3, days=-1) 

uscita:

>>> d1=datetime.date(2017,2,15) 
>>> d2=datetime.date(2017,1,1) 
>>> d3=datetime.date(2017,10,1) 
>>> d4=datetime.date(2017,12,31) 
>>> 
>>> getQuarterStart(d1) 
datetime.date(2017, 1, 1) 
>>> getQuarterStart(d2) 
datetime.date(2017, 1, 1) 
>>> getQuarterStart(d3) 
datetime.date(2017, 10, 1) 
>>> getQuarterStart(d4) 
datetime.date(2017, 10, 1) 
>>> getQuarterEnd1(d1) 
datetime.date(2017, 3, 31) 
>>> getQuarterEnd1(d2) 
datetime.date(2017, 3, 31) 
>>> getQuarterEnd1(d3) 
datetime.date(2017, 12, 31) 
>>> getQuarterEnd1(d4) 
datetime.date(2017, 12, 31) 
>>> getQuarterEnd2(d1) 
datetime.date(2017, 3, 31) 
>>> getQuarterEnd2(d2) 
datetime.date(2017, 3, 31) 
>>> getQuarterEnd2(d3) 
datetime.date(2017, 12, 31) 
>>> getQuarterEnd2(d4) 
datetime.date(2017, 12, 31) 
Problemi correlati