Search code examples
sql-servert-sqlleap-year

SQL Server : birthdays on Leap Year


I have a table with employee birthdays. I'm trying to create a stored procedure that returns everyone's birthdays within 2 given dates. We have employees born on a leap year.

I can successfully get return a person when their birthday falls on a leap year following the example at http://www.berezniker.com/content/pages/sql/microsoft-sql-server/birthday-query-ms-sql-server

DECLARE @StartDate DATETIME, @EndDate DATETIME

SET @StartDate = '2009-02-22'
SET @EndDate   = '2009-02-28'

--SET @StartDate = '2008-02-22'
--SET @EndDate   = '2008-02-29'

SELECT 
  FullName, 
  DATEPART(MONTH, dob) AS MONTH, 
  DATEPART(DAY, dob) AS DAY, 
  CONVERT(VARCHAR(10), dob, 111) AS dob
FROM 
  People
WHERE  
  DATEADD(YEAR, DATEDIFF(YEAR,  dob, @StartDate), dob) BETWEEN @StartDate AND @EndDate
OR 
  DATEADD(YEAR, DATEDIFF(YEAR,  dob, @EndDate), dob) BETWEEN @StartDate AND @EndDate
ORDER BY 
  CASE WHEN DATEADD(YEAR, DATEDIFF(YEAR,  dob, @StartDate), dob) 
  BETWEEN @StartDate AND @EndDate THEN 1 ELSE 2 END, 
  DATEPART(MONTH, dob), DATEPART(DAY, dob)


CREATE TABLE People 
    (
        PK INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
        FullName VARCHAR(30) NOT NULL,
        dob DATETIME NULL
    )
GO

INSERT INTO People (FullName, dob) VALUES ('John Smith', '1965-02-28')
INSERT INTO People (FullName, dob) VALUES ('Alex Black', '1960-02-29')
INSERT INTO People (FullName, dob) VALUES ('Bill Doors', '1968-02-27')
...
--shortened for clarity

However, with the data above, my goal is to display Alex Black's birthday for year 2014 as 2/28/2014 and for year 2016 as 2/29/2016.

Also, if you are in the mood, my full intentions are the following:

I want to pass 2 dates, not matter how far apart: @DateFrom date = '1/1/2014' and @DateTo date = '12/31/2016'. The result I want back is

FULLNAME        DOB
Bill Doors      2014-02-27
John Smith      2014-02-28
Alex Black      2014-02-28
Bill Doors      2015-02-27
John Smith      2015-02-28
Alex Black      2015-02-28
Bill Doors      2016-02-27
John Smith      2016-02-28
Alex Black      2016-02-29 -- note this year the date is feb 29th

Solution

  • you can try this

    declare
      @DateFrom date = '20140101',
      @DateTo date = '20161231'
    
    ;with
    -- All years between @DateFrom and @DateTo
    CTE_Years as (
      select datepart(yy, @DateFrom) as y
      union all
      select y + 1 as y
      from CTE_Years
      where y < datepart(yy, @DateTo)
    ), 
    -- Calculate leap years
    CTE_Years2 as (
      select
        cast(y as nvarchar(4)) as y,
        case
          when y / 400 * 400 = y then 1
          when y / 100 * 100 = y then 0
          when y / 4 * 4 = y then 1
          else 0
        end as Is_Leap_Year
      from CTE_Years
    ),
    -- get peoples birth day and month in form 'mmdd'
    CTE_People as (
      select
        FullName,
        right(convert(nvarchar(8), dob, 112), 4) as dob
      from People
    ),
    -- get peoples birth date in given years
    CTE_DOB as (
      select
        P.FullName,
        convert(
          date,
          Y.y + 
          case
            when Y.Is_Leap_Year = 0 and P.dob = '0229' then '0228'
            else P.dob
          end,
          112
        ) as dob
      from CTE_Years2 as Y
        cross join CTE_People as P
    )
    -- Final query
    select *
    from CTE_DOB
    where dob > @DateFrom and dob < @DateTo
    order by DOB asc
    

    TAKE A LOOK AT SQL FIDDLE EXAMPLE

    EDIT: Lamak remind me a great way to calculate birthday, so here's edited version

    declare
      @DateFrom date = '1/1/2014',
      @DateTo date = '12/31/2016'
    
    ;with
    CTE_Years as (
      select dateadd(yy, datediff(yy, 0, @DateFrom), 0) as y
      union all
      select dateadd(yy, 1, y) as y
      from CTE_Years
      where y < @DateTo
    ), 
    CTE_DOB as (
      select
        P.FullName,
        dateadd(yy, datediff(yy, P.dob, Y.y), P.dob) as dob
      from CTE_Years as Y
          cross join People as P
    )
    select *
    from CTE_DOB
    where dob > @DateFrom and dob < @DateTo
    order by DOB asc
    

    SQL FIDDLE EXAMPLE