MYSQL Add working days to date - mysql

I want to add 5 days to the provided date, but the calculation must skip weekends.
I already know how to add 5 days without skipping weekends:
SELECT DATE_ADD(`date_field`, INTERVAL 5 DAY) As FinalDate
FROM `table_name`;
Now I want the returned value to skip weekends.
Currently if date_field = 2016-07-22 the results will be 2016-07-27
But I want the results to be 2016-07-29

Try this:
SELECT DATE_ADD(
date_field,
INTERVAL 5 +
IF(
(WEEK(date_field) <> WEEK(DATE_ADD(date_field, INTERVAL 5 DAY)))
OR (WEEKDAY(DATE_ADD(date_field, INTERVAL 5 DAY)) IN (5, 6)),
2,
0)
DAY
) AS FinalDate
FROM `table_name`;
How it works:
Firstly, it will add 5 days on your date.
Secondly, when date_field and 5 days later are in two different weeks, it must be added additional 2 days.
Thirdly, when 5 days later is Sat or Sun, it must be added additional 2 days.

This can easily be done for an arbitrary amount of days with a recursive CTE:
WITH RECURSIVE a AS (
SELECT
CURRENT_DATE date, -- Start date
0 days
UNION
SELECT
-- Always increment the date
a.date + INTERVAL 1 DAY AS date,
-- Increment the work day count only if it's not a weekend day
a.days + (WEEKDAY(a.date + INTERVAL 1 DAY) < 5) AS days
FROM a
WHERE
-- Keep going until the week day count reaches 10
a.days < 10 -- Amount of days to add
)
SELECT MAX(date)
FROM a
In the example situation you would use a subquery:
SELECT
(
WITH RECURSIVE a AS (
SELECT
date_field date,
0 days
UNION
SELECT
a.date + INTERVAL 1 DAY AS date,
a.days + (WEEKDAY(a.date + INTERVAL 1 DAY) < 5) AS days
FROM a
WHERE a.days < 5
)
SELECT MAX(date)
FROM a
) AS final_date
FROM table_name

I did try your solution but faced a problem when using it with a larger interval (e.g 20 days). It works perfectly with little intervals though.
Example : for '2017-10-04' + 20 days, your algorithm return '2017-10-26'. It should be '2017-11-01' since we skip 4 weekends.
The numbers of days you add isn't calculated depending on the difference between the 2 week #, so the maximum days you can add is 2 and in my case, it should be 8 (4x2).
I modified your code to end up with this (I also add variables, much more convenient to modify)
SELECT
#ID:='2017-10-04' as initial_date, -- the initial date in the right format to manipulate (add x day)
#DTA:=20 as days_to_add, -- number of days to add
#DA:= DATE_ADD(#ID, INTERVAL #DTA DAY) as date_add,
#LASTDAY := WEEKDAY(#DA) as last_day, -- the day (Monday, Tuesday...) corresponding to the initial date + number of days to add
#WEEK1 := DATE_FORMAT(#ID, '%v') as initial_date_week, -- format the initial date to match week mode 3 (Monday 1-53)
#WEEK2 := DATE_FORMAT(#DA, '%v') as added_date_week_nbr, -- the week # of the initial_date + number of days to add
#WEEKDIFF := #WEEK2 - #WEEK1 as week_difference, -- the difference between week 2 and week 1
DATE_ADD(#ID,
INTERVAL #DTA +
if ( #WEEKDIFF > 0 or #LASTDAY in (5,6),
2,
0
) +
if (#WEEKDIFF > 1,
#WEEKDIFF*2,
0
) DAY
) AS FinalDate
The way I get my week numbers can seems weird but this is because I'm running this in France and my DB seems to be configured in a way that weeks are natively starting by Sunday, "%v" represent the 'mode 3' for weeks, you can check the MySQL documentation here for more details : https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html (ctrl + F > '%v')
I didn't implement the public holiday yet, but I'm thinking of adding X days in the calculation each times one of this day is in the period we're looking at.
According to my (few) tests, this should work. Let me know if not

try this out, should work nicely, basically loop through each of the days and check if they are saturday or sunday, ignore them.
https://social.technet.microsoft.com/wiki/contents/articles/30877.t-sql-extending-dateadd-function-to-skip-weekend-days.aspx

Plus hollydays 1 or 2
select GREATEST(WEEKDAY(NOW()) - 4, 0) 'hollydays'

