Joao Figueiredo
Joao Figueiredo

Reputation: 3188

How do I get the week number of the month?

Does Python offer a way to directly get the current week of the month (e.g., from 1 to 4)?

I'm accessing an Oracle DB with cx_Oracle and trying to optimize the computation time passing queries already with the month and week of the month(the table is partitioned by month and subpartitioned by week of the month, which John Machin's comment link has a definition as: "WEEK_OF_MONTH field range from .. 1 to .. 5")

This is to avoid using PARTITION_KEY = ((TO_CHAR(SYSDATE, 'MM'))-1) and SUBPARTITION_WEEK = TO_NUMBER(TO_CHAR(SYSDATE, 'W')) as that would invalidate the index use. Passing the query with the correct numbers allows using the indexes.

Upvotes: 50

Views: 86616

Answers (23)

Dan
Dan

Reputation: 1

This should do it for Python 2:

#! /usr/bin/env python2

import calendar, datetime

#FUNCTIONS
def week_of_month(date):
    """Determines the week (number) of the month"""

    #Calendar object. 6 = Start on Sunday, 0 = Start on Monday
    cal_object = calendar.Calendar(6)
    month_calendar_dates = cal_object.itermonthdates(date.year,date.month)

    day_of_week = 1
    week_number = 1

    for day in month_calendar_dates:
        #add a week and reset day of week
        if day_of_week > 7:
            week_number += 1
            day_of_week = 1

        if date == day:
            break
        else:
            day_of_week += 1

    return week_number


#MAIN
example_date = datetime.date(2015,9,21)

print "Week",str(week_of_month(example_date))
#Returns 'Week 4'

Upvotes: 0

Aurelie Giraud
Aurelie Giraud

Reputation: 93

You can do as follows:

  1. First extract the month and the week of year number

    df['month'] = df['Date'].dt.month
    df['week'] = df['Date'].dt.week 
    
  2. Then group by month and rank the week numbers

    df['weekOfMonth'] = df.groupby('month')["week"].rank("dense",    ascending=False)
    

Upvotes: 0

Lin Jingjing
Lin Jingjing

Reputation: 11

Say we have some month's calendar as follows:

Mon Tue Wed Thur Fri Sat Sun
                 1   2   3 
4   5   6   7    8   9   10

We say day 1 ~ 3 belongs to week 1 and day 4 ~ 10 belongs to week 2 etc.

In this case, I believe the week_of_month for a specific day should be calculated as follows:

import datetime
def week_of_month(year, month, day):
    weekday_of_day_one = datetime.date(year, month, 1).weekday()
    weekday_of_day = datetime.date(year, month, day)
    return (day - 1)//7 + 1 + (weekday_of_day < weekday_of_day_one)

However, if instead we want to get the nth of the weekday that date is, such as day 1 is the 1st Friday, day 8 is the 2nd Friday, and day 6 is the 1st Wednesday, then we can simply return (day - 1)//7 + 1

Upvotes: 1

manus
manus

Reputation: 525

This version could be improved, but as a first look into Python modules (datetime and calendar), I made this solution:

from datetime import datetime
n = datetime.now()
#from django.utils.timezone import now
#n = now() #if you use django with timezone

from calendar import Calendar
cal = Calendar() # week starts Monday
#cal = Calendar(6) # week stars Sunday

weeks = cal.monthdayscalendar(n.year, n.month)
for x in range(len(weeks)):
    if n.day in weeks[x]:
        print x+1

Upvotes: 6

vekerdyb
vekerdyb

Reputation: 1263

A variation on Manuel Solorzano's answer:

from calendar import monthcalendar
def get_week_of_month(year, month, day):
    return next(
        (
            week_number
            for week_number, days_of_week in enumerate(monthcalendar(year, month), start=1)
            if day in days_of_week
        ),
        None,
    )

E.g.:

>>> get_week_of_month(2020, 9, 1)
1
>>> get_week_of_month(2020, 9, 30)
5
>>> get_week_of_month(2020, 5, 35)
None

Upvotes: 1

crazyDiamond
crazyDiamond

Reputation: 1080

Josh's answer has to be tweaked slightly to accommodate the first day falling on a Sunday.

def get_week_of_month(date):
   first_day = date.replace(day=1)

   day_of_month = date.day

   if(first_day.weekday() == 6):
       adjusted_dom = (1 + first_day.weekday()) / 7
   else:
       adjusted_dom = day_of_month + first_day.weekday()

   return int(ceil(adjusted_dom/7.0))

Upvotes: 5

ifashion1210
ifashion1210

Reputation: 21

data['wk_of_mon']  = (data['dataset_date'].dt.day - 1) // 7 + 1

Upvotes: 2

Texas P Renegade
Texas P Renegade

Reputation: 413

