8

I've searched through many examples , good ones I got :

  1. Count days between two dates, excluding weekends (MySQL only)

  2. How to count date difference excluding weekend and holidays in MySQL

  3. Calculate diffference between 2 dates in SQL, excluding weekend days

but didn't get most promising solution , so that i can use in my mysql-function for quering lakhs of rows.

This one was very new concept , but didn't worked for inputs like @start_date = '2013-08-03' , @end_date = '2013-08-21' Expected ans : 13 , its giving only 12,

SELECT 5 * (DATEDIFF(@end_date, @start_date) DIV 7) + MID('0123444401233334012222340111123400012345001234550', 7 * WEEKDAY(@start_date) + WEEKDAY(@end_date) + 1, 1);

So i'did tried to make it by myself -

Concept : 
Input : 1. period_from_date  - from date
        2. period_to_date    - to date
        3. days_to_exclude   - mapping : S M T W TH F Sat   =>  2^0 + 2^6
          (sat and sun to exclude)       ^ ^ ^ ^ ^  ^  ^
                                         0 1 2 3 4  5  6

DELIMITER $$

USE `db_name`$$

DROP FUNCTION IF EXISTS `FUNC_CALC_TOTAL_WEEKDAYS`$$

CREATE DEFINER=`name`@`%` FUNCTION `FUNC_CALC_TOTAL_WEEKDAYS`( period_from_date DATE, period_to_date DATE, days_to_exclude INT ) RETURNS INT(11)
BEGIN

DECLARE period_total_num_days      INT DEFAULT 0;
DECLARE period_total_working_days  INT DEFAULT 0;
DECLARE period_extra_days          INT DEFAULT 0;
DECLARE period_complete_weeks      INT DEFAULT 0;
DECLARE extra_days_start_date      DATE DEFAULT '0000-00-00';
DECLARE num_days_to_exclude        INT DEFAULT 0;
DECLARE start_counter_frm          INT DEFAULT 0;
DECLARE end_counter_to             INT DEFAULT 6;
DECLARE temp_var                   INT DEFAULT 0;

# if no day to exclude return date-diff only
IF days_to_exclude = 0 THEN
    RETURN DATEDIFF( period_to_date, period_from_date ) + 1 ;
END IF;

# get total no of days to exclude
WHILE start_counter_frm <= end_counter_to  DO
   SET temp_var = POW(2,start_counter_frm) ;
   IF (temp_var  & days_to_exclude) = temp_var  THEN
            SET num_days_to_exclude = num_days_to_exclude + 1;
   END IF;
 SET start_counter_frm = start_counter_frm + 1;
END WHILE;

# Get period days count
SET period_total_num_days       = DATEDIFF( period_to_date, period_from_date ) + 1 ;
SET period_complete_weeks       = FLOOR( period_total_num_days /7 );
SET period_extra_days           = period_total_num_days  - ( period_complete_weeks * 7 );
SET period_total_working_days   = period_complete_weeks * (7 - num_days_to_exclude);
SET extra_days_start_date       = DATE_SUB(period_to_date,INTERVAL period_extra_days DAY);

# get total working days from the left days
WHILE period_extra_days > 0 DO
    SET temp_var = DAYOFWEEK(period_to_date) -1;

    IF POW(2,temp_var) & days_to_exclude != POW(2,temp_var) THEN
        SET period_total_working_days = period_total_working_days +1;
    END IF;

    SET period_to_date = DATE_SUB(period_to_date,INTERVAL 1 DAY);
    SET period_extra_days = period_extra_days -1;

END WHILE; 

RETURN period_total_working_days;
END$$

DELIMITER ;

Please let me know the holes where this would fail.Open to any suggestions and comments.

Community
  • 1
  • 1
sourcecode
  • 1,802
  • 2
  • 15
  • 17
  • Your question appears to be a duplicate of the three you mentioned. I understand that if they're not working then it might not be an exact duplicate but "it didn't work" is not a reason... Please explain exactly _why_ it didn't work and for what inputs. – Ben Aug 18 '13 at 18:09
  • @ben - I've added 1 example.plz check. – sourcecode Aug 18 '13 at 18:19

