How to calculate a third monday of every month using SQL statement? - mysql

i need to trigger a notification. this notification has to be triggered every third monday of every month.

SELECT
(
DAYOFWEEK(NOW()) = 2
AND
DAYOFMONTH(NOW()) BETWEEN 15 AND 21
)
AS send_notice_today;

Try using dayofweek and dayofmonth functions. http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_dayofweek
Somehow you can check how many weeks are there from 1st of month to curdate() with dayofmonth (using an operation mod 7), and dayofweek should be 5 (thursday)

So perhaps MORE GENERALLY if you can get the "week of month" for the date using this:
(FLOOR((DAYOFMONTH(given) - 1) / 7)) AS 'week_of_month'
which I believe provides an accurate 0 based week-of-month index for a given date. then you can use the value to find any nth as in:
WHERE (week_of_month) = n AND weekday = {weekday}
you can also use the above to get the "last {weekday}" by:
WHERE (week_of_month >= 4) and weekday = {weekday}
note that the week_of_month can range from 0 to 5 (e.g., a 31 day month whose 1st falls on Saturday will have the 31st in the 6th week (5 as a 0 based index)
hope this helps ...
OK a bit more ... you might define the above as a function as in:
CREATE FUNCTION WEEKOFMONTH(given date) RETURNS int DETERMINISTIC RETURN (FLOOR((DAYOFMONTH(given) - 1) / 7))
and add another function:
CREATE FUNCTION WEEKNAME(given date) RETURNS text CHARACTER SET utf8 COLLATE utf8_unicode_ci DETERMINISTIC RETURN (CONCAT(ELT(WEEKOFMONTH(given)+1,'1st ','2nd ','3rd ','4th/Last ','5th/Last '),DAYNAME(given)))
then you can simply say things like
SELECT * FROM dataTable WHERE WEEKNAME(your_date_field) = "3rd Wednesday"
... I struggled with how the 4th/5th should be returned from WEEKDAY and settled on adding "/Last" for both under the theory that this is "good enough" should one want to test for either 4th, 5th or Last. Using this you can do:
SELECT * FROM dataTable WHERE WEEKNAME(your_date_field) LIKE "%Last%"

The date of the third monday of the current month would be given by the SQL statement:
SELECT date_add(date_sub(curdate(),INTERVAL dayofmonth(curdate())-1 DAY),
INTERVAL (7-weekday(date_sub(curdate(),INTERVAL dayofmonth(curdate())-1 DAY)))+14 DAY)

The approach that I'm taking is get the 1st Monday of the month, and depending on when in the month it is, add either 2 or 3 weeks to it (since when it falls out before/on Monday, you only need to walk 2 more weeks):
;with
filler as (select row_number() over (order by a) a from (select top 100 1 as a from syscolumns) a cross join (select top 100 1 as b from syscolumns) b),
dates as (select dateadd(month, a-1, '1/1/1900') date from filler where a <= 2000),
FirstMonday as (
select dateadd(day, case datepart(weekday,Date)
when 1 then 1
when 2 then 0
when 3 then 6
when 4 then 5
when 5 then 4
when 6 then 3
when 7 then 2
end, Date) as Date
,case when datepart(weekday,Date) = 1 then 3 else 2 end as Weeks
from dates
)
select dateadd(week, Weeks, Date) as ThirdMonday
from FirstMonday

This one calculates the first Monday of any month given the year-month-day
SET #firstday = '2015-04-01';
SELECT ADDDATE( #firstday , MOD((9-DAYOFWEEK(#firstday)),7)) as first_monday;
This one calculates the third Monday of any month given the year-month-day
SET #firstday = '2015-01-01';
SELECT ADDDATE( #firstday , MOD((23-DAYOFWEEK(#firstday)),21)) as third_monday;
This one calculates the third Friday of any month given the year-month-day
SET #firstday = '2015-09-01';
SELECT ADDDATE( #firstday , MOD((20-DAYOFWEEK(#firstday)),20)) as third_friday;
Thanks to #Brewal for the original question and #User2208436 for pointing us toward the answer.

Here's an answer that does not use DAYOFWEEK or DAYOFMONTH. It uses DATEADD and DATEPART only.
We'll need two helper functions:
CREATE FUNCTION dbo.day_of_week(#date Date)
RETURNS INT
AS BEGIN
-- (1 for Sunday, 2 for Monday, etc)
RETURN DATEPART(dw, DATEADD(year, year(#date)-1900, DATEADD(month, month(#date)-1, DATEADD(day, day(#date)-1, 0))))
END
GO
CREATE FUNCTION dbo.date_from_parts(#year INT, #month INT, #day INT)
RETURNS DATE
AS BEGIN
RETURN DATEADD(year, #year-1900, DATEADD(month, #month-1, DATEADD(day, #day-1, 0)))
END
GO
Then using the following example data:
DECLARE #day_of_week INT
SET #day_of_week = 2 -- Monday
DECLARE #year INT
DECLARE #month INT
SET #year = 2016
SET #month = 11
Let's first obtain the FIRST Monday of the month:
We will add an offset, (day_of_week - day of week of first day of the month) % 7, to the first day of the month.
(Also notice we need the construction ((x % n) + n) % n instead of just x % 7, to keep the answer within 0 and 6. For example, just SELECT -3 % 7 returns -3! See Mod negative numbers in SQL just like excel)
Now here's the final construction to obtain the first Monday of the month:
SELECT
DATEADD(
dd,
(((#day_of_week -
dbo.day_of_week(dbo.date_from_parts(#year, #month, 1))) % 7) + 7) % 7,
dbo.date_from_parts(#year, #month, 1)
)
To obtain the third Monday of the month, add 14 to the second term of this answer.

If #firstday is the first day of the month
select date_add(#firstday,
interval (if(weekday(#firstday)>0,21-weekday(#firstday),14)) day);
yields the 3rd monday of the month.
Tested with all months of 2018.
Seems simpler than previous solutions.
The key is the if function on weekday.
if weekday = 0 (first day is a monday), add 14 days
if weekday > 0, add 21 days and subtract the weekday

DECLARE #YEAR DATE='2019-01-01'
SELECT DATEADD( d, 23-(DATEPART(dw,#YEAR )%21),#YEAR )
This will help to get the third Monday of whatever month you want.

Related

MySQL: need to calculate the last Friday of a month

I'm trying to solve a task: I have a table containing information about ships' battles. Battle is made of name and date. The problem is to get the last friday of the month when the battle occurred.
WITH num(n) AS(
SELECT 0
UNION ALL
SELECT n+1 FROM num
WHERE n < 31),
dat AS (
SELECT DATEADD(dd, n, CAST(battles.date AS DATE)) AS day,
dateadd(dd, 0, cast(battles.date as date)) as fight,
name FROM num,battles)
SELECT name, fight, max(day) FROM dat WHERE DATENAME(dw, day) = 'friday'
I thought there must be a maximum of date or something, but my code is wrong.
The result should look like this:
Please, help!!
P.S. DATE_FORMAT is not available
Possible problem: as spencer7593 noticed - and as I should have done and didn't - your original query is not MySQL at all. If you're porting a query that's OK. Otherwise this answer will not be helpful, as it makes use of MySQL functions.
The day you want is number 4 (0 being Sunday in MySQL).
So you want the last day of the month if the last day of the month is a 4; if the day of the month is a 5 you want a date which is 1 day earlier; if the day of the month is a 3 you want a date which is 1 day later, but that's impossible (the month ends), so you really need a date six days earlier.
This means that if the daynumber difference is negative, you want it modulo seven.
You can then build this expression (#DATE is your date; I use a fake date for testing)
SET #DATE='2015-02-18';
DATE_SUB(LAST_DAY(#DATE), INTERVAL ((WEEKDAY(LAST_DAY(#DATE))+7-4))%7 DAY);
It takes the last day of the month (LASTDAY(#DATE)), then it computes its weekday, getting a number from 0 to 6. Adds seven to ensure positivity after subtracting; then subtract the desired daynumber, in this case 4 for Friday.
The result, modulo seven, is the difference (always positive) from the last day's daynumber to the wanted daynumber. Since DATE_SUB(date, 0) returns the argument date, we needn't use IF.
SET #DATE='1962-10-20';
SELECT DATE_SUB(LAST_DAY(#DATE), INTERVAL ((WEEKDAY(LAST_DAY(#DATE))+7-4))%7 DAY) AS friday;
+------------+
| friday |
+------------+
| 1962-10-26 |
+------------+
Your query then would become something like:
SELECT `name`, `date`,
DATE_SUB(LAST_DAY(`date`),
INTERVAL ((WEEKDAY(LAST_DAY(`date`))+7-4))%7 DAY) AS friday
FROM battles;

Nth weekday of the month

Pseudo code of what I'd like to do:
SELECT * FROM table WHERE NTH_DAY(DATE(table_date)) = NTH_DAY($input_date);
I want to determine what the nth weekday of the month is for a given date. For example, if given the input "2013-08-30" it should return 5, since it is the fifth occurrence of that weekday (Friday) in the month.
I've been reading through countless similar questions but the majority are looking for just the opposite, for example they want to find the date of the fifth Friday, whereas I want to determine what the nth number of a date is. Other questions appear to be what I'm after but they're in different programming languages I don't understand.
Can someone please explain if this is possible in a MySQL query and how to do it?
To find which "nth" a given day is is rather easy:
select (case when day(table_date) between 1 and 7 then 1
when day(table_date) between 8 and 14 then 2
when day(table_date) between 15 and 21 then 3
when day(table_date) between 22 and 28 then 4
else 5
end) as NthDay
You can also do this using "remainder" arithmetic:
select 1 + floor((day(table_date) - 1) / 7) ) as NthDay
I'm going to throw my two cents in here with a third option that gets it based on finding the 1st of each day, and then adding multiples of 7 to get to the final result.
Function
CREATE FUNCTION `ISNTHDAYINMONTH`(checkDate DATE, weekNumber INTEGER, dayOfWeek INTEGER, monthOfYear INTEGER) RETURNS INTEGER
NO SQL
DETERMINISTIC
BEGIN
DECLARE firstOfMonth DATE;
SET firstOfMonth = DATE_SUB(checkDate, INTERVAL DAYOFMONTH(checkDate) - 1 DAY); #Find the first day of the current month
IF DAYOFWEEK(checkDate) = dayOfWeek AND MONTH(checkDate) = monthOfYear #Make sure at least this matches
AND DAYOFMONTH(checkDate) = ((1 + (7 + dayOfWeek - DAYOFWEEK(firstOfMonth)) % 7) + 7 * (weekNumber - 1)) THEN #When the date matches the nth dayOfWeek day of the month
RETURN 1;
ELSE
RETURN 0; #Nope
END IF;
END
Use
SELECT ISNTHDAYINMONTH(datecol, weekNumber, dayOfWeek, monthOfYear);
Where weekNumber is 1 through 5, dayOfWeek is 1 through 7 (1 = Sun), and monthOfYear is 1 through 12.
Example
To check if datecol is the second Tuesday of April:
SELECT ISNTHDAYINMONTH(datecol, 2, 3, 4);
Calculations
Let's break it down. This line gets the first day of the month for the date that is passed in.
SET firstOfMonth = DATE_SUB(checkDate, INTERVAL DAYOFMONTH(checkDate) - 1 DAY); #Find the first day of the current month
This check ensures that the date has the correct day of the week, and correct month (just in case the user passed in a date that isn't even the right day of the week).
DAYOFWEEK(checkDate) = dayOfWeek AND MONTH(checkDate) = monthOfYear
Now for the big one:
DAYOFMONTH(checkDate) = ((1 + (7 + dayOfWeek - DAYOFWEEK(firstOfMonth)) % 7) + 7 * (weekNumber - 1))
To get the amount we need to add to 1 to get the date, we calculate dayOfWeek that we are looking at, minus the day of the week that the first day of the month falls on to get the offset mod 7 ((dayOfWeek - DayOfWeek(first)) % 7).
Note that because the first of the month could land on a day before or on the day we are looking at, we add an additional 7 and then mod by seven. This is needed because MySQL's Mod function does not properly compute mod. I.e. -1 % 7 should be 6, but MySQL returns -1. Adding 7 and taking the modulus ensures the result will always be correct.
To summarize:
NumberToAddToOneToGetDateOfFirstWeekDay = (7 + DayOfWeek - DayOfWeek(firstDayOfMonth)) %
Then we add one and how every many multiples of 7 are needed to get to the correct week.
select ceiling(DATEPART(day, GETDATE())/7.0)
This will always give you nth occurrence number of current day in a month.
For example, if the current day is Friday, and it occurred 3rd time this month. It will return 3.
If you replace GETDATE() with your Date variable or column name it will return the occurrence of the day on that date in the corresponding month.
This would give the number of the week (which is actually the same as if it is the 5th friday or 5th Monday)
SELECT WEEK(dateasd,5) -
WEEK(DATE_SUB(dateasd, INTERVAL DAYOFMONTH(dateasd)-1 DAY),5)
from test
Use Weekday() function to find which day it is
Fiddle at
http://www.sqlfiddle.com/#!2/7a8b6/2/0

How to get week number of the month from the date in sql server 2008

In SQL Statement in microsoft sql server, there is a built-in function to get week number but it is the week of the year.
Select DatePart(week, '2012/11/30') // **returns 48**
The returned value 48 is the week number of the year.
Instead of 48, I want to get 1, 2, 3 or 4 (week number of the month). I think the week number of the month can be achieved by modules with Month Number of this week. For e.g.
Select DATEPART(week, '2012/11/30')%MONTH('2012/11/30')
But I want to know is there other built-in functions to get WeekNumber of the month in MS SQL SERVER.
Here are 2 different ways, both are assuming the week starts on monday
If you want weeks to be whole, so they belong to the month in which they start:
So saturday 2012-09-01 and sunday 2012-09-02 is week 4 and monday 2012-09-03 is week 1 use this:
DECLARE #date date = '2012-09-01'
SELECT (day(datediff(d,0,#date)/7*7)-1)/7+1
If your weeks cut on monthchange so saturday 2012-09-01 and sunday 2012-09-02 is week 1 and monday 2012-09-03 is week 2 use this:
DECLARE #date date = '2012-09-01'
SELECT
datediff(ww,datediff(d,0,dateadd(m,datediff(m,7,#date),0)
)/7*7,dateadd(d,-1,#date))+1
I received an email from Gerald. He pointed out a flaw in the second method. This should be fixed now
I received an email from Ben Wilkins. He pointed out a flaw in the first method. This should be fixed now
DECLARE #DATE DATETIME
SET #DATE = '2013-08-04'
SELECT DATEPART(WEEK, #DATE) -
DATEPART(WEEK, DATEADD(MM, DATEDIFF(MM,0,#DATE), 0))+ 1 AS WEEK_OF_MONTH
No built-in function. It depends what you mean by week of month. You might mean whether it's in the first 7 days (week 1), the second 7 days (week 2), etc. In that case it would just be
(DATEPART(day,#Date)-1)/7 + 1
If you want to use the same week numbering as is used with DATEPART(week,), you could use the difference between the week numbers of the first of the month and the date in question (+1):
(DATEPART(week,#Date)- DATEPART(week,DATEADD(m, DATEDIFF(m, 0, #Date), 0))) + 1
Or, you might need something else, depending on what you mean by the week number.
Just look at the date and see what range it falls in.
Range 1-7 is the 1st week, Range 8-14 is the 2nd week, etc.
SELECT
CASE WHEN DATEPART(day,yourdate) < 8 THEN '1'
ELSE CASE WHEN DATEPART(day,yourdate) < 15 then '2'
ELSE CASE WHEN DATEPART(day,yourdate) < 22 then '3'
ELSE CASE WHEN DATEPART(day,yourdate) < 29 then '4'
ELSE '5'
END
END
END
END
Similar to the second solution, less code:
declare #date datetime = '2014-03-31'
SELECT DATEDIFF(week,0,#date) - (DATEDIFF(week,0,DATEADD(dd, -DAY(#date)+1, #date))-1)
Check this out... its working fine.
declare #date as datetime = '2014-03-10'
select DATEPART(week,#date) - DATEPART(week,cast(cast(year(#date) as varchar(4))+'-' + cast(month(#date) as varchar(2)) + '-01' as datetime))+1
WeekMonth = CASE WHEN (DATEPART(day,TestDate) - datepart(dw,TestDate))>= 22 THEN '5'
WHEN (DATEPART(day,TestDate) - datepart(dw,TestDate))>= 15 THEN '4'
WHEN (DATEPART(day,TestDate) - datepart(dw,TestDate))>= 8 THEN '3'
WHEN (DATEPART(day,TestDate) - datepart(dw,TestDate))>= 1 THEN '2'
ELSE '1'
END
There is no inbuilt function to get you the week number. I dont think dividing will help you anyway as the number of weeks in a month is not constant.
http://msdn.microsoft.com/en-us/library/bb675168.aspx
I guess you can divide the number(48) by 4 and take the modules of the same and project that as the week number of that month, by adding one to the result.
Here's a suggestion for getting the first and last days of the week for a month:
-- Build a temp table with all the dates of the month
drop table #tmp_datesforMonth
go
declare #begDate datetime
declare #endDate datetime
set #begDate = '6/1/13'
set #endDate = '6/30/13';
WITH N(n) AS
( SELECT 0
UNION ALL
SELECT n+1
FROM N
WHERE n <= datepart(dd,#enddate)
)
SELECT DATEADD(dd,n,#BegDate) as dDate
into #tmp_datesforMonth
FROM N
WHERE MONTH(DATEADD(dd,n,#BegDate)) = MONTH(#BegDate)
--- pull results showing the weeks' dates and the week # for the month (not the week # for the current month)
select MIN(dDate) as BegOfWeek
, MAX(dDate) as EndOfWeek
, datediff(week, dateadd(week, datediff(week, 0, dateadd(month, datediff(month, 0, dDate), 0)), 0), dDate) as WeekNumForMonth
from #tmp_datesforMonth
group by datediff(week, dateadd(week, datediff(week, 0, dateadd(month, datediff(month, 0, dDate), 0)), 0), dDate)
order by 3, 1
A dirty but easy one liner using Dense_Rank function. Performance WILL suffer, but effective none the less.
DENSE_RANK()over(Partition by Month(yourdate),Year(yourdate) Order by Datepart(week,yourdate) asc) as Week
Here is the query that brings the week number on whatever the startday and endday of the week it may be.
SET DATEFIRST 2
DECLARE #FROMDATE DATE='12-JAN-2015'
-- Get the first day of month
DECLARE #ALLDATE DATE=DATEADD(month, DATEDIFF(month, 0, #FROMDATE), 0)
DECLARE #FIRSTDATE DATE
;WITH CTE as
(
-- Get all dates in that month
SELECT 1 RNO,CAST(#ALLDATE AS DATE) as DATES
UNION ALL
SELECT RNO+1, DATEADD(DAY,1,DATES )
FROM CTE
WHERE DATES < DATEADD(MONTH,1,#ALLDATE)
)
-- Retrieves the first day of week, ie, if first day of week is Tuesday, it selects first Tuesday
SELECT TOP 1 #FIRSTDATE = DATES
FROM CTE
WHERE DATEPART(W,DATES)=1
SELECT (DATEDIFF(DAY,#FIRSTDATE,#FROMDATE)/7)+1 WEEKNO
For more information I have answered for the below question. Can check that.
How do I find week number of a date according to DATEFIRST
floor((day(#DateValue)-1)/7)+1
Here you go....
Im using the code below..
DATEPART(WK,#DATE_INSERT) - DATEPART(WK,DATEADD(DAY,1,DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#DATE_INSERT),0)))) + 1
Try Below Code:
declare #dt datetime='2018-03-15 05:16:00.000'
IF (Select (DatePart(DAY,#dt)%7))>0
Select (DatePart(DAY,#dt)/7) +1
ELSE
Select (DatePart(DAY,#dt)/7)
There is an inbuilt option to get the week number of the year
**select datepart(week,getdate())**
You can simply get week number by getting minimum week number of month and deduct it from week number. Suppose you have a table with dates
select
emp_id, dt , datepart(wk,dt) - (select min(datepart(wk,dt))
from
workdates ) + 1 from workdates
Solution:
declare #dt datetime='2018-03-31 05:16:00.000'
IF (Select (DatePart(DAY,#dt)%7))>0
Select (DatePart(DAY,#dt)/7) +1
ELSE
Select (DatePart(DAY,#dt)/7)
declare #end_date datetime = '2019-02-28';
select datepart(week, #end_date) - datepart(week, convert(datetime, substring(convert(nvarchar, convert(datetime, #end_date), 127), 1, 8) + '01')) + 1 [Week of Month];
Here is the tried and tested solution for this query in any situation - like if 1st of the month is on Friday , then also this will work -
select (DATEPART(wk,#date_given)-DATEPART(wk,dateadd(d,1-day(#date_given),#date_given)))+1
above are some solutions which will fail if the month's first date is on Friday , then 4th will be 2nd week of the month
Logic here works as well 4.3 weeks in every month. Take that from the DATEPART(WEEK) on every month but January. Just another way of looking at things. This would also account for months where there is a 5th week
DECLARE #date VARCHAR(10)
SET #date = '7/27/2019'
SELECT CEILING(DATEPART(WEEK,#date)-((DATEPART(MONTH,#date)-1)*4.3333)) 'Week of Month'
Below will only work if you have every week of the month represented in the select list. Else the rank function will not work, but it is a good solution.
SELECT DENSE_RANK() OVER (PARTITION BY MONTH(DATEFIELD)
ORDER BY DATEPART(WEEK,DATEFIELD) ASC) AS WeekofMont
try this one
declare #date datetime = '20210928'
select convert(int,(((cast(datepart(day,#date) as decimal(4,2))/7)-(1.00/7.00))+1.00))
select datepart(week,#date)-datepart(week,dateadd(day,1,eomonth(dateadd(m,-1,#date))))+1
or
select datepart(week,#date)-datepart(week,dateadd(d,-datepart(d,#date)+1,#date))+1
steps:
1,get the first day of month
2,week of year of the date - week of year of the first day of the month
3,+1
2023-1-1 is sunday
SET DATEFIRST 1;
DECLARE #date date = '2023-1-02';
select datepart(week,#date)-datepart(week,dateadd(day,1,eomonth(dateadd(m,-1,#date))))+1
return 2
SET DATEFIRST 7;
DECLARE #date date = '2023-1-02';
select datepart(week,#date)-datepart(week,dateadd(day,1,eomonth(dateadd(m,-1,#date))))+1
return 1
Code is below:
set datefirst 7
declare #dt datetime='29/04/2016 00:00:00'
select (day(#dt)+datepart(WEEKDAY,dateadd(d,-day(#dt),#dt+1)))/7
select #DateCreated, DATEDIFF(WEEK, #DateCreated, GETDATE())

Number of e.g. Mondays left in month

How do you most easily calculate how many e.g. Mondays are left in a month using MySQL (counting today)?
Bonus points for a solution that solves it for all days of the week in one query.
Desired output (run on Tuesday August 17th 2010):
dayOfWeek left
1 2 -- Sunday
2 2 -- Monday
3 3 -- Tuesday (yep, including today)
4 2 -- Wednesday
5 2 -- Thursday
6 2 -- Friday
7 2 -- Saturday
Create a date table that contains one row for each day that you care about (say Jan 1 2000 - Dec 31 2099):
create table dates (the_date date primary key);
delimiter $$
create procedure populate_dates (p_start_date date, p_end_date date)
begin
declare v_date date;
set v_date = p_start_date;
while v_date <= p_end_date
do
insert ignore into dates (the_date) values (v_date);
set v_Date = date_add(v_date, interval 1 day);
end while;
end $$
delimiter ;
call populate_dates('2000-01-01','2099-12-31');
Then you can run a query like this to get your desired output:
set #date = curdate();
select dayofweek(the_date) as dayOfWeek, count(*) as numLeft
from dates
where the_date >= #date
and the_date < str_to_date(period_add(date_format(#date,'%Y%m'),1),'%Y%m')
group by dayofweek(the_date);
That will exclude days of the week that have 0 occurrences left in the month. If you want to see those you can create another table with the days of the week (1-7):
create table days_of_week (
id tinyint unsigned not null primary key,
name char(10) not null
);
insert into days_of_week (id,name) values (1,'Sunday'),(2,'Monday'),
(3,'Tuesday'),(4,'Wednesday'),(5,'Thursday'),(6,'Friday'),(7,'Saturday');
And query that table with a left join to the dates table:
select w.id, count(d.the_Date) as numLeft
from days_of_week w
left outer join dates d on w.id = dayofweek(d.the_date)
and d.the_date >= #date
and d.the_date < str_to_date(period_add(date_format(#date,'%Y%m'),1),'%Y%m')
group by w.id;
i found something
according to this article "find next monday"
http://www.gizmola.com/blog/archives/99-Finding-Next-Monday-using-MySQL-Dates.html
SELECT DATE_ADD(CURDATE(), INTERVAL (9 - IF(DAYOFWEEK(CURDATE())=1, 8,
DAYOFWEEK(CURDATE()))) DAY) AS NEXTMONDAY;
what we need to do is calculate the days between end month and next Monday,
and divide in 7 .
update (include current day) :
so the result is like :
for Monday
SELECT CEIL( ((DATEDIFF(LAST_DAY(NOW()),DATE_ADD(CURDATE(),
INTERVAL (9 - IF(DAYOFWEEK(CURDATE())=1, 8, DAYOFWEEK(CURDATE()))) DAY)))+1)/7)
+ IF(DAYOFWEEK(CURDATE())=2,1,0)
for Tuesday :
SELECT CEIL( ((DATEDIFF(LAST_DAY(NOW()),DATE_ADD(CURDATE(),
INTERVAL (10 - IF(DAYOFWEEK(CURDATE())=1, 8, DAYOFWEEK(CURDATE()))) DAY)))+1)/7)
+ IF(DAYOFWEEK(CURDATE())=3,1,0)
Have a look at my responses to;
MySQL: Using the dates in a between condition for the results
and
Select all months within given date span, including the ones with 0 values
for a way I think would work nicely, similar to #Walker's above, but without having to do the dayofweek() function within the query, and possibly more flexible too. One of the responses has a link to a SQL dump of my table which can be imported if it helps!

How to get difference between two dates in months using MySQL query?

I'm looking to calculate the number of months between 2 date time fields.
Is there a better way than getting the Unix timestamp and then dividing by 2 592 000 (seconds) and rounding up within MySQL?
Month-difference between any given two dates:
I'm surprised this hasn't been mentioned yet:
Have a look at the TIMESTAMPDIFF() function in MySQL.
What this allows you to do is pass in two TIMESTAMP or DATETIME values (or even DATE as MySQL will auto-convert) as well as the unit of time you want to base your difference on.
You can specify MONTH as the unit in the first parameter:
SELECT TIMESTAMPDIFF(MONTH, '2012-05-05', '2012-06-04')
-- Outputs: 0
SELECT TIMESTAMPDIFF(MONTH, '2012-05-05', '2012-06-05')
-- Outputs: 1
SELECT TIMESTAMPDIFF(MONTH, '2012-05-05', '2012-06-15')
-- Outputs: 1
SELECT TIMESTAMPDIFF(MONTH, '2012-05-05', '2012-12-16')
-- Outputs: 7
It basically gets the number of months elapsed from the first date in the parameter list. This solution automatically compensates for the varying amount of days in each month (28,30,31) as well as taking into account leap years — you don't have to worry about any of that stuff.
Month-difference with precision:
It's a little more complicated if you want to introduce decimal precision in the number of months elapsed, but here is how you can do it:
SELECT
TIMESTAMPDIFF(MONTH, startdate, enddate) +
DATEDIFF(
enddate,
startdate + INTERVAL
TIMESTAMPDIFF(MONTH, startdate, enddate)
MONTH
) /
DATEDIFF(
startdate + INTERVAL
TIMESTAMPDIFF(MONTH, startdate, enddate) + 1
MONTH,
startdate + INTERVAL
TIMESTAMPDIFF(MONTH, startdate, enddate)
MONTH
)
Where startdate and enddate are your date parameters, whether it be from two date columns in a table or as input parameters from a script:
Examples:
With startdate = '2012-05-05' AND enddate = '2012-05-27':
-- Outputs: 0.7097
With startdate = '2012-05-05' AND enddate = '2012-06-13':
-- Outputs: 1.2667
With startdate = '2012-02-27' AND enddate = '2012-06-02':
-- Outputs: 3.1935
PERIOD_DIFF calculates months between two dates.
For example, to calculate the difference between now() and a time column in your_table:
select period_diff(date_format(now(), '%Y%m'), date_format(time, '%Y%m')) as months from your_table;
I use also PERIOD_DIFF. To get the year and the month of the date, I use the function EXTRACT:
SELECT PERIOD_DIFF(EXTRACT(YEAR_MONTH FROM NOW()), EXTRACT(YEAR_MONTH FROM time)) AS months FROM your_table;
The DATEDIFF function can give you the number of days between two dates. Which is more accurate, since... how do you define a month? (28, 29, 30, or 31 days?)
As many of the answers here show, the 'right' answer depends on exactly what you need. In my case, I need to round to the closest whole number.
Consider these examples:
1st January -> 31st January: It's 0 whole months, and almost 1 month long.
1st January -> 1st February? It's 1 whole month, and exactly 1 month long.
To get the number of whole (complete) months, use:
SELECT TIMESTAMPDIFF(MONTH, '2018-01-01', '2018-01-31'); => 0
SELECT TIMESTAMPDIFF(MONTH, '2018-01-01', '2018-02-01'); => 1
To get a rounded duration in months, you could use:
SELECT ROUND(TIMESTAMPDIFF(DAY, '2018-01-01', '2018-01-31')*12/365.24); => 1
SELECT ROUND(TIMESTAMPDIFF(DAY, '2018-01-01', '2018-01-31')*12/365.24); => 1
This is accurate to +/- 5 days and for ranges over 1000 years. Zane's answer is obviously more accurate, but it's too verbose for my liking.
I prefer this way, because evryone will understand it clearly at the first glance:
SELECT
12 * (YEAR(to) - YEAR(from)) + (MONTH(to) - MONTH(from)) AS months
FROM
tab;
From the MySQL manual:
PERIOD_DIFF(P1,P2)
Returns the number of months between periods P1 and P2. P1 and P2 should be in the format YYMM or YYYYMM. Note that the period arguments P1 and P2 are not date values.
mysql> SELECT PERIOD_DIFF(200802,200703);
-> 11
So it may be possible to do something like this:
Select period_diff(concat(year(d1),if(month(d1)<10,'0',''),month(d1)), concat(year(d2),if(month(d2)<10,'0',''),month(d2))) as months from your_table;
Where d1 and d2 are the date expressions.
I had to use the if() statements to make sure that the months was a two digit number like 02 rather than 2.
Is there a better way?
yes. Do not use MySQL Timestamps. Apart from the fact that they occupy 36 Bytes, they are not at all convenient to work with.
I would reccomend using Julian Date and Seconds from midnight for all date/time values.
These can be combined to form a UnixDateTime. If this is stored in a DWORD (unsigned 4 Byte Integer) then dates all the way up to 2106 can be stored as seconds since epoc, 01/01/1970
DWORD max val = 4,294,967,295 - A DWORD can hold 136 years of Seconds
Julian Dates are very nice to work with when making date calculations
UNIXDateTime values are good to work with when making Date/Time calculations
Neither are good to look at, so I use the Timestamps when I need a column that I will not be doing much calculation with, but I want an at-a-glance indication.
Converting to Julian and back can be done very quickly in a good language. Using pointers I have it down to about 900 Clks (This is also a conversion from a STRING to an INTEGER of course)
When you get into serious applications that use Date/Time information like for example the financial markets, Julian dates are de-facto.
The Query will be like:
select period_diff(date_format(now(),"%Y%m"),date_format(created,"%Y%m")) from customers where..
Gives a number of calendar months since the created datestamp on a customer record, letting MySQL do the month selection internally.
DROP FUNCTION IF EXISTS `calcula_edad` $$
CREATE DEFINER=`root`#`localhost` FUNCTION `calcula_edad`(pFecha1 date, pFecha2 date, pTipo char(1)) RETURNS int(11)
Begin
Declare vMeses int;
Declare vEdad int;
Set vMeses = period_diff( date_format( pFecha1, '%Y%m' ), date_format( pFecha2, '%Y%m' ) ) ;
/* Si el dia de la fecha1 es menor al dia de fecha2, restar 1 mes */
if day(pFecha1) < day(pFecha2) then
Set vMeses = VMeses - 1;
end if;
if pTipo='A' then
Set vEdad = vMeses div 12 ;
else
Set vEdad = vMeses ;
end if ;
Return vEdad;
End
select calcula_edad(curdate(),born_date,'M') -- for number of months between 2 dates
Execute this code and it will create a function datedeifference which will give you the difference in date format yyyy-mm-dd.
DELIMITER $$
CREATE FUNCTION datedifference(date1 DATE, date2 DATE) RETURNS DATE
NO SQL
BEGIN
DECLARE dif DATE;
IF DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(date1), '-', DAY(date2)))) < 0 THEN
SET dif=DATE_FORMAT(
CONCAT(
PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))DIV 12 ,
'-',
PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))% 12 ,
'-',
DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(DATE_SUB(date1, INTERVAL 1 MONTH)), '-', DAY(date2))))),
'%Y-%m-%d');
ELSEIF DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(date1), '-', DAY(date2)))) < DAY(LAST_DAY(DATE_SUB(date1, INTERVAL 1 MONTH))) THEN
SET dif=DATE_FORMAT(
CONCAT(
PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))DIV 12 ,
'-',
PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))% 12 ,
'-',
DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(date1), '-', DAY(date2))))),
'%Y-%m-%d');
ELSE
SET dif=DATE_FORMAT(
CONCAT(
PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))DIV 12 ,
'-',
PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))% 12 ,
'-',
DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(date1), '-', DAY(date2))))),
'%Y-%m-%d');
END IF;
RETURN dif;
END $$
DELIMITER;
This depends on how you want the # of months to be defined. Answer this questions: 'What is difference in months: Feb 15, 2008 - Mar 12, 2009'. Is it defined by clear cut # of days which depends on leap years- what month it is, or same day of previous month = 1 month.
A calculation for Days:
Feb 15 -> 29 (leap year) = 14
Mar 1, 2008 + 365 = Mar 1, 2009.
Mar 1 -> Mar 12 = 12 days.
14 + 365 + 12 = 391 days.
Total = 391 days / (avg days in month = 30) = 13.03333
A calculation of months:
Feb 15 2008 - Feb 15 2009 = 12
Feb 15 -> Mar 12 = less than 1 month
Total = 12 months, or 13 if feb 15 - mar 12 is considered 'the past month'
SELECT *
FROM emp_salaryrevise_view
WHERE curr_year Between '2008' AND '2009'
AND MNTH Between '12' AND '1'
I needed month-difference with precision. Although Zane Bien's solution is in the right direction, his second and third examples give inaccurate results. A day in February divided by the number of days in February is not equal to a day in May divided by the number of days in May. So the second example should output ((31-5+1)/31 + 13/30 = ) 1.3043 and the third example ((29-27+1)/29 + 2/30 + 3 = ) 3.1701.
I ended up with the following query:
SELECT
'2012-02-27' AS startdate,
'2012-06-02' AS enddate,
TIMESTAMPDIFF(DAY, (SELECT startdate), (SELECT enddate)) AS days,
IF(MONTH((SELECT startdate)) = MONTH((SELECT enddate)), 0, (TIMESTAMPDIFF(DAY, (SELECT startdate), LAST_DAY((SELECT startdate)) + INTERVAL 1 DAY)) / DAY(LAST_DAY((SELECT startdate)))) AS period1,
TIMESTAMPDIFF(MONTH, LAST_DAY((SELECT startdate)) + INTERVAL 1 DAY, LAST_DAY((SELECT enddate))) AS period2,
IF(MONTH((SELECT startdate)) = MONTH((SELECT enddate)), (SELECT days), DAY((SELECT enddate))) / DAY(LAST_DAY((SELECT enddate))) AS period3,
(SELECT period1) + (SELECT period2) + (SELECT period3) AS months
PERIOD_DIFF() function
One of the way is MySQL PERIOD_DIFF() returns the difference between two periods. Periods should be in the same format i.e. YYYYMM or YYMM. It is to be noted that periods are not date values.
Code:
SELECT PERIOD_DIFF(200905,200811);
You can get years, months and days this way:
SELECT
username
,date_of_birth
,DATE_FORMAT(CURDATE(), '%Y') - DATE_FORMAT(date_of_birth, '%Y') - (DATE_FORMAT(CURDATE(), '00-%m-%d') < DATE_FORMAT(date_of_birth, '00-%m-%d')) AS years
,PERIOD_DIFF( DATE_FORMAT(CURDATE(), '%Y%m') , DATE_FORMAT(date_of_birth, '%Y%m') ) AS months
,DATEDIFF(CURDATE(),date_of_birth) AS days
FROM users
You can also try this:
select MONTH(NOW())-MONTH(table_date) as 'Total Month Difference' from table_name;
OR
select MONTH(Newer_date)-MONTH(Older_date) as 'Total Month Difference' from table_Name;
Simple answer given start date as ins_frm and end date as ins_to
SELECT convert(TIMESTAMPDIFF(year, ins_frm, ins_to),UNSIGNED) as yrs,
mod(TIMESTAMPDIFF(MONTH, ins_frm, ins_to),12) mnths
FROM table_name
Enjoy :)))
Try this
SELECT YEAR(end_date)*12 + MONTH(end_date) - (YEAR(start_date)*12 + MONTH(start_date))
Although it's an old topic it shows on top in google and I don't see newer questions related to Mysql to calculate the difference in months. And I needed a very precise calculation including the fraction of the month.
This for the purpose to calculate a subscription fee e.g. 8 euro per month. Then 1 day in februari does have a different price compared to other months. So the fraction of months needs to be calculated and here the precision of the fraction is based on seconds.
What it does is to split the calculation into 3 parts when calculation between #from and #to dates:
fraction of the calendar month between #from and the end of the #from calendar month
number of whole calendar months between #from and #to
fraction of the calendar month between start of the calendar month and #to
E.g from '2021-09-29 12:00:00' to '2021-11-07 00:00:00':
The 1.5 days at the end of september 2021. September does have 30
days so the fraction is 0.05 month (1.5/30).
the whole month oktober 2021 so 1 full month
The 6 full days at the begin of november 2021. November does have 30 days so the faction is 0.2 month (6/30).
So the outcome is 1.25 month.
set #from = '2021-09-29 12:00:00';
set #to = '2021-11-07 00:00:00';
select
/* part 1 */ (unix_timestamp(last_day(#from)) + 86400 - unix_timestamp(#from)) / 86400 / day(last_day(#from))
/* part 2 */ + PERIOD_DIFF(EXTRACT(YEAR_MONTH FROM #to), EXTRACT(YEAR_MONTH FROM #from)) - 1 +
/* part 3 */ 1 - (unix_timestamp(last_day(#to)) + 86400 - unix_timestamp(#to)) / 86400 / day(last_day(#to))
month_fraction;
Exactly the same calculation but now based on fields for people not using mysql variables and easier to take over your own fields:
select
/* part 1 */ (unix_timestamp(last_day(periodStart)) + 86400 - unix_timestamp(periodStart)) / 86400 / day(last_day(periodStart))
/* part 2 */ + PERIOD_DIFF(EXTRACT(YEAR_MONTH FROM periodTill), EXTRACT(YEAR_MONTH FROM periodStart)) - 1 +
/* part 3 */ 1 - (unix_timestamp(last_day(periodTill)) + 86400 - unix_timestamp(periodTill)) / 86400 / day(last_day(periodTill))
month_fraction
from (select '2021-09-29 12:00:00' periodStart, '2021-11-07 00:00:00' periodTill) period
For speed optimization I've used unix_timestamp which should perform fast as it is able to use mathematic calculation. The unix_timestamp returns a number in seconds. The 86400 is the number of seconds in a day.
This query worked for me:)
SELECT * FROM tbl_purchase_receipt
WHERE purchase_date BETWEEN '2008-09-09' AND '2009-09-09'
It simply take two dates and retrieves the values between them.