My solution is to create a function that returns the calculated date, it will consider the consecutive weekends:
DELIMITER $$
CREATE FUNCTION `add_working_days_to_current_month`(ndays INT) RETURNS DATE
NO SQL
BEGIN
declare finalDate, startDate, originalFinalDate DATE;
declare weekNumberStartDate, weekNumberEndDate, weekDiff INT;
set startDate = DATE(CONCAT(YEAR(DATE_SUB(current_date(), INTERVAL 1 MONTH)),"-",MONTH(DATE_SUB(current_date(), INTERVAL 1 MONTH)),"-",DAY(LAST_DAY(DATE_SUB(current_date(), INTERVAL 1 MONTH)))));
set weekNumberStartDate = WEEK(startDate);
set finalDate = DATE_ADD(startDate, INTERVAL ndays DAY);
set originalFinalDate = finalDate;
set weekNumberEndDate = WEEK(finalDate);
IF(weekNumberEndDate != weekNumberStartDate) THEN
set weekDiff = (weekNumberEndDate - weekNumberStartDate) * 2;
set finalDate = DATE_ADD(finalDate, INTERVAL weekDiff DAY);
END IF;
set weekNumberStartDate = WEEK(originalFinalDate);
set weekNumberEndDate = WEEK(finalDate);
IF(weekNumberEndDate != weekNumberStartDate) THEN
set weekDiff = (weekNumberEndDate - weekNumberStartDate) * 2;
set finalDate = DATE_ADD(finalDate, INTERVAL weekDiff DAY);
END IF;
IF(WEEKDAY(finalDate) IN (5, 6)) THEN
set finalDate = DATE_ADD(finalDate, INTERVAL 2 DAY);
END IF;
return finalDate;
END$$
DELIMITER ;
Basically i'm using the same logic as the accepted answer but acumulating the weekends. So for each weekend i will add 2 days and at the end i will if the result date is on weekend i will add 2 more

WHERE datefield BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL 7 DAY
AND WEEKDAY(datefield) NOT IN (5,6);

Related

How to calculate age in SQL view [duplicate]