6 Answers6

18

UPDATED: If you just need a number of weekdays between two dates you can get it like this

CREATE FUNCTION TOTAL_WEEKDAYS(date1 DATE, date2 DATE)
RETURNS INT
RETURN ABS(DATEDIFF(date2, date1)) + 1
     - ABS(DATEDIFF(ADDDATE(date2, INTERVAL 1 - DAYOFWEEK(date2) DAY),
                    ADDDATE(date1, INTERVAL 1 - DAYOFWEEK(date1) DAY))) / 7 * 2
     - (DAYOFWEEK(IF(date1 < date2, date1, date2)) = 1)
     - (DAYOFWEEK(IF(date1 > date2, date1, date2)) = 7);

Note: The function will still work if you switch start date1 and end date2 dates.

Sample usage:

SELECT TOTAL_WEEKDAYS('2013-08-03', '2013-08-21') weekdays1,
       TOTAL_WEEKDAYS('2013-08-21', '2013-08-03') weekdays2;

Output:

| WEEKDAYS1 | WEEKDAYS2 |
-------------------------
|        13 |        13 |

Here is DBFiddle demo

peterm
  • 91,357
  • 15
  • 148
  • 157
  • @peterm- this is a good solution, but if i'm considering calculating weekdays for data from different countries , then i need 1 variable that would determine which day should i consider weekend..(say in some countries friday is considered as weekend)....in that senario how this can help? – sourcecode Sep 02 '13 at 16:48
  • This is a very nice solution. Thanks peterm! – jmiraglia Feb 24 '14 at 16:00
  • For those who might be using a database that doesn't evaluate in-line logical expressions, replace `(DAYOFWEEK(IF(date1 < date2, date1, date2)) = 1)` with `CASE WHEN DAYOFWEEK(CASE WHEN date1 < date2 THEN date1 ELSE date2 END) = 1 THEN 1 ELSE 0 END` and make the same sort of change to the last expression (using `= 7` instead of `= 1`). – Kevin Rahe Mar 06 '14 at 21:45
  • Works like a charm, but how about only determine weekends only? – Edwin Bermejo Jul 26 '18 at 15:38
  • @OzanKurt It's correct all right. Exactly 12 week days (Mon through Fri) between 2019-11-21 and 2019-12-06. What seems to be the problem? – peterm Dec 06 '19 at 03:38
  • @OzanKurt It seems like you counting it wrong. 2019-11-21 is Thu. – peterm Dec 06 '19 at 03:43
  • @peterm it was actually friday for me, this might be a timezone issue then. Thanks. – Ozan Kurt Dec 06 '19 at 04:59
8

This query will work fine, all the queries above are not working well. Try this :

SELECT ((DATEDIFF(date2, date1)) -
        ((WEEK(date2) - WEEK(date1)) * 2) -
        (case when weekday(date2) = 6 then 1 else 0 end) -
        (case when weekday(date1) = 5 then 1 else 0 end)) as DifD

Test it like this :

SELECT ((DATEDIFF('2014-10-25', '2014-10-15')) -
            ((WEEK('2014-10-25') - WEEK('2014-10-15')) * 2) -
            (case when weekday('2014-10-25') = 6 then 1 else 0 end) -
            (case when weekday('2014-10-15') = 5 then 1 else 0 end)) as DifD

The result :

DifD    
8
pollux1er
  • 5,372
  • 5
  • 37
  • 36
  • 3
    I really like this idea. Although it needs to address the dates that across years, so I added that into above query: -- date2 = '2017-01-11 00:00:00'; -- date1 = '2016-12-29 00:00:00'; SELECT ((DATEDIFF(date2, date1)) - ((WEEK(date2) + ((YEAR(date2) - YEAR(date1)) * 52) - WEEK(date1)) * 2) - (CASE WHEN WEEKDAY(date2) = 6 THEN 1 ELSE 0 END) - (CASE WHEN WEEKDAY(date1) = 5 THEN 1 ELSE 0 END)) AS DifD; – simonxy Feb 10 '17 at 20:01
