SQLite: Get average weekdays between two dates - sqlite
I have a table called Trucks with two date columns: Arrival and Released. I can calculate the average number of days between those dates like so:
SELECT avg(julianday(released) - julianday(arrival))
FROM Trucks
However, I only want to count weekdays--that is, I want to ignore Saturdays and Sundays. Is there any way to do this in SQLite? I have seen solutions for more robust RDBMSs like Oracle and MSSQL, but none that work for SQLite.
The raw difference in days must be adjusted depending (only) on the week days of the arrival and released days:
rel arr|0 1 2 3 4 5 6
-------+-+-+-+-+-+-+-
0 |2 0 0 0 0 0 1
1 |2 2 0 0 0 0 1
2 |2 2 2 0 0 0 1
3 |2 2 2 2 0 0 1
4 |2 2 2 2 2 0 1
5 |2 2 2 2 2 2 1
6 |2 2 2 2 2 2 2
This number can be computed with a simple expression (here: the inner CASE expression):
SELECT
AVG(julianday(released) - julianday(arrival) -
CASE WHEN julianday(released) = julianday(arrival) THEN 0
ELSE (CAST((julianday(released) - julianday(arrival)) / 7 AS INTEGER) * 2
) +
CASE WHEN strftime('%w', arrival) <= strftime('%w', released) THEN 2
ELSE strftime('%w', arrival) = '6'
END
END)
FROM trucks
(A boolean expression like x='6' returns 0 or 1.)
Okay, I figured out a very messy solution using lots of nested CASE statements. It checks the weekday number of released and then the weekday number of arrival and does a calculation to figure out how many weeks have passed. After that, I add 0, 1, or 2 as a base number of weekend days that must have passed between those two days (ie from Friday to Monday is always +2 weekend days, even if less than a whole week has passed between the dates).
Here it is, in case anyone finds it useful. Quite possibly the ugliest SQL I have ever written. If anyone figures out a better way, please let me know.
(Edited for simplicity based on CL's feedback)
SELECT
avg(
julianday(released) - julianday(arrival) -
CASE WHEN julianday(released) = julianday(arrival) THEN 0 ELSE
CASE strftime('%w', released)
WHEN '0' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 1)
END
WHEN '1' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 1)
END
WHEN '2' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 1)
END
WHEN '3' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 1)
END
WHEN '4' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 0)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 1)
END
WHEN '5' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 1)
END
WHEN '6' THEN
CASE strftime('%w', arrival)
WHEN '0' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '1' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '2' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '3' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '4' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '5' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
WHEN '6' THEN ((cast((julianday(released)-julianday(arrival)) / 7 as int) * 2) + 2)
END
END
END
), avg(julianday(released)-julianday(arrival))
from trucks
(Note: the avg(julianday(released)-julianday(arrival)) at the end is just for testing purposes to show that the new calculated average is in fact less than a straight average of the difference between the two dates).
I found this post while searching to see if SQLite had a weekday function. If you use the top answer you're not likely to get a very solid value for days of the week. Here is the code I used for my project, which calculates workdays between ticket creation and closure in SpiceWorks:
SELECT t.id
,t.summary
,t.category
,u.email
,(CAST(strftime('%j', t.closed_at) as INTEGER) - CAST(strftime('%j', t.created_at) as INTEGER) - /*I can't figure it out, but julianday() wasn't giving me the correct distances for some numbers. I used the day of the year, instead, which resolved things as expected*/
CASE
WHEN CAST(strftime('%W', t.closed_at) as INTEGER) - CAST(strftime('%W', t.created_at) as INTEGER) = 0 THEN 0 /*If they are in the same week then there is no weekend to count. If you closed a ticket, that was opened on a Monday, on Saturday, then you must of worked on Saturday and therefore counts as work day the same as if you finished it on Monday*/
WHEN CAST(strftime('%w', t.created_at) as INTEGER) = 0 THEN (CAST(strftime('%W', t.closed_at) as INTEGER) - CAST(strftime('%W', t.created_at) as INTEGER)) * 2 - 1 /*If they made their ticket on Sunday, don't count that Sunday*/
WHEN CAST(strftime('%w', t.created_at) as INTEGER) = 6 THEN (CAST(strftime('%W', t.closed_at) as INTEGER) - CAST(strftime('%W', t.created_at) as INTEGER)) * 2 - 2 /*If they made their ticket on Saturday, don't count that Saturday*/
ELSE (CAST(strftime('%W', t.closed_at) as INTEGER) - CAST(strftime('%W', t.created_at) as INTEGER)) * 2 /*Ignoring the possibility for closed dates that happen before open dates, take the number of weeks between each date and assume each had a Saturday and Sunday within them*/
END) as 'Week Days Elapsed' /*This equation in full represents: Full_Distance_In_Days_Between - Number of weekend days calculated*/
FROM Tickets as t
LEFT JOIN Users as u
on t.created_by = u.id
WHERE strftime('%Y-%m', t.created_at) = strftime('%Y-%m', 'now')
Related
Sqlite INSERT INTO with a group_concat which contains a subselect
I need to take data and combine to insert into an table as a menu code. The menu code takes the form LNNLLLL (L = Letter and N=Number) and it is made up of MenU Cycle (hard-coded, one off project), Week Number (1-3), Day Number (1-7 - Monday=1), MealCode, Customer Type Code (3 Letters). The following SELECT query returns exactly what I want insert. The hardcoded date is a known date from which the week number and day number can be derived ie it is a Week 1 Monday. SELECT group_concat('C' || CASE WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 <= 6 THEN 1 WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 <= 13 THEN 2 ELSE 3 END || CASE WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (1, 8, 15) THEN 1 WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (2, 9, 16) THEN 2 WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (3, 10, 17) THEN 3 WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (4, 11, 18) THEN 4 WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (5, 12, 19) THEN 5 WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (6, 13, 20) THEN 6 WHEN (CAST(julianday(mealdate) AS INT) - cast(julianday('2022-08-22') AS INT)) % 21 + 1 IN (7, 14, 21) THEN 7 END || MealCode || (SELECT CustTypeCodeNew FROM CustomerNew WHERE CustCodeNew LIKE SalesOrderNew.CustomerCode)) AS MenuCode FROM SalesOrderNew GROUP BY SalesOrderID Here is some output from that (I've added MealDate and MealCode to the select just to give better context to the resulting MenuCode column which is the one that needs inserted. MealDate MealCode MenuCode 2022-08-23 K C12KNRE 2022-08-23 K C12KRES 2022-08-23 K C12KSHO 2022-08-23 K C12KRES 2022-08-25 T C14TNRE 2022-08-25 T C14TRES 2022-08-25 L C14LNRE 2022-08-25 L C14LNRE 2022-08-25 T C14TNRE The MenuCode column is currently filled with NULL values. However if I put add a line above to INSERT INTO SalesOrderNew (MenuCode) I get this error Result: NOT NULL constraint failed: SalesOrderNew.CustomerCode. It does not seem to like the subselect that used within the group_contact. How do I go about resolving this?
SQLite how to convert BLOB byte to INT with built-in functions?
i have an sqlite database that contains a column with BLOB data. these BLOB data are 4 byte width. i want to split the 4 bytes appart and convert each part to an integer value to calculate with it. i found out, that i can use SUBSTR(val, start, length) to take the BLOB value appart. the result is still of type BLOB. but how can i convert the BLOB/byte to an integer value? is there a built-in function that can convert byte BLOB values to an integer? or is there a way to convert a hex-string-value into an integer value, so i could play with HEX(val) or QUOTE(val) CREATE TEMP TABLE IF NOT EXISTS test AS SELECT x'cafe1a7e' AS val; SELECT (val) , TYPEOF(val) , HEX(val) , QUOTE(val) , TYPEOF(HEX(val)) , TYPEOF(QUOTE(val)) , CAST(val AS INT) , CAST(HEX(val) AS INT) , CAST(QUOTE(val) AS INT) , SUBSTR(val, 1, 1) , TYPEOF(SUBSTR(val, 1, 1)) , HEX(SUBSTR(val, 1, 1)) , HEX(SUBSTR(val, 2, 1)) , HEX(SUBSTR(val, 3, 2)) , val + val , SUBSTR(val, 1, 1) + 1 , CAST(SUBSTR(val, 1, 1) AS INT) FROM test; DROP TABLE test;
You can convert one hex digit at a time using instr: SELECT hex(b), n, printf("%04X", n) FROM (SELECT b, (instr("123456789ABCDEF", substr(hex(b), -1, 1)) << 0) | (instr("123456789ABCDEF", substr(hex(b), -2, 1)) << 4) | (instr("123456789ABCDEF", substr(hex(b), -3, 1)) << 8) | (instr("123456789ABCDEF", substr(hex(b), -4, 1)) << 12) | (instr("123456789ABCDEF", substr(hex(b), -5, 1)) << 16) | (instr("123456789ABCDEF", substr(hex(b), -6, 1)) << 20) | (instr("123456789ABCDEF", substr(hex(b), -7, 1)) << 24) | (instr("123456789ABCDEF", substr(hex(b), -8, 1)) << 28) AS n FROM (SELECT randomblob(4) AS b)) Example output: D91F8E91|3642723985|D91F8E91 (Simplification of idea from [1].)
There is no built in function that I know of, so this is how I do it - if you know how many bytes you want to convert: --creates table h2i with numbers 0 to 255 in hex and int CREATE TEMP TABLE bits (bit INTEGER PRIMARY KEY);INSERT INTO bits VALUES (0);INSERT INTO bits VALUES (1); CREATE TEMP TABLE h2i (h TEXT, i INT); INSERT INTO h2i (h, i) SELECT printf('%02X',num),num FROM (SELECT b7.bit * 128 + b6.bit * 64 + b5.bit * 32 + b4.bit * 16 + b3.bit * 8 + b2.bit * 4 + b1.bit * 2 + b0.bit AS num FROM bits b7, bits b6, bits b5,bits b4, bits b3, bits b2, bits b1, bits b0) as nums; SELECT HEX(SUBSTR(val, 1, 1)),h2i0.i ,HEX(SUBSTR(val, 2, 1)),h2i1.i ,HEX(SUBSTR(val, 3, 2)),h2i2.i*256+h2i3.i ,HEX(SUBSTR(val, 1, 4)),h2i0.i*16777216+h2i1.i*65536+h2i2.i*256+h2i3.i FROM test JOIN h2i h2i0 ON h2i0.h=HEX(SUBSTR(val, 1, 1)) JOIN h2i h2i1 ON h2i1.h=HEX(SUBSTR(val, 2, 1)) JOIN h2i h2i2 ON h2i2.h=HEX(SUBSTR(val, 3, 1)) JOIN h2i h2i3 ON h2i3.h=HEX(SUBSTR(val, 4, 1)) ;
#rayzinnz, thank you for the hint. in the meantime i gave up. i puzzled together a kind of a solution, but i never got it work to set the initial x'cafe1a7e' value from outside the WITH RECURSIVE construction. WITH RECURSIVE fx(val_hex, val_int, iter) AS ( VALUES(HEX(x'cafe1a7e'), 0, 0) UNION ALL SELECT SUBSTR(val_hex, 1, LENGTH(val_hex) - 1), val_int + ( CASE SUBSTR(val_hex, -1) WHEN '0' THEN 0 WHEN '1' THEN 1 WHEN '2' THEN 2 WHEN '3' THEN 3 WHEN '4' THEN 4 WHEN '5' THEN 5 WHEN '6' THEN 6 WHEN '7' THEN 7 WHEN '8' THEN 8 WHEN '9' THEN 9 WHEN 'A' THEN 10 WHEN 'B' THEN 11 WHEN 'C' THEN 12 WHEN 'D' THEN 13 WHEN 'E' THEN 14 WHEN 'F' THEN 15 ELSE 0 END << (iter * 4) ), iter + 1 FROM fx WHERE val_hex != '' LIMIT 9 ) --SELECT * FROM fx SELECT val_int FROM fx WHERE val_hex == '' ; the BLOB value there is hardcoded. maybe you find a way.
11- Proof method in PL/SQL
A Dutch bank account consists of 9 digits e.g.: 1334.36.915. To check whether the bank account is valid we use the so called ‘11-proef’ (11-test). In this test each digit is multiplied with its place in the row.The result of this multiplication is added up. (1*9)+(3*8)+(3*7)+(4*6)+(3*5)+(6*4)+(9*3)+(1*2)+(5*1) = R This result has to be dividable by 11. That means the remainder of the division must be 0. If the R is dividable by 11 the bank account number is valid! Can someone help with this question?
A Dutch bank account number does not consist of 9 digits anymore, we now use Iban Numbers. If you want to do a check on it, you should have a look at https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN and implement that check. For now, you can however still to the 11-check on the last 9 or 10 digits, but it's not guaranteed that will still work for new bank accounts in the future. If you still want to do a 11-check, you can create a function like this: CREATE OR REPLACE FUNCTION elfproof (accountnummer IN varchar2) RETURN VARCHAR2 AS multiplier int:= 10; outcome varchar2(10); total int := 0; BEGIN FOR i IN 1 .. 9 LOOP multiplier := multiplier - 1; total := total + (multiplier * TO_NUMBER (SUBSTR (accountnummer, i, 1))); END LOOP; IF MOD (total, 11) = 0 THEN outcome := 'good'; ELSE outcome := 'bad'; END IF; return outcome; END;
This gives the remainder of division on 11: SELECT MOD (SUM (TO_NUMBER (SUBSTR (str, LEVEL, 1)) * (10 - LEVEL)), 11) remdiv11 FROM ( SELECT REPLACE ('1334.36.915', '.') str FROM DUAL ) d CONNECT BY LEVEL <= LENGTH (str) and IBAN check;) : SELECT DECODE (MOD (TO_NUMBER (LISTAGG (n, '') WITHIN GROUP (ORDER BY l)), 97), 1, 'OK', 'Fail') AS iban_check FROM ( SELECT TO_CHAR (CASE WHEN ASCII (c) >= 65 THEN ASCII (c) - 55 ELSE ASCII (c) - 48 END) n, c, l FROM ( SELECT SUBSTR (str, LEVEL, 1) c, LEVEL l FROM ( SELECT SUBSTR (s, 5) || SUBSTR (s, 1, 4) str FROM ( SELECT REPLACE ('GB82 WEST 1234 5698 7654 32', ' ') s FROM DUAL ) ) CONNECT BY LEVEL <= LENGTH (str) ) )
Why don't WolframAlpha's and Sage's answers match?
Consider this input to WolframAlpha, solve [ 0 = x^4 - 6*x^2 - 8*x*cos( (2*pi )/5 ) - 2*cos( (4*pi)/5) - 1 ] The solutions it gives are, {x == (1 - Sqrt[5])/2 || x == (3 + Sqrt[5])/2 || x == (-2 - Sqrt[2 (5 - Sqrt[5])])/2 || x == (-2 + Sqrt[2 (5 - Sqrt[5])])/2} But the same equation on sage gives the roots, h(x) = x^4 - 6*x^2 - 8*x*cos( (2*pi )/5 ) - 2*cos( (4*pi)/5) - 1 h(x).solve(x) [x == -1/2*sqrt(-2*sqrt(5) + 10) - 1, x == 1/2*sqrt(-2*sqrt(5) + 10) - 1, x == -1/2*sqrt(2*sqrt(5) + 6) + 1, x == 1/2*sqrt(2*sqrt(5) + 6) + 1] It seems that the first two roots given by WolframAlpha differ from the last two roots given by Sage. Why?
They're not different; they're exactly the same, simply listed in a different order. sage: h(x) = x^4 - 6*x^2 - 8*x*cos( (2*pi )/5 ) - 2*cos( (4*pi)/5) - 1 sage: sols = h(x).solve(x, solution_dict=True) sage: [CC(d[x]) for d in sols] [-2.17557050458495, 0.175570504584946, -0.618033988749895, 2.61803398874989] sage: wa = [ (1 - sqrt(5))/2 , (3 + sqrt(5))/2 , (-2 - sqrt(2* (5 - sqrt(5))))/2 , (-2 + sqrt(2* (5 - sqrt(5))))/2 ] sage: [CC(v) for v in wa] [-0.618033988749895, 2.61803398874989, -2.17557050458495, 0.175570504584946]
Function to return date of Easter for the given year
So, here's a funny little programming challenge. I was writing a quick method to determine all the market holidays for a particular year, and then I started reading about Easter and discovered just how crazy* the logic is for determining its date--the first Sunday after the Paschal Full Moon following the spring equinox! Does anybody know of an existing function to calculate the date of Easter for a given year? Granted, it's probably not all that hard to do; I just figured I'd ask in case somebody's already done this. (And that seems very likely.) UPDATE: Actually, I'm really looking for the date of Good Friday (the Friday before Easter)... I just figured Easter would get me there. And since I'm in the U.S., I assume I'm looking for the Catholic Easter? But perhaps someone can correct me on that if I'm wrong. *By "crazy" I meant, like, involved. Not anything offensive...
Python: using dateutil's easter() function. >>> from dateutil.easter import * >>> print easter(2010) 2010-04-04 >>> print easter(2011) 2011-04-24 The functions gets, as an argument, the type of calculation you like: EASTER_JULIAN = 1 EASTER_ORTHODOX = 2 EASTER_WESTERN = 3 You can pick the one relevant to the US. Reducing two days from the result would give you Good Friday: >>> from datetime import timedelta >>> d = timedelta(days=-2) >>> easter(2011) datetime.date(2011, 4, 24) >>> easter(2011)+d datetime.date(2011, 4, 22) Oddly enough, someone was iterating this, and published the results in Wikipedia's article about the algorithm:
in SQL Server Easter Sunday would look like this, scroll down for Good Friday CREATE FUNCTION dbo.GetEasterSunday ( #Y INT ) RETURNS SMALLDATETIME AS BEGIN DECLARE #EpactCalc INT, #PaschalDaysCalc INT, #NumOfDaysToSunday INT, #EasterMonth INT, #EasterDay INT SET #EpactCalc = (24 + 19 * (#Y % 19)) % 30 SET #PaschalDaysCalc = #EpactCalc - (#EpactCalc / 28) SET #NumOfDaysToSunday = #PaschalDaysCalc - ( (#Y + #Y / 4 + #PaschalDaysCalc - 13) % 7 ) SET #EasterMonth = 3 + (#NumOfDaysToSunday + 40) / 44 SET #EasterDay = #NumOfDaysToSunday + 28 - ( 31 * (#EasterMonth / 4) ) RETURN ( SELECT CONVERT ( SMALLDATETIME, RTRIM(#Y) + RIGHT('0'+RTRIM(#EasterMonth), 2) + RIGHT('0'+RTRIM(#EasterDay), 2) ) ) END GO Good Friday is like this and it uses the Easter function above CREATE FUNCTION dbo.GetGoodFriday ( #Y INT ) RETURNS SMALLDATETIME AS BEGIN RETURN (SELECT dbo.GetEasterSunday(#Y) - 2) END GO From here: http://web.archive.org/web/20070611150639/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-calendar-table.html
When it came for me to write this (traffic prediction based on day of week and holiday), I gave up on trying to write it by myself. I found it somewhere on the net. The code was public domain, but... sigh see for yourself. void dateOfEaster(struct tm* p) { int Y = p->tm_year; int a = Y % 19; int b = Y / 100; int c = Y % 100; int d = b / 4; int e = b % 4; int f = (b + 8) / 25; int g = (b - f + 1) / 3; int h = (19 * a + b - d - g + 15) % 30; int i = c / 4; int k = c % 4; int L = (32 + 2 * e + 2 * i - h - k) % 7; int m = (a + 11 * h + 22 * L) / 451; p->tm_mon = ((h + L - 7 * m + 114) / 31 ) - 1; p->tm_mday = ((h + L - 7 * m + 114) % 31) + 1; p->tm_hour = 12; const time_t tmp = mktime(p); *p = *localtime(&tmp); //recover yday from mon+mday } Some questions are better left unasked. I feel lucky that all moving holidays in my country are a fixed offset from the date of Easter.
The SQL Server function below is more general than the accepted answer The accepted answer is only correct for the range (inclusive) : 1900-04-15 to 2099-04-12 It uses the algorithm provided by The United States Naval Observatory (USNO) http://aa.usno.navy.mil/faq/docs/easter.php CREATE FUNCTION dbo.GetEasterSunday (#Y INT) RETURNS DATETIME AS BEGIN -- Source of algorithm : http://aa.usno.navy.mil/faq/docs/easter.php DECLARE #c INT = #Y / 100 DECLARE #n INT = #Y - 19 * (#Y / 19) DECLARE #k INT = (#c - 17) / 25 DECLARE #i INT = #c - #c / 4 - (#c - #k) / 3 + 19 * #n + 15 SET #i = #i - 30 * (#i / 30) SET #i = #i - (#i / 28) * (1 - (#i / 28) * (29 / (#i + 1)) * ((21 - #n) / 11)) DECLARE #j INT = #Y + #Y / 4 + #i + 2 - #c + #c / 4 SET #j = #j - 7 * (#j / 7) DECLARE #l INT = #i - #j DECLARE #m INT = 3 + (#l + 40) / 44 DECLARE #d INT = #l + 28 - 31 * (#m / 4) RETURN ( SELECT CONVERT ( DATETIME, RTRIM(#Y) + RIGHT('0'+RTRIM(#m), 2) + RIGHT('0'+RTRIM(#d), 2) ) ) END GO
VB .NET Functions for Greek Orthodox and Catholic Easter: Public Shared Function OrthodoxEaster(ByVal Year As Integer) As Date Dim a = Year Mod 19 Dim b = Year Mod 7 Dim c = Year Mod 4 Dim d = (19 * a + 16) Mod 30 Dim e = (2 * c + 4 * b + 6 * d) Mod 7 Dim f = (19 * a + 16) Mod 30 Dim key = f + e + 3 Dim month = If((key > 30), 5, 4) Dim day = If((key > 30), key - 30, key) Return New DateTime(Year, month, day) End Function Public Shared Function CatholicEaster(ByVal Year As Integer) As DateTime Dim month = 3 Dim a = Year Mod 19 + 1 Dim b = Year / 100 + 1 Dim c = (3 * b) / 4 - 12 Dim d = (8 * b + 5) / 25 - 5 Dim e = (5 * Year) / 4 - c - 10 Dim f = (11 * a + 20 + d - c) Mod 30 If f = 24 Then f += 1 If (f = 25) AndAlso (a > 11) Then f += 1 Dim g = 44 - f If g < 21 Then g = g + 30 Dim day = (g + 7) - ((e + g) Mod 7) If day > 31 Then day = day - 31 month = 4 End If Return New DateTime(Year, month, day) End Function
The below code determines Easter through powershell: function Get-DateOfEaster { param( [Parameter(ValueFromPipeline)] $theYear=(Get-Date).Year ) if($theYear -lt 1583) { return $null } else { # Step 1: Divide the theYear by 19 and store the # remainder in variable A. Example: If the theYear # is 2000, then A is initialized to 5. $a = $theYear % 19 # Step 2: Divide the theYear by 100. Store the integer # result in B and the remainder in C. $c = $theYear % 100 $b = ($theYear -$c) / 100 # Step 3: Divide B (calculated above). Store the # integer result in D and the remainder in E. $e = $b % 4 $d = ($b - $e) / 4 # Step 4: Divide (b+8)/25 and store the integer # portion of the result in F. $f = [math]::floor(($b + 8) / 25) # Step 5: Divide (b-f+1)/3 and store the integer # portion of the result in G. $g = [math]::floor(($b - $f + 1) / 3) # Step 6: Divide (19a+b-d-g+15)/30 and store the # remainder of the result in H. $h = (19 * $a + $b - $d - $g + 15) % 30 # Step 7: Divide C by 4. Store the integer result # in I and the remainder in K. $k = $c % 4 $i = ($c - $k) / 4 # Step 8: Divide (32+2e+2i-h-k) by 7. Store the # remainder of the result in L. $l = (32 + 2 * $e + 2 * $i - $h - $k) % 7 # Step 9: Divide (a + 11h + 22l) by 451 and # store the integer portion of the result in M. $m = [math]::floor(($a + 11 * $h + 22 * $l) / 451) # Step 10: Divide (h + l - 7m + 114) by 31. Store # the integer portion of the result in N and the # remainder in P. $p = ($h + $l - 7 * $m + 114) % 31 $n = (($h + $l - 7 * $m + 114) - $p) / 31 # At this point p+1 is the day on which Easter falls. # n is 3 for March and 4 for April. $DateTime = New-Object DateTime $theyear, $n, ($p+1), 0, 0, 0, ([DateTimeKind]::Utc) return $DateTime } } $eastersunday=Get-DateOfEaster 2015 Write-Host $eastersunday
Found this Excel formula somewhere Assuming cell A1 contains year e.g. 2020 ROUND(DATE(A1;4;1)/7+MOD(19*MOD(A1;19)-7;30)*0,14;0)*7-6 Converted to T-SQL lead me to this: DECLARE #yr INT=2020 SELECT DATEADD(dd, ROUND(DATEDIFF(dd, '1899-12-30', DATEFROMPARTS(#yr, 4, 1)) / 7.0 + ((19.0 * (#yr % 19) - 7) % 30) * 0.14, 0) * 7.0 - 6, -2)
In JS, taken from here. var epoch=2444238.5,elonge=278.83354,elongp=282.596403,eccent=.016718,sunsmax=149598500,sunangsiz=.533128,mmlong=64.975464,mmlongp=349.383063,mlnode=151.950429,minc=5.145396,mecc=.0549,mangsiz=.5181,msmax=384401,mparallax=.9507,synmonth=29.53058868,lunatbase=2423436,earthrad=6378.16,PI=3.141592653589793,epsilon=1e-6;function sgn(x){return x<0?-1:x>0?1:0}function abs(x){return x<0?-x:x}function fixAngle(a){return a-360*Math.floor(a/360)}function toRad(d){return d*(PI/180)}function toDeg(d){return d*(180/PI)}function dsin(x){return Math.sin(toRad(x))}function dcos(x){return Math.cos(toRad(x))}function toJulianTime(date){var year,month,day;year=date.getFullYear();var m=(month=date.getMonth()+1)>2?month:month+12,y=month>2?year:year-1,d=(day=date.getDate())+date.getHours()/24+date.getMinutes()/1440+(date.getSeconds()+date.getMilliseconds()/1e3)/86400,b=isJulianDate(year,month,day)?0:2-y/100+y/100/4;return Math.floor(365.25*(y+4716)+Math.floor(30.6001*(m+1))+d+b-1524.5)}function isJulianDate(year,month,day){if(year<1582)return!0;if(year>1582)return!1;if(month<10)return!0;if(month>10)return!1;if(day<5)return!0;if(day>14)return!1;throw"Any date in the range 10/5/1582 to 10/14/1582 is invalid!"}function jyear(td,yy,mm,dd){var z,f,alpha,b,c,d,e;return f=(td+=.5)-(z=Math.floor(td)),b=(z<2299161?z:z+1+(alpha=Math.floor((z-1867216.25)/36524.25))-Math.floor(alpha/4))+1524,c=Math.floor((b-122.1)/365.25),d=Math.floor(365.25*c),e=Math.floor((b-d)/30.6001),{day:Math.floor(b-d-Math.floor(30.6001*e)+f),month:Math.floor(e<14?e-1:e-13),year:Math.floor(mm>2?c-4716:c-4715)}}function jhms(j){var ij;return j+=.5,ij=Math.floor(86400*(j-Math.floor(j))+.5),{hour:Math.floor(ij/3600),minute:Math.floor(ij/60%60),second:Math.floor(ij%60)}}function jwday(j){return Math.floor(j+1.5)%7}function meanphase(sdate,k){var t,t2;return 2415020.75933+synmonth*k+1178e-7*(t2=(t=(sdate-2415020)/36525)*t)-155e-9*(t2*t)+33e-5*dsin(166.56+132.87*t-.009173*t2)}function truephase(k,phase){var t,t2,t3,pt,m,mprime,f,apcor=!1;if(pt=2415020.75933+synmonth*(k+=phase)+1178e-7*(t2=(t=k/1236.85)*t)-155e-9*(t3=t2*t)+33e-5*dsin(166.56+132.87*t-.009173*t2),m=359.2242+29.10535608*k-333e-7*t2-347e-8*t3,mprime=306.0253+385.81691806*k+.0107306*t2+1236e-8*t3,f=21.2964+390.67050646*k-.0016528*t2-239e-8*t3,phase<.01||abs(phase-.5)<.01?(pt+=(.1734-393e-6*t)*dsin(m)+.0021*dsin(2*m)-.4068*dsin(mprime)+.0161*dsin(2*mprime)-4e-4*dsin(3*mprime)+.0104*dsin(2*f)-.0051*dsin(m+mprime)-.0074*dsin(m-mprime)+4e-4*dsin(2*f+m)-4e-4*dsin(2*f-m)-6e-4*dsin(2*f+mprime)+.001*dsin(2*f-mprime)+5e-4*dsin(m+2*mprime),apcor=!0):(abs(phase-.25)<.01||abs(phase-.75)<.01)&&(pt+=(.1721-4e-4*t)*dsin(m)+.0021*dsin(2*m)-.628*dsin(mprime)+.0089*dsin(2*mprime)-4e-4*dsin(3*mprime)+.0079*dsin(2*f)-.0119*dsin(m+mprime)-.0047*dsin(m-mprime)+3e-4*dsin(2*f+m)-4e-4*dsin(2*f-m)-6e-4*dsin(2*f+mprime)+.0021*dsin(2*f-mprime)+3e-4*dsin(m+2*mprime)+4e-4*dsin(m-2*mprime)-3e-4*dsin(2*m+mprime),pt+=phase<.5?.0028-4e-4*dcos(m)+3e-4*dcos(mprime):4e-4*dcos(m)-.0028-3e-4*dcos(mprime),apcor=!0),!apcor)throw"Error calculating moon phase!";return pt}function phasehunt(sdate,phases){var adate,k1,k2,nt1,nt2,yy,mm,dd,jyearResult=jyear(adate=sdate-45,yy,mm,dd);for(yy=jyearResult.year,mm=jyearResult.month,dd=jyearResult.day,adate=nt1=meanphase(adate,k1=Math.floor(12.3685*(yy+1/12*(mm-1)-1900)));nt2=meanphase(adate+=synmonth,k2=k1+1),!(nt1<=sdate&&nt2>sdate);)nt1=nt2,k1=k2;return phases[0]=truephase(k1,0),phases[1]=truephase(k1,.25),phases[2]=truephase(k1,.5),phases[3]=truephase(k1,.75),phases[4]=truephase(k2,0),phases}function kepler(m,ecc){var e,delta;e=m=toRad(m);do{e-=(delta=e-ecc*Math.sin(e)-m)/(1-ecc*Math.cos(e))}while(abs(delta)>epsilon);return e}function getMoonPhase(julianDate){var Day,N,M,Ec,Lambdasun,ml,MM,MN,Ev,Ae,MmP,mEc,lP,lPP,NP,y,x,MoonAge,MoonPhase,MoonDist,MoonDFrac,MoonAng,F,SunDist,SunAng;return N=fixAngle(360/365.2422*(Day=julianDate-epoch)),Ec=kepler(M=fixAngle(N+elonge-elongp),eccent),Ec=Math.sqrt((1+eccent)/(1-eccent))*Math.tan(Ec/2),Lambdasun=fixAngle((Ec=2*toDeg(Math.atan(Ec)))+elongp),F=(1+eccent*Math.cos(toRad(Ec)))/(1-eccent*eccent),SunDist=sunsmax/F,SunAng=F*sunangsiz,ml=fixAngle(13.1763966*Day+mmlong),MM=fixAngle(ml-.1114041*Day-mmlongp),MN=fixAngle(mlnode-.0529539*Day),MmP=MM+(Ev=1.2739*Math.sin(toRad(2*(ml-Lambdasun)-MM)))-(Ae=.1858*Math.sin(toRad(M)))-.37*Math.sin(toRad(M)),lPP=(lP=ml+Ev+(mEc=6.2886*Math.sin(toRad(MmP)))-Ae+.214*Math.sin(toRad(2*MmP)))+.6583*Math.sin(toRad(2*(lP-Lambdasun))),NP=MN-.16*Math.sin(toRad(M)),y=Math.sin(toRad(lPP-NP))*Math.cos(toRad(minc)),x=Math.cos(toRad(lPP-NP)),toDeg(Math.atan2(y,x)),NP,toDeg(Math.asin(Math.sin(toRad(lPP-NP))*Math.sin(toRad(minc)))),MoonAge=lPP-Lambdasun,MoonPhase=(1-Math.cos(toRad(MoonAge)))/2,MoonDist=msmax*(1-mecc*mecc)/(1+mecc*Math.cos(toRad(MmP+mEc))),MoonAng=mangsiz/(MoonDFrac=MoonDist/msmax),mparallax/MoonDFrac,{moonIllumination:MoonPhase,moonAgeInDays:synmonth*(fixAngle(MoonAge)/360),distanceInKm:MoonDist,angularDiameterInDeg:MoonAng,distanceToSun:SunDist,sunAngularDiameter:SunAng,moonPhase:fixAngle(MoonAge)/360}}function getMoonInfo(date){return null==date?{moonPhase:0,moonIllumination:0,moonAgeInDays:0,distanceInKm:0,angularDiameterInDeg:0,distanceToSun:0,sunAngularDiameter:0}:getMoonPhase(toJulianTime(date))}function getEaster(year){var previousMoonInfo,moonInfo,fullMoon=new Date(year,2,21),gettingDarker=void 0;do{previousMoonInfo=getMoonInfo(fullMoon),fullMoon.setDate(fullMoon.getDate()+1),moonInfo=getMoonInfo(fullMoon),void 0===gettingDarker?gettingDarker=moonInfo.moonIllumination<previousMoonInfo.moonIllumination:gettingDarker&&moonInfo.moonIllumination>previousMoonInfo.moonIllumination&&(gettingDarker=!1)}while(gettingDarker&&moonInfo.moonIllumination<previousMoonInfo.moonIllumination||!gettingDarker&&moonInfo.moonIllumination>previousMoonInfo.moonIllumination);for(fullMoon.setDate(fullMoon.getDate()-1);0!==fullMoon.getDay();)fullMoon.setDate(fullMoon.getDate()+1);return fullMoon} Then run getEaster(2020); // -> Sun Apr 12 2020