First here is code for converting from Year/Day to Days ( the language is very similar to Microsoft SQL Server T-SQL, except variable names do not begin with '@', and there is a 'bool" data type ) :
CREATE FUNCTION [date].[YearDayToDays]( yd int ) RETURNS int AS
BEGIN
-- Given a date in Year/Day representation stored as y * 512 + d where 1 <= d <= 366 ( so d is day in year )
-- returns the number of days since "day zero" (1 Jan 0000)
-- using the Gregorian calendar where days divisible by 4 are leap years, except if divisible by 100, except if divisible by 400.
DECLARE y int, d int, cycle int
-- Extract y and d from yd.
SET y = yd / 512, d = yd % 512 - 1
SET cycle = y / 400, y = y % 400 -- The Gregorian calendar repeats every 400 years.
-- Result days come from cycles, from years having at least 365 days, from leap years and finally d.
-- 146097 is the number of the days in a 400 year cycle ( 400 * 365 + 97 leap years ).
RETURN cycle * 146097
+ y * 365
+ ( y + 3 ) / 4 - ( y + 99 ) / 100 + ( y + 399 ) / 400
+ d
END
Now the opposite conversion, Days to Year/Day:
CREATE FUNCTION [date].[DaysToYearDay]( days int ) returns int as
begin
-- Given a date represented by the number of days since 1 Jan 0000
-- calculate a date in Year/Day representation stored as
-- y * 512 + d where d is 1..366
DECLARE y int, d int, cycle int
-- 146097 is the number of the days in a 400 year cycle ( 400 * 365 + 97 leap years )
SET cycle = days / 146097
SET days = days % 146097
SET y = days / 365
SET d = days % 365
-- Need to adjust d to allow for leap years.
-- Leap years are 0, 4, 8, 12 ... 96, not 100, 104 ... not 200... not 300, 400, 404 ... not 500.
-- Adjustment as function of y is 0 => 0, 1 => 1, 2 =>1, 3 => 1, 4 => 1, 5 => 2 ..
SET d = d - ( y + 3 ) / 4 - ( y + 99 ) / 100 + ( y + 399 ) / 400
IF d < 0
BEGIN
SET y = y - 1
SET d = d + CASE WHEN date.IsLeapYear( y ) THEN 366 ELSE 365 END
END
RETURN date.YearDay( cycle * 400 + y, d + 1 )
END
The auxiliary function date.IsLeapYear:
CREATE FUNCTION [date].[IsLeapYear]( y int ) RETURNS bool AS
BEGIN
RETURN y % 4 = 0 AND ( y % 100 != 0 OR y % 400 = 0 )
END
and date.YearDay :
CREATE FUNCTION [date].[YearMonthDay]( year int, month int, day int ) RETURNS int AS
BEGIN
RETURN year * 512 + month * 32 + day
END
Conversion from Year/Day to Year/Month/Day:
CREATE FUNCTION [date].[YearDayToYearMonthDay]( yd int ) returns int AS
BEGIN
DECLARE y int, d int, leap bool, fdm int, m int, dim int
SET y = yd / 512
SET d = yd % 512 - 1
SET leap = date.IsLeapYear( y )
-- Jan = 0..30, Feb = 0..27 or 0..28
IF NOT leap AND d >= 59 SET d = d + 1
SET fdm = CASE
WHEN d < 31 THEN 0 -- Jan
WHEN d < 60 THEN 31 -- Feb
WHEN d < 91 THEN 60 -- Mar
WHEN d < 121 THEN 91 -- Apr
WHEN d < 152 THEN 121 -- May
WHEN d < 182 THEN 152 -- Jun
WHEN d < 213 THEN 182 -- Jul
WHEN d < 244 THEN 213 -- Aug
WHEN d < 274 THEN 244 -- Sep
WHEN d < 305 THEN 274 -- Oct
WHEN d < 335 THEN 305 -- Nov
ELSE 335 -- Dec
END
SET dim = d - fdm
SET m = ( d - dim + 28 ) / 31
RETURN date.YearMonthDay( y, m+1, dim+1 )
END
The auxiliary function date.YearMonthDay:
CREATE FUNCTION [date].[YearMonthDay]( year int, month int, day int ) RETURNS int AS
BEGIN
RETURN year * 512 + month * 32 + day
END
Finally conversion from Year/Month/Day to Year/Day:
CREATE FUNCTION [date].[YearMonthDayToYearDay]( ymd int ) RETURNS int AS
BEGIN
DECLARE y int, m int, d int
-- Extract y, m, d from ymd
SET d = ymd % 32, m = ymd / 32
SET y = m / 16, m = m % 16
-- Incorporate m into d ( assuming Feb has 29 days ).
SET d = d + CASE
WHEN m = 1 THEN 0 -- Jan
WHEN m = 2 THEN 31 -- Feb
WHEN m = 3 THEN 60 -- Mar
WHEN m = 4 THEN 91 -- Apr
WHEN m = 5 THEN 121 -- May
WHEN m = 6 THEN 152 -- Jun
WHEN m = 7 THEN 182 -- Jul
WHEN m = 8 THEN 213 -- Aug
WHEN m = 9 THEN 244 -- Sep
WHEN m = 10 THEN 274 -- Oct
WHEN m = 11 THEN 305 -- Nov
ELSE 335 -- Dec
END
-- Allow for Feb being only 28 days in a non-leap-year.
IF m >= 3 AND NOT date.IsLeapYear( y ) SET d = d - 1
RETURN date.YearDay( y, d )
END
I hope it's useful to someone, and I hope it's correct - I have tested by generating test ranges of days to check the generated calendar looks correct, with the correct number of days in each month, especially February. The functions do not check whether the input is valid, that is assumed. This is part of a project to implement SQL in C#.