1

I use this. Means there are no functions so can be used in views:

select 
datediff(@dateto, @datefrom) + 
datediff(@datefrom, 
    date_add(@datefrom, INTERVAL 
     floor(datediff(@dateto, @datefrom) / 7) day)) * 2
- case
    when weekday(@dateto) = 6 then 2
    when weekday(@dateto) = 5 then 1
    when weekday(@dateto) < weekday(@datefrom) then 2
    else 0 
end;
Neptunemo
  • 135
  • 2
  • 8
0

Had a similar issue, I used PHP to remove the weekends, need to know start day and number of days:

EG SQL:

  SELECT DAYOFWEEK(`date1`) AS `startday`, TIMESTAMPDIFF(DAY, `date1`, `date2`) AS `interval` FROM `table`

Then run the result through a PHP function:

    function noweekends($startday, $interval) {
        //Remove weekends from an interval
        $wecount = 0; $tmp = $interval;
        while($interval/7 > 1) { $interval-=7; $wecount++; }
        if($interval+$startday > 5) $wecount++;
        $interval = $tmp-($wecount*2);
        return $interval;
    }
Dave
  • 386
  • 2
  • 8
  • This method would not be provable, if from table there is returning rows in thousands or sometimes in lakhs, there would be two computations for every row - one in mysql and other in php , and hence it would be exhausting for the need.. – sourcecode Sep 10 '13 at 16:48
  • Correct, same problem i am facing...(when data is huge) this is not convenient – dipenparmar12 May 11 '20 at 10:38
0

To exclude only Sunday:

CREATE FUNCTION TOTAL_WEEKDAYS(date1 DATE, date2 DATE)
RETURNS INT
RETURN ABS(DATEDIFF(date2, date1)) + 1
     - ABS(DATEDIFF(ADDDATE(date2, INTERVAL 1 - DAYOFWEEK(date2) DAY),
                    ADDDATE(date1, INTERVAL 1 - DAYOFWEEK(date1) DAY))) / 7
     - (DAYOFWEEK(IF(date1 < date2, date1, date2)) = 1);
Er.KT
  • 2,852
  • 1
  • 36
  • 70
0

You can also create triggers to automatically calculate it in another column, and you can specify legal holidays in another table:

CREATE OR REPLACE TRIGGER `vacation_before_insert` BEFORE INSERT ON `vacation` FOR EACH ROW 
BEGIN  
SET @start_date = NEW.Start_date;
SET @end_date = NEW.End_date;
SET @numofholydays = (IFNULL((SELECT SUM(IF(`Date` BETWEEN NEW.Start_date AND NEW.End_date, 1, 0))  as numofdays FROM free_legal_days),0));
SET @totaldays = DATEDIFF(@end_date , @start_date) + 1;
SET @saturdays = WEEK(DATE_ADD(@end_date, INTERVAL 1 DAY))-WEEK(@start_date);
SET @sundays = WEEK(@end_date) - WEEK(@start_date);
SET NEW.Number_of_days = @totaldays-@saturdays-@sundays-@numofholydays; 
END;  

 CREATE OR REPLACE TRIGGER `vacation_before_update` BEFORE UPDATE ON `vacation` FOR EACH ROW 
BEGIN  
SET @start_date = NEW.Start_date;
SET @end_date = NEW.End_date;
SET @numofholydays = (IFNULL((SELECT SUM(IF(`Date` BETWEEN NEW.Start_date AND NEW.End_date, 1, 0))  as numofdays FROM free_legal_days),0));
SET @totaldays = DATEDIFF(@end_date , @start_date) + 1;
SET @saturdays = WEEK(DATE_ADD(@end_date, INTERVAL 1 DAY))-WEEK(@start_date);
SET @sundays = WEEK(@end_date) - WEEK(@start_date);
SET NEW.Number_of_days = @totaldays-@saturdays-@sundays-@numofholydays; 
END;
 
ciprianp
  • 1
  • 1