I made my own method and thought I should share. The calendar module has a monthcalendar method that returns a 2D array where each row represents a week. For example:

import calendar
calendar.monthcalendar(2015,9)

result:

[[0,0,1,2,3,4,5],
 [6,7,8,9,10,11,12],
 [13,14,15,16,17,18,19],
 [20,21,22,23,24,25,26],
 [27,28,29,30,0,0,0]]

Numpy's where is your friend here. I'm in USA so I want the week to start on Sunday and the first week to be labelled 1:

import calendar
import numpy as np
calendar.setfirstweekday(6)

def get_week_of_month(year, month, day):
    x = np.array(calendar.monthcalendar(year, month))
    week_of_month = np.where(x==day)[0][0] + 1
    return(week_of_month)

get_week_of_month(2015,9,14)

returns

3

Upvotes: 29

Temmie
Temmie

Reputation: 1

This solution was intended to replicate the Java implementation of WeekOfMonth in Python and follow a pattern similar to the ISO 8601 convention.

  1. First day of the week is Monday

  2. Minimal number of days in the first week is 4. This implies weeks starting on and between Monday and Thursday inclusive are full weeks

        from datetime import date
    
        def week_of_month(calendar_date: date) -> int:
        """
          Python implementation of Week of Month
          following ISO 8601 https://en.wikipedia.org/wiki/ISO_week_date,
    
          1. First day of the week is Monday
          2. Minimal days in the first week is 4 so
              weeks starting on and between Monday and Thursday
              inclusive are full weeks
    
          This function returns the week's number
          within a month for a calendar date
    
          :param calendar_date:
          :return: week of the month
        """
        _, month, _ = calendar_date.year, \
                      calendar_date.month, \
                      calendar_date.day
        _, week_of_year, _ = calendar_date.isocalendar()
    
        date_at_month_start = calendar_date.replace(day=1)
        _, month_start_week_of_year, month_start_day_of_week = \
            date_at_month_start.isocalendar()
    
        if month == 1:
            return 0 if week_of_year >= 51 else week_of_year
    
        elif month_start_day_of_week > 4:
            return week_of_year - month_start_week_of_year
    
        else:
            return week_of_year - month_start_week_of_year + 1
    

Upvotes: -1

marco
marco

Reputation: 15

the best solution that I found is this function

def get_week_of_month(date):
date = datetime.strptime(date, "%Y-%m-%d") # date is str
first_day = date.replace(day=1)
print(first_day)
day_of_month = date.day
print(day_of_month)
print(first_day.weekday())
if(first_day.weekday() == 6):
    adjusted_dom = day_of_month + ((1 + first_day.weekday()) / 7)
else:
    adjusted_dom = day_of_month + first_day.weekday()
print(adjusted_dom)
return int(ceil(adjusted_dom/7.0))

Upvotes: 0

Talha Khalid Qureshi
Talha Khalid Qureshi

Reputation: 21

An Easy way to get a week number of month;

if the datatype is datetime64 then

week_number_of_month = date_value.dayofweek 

Upvotes: 0

Sayed
Sayed

Reputation: 1

def week_number(time_ctime = None):
    import time
    import calendar
    if time_ctime == None:
        time_ctime = str(time.ctime())
    date = time_ctime.replace('  ',' ').split(' ')
    months = {'Jan':1,'Feb':2,'Mar':3,'Apr':4,'May':5,'Jun':6,'Jul':7,'Aug':8,'Sep':9,'Oct':10,'Nov':11,'Dec':12}
    week, day, month, year = (-1, str(date[2]), months[date[1]], int(date[-1]))
    cal = calendar.monthcalendar(year,month)
    for wk in range(len(cal)):
        wstr = [str(x) for x in cal[wk]]
        if day in wstr:
            week = wk
            break
    return week

import time
print(week_number())
print(week_number(time.ctime()))

Upvotes: 0

TrafficDataAnalyst
TrafficDataAnalyst

Reputation: 25

One more solution, where Sunday is first day of week, base Python only.

def week_of_month(dt):
""" Returns the week of the month for the specified date.
TREATS SUNDAY AS FIRST DAY OF WEEK!
"""
    first_day = dt.replace(day=1)
    dom = dt.day
    adjusted_dom = dom + (first_day.weekday() + 1) % 7
    return (adjusted_dom - 1) // 7 + 1

Upvotes: 0

saravanan saminathan
saravanan saminathan

Reputation: 702

def week_of_month(date_value):
    week = date_value.isocalendar()[1] - date_value.replace(day=1).isocalendar()[1] + 1
    return date_value.isocalendar()[1] if week < 0 else week

date_value should in timestamp format This will give the perfect answer in all the cases. It is purely based on ISO calendar

Upvotes: 6

Tyson Whitehead
Tyson Whitehead

Reputation: 11

The answer you are looking for is (dm-dw+(dw-dm)%7)/7+1 where dm is the day of the month, dw is the day of the week, and % is the positive remainder.

This comes from relating the month offset (mo) and the week of the month (wm), where the month offset is how far into the week the first day starts. If we consider all of these variables to start at 0 we have

wm*7+dw = dm+mo

You can solve this modulo 7 for mo as that causes the wm variable drops out as it only appears as a multiple of 7

dw = dm+mo   (%7)
mo = dw-dm   (%7)
mo = (dw-dm)%7  (since the month offset is 0-6)

Then you just substitute the month offset into the original equation and solve for wm

wm*7+dw = dm+mo
wm*7 = dm-dw + mo
wm*7 = dm-dw + (dw-dm)%7
wm = (dm-dw + (dw-dm)%7) / 7

As dm and dw are always paired, these can be offset by any amount, so, switching everything to start a 1 only changes the the equation to (dm-dw + (dw-dm)%7)/7 + 1.

Of course the python datetime library starts dm at 1 and dw at 0. So, assuming date is a datatime.date object, you can go with

(date.day-1-date.dayofweek() + (date.dayofweek()-date.day+1)%7) / 7 + 1

As the inner bit is always a multiple of 7 (it is literally dw*7), you can see that the first -date.dayofweek() simply adjusts the value backwards to closest multiple of 7. Integer division does this too, so it can be further simplified to

(date.day-1 + (date.dayofweek()-date.day+1)%7) // 7 + 1

Be aware that dayofweek() puts Sunday at the end of the week.

Upvotes: 1

kamran kausar
kamran kausar

Reputation: 4593

  import datetime
    
  def week_number_of_month(date_value):
            week_number = (date_value.isocalendar()[1] - date_value.replace(day=1).isocalendar()[1] + 1)
            if week_number == -46:
                week_number = 6
           return week_number
                
 date_given = datetime.datetime(year=2018, month=12, day=31).date()
                
 week_number_of_month(date_given)

Upvotes: -1

Tan Nguyen
Tan Nguyen

Reputation: 1091

Move to last day of week in month and divide to 7

from math import ceil

def week_of_month(dt):
    """ Returns the week of the month for the specified date.
    """
    # weekday from monday == 0 ---> sunday == 6
    last_day_of_week_of_month = dt.day + (7 - (1 + dt.weekday()))
    return int(ceil(last_day_of_week_of_month/7.0))

Upvotes: 0

Prateek Dorwal
Prateek Dorwal

Reputation: 343

Check out the package Pendulum

>>> dt = pendulum.parse('2018-09-30')
>>> dt.week_of_month
5

Upvotes: 18

benelgiac
benelgiac

Reputation: 1011

Josh' answer seems the best but I think that we should take into account the fact that a week belongs to a month only if its Thursday falls into that month. At least that's what the iso says.

According to that standard, a month can have up to 5 weeks. A day could belong to a month, but the week it belongs to may not.

I have taken into account that just by adding a simple

if (first_day.weekday()>3) :
        return ret_val-1
    else:
        return ret_val

where ret_val is exactly Josh's calculated value. Tested on June 2017 (has 5 weeks) and on September 2017. Passing '2017-09-01' returns 0 because that day belongs to a week that does not belong to September.

The most correct way would be to have the method return both the week number and the month name the input day belongs to.

Upvotes: 1

Josh
Josh

Reputation: 13516

In order to use straight division, the day of month for the date you're looking at needs to be adjusted according to the position (within the week) of the first day of the month. So, if your month happens to start on a Monday (the first day of the week), you can just do division as suggested above. However, if the month starts on a Wednesday, you'll want to add 2 and then do the division. This is all encapsulated in the function below.

from math import ceil

def week_of_month(dt):
    """ Returns the week of the month for the specified date.
    """

    first_day = dt.replace(day=1)

    dom = dt.day
    adjusted_dom = dom + first_day.weekday()

    return int(ceil(adjusted_dom/7.0))

Upvotes: 59

I found a quite simple way:

import datetime
def week(year, month, day):
    first_week_month = datetime.datetime(year, month, 1).isocalendar()[1]
    if month == 1 and first_week_month > 10:
        first_week_month = 0
    user_date = datetime.datetime(year, month, day).isocalendar()[1]
    if month == 1 and user_date > 10:
        user_date = 0
    return user_date - first_week_month

returns 0 if first week

Upvotes: 0

Mark Byers
Mark Byers

Reputation: 838086

If your first week starts on the first day of the month you can use integer division:

import datetime
day_of_month = datetime.datetime.now().day
week_number = (day_of_month - 1) // 7 + 1

Upvotes: 18

vito huang
vito huang

Reputation: 4448

Check out the python calendar module

Upvotes: 0

Related Questions