I have a table listing people along with their date of birth (currently a nvarchar(25))
How can I convert that to a date, and then calculate their age in years?
My data looks as follows
ID Name DOB
1 John 1992-01-09 00:00:00
2 Sally 1959-05-20 00:00:00
I would like to see:
ID Name AGE DOB
1 John 17 1992-01-09 00:00:00
2 Sally 50 1959-05-20 00:00:00
There are issues with leap year/days and the following method, see the update below:
try this:
DECLARE #dob datetime
SET #dob='1992-01-09 00:00:00'
SELECT DATEDIFF(hour,#dob,GETDATE())/8766.0 AS AgeYearsDecimal
,CONVERT(int,ROUND(DATEDIFF(hour,#dob,GETDATE())/8766.0,0)) AS AgeYearsIntRound
,DATEDIFF(hour,#dob,GETDATE())/8766 AS AgeYearsIntTrunc
OUTPUT:
AgeYearsDecimal AgeYearsIntRound AgeYearsIntTrunc
--------------------------------------- ---------------- ----------------
17.767054 18 17
(1 row(s) affected)
UPDATE here are some more accurate methods:
BEST METHOD FOR YEARS IN INT
DECLARE #Now datetime, #Dob datetime
SELECT #Now='1990-05-05', #Dob='1980-05-05' --results in 10
--SELECT #Now='1990-05-04', #Dob='1980-05-05' --results in 9
--SELECT #Now='1989-05-06', #Dob='1980-05-05' --results in 9
--SELECT #Now='1990-05-06', #Dob='1980-05-05' --results in 10
--SELECT #Now='1990-12-06', #Dob='1980-05-05' --results in 10
--SELECT #Now='1991-05-04', #Dob='1980-05-05' --results in 10
SELECT
(CONVERT(int,CONVERT(char(8),#Now,112))-CONVERT(char(8),#Dob,112))/10000 AS AgeIntYears
you can change the above 10000 to 10000.0 and get decimals, but it will not be as accurate as the method below.
BEST METHOD FOR YEARS IN DECIMAL
DECLARE #Now datetime, #Dob datetime
SELECT #Now='1990-05-05', #Dob='1980-05-05' --results in 10.000000000000
--SELECT #Now='1990-05-04', #Dob='1980-05-05' --results in 9.997260273973
--SELECT #Now='1989-05-06', #Dob='1980-05-05' --results in 9.002739726027
--SELECT #Now='1990-05-06', #Dob='1980-05-05' --results in 10.002739726027
--SELECT #Now='1990-12-06', #Dob='1980-05-05' --results in 10.589041095890
--SELECT #Now='1991-05-04', #Dob='1980-05-05' --results in 10.997260273973
SELECT 1.0* DateDiff(yy,#Dob,#Now)
+CASE
WHEN #Now >= DATEFROMPARTS(DATEPART(yyyy,#Now),DATEPART(m,#Dob),DATEPART(d,#Dob)) THEN --birthday has happened for the #now year, so add some portion onto the year difference
( 1.0 --force automatic conversions from int to decimal
* DATEDIFF(day,DATEFROMPARTS(DATEPART(yyyy,#Now),DATEPART(m,#Dob),DATEPART(d,#Dob)),#Now) --number of days difference between the #Now year birthday and the #Now day
/ DATEDIFF(day,DATEFROMPARTS(DATEPART(yyyy,#Now),1,1),DATEFROMPARTS(DATEPART(yyyy,#Now)+1,1,1)) --number of days in the #Now year
)
ELSE --birthday has not been reached for the last year, so remove some portion of the year difference
-1 --remove this fractional difference onto the age
* ( -1.0 --force automatic conversions from int to decimal
* DATEDIFF(day,DATEFROMPARTS(DATEPART(yyyy,#Now),DATEPART(m,#Dob),DATEPART(d,#Dob)),#Now) --number of days difference between the #Now year birthday and the #Now day
/ DATEDIFF(day,DATEFROMPARTS(DATEPART(yyyy,#Now),1,1),DATEFROMPARTS(DATEPART(yyyy,#Now)+1,1,1)) --number of days in the #Now year
)
END AS AgeYearsDecimal
Gotta throw this one out there. If you convert the date using the 112 style (yyyymmdd) to a number you can use a calculation like this...
(yyyyMMdd - yyyyMMdd) / 10000 = difference in full years
declare #as_of datetime, #bday datetime;
select #as_of = '2009/10/15', #bday = '1980/4/20'
select
Convert(Char(8),#as_of,112),
Convert(Char(8),#bday,112),
0 + Convert(Char(8),#as_of,112) - Convert(Char(8),#bday,112),
(0 + Convert(Char(8),#as_of,112) - Convert(Char(8),#bday,112)) / 10000
output
20091015 19800420 290595 29
I have used this query in our production code for nearly 10 years:
SELECT FLOOR((CAST (GetDate() AS INTEGER) - CAST(Date_of_birth AS INTEGER)) / 365.25) AS Age
You need to consider the way the datediff command rounds.
SELECT CASE WHEN dateadd(year, datediff (year, DOB, getdate()), DOB) > getdate()
THEN datediff(year, DOB, getdate()) - 1
ELSE datediff(year, DOB, getdate())
END as Age
FROM <table>
Which I adapted from here.
Note that it will consider 28th February as the birthday of a leapling for non-leap years e.g. a person born on 29 Feb 2020 will be considered 1 year old on 28 Feb 2021 instead of 01 Mar 2021.
So many of the above solutions are wrong DateDiff(yy,#Dob, #PassedDate) will not consider the month and day of both dates. Also taking the dart parts and comparing only works if they're properly ordered.
THE FOLLOWING CODE WORKS AND IS VERY SIMPLE:
create function [dbo].[AgeAtDate](
#DOB datetime,
#PassedDate datetime
)
returns int
with SCHEMABINDING
as
begin
declare #iMonthDayDob int
declare #iMonthDayPassedDate int
select #iMonthDayDob = CAST(datepart (mm,#DOB) * 100 + datepart (dd,#DOB) AS int)
select #iMonthDayPassedDate = CAST(datepart (mm,#PassedDate) * 100 + datepart (dd,#PassedDate) AS int)
return DateDiff(yy,#DOB, #PassedDate)
- CASE WHEN #iMonthDayDob <= #iMonthDayPassedDate
THEN 0
ELSE 1
END
End
EDIT: THIS ANSWER IS INCORRECT. I leave it in here as a warning to anyone tempted to use dayofyear, with a further edit at the end.
If, like me, you do not want to divide by fractional days or risk rounding/leap year errors, I applaud #Bacon Bits comment in a post above https://stackoverflow.com/a/1572257/489865 where he says:
If we're talking about human ages, you should calculate it the way
humans calculate age. It has nothing to do with how fast the earth
moves and everything to do with the calendar. Every time the same
month and day elapses as the date of birth, you increment age by 1.
This means the following is the most accurate because it mirrors what
humans mean when they say "age".
He then offers:
DATEDIFF(yy, #date, GETDATE()) -
CASE WHEN (MONTH(#date) > MONTH(GETDATE())) OR (MONTH(#date) = MONTH(GETDATE()) AND DAY(#date) > DAY(GETDATE()))
THEN 1 ELSE 0 END
There are several suggestions here involving comparing the month & day (and some get it wrong, failing to allow for the OR as correctly here!). But nobody has offered dayofyear, which seems so simple and much shorter. I offer:
DATEDIFF(year, #date, GETDATE()) -
CASE WHEN DATEPART(dayofyear, #date) > DATEPART(dayofyear, GETDATE()) THEN 1 ELSE 0 END
[Note: Nowhere in SQL BOL/MSDN is what DATEPART(dayofyear, ...) returns actually documented! I understand it to be a number in the range 1--366; most importantly, it does not change by locale as per DATEPART(weekday, ...) & SET DATEFIRST.]
EDIT: Why dayofyear goes wrong: As user #AeroX has commented, if the birth/start date is after February in a non leap year, the age is incremented one day early when the current/end date is a leap year, e.g. '2015-05-26', '2016-05-25' gives an age of 1 when it should still be 0. Comparing the dayofyear in different years is clearly dangerous. So using MONTH() and DAY() is necessary after all.
I believe this is similar to other ones posted here.... but this solution worked for the leap year examples 02/29/1976 to 03/01/2011 and also worked for the case for the first year.. like 07/04/2011 to 07/03/2012 which the last one posted about leap year solution did not work for that first year use case.
SELECT FLOOR(DATEDIFF(DAY, #date1 , #date2) / 365.25)
Found here.
Since there isn't one simple answer that always gives the correct age, here's what I came up with.
SELECT DATEDIFF(YY, DateOfBirth, GETDATE()) -
CASE WHEN RIGHT(CONVERT(VARCHAR(6), GETDATE(), 12), 4) >=
RIGHT(CONVERT(VARCHAR(6), DateOfBirth, 12), 4)
THEN 0 ELSE 1 END AS AGE
This gets the year difference between the birth date and the current date. Then it subtracts a year if the birthdate hasn't passed yet.
Accurate all the time - regardless of leap years or how close to the birthdate.
Best of all - no function.
I've done a lot of thinking and searching about this and I have 3 solutions that
calculate age correctly
are short (mostly)
are (mostly) very understandable.
Here are testing values:
DECLARE #NOW DATETIME = '2013-07-04 23:59:59'
DECLARE #DOB DATETIME = '1986-07-05'
Solution 1: I found this approach in one js library. It's my favourite.
DATEDIFF(YY, #DOB, #NOW) -
CASE WHEN DATEADD(YY, DATEDIFF(YY, #DOB, #NOW), #DOB) > #NOW THEN 1 ELSE 0 END
It's actually adding difference in years to DOB and if it is bigger than current date then subtracts one year. Simple right? The only thing is that difference in years is duplicated here.
But if you don't need to use it inline you can write it like this:
DECLARE #AGE INT = DATEDIFF(YY, #DOB, #NOW)
IF DATEADD(YY, #AGE, #DOB) > #NOW
SET #AGE = #AGE - 1
Solution 2: This one I originally copied from #bacon-bits. It's the easiest to understand but a bit long.
DATEDIFF(YY, #DOB, #NOW) -
CASE WHEN MONTH(#DOB) > MONTH(#NOW)
OR MONTH(#DOB) = MONTH(#NOW) AND DAY(#DOB) > DAY(#NOW)
THEN 1 ELSE 0 END
It's basically calculating age as we humans do.
Solution 3: My friend refactored it into this:
DATEDIFF(YY, #DOB, #NOW) -
CEILING(0.5 * SIGN((MONTH(#DOB) - MONTH(#NOW)) * 50 + DAY(#DOB) - DAY(#NOW)))
This one is the shortest but it's most difficult to understand. 50 is just a weight so the day difference is only important when months are the same. SIGN function is for transforming whatever value it gets to -1, 0 or 1. CEILING(0.5 * is the same as Math.max(0, value) but there is no such thing in SQL.
What about:
DECLARE #DOB datetime
SET #DOB='19851125'
SELECT Datepart(yy,convert(date,GETDATE())-#DOB)-1900
Wouldn't that avoid all those rounding, truncating and ofsetting issues?
Just check whether the below answer is feasible.
DECLARE #BirthDate DATE = '09/06/1979'
SELECT
(
YEAR(GETDATE()) - YEAR(#BirthDate) -
CASE WHEN (MONTH(GETDATE()) * 100) + DATEPART(dd, GETDATE()) >
(MONTH(#BirthDate) * 100) + DATEPART(dd, #BirthDate)
THEN 1
ELSE 0
END
)
select floor((datediff(day,0,#today) - datediff(day,0,#birthdate)) / 365.2425) as age
There are a lot of 365.25 answers here. Remember how leap years are defined:
Every four years
except every 100 years
except every 400 years
There are many answers to this question, but I think this one is close to the truth.
The datediff(year,…,…) function, as we all know, only counts the boundaries crossed by the date part, in this case the year. As a result it ignores the rest of the year.
This will only give the age in completed years if the year were to start on the birthday. It probably doesn’t, but we can fake it by adjusting the asking date back by the same amount.
In pseudopseudo code, it’s something like this:
adjusted_today = today - month(dob) + 1 - day(dob) + 1
age = year(adjusted_today - dob)
The + 1 is to allow for the fact that the month and day numbers start from 1 and not 0.
The reason we subtract the month and the day separately rather than the day of the year is because February has the annoying tendency to change its length.
The calculation in SQL is:
datediff(year,dob,dateadd(month,-month(dob)+1,dateadd(day,-day(dob)+1,today)))
where dob and today are presumed to be the date of birth and the asking date.
You can test this as follows:
WITH dates AS (
SELECT
cast('2022-03-01' as date) AS today,
cast('1943-02-25' as date) AS dob
)
select
datediff(year,dob,dateadd(month,-month(dob)+1,dateadd(day,-day(dob)+1,today))) AS age
from dates;
which gives you George Harrison’s age in completed years.
This is much cleaner than fiddling about with quarter days which will generally give you misleading values on the edges.
If you have the luxury of creating a scalar function, you can use something like this:
DROP FUNCTION IF EXISTS age;
GO
CREATE FUNCTION age(#dob date, #today date) RETURNS INT AS
BEGIN
SET #today = dateadd(month,-month(#dob)+1,#today);
SET #today = dateadd(day,-day(#dob)+1,#today);
RETURN datediff(year,#dob,#today);
END;
GO
Remember, you need to call dbo.age() because, well, Microsoft.
DECLARE #DOB datetime
set #DOB ='11/25/1985'
select floor(
( cast(convert(varchar(8),getdate(),112) as int)-
cast(convert(varchar(8),#DOB,112) as int) ) / 10000
)
source: http://beginsql.wordpress.com/2012/04/26/how-to-calculate-age-in-sql-server/
Try This
DECLARE #date datetime, #tmpdate datetime, #years int, #months int, #days int
SELECT #date = '08/16/84'
SELECT #tmpdate = #date
SELECT #years = DATEDIFF(yy, #tmpdate, GETDATE()) - CASE WHEN (MONTH(#date) > MONTH(GETDATE())) OR (MONTH(#date) = MONTH(GETDATE()) AND DAY(#date) > DAY(GETDATE())) THEN 1 ELSE 0 END
SELECT #tmpdate = DATEADD(yy, #years, #tmpdate)
SELECT #months = DATEDIFF(m, #tmpdate, GETDATE()) - CASE WHEN DAY(#date) > DAY(GETDATE()) THEN 1 ELSE 0 END
SELECT #tmpdate = DATEADD(m, #months, #tmpdate)
SELECT #days = DATEDIFF(d, #tmpdate, GETDATE())
SELECT Convert(Varchar(Max),#years)+' Years '+ Convert(Varchar(max),#months) + ' Months '+Convert(Varchar(Max), #days)+'days'
After trying MANY methods, this works 100% of the time using the modern MS SQL FORMAT function instead of convert to style 112. Either would work but this is the least code.
Can anyone find a date combination which does not work? I don't think there is one :)
--Set parameters, or choose from table.column instead:
DECLARE #DOB DATE = '2000/02/29' -- If #DOB is a leap day...
,#ToDate DATE = '2018/03/01' --...there birthday in this calculation will be
--0+ part tells SQL to calc the char(8) as numbers:
SELECT [Age] = (0+ FORMAT(#ToDate,'yyyyMMdd') - FORMAT(#DOB,'yyyyMMdd') ) /10000
CASE WHEN datepart(MM, getdate()) < datepart(MM, BIRTHDATE) THEN ((datepart(YYYY, getdate()) - datepart(YYYY, BIRTH_DATE)) -1 )
ELSE
CASE WHEN datepart(MM, getdate()) = datepart(MM, BIRTHDATE)
THEN
CASE WHEN datepart(DD, getdate()) < datepart(DD, BIRTHDATE) THEN ((datepart(YYYY, getdate()) - datepart(YYYY, BIRTHDATE)) -1 )
ELSE (datepart(YYYY, getdate()) - datepart(YYYY, BIRTHDATE))
END
ELSE (datepart(YYYY, getdate()) - datepart(YYYY, BIRTHDATE)) END
END
SELECT ID,
Name,
DATEDIFF(yy,CONVERT(DATETIME, DOB),GETDATE()) AS AGE,
DOB
FROM MyTable
How about this:
SET #Age = CAST(DATEDIFF(Year, #DOB, #Stamp) as int)
IF (CAST(DATEDIFF(DAY, DATEADD(Year, #Age, #DOB), #Stamp) as int) < 0)
SET #Age = #Age - 1
Try this solution:
declare #BirthDate datetime
declare #ToDate datetime
set #BirthDate = '1/3/1990'
set #ToDate = '1/2/2008'
select #BirthDate [Date of Birth], #ToDate [ToDate],(case when (DatePart(mm,#ToDate) < Datepart(mm,#BirthDate))
OR (DatePart(m,#ToDate) = Datepart(m,#BirthDate) AND DatePart(dd,#ToDate) < Datepart(dd,#BirthDate))
then (Datepart(yy, #ToDate) - Datepart(yy, #BirthDate) - 1)
else (Datepart(yy, #ToDate) - Datepart(yy, #BirthDate))end) Age
This will correctly handle the issues with the birthday and rounding:
DECLARE #dob datetime
SET #dob='1992-01-09 00:00:00'
SELECT DATEDIFF(YEAR, '0:0', getdate()-#dob)
Ed Harper's solution is the simplest I have found which never returns the wrong answer when the month and day of the two dates are 1 or less days apart. I made a slight modification to handle negative ages.
DECLARE #D1 AS DATETIME, #D2 AS DATETIME
SET #D2 = '2012-03-01 10:00:02'
SET #D1 = '2013-03-01 10:00:01'
SELECT
DATEDIFF(YEAR, #D1,#D2)
+
CASE
WHEN #D1<#D2 AND DATEADD(YEAR, DATEDIFF(YEAR,#D1, #D2), #D1) > #D2
THEN - 1
WHEN #D1>#D2 AND DATEADD(YEAR, DATEDIFF(YEAR,#D1, #D2), #D1) < #D2
THEN 1
ELSE 0
END AS AGE
The answer marked as correct is nearer to accuracy but, it fails in following scenario - where Year of birth is Leap year and day are after February month
declare #ReportStartDate datetime = CONVERT(datetime, '1/1/2014'),
#DateofBirth datetime = CONVERT(datetime, '2/29/1948')
FLOOR(DATEDIFF(HOUR,#DateofBirth,#ReportStartDate )/8766)
OR
FLOOR(DATEDIFF(HOUR,#DateofBirth,#ReportStartDate )/8765.82) -- Divisor is more accurate than 8766
-- Following solution is giving me more accurate results.
FLOOR(DATEDIFF(YEAR,#DateofBirth,#ReportStartDate) - (CASE WHEN DATEADD(YY,DATEDIFF(YEAR,#DateofBirth,#ReportStartDate),#DateofBirth) > #ReportStartDate THEN 1 ELSE 0 END ))
It worked in almost all scenarios, considering leap year, date as 29 feb, etc.
Please correct me if this formula have any loophole.
Declare #dob datetime
Declare #today datetime
Set #dob = '05/20/2000'
set #today = getdate()
select CASE
WHEN dateadd(year, datediff (year, #dob, #today), #dob) > #today
THEN datediff (year, #dob, #today) - 1
ELSE datediff (year, #dob, #today)
END as Age
Here is how i calculate age given a birth date and current date.
select case
when cast(getdate() as date) = cast(dateadd(year, (datediff(year, '1996-09-09', getdate())), '1996-09-09') as date)
then dateDiff(yyyy,'1996-09-09',dateadd(year, 0, getdate()))
else dateDiff(yyyy,'1996-09-09',dateadd(year, -1, getdate()))
end as MemberAge
go
CREATE function dbo.AgeAtDate(
#DOB datetime,
#CompareDate datetime
)
returns INT
as
begin
return CASE WHEN #DOB is null
THEN
null
ELSE
DateDiff(yy,#DOB, #CompareDate)
- CASE WHEN datepart(mm,#CompareDate) > datepart(mm,#DOB) OR (datepart(mm,#CompareDate) = datepart(mm,#DOB) AND datepart(dd,#CompareDate) >= datepart(dd,#DOB))
THEN 0
ELSE 1
END
END
End
GO
DECLARE #FromDate DATETIME = '1992-01-2623:59:59.000',
#ToDate DATETIME = '2016-08-10 00:00:00.000',
#Years INT, #Months INT, #Days INT, #tmpFromDate DATETIME
SET #Years = DATEDIFF(YEAR, #FromDate, #ToDate)
- (CASE WHEN DATEADD(YEAR, DATEDIFF(YEAR, #FromDate, #ToDate),
#FromDate) > #ToDate THEN 1 ELSE 0 END)
SET #tmpFromDate = DATEADD(YEAR, #Years , #FromDate)
SET #Months = DATEDIFF(MONTH, #tmpFromDate, #ToDate)
- (CASE WHEN DATEADD(MONTH,DATEDIFF(MONTH, #tmpFromDate, #ToDate),
#tmpFromDate) > #ToDate THEN 1 ELSE 0 END)
SET #tmpFromDate = DATEADD(MONTH, #Months , #tmpFromDate)
SET #Days = DATEDIFF(DAY, #tmpFromDate, #ToDate)
- (CASE WHEN DATEADD(DAY, DATEDIFF(DAY, #tmpFromDate, #ToDate),
#tmpFromDate) > #ToDate THEN 1 ELSE 0 END)
SELECT #FromDate FromDate, #ToDate ToDate,
#Years Years, #Months Months, #Days Days
What about a solution with only date functions, not math, not worries about leap year
CREATE FUNCTION dbo.getAge(#dt datetime)
RETURNS int
AS
BEGIN
RETURN
DATEDIFF(yy, #dt, getdate())
- CASE
WHEN
MONTH(#dt) > MONTH(GETDATE()) OR
(MONTH(#dt) = MONTH(GETDATE()) AND DAY(#dt) > DAY(GETDATE()))
THEN 1
ELSE 0
END
END
declare #birthday as datetime
set #birthday = '2000-01-01'
declare #today as datetime
set #today = GetDate()
select
case when ( substring(convert(varchar, #today, 112), 5,4) >= substring(convert(varchar, #birthday, 112), 5,4) ) then
(datepart(year,#today) - datepart(year,#birthday))
else
(datepart(year,#today) - datepart(year,#birthday)) - 1
end
The following script checks the difference in years between now and the given date of birth; the second part checks whether the birthday is already past in the current year; if not, it subtracts it:
SELECT year(NOW()) - year(date_of_birth) - (CONCAT(year(NOW()), '-', month(date_of_birth), '-', day(date_of_birth)) > NOW()) AS Age
FROM tableName;

Date difference in mysql based on 360 days

I working on a financial project where i need to calculate the difference of arrear days. If I use the method datediff() of mysql then it returns the result based on 365 days. I need the result based on 360 days. if use the following sql query
select datediff('20140908','20130908') from dual;
mysql returns the date difference 365. This is the actual date difference but in accounting/Financial calculation the difference is exactly one year (360 days). This is what I want. The result should be 360 instead 365.
Currently I want to use US standard.
to get the same result like Excel, I found the following code to use in MySQL:
select case
when (day(Startdate)>=30 or Startdate=last_day(Startdate) then
case
when(day(Enddate)>=30) then
30*(12*(year(Enddate)-year(Startdate))+month(Enddate)-month(Startdate))
else
30*(12*(year(Enddate)-year(Startdate))+month(Enddate)-month(Startdate))+days(Enddate)-30
end
else
30*(12*(year(Enddate)-year(Startdate))+month(Enddate)-month(Startdate))+days(Enddate)-days(Startdate)
end
Use
TO_DAYS(date2) - To_DAYS(date1)
It returns the number of days between date1 and date2. Then you can do with the result what you need.
sdate = from date
edate = to date
this calculate the days between the 2 date, taking into account that a month is 30 days, 1 year is 360 days, and checking if the date are at the end of the month, so e.g. from 1.1.2019 to 28.2.2019 = 60 days etc.
to be 1 month or 1 year, the edate should be a day before, so 1.1 - 31.1 = 30 days, 1.1 - 1.2 = 31 days
SELECT GREATEST(
0,
(YEAR(DATE_ADD(edate, INTERVAL 1 DAY)) - YEAR(sdate)) * 360 +
(MONTH(DATE_ADD(edate, INTERVAL 1 DAY)) - MONTH(sdate)) * 30 +
(
IF(DAYOFMONTH(DATE_ADD(edate, INTERVAL 1 DAY)) = DAYOFMONTH(LAST_DAY(DATE_ADD(edate, INTERVAL 1 DAY))), 30, DAYOFMONTH(DATE_ADD(edate, INTERVAL 1 DAY))) -
IF(DAYOFMONTH(sdate) = DAYOFMONTH(LAST_DAY(sdate)), 30, DAYOFMONTH(sdate))
)
Pretty late here, but posting my solution for future reference
CREATE FUNCTION `DAYS360_UDF`(sdate DATE, edate DATE)
RETURNS INTEGER
DETERMINISTIC
CONTAINS SQL
BEGIN
DECLARE sdate_360 INTEGER;
DECLARE edate_360 INTEGER;
SET sdate_360 = ( YEAR(sdate) * 360 ) + ( (MONTH(sdate)-1)*30 ) + DAY(sdate);
SET edate_360 = ( YEAR(edate) * 360 ) + ( (MONTH(edate)-1)*30 ) + DAY(edate);
RETURN edate_360 - sdate_360;
END
Usage -
select DAYS360_UDF('20130908', '20140908')

Mysql: Time + int

i have a table with a time field and i need to add an int field to it.
here's the line of code (it's a piece of a trigger.)
DECLARE duration Int;
[....]
SET NEW.appointmentEnd = NEW.appointmentTime + duration;
This works until the time gets to a full hour. So 8:30:00 + 1500 = 8:45:00 But 8:30:00 + 3000 = 0:00:00.
How to fix this?
Thanks in advance! :)
Instead of adding a "plain" int, you should be adding anexplicit interval:
new.appointmenttime = (new.appointmenttime + INTERVAL durationInMinutes MINUTE)
You are not limited to using MINUTE, of course: you can use SECOND if you need more precision.
MySQL doesn't do direct date/time math by adding what you are trying of "seconds". But you can do via Date_Add() and Date_Sub() such as
set appointmentEnd = date_Add( appointmentTime, interval 15 minutes )
You should use date arithmetic, like:
mysql> SELECT '2008-12-31 23:59:59' + INTERVAL 1 SECOND;
-> '2009-01-01 00:00:00'
Or
mysql> SELECT DATE_ADD('2010-12-31 23:59:59', INTERVAL 1 DAY);
-> '2011-01-01 23:59:59'
So if duration is, for example, in seconds:
SET NEW.appointmentEnd = NEW.appointmentTime + INTERVAL duration SECOND;

SQL Function for Financial Day of the year

How to get the financial day of the year i.e., if i pass 2nd of april to the function, it should return 2. The financial year starts on 1st of April for every year.
Fiscal calendars are specific to the organization and, although rare, can change. The simplest solution is to create a table that outlines the Fiscal calendar. So, you can mimic this using a CTE but it is better to store it as a table.
With FiscalCalendarStarts As
(
Select 1 As FiscalPeriod
, Cast('20120401' As DateTime) As StartDate
Union All
Select FiscalPeriod + 1
, Case
When Month(StartDate) = 12 Then DateAdd(m, Month(StartDate) - 12 + 1, StartDate)
Else DateAdd(m, 1, StartDate)
End
From FiscalCalendarStarts
Where FiscalPeriod < 12
)
, FiscalCalendar As
(
Select FiscalPeriod
, StartDate
, DateAdd(d, -1, DateAdd(m, 1, StartDate)) As EndDate
From FiscalCalendarStarts
)
Select *
From FiscalCalendar
Where #SomeDate Between StartDate And EndDate
Edit
To get the day count (which I admit I did not provide in the above solution), the trick is to determine the actual fiscal year start date based on the input date. To do that, you could do something like the following, which per your request, I've put into a function
Create Function dbo.FiscalDay ( #Input datetime )
Returns int
As
Begin
Declare #StartDayMonth char(4);
Set #StartDayMonth = '0401';
Return (
Select DateDiff(d, FYStartDate, #Input) + 1
From (
Select DateAdd(yyyy
, Case
When DatePart(dy, #Input) >= DatePart(dy, StartDate) Then 0
Else -1
End
, StartDate) As FYStartDate
From (
Select Cast( Cast(Year(#Input) As char(4))
+ #StartDayMonth As datetime ) As StartDate
) As S1
) As S
)
End
I start with the stub of 0401 which represents the month and day of the start of the fiscal year. To that I prepend the passed date's year so I get something like 20120401 if a date in 2012 was passed. If #Input is later than 1-Apr, then we're in the new fiscal year for the year of the #Input. If #Input is earlier than 1-Apr, then we're in the fiscal year that start on 1-Apr of the previous year. Now that we have the fiscal start date, we can simply find the numbers of days between them and add 1 (otherwise 1-Apr will be seen as day 0 instead of day 1). Note that passing 31-Mar-2012 returns 366 instead of 365 since 2012 was a leap year.
#Olivarsham, The financial year in not common for every country. Some where it is Apr-mar, some where it is Jan-Dec. So It it is your special application requirement then you need to write for your self. I think no standard query for that.
Kindly try this function. This will return your the day number of the fiscal year.
CREATE FUNCTION [dbo].[FiscalDay] (#CurrentDATE datetime)
RETURNS int
AS
BEGIN
DECLARE #FiscalDay int;
DECLARE #YearStartDate DateTime;
Set #YearStartDate=Cast('20120401' As DateTime)
set #FiscalDay = DATEDIFF(DAY,#YearStartDate , #CurrentDATE)
RETURN(#FiscalDay);
END;
GO

last friday of the month in MySQL

Hallo. How can I get the date of last friday of current month with mysql?
Thanks in advance.
edit. Hi Stefan. This is what I've done
set #ldom = dayofweek(last_day(curdate()));
select
case
when #ldom = 7 then last_day(curdate()) - interval 1 day
when #ldom = 6 then last_day(curdate())
when #ldom = 5 then last_day(curdate()) - interval 6 day
when #ldom = 4 then last_day(curdate()) - interval 5 day
when #ldom = 3 then last_day(curdate()) - interval 4 day
when #ldom = 2 then last_day(curdate()) - interval 3 day
else last_day(curdate()) - interval 2 day
end as last_friday
but I'd like to know if there is a smarter way.
EDIT. I made some test bases on samplebias answer to find last monday,tuesday and so on of a specific month.
These are the correct queries.
-- last sunday of month
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data)) - 6) % 7),"%Y%m%d") -- 2011-04-24
-- last saturday
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data)) - 5) % 7),"%Y%m%d") -- 2011-04-30
-- last friday
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data)) - 4) % 7),"%Y%m%d") -- 2011-04-29
-- last thursday
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data)) - 3) % 7),"%Y%m%d") -- 2011-04-28
-- last wednesday
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data)) - 2) % 7),"%Y%m%d") -- 2011-04-27
-- last tuesday
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data)) - 1) % 7),"%Y%m%d") -- 2011-04-26
-- last monday
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data))) % 7),"%Y%m%d") -- 2011-04-25
Hope that it helps someone else.
Thanks again to samplebias. ;)
Here is a simplified version using just date math:
SELECT LAST_DAY(NOW()) - ((7 + WEEKDAY(LAST_DAY(NOW())) - 4) % 7);
Depending on how NOW() gets evaluated (once or twice per statement), you might want to still wrap this in a function and store the result of NOW() into a variable, and then use the variable for the LAST_DAY(var) call, to avoid a race condition where the month rolls over between calls to NOW().
-- Today is 05 April 2013
-- Get Last Friday from MySQL
SELECT DATE_FORMAT(LAST_DAY(NOW()) - ((7 + WEEKDAY(LAST_DAY(NOW())) - 4) % 7), '%Y-%m-%d') last_friday;
-- Output
last_friday
-------------
2013-04-26