VBA: DateDiff Year off - ms-access

I have the following code in VBA to return the year between two dates: DateDiff("yyyy", "10/10/1930","06/07/2008 8:30:00 AM")
It returns 78, but it should really be 77.
What is going on here?

VBA's DateDiff function was not designed to track elapsed time. That statement is simply evaluating the year.
See this msdn article, which offers a function to calculate the years elapsed: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvbadev/html/workingwithelapsedtime.asp
Function elapsed_years_function(first_date As Date, Optional second_date As Date = 0) As Integer
' This procedure is from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvbadev/html/workingwithelapsedtime.asp
Dim elapsed_years As Integer
If second_date = 0 Then
' Did the caller pass in a date? If not, use
' the current date.
second_date = Date
End If
elapsed_years = DateDiff("yyyy", first_date, second_date)
If second_date < DateSerial(Year(second_date), Month(first_date), Day(first_date)) Then
elapsed_years = elapsed_years - 1
End If
elapsed_years_function = elapsed_years
End Function

Edit: According to this (VB.Net) As pointed out by #Justin, this is for VB.Net, not Visual Basic, but the implementation is most likely identical for backwards compatibility. I've referenced the relevant VBScript documentation below for completeness.
Larger Intervals. If Interval is set
to DateInterval.Year, the return value
is calculated purely from the year
parts of Date1 and Date2. Similarly,
the return value for
DateInterval.Month is calculated
purely from the year and month parts
of the arguments, and for
DateInterval.Quarter from the quarters
containing the two dates.
For example, when comparing December
31 to January 1 of the following year,
DateDiff returns 1 for
DateInterval.Year,
DateInterval.Quarter, or
DateInterval.Month, even though at
most only one day has elapsed.
Also see this (VBScript):
When comparing December 31 to January
1 of the immediately succeeding year,
DateDiff for Year ("yyyy") returns 1
even though only a day has elapsed.
So it's likely implemented like this, which gives 78:
Year(SecondDate) - Year(FirstDate)
See #Ken's solution for an implementation that should work as intended.

If you did DateDiff("yyyy", "12/31/2010", "1/1/2011") it would return 1, even though there is only a day difference.

Working as designed: see Remarks (larger intervals) under http://msdn.microsoft.com/en-us/library/b5xbyt6f%28v=vs.80%29.aspx WHICH STATES:
If Interval is set to DateInterval.Year, the return value is calculated purely from the year parts of Date1 and Date2.
When you do datediff by year the operation performed is 2008 - 1930

Related

How to make SSRS subscriptions Kick off on Business Day 6 of month

Found a similar question
5th BUSINESS DAY subscription SSRS
But in that case a work around -schedule for 1st of month- was suggested and accepted as the answer.
I want to know if anyone has found a clever way to make SSRS subscriptions run on a specified Business Day. I.E run on Business Day 6. This is not just to prevent the report from going out on a weekend, but also because certain Finance operations related to closing the month have an agreed upon date (EX. "Will be done by BD 3") And I need my report to run after that each month.
One comment also suggested setting up a sql agent job to calculate what BD X would be each month, and insert a SQL job for that date to kick off the Report. I can see in theory how this would work - but the subscription wouldn't be managed within SSRS then and could easily be over-looked in the future.
I have been getting by with the following imperfect code:
--: returns last day of last month (DT), but only provides a result row if we are on BusinessDay X of the month.
--No rows returned prevents the report from being processed & sent.
--SSRS Schedule must be set to run monthly in a range covering all possible Calendar Days that could be Business Day X
declare #dt datetime = Getdate(), #BDTarget int = 6
SELECT DATEADD(MONTH, DATEDIFF(MONTH, -1,#dt)-1, -1) as DT,sum(dimdate.BusinessDay) as BD
FROM DimDate
where FullDate between DATEADD(mm, DATEDIFF(mm, 0, #dt), 0) and #dt
having sum(dimdate.BusinessDay)=#BDTarget
Though recently discovered that this logic can kick off the report two days in a row, for example if we are looking for BD 6, this month July 2021 the above query returned a row on both Friday 7/9 and Sat 7/10.
You can use a data-driven subscription that runs daily.
Use a query that will only give a result on the 6th business day.
e.g.
WITH cte_6th
AS
(
SELECT *
FROM dimDate dd
WHERE dd.TheMonth = MONTH(current_timestamp)
AND dd.TheYear = YEAR(current_timestamp)
AND dd.BusinessDay = 1
ORDER BY dd.Date
OFFSET 5 ROWS
FETCH NEXT 1 ROWS ONLY
)
SELECT *
FROM cte_6th c
WHERE c.Date = CAST(current_timestamp as DATE);

Counting days in a product life excluding the summers

I am trying to write an ms access SQL query in order to calculate the number of days that have passed since a date in two year periods: "summer" (from 1/6 to 30/09) and non-summer (from 1/1 to 30/05 and from 1/10 to 31/12).
This date is the last "measure" of the product and it could be as old as two years.
Then I would like to know, as today, how many days of summer and vice-versa how many days of nonsummer has passed since the product has been measured.
ie. the date is 31/10/2017, I'd like two columns to show summer days passed (=30days*4months in 2018+ 28days in 2019 = 148days) and non-summer days passed ( 7months between Nov17 and may17+ 8 months from Ott18 to May 19 = 450days)
I have been trying using IIF cycles like that one:
(ANNI is a variable that means in which year we are 0=this year, 1 past year, etc)
days of non-summer
IIF(ANNI=0; "150"; IIF(ANNI=1;
IIF(G2.DATA_PRODUZ<#01/06/2018#;DateDiff("d",[G2]!
[DATA_PRODUZ],#01/06/2018#)+90+150;
IIF(G2.DATA_PRODUZ>#30/08/18#;DateDiff("d",[G2]!
[DATA_PRODUZ],#31/12/2018#)+150));"etc");)
But it won't work and it does it would only work this summer. I'd accept any ideas.
Perhaps not the most efficient method, but certainly the most readable might be to define a VBA function (in a public module) such as:
Function SummerDays(ByVal dateFrom As Date, ByVal dateTo As Date) As Long
While dateFrom < dateTo
If dateFrom >= DateSerial(Year(dateFrom), 6, 1) And dateFrom <= DateSerial(Year(dateFrom), 9, 30) Then
SummerDays = SummerDays + 1
End If
dateFrom = dateFrom + 1
Wend
End Function
And then call this from your query:
SummerDays(G2.DATA_PRODUZ, Date())
Then, for the non-summer days, you could either define another function with an inverse if statement, or subtract the number of summer days from the total number of days between the two dates, e.g.:
DateDiff("d",G2.DATA_PRODUZ, Date()) - SummerDays(G2.DATA_PRODUZ, Date())
To create a public module in which to define the above function:
Open the VBA IDE using Alt+F11
Insert a new public module by going to Insert > Module or Alt, I, M
Copy the above function definition into the Module, below the Option statements.
Save the Module with an appropriate name.

MS-ACCESS find data older than 3 years

I am trying to have a push button that finds all records that are older than 3 years. I have a push button (named Expiring) on a combo box that searches a field Intro Date in table RECS. Here is what I have:
Private Sub Expiring_Click()
Call Search
End Sub
Sub Search()
Dim task As String
Me.Refresh
task = "SELECT * FROM RECS WHERE DateDiff('m', Date(), [Intro Date])" > 36
DoCmd.ApplyFilter task
End Sub
This does not work, I get a type mismatch for my task line. I simply want a push button that will only show data that is 3 years old. . .
I am super new to MS-ACCESS and SQL/VBA. This code was based off another function I created that searches if Intro Date older than a date specified in a unbounded box.
correct line of code
task = "SELECT * FROM RECS WHERE DateDiff('m', Date(), [Intro Date]) > 36"
You were comparing text to the number 36 the result is a boolean instead of a string
As stated by others, the issue with your code is that the comparison operator lies outside of the SQL statement -
task = "SELECT * FROM RECS WHERE DateDiff('m', Date(), [Intro Date])" > 36
Should be:
task = "SELECT * FROM RECS WHERE DateDiff('m', Date(), [Intro Date]) > 36"
DateDiff Considerations
When calculating temporal differences using the DateDiff function, consider that the choice of interval argument will impact the accuracy of the result.
For example, the intuitive solution might be to calculate the three-year range by supplying the DateDiff function with the yyyy interval and the two dates in question, and simply check whether the result is greater than 3:
DateDiff("yyyy", Date(), [Intro Date]) > 3
However, observe that this will omit all dates whose year components differ by 3 or less, not dates which are exactly 3 years or fewer apart - meaning that up to an extra year's worth of time may be excluded from your results:
?datediff("yyyy", #2018-01-01#, #2021-01-01#) ' 3 years apart
3
?datediff("yyyy", #2018-01-01#, #2021-12-31#) ' 3 years + 364 days apart
3
Similarly, if you opt for the month interval (m), the remainder of a month three years in future will also be excluded:
?datediff("m", #2018-01-01#, #2021-01-01#) ' 3 years apart
36
?datediff("m", #2018-01-01#, #2021-01-31#) ' 3 years + 30 days apart
36
Obviously we now descend into an infinite regression: if days are selected, the time component will be ignored, if hours are selected, the minutes component will be ignored, ad infinitum...
And so ultimately, the choice of interval depends upon the requirements of the application: if your application requires that something expire or be validated exactly 3 years to the day, then if using the DateDiff function, the day interval (d) should be used (and note that this would include leap days, and so the resulting date could be a day prior to the date 3 years earlier); if the remainder of the month is permitted, then the month interval (m) may suffice.

Convert "YYYYWW" into month (SSRS)

I am creating a report and would like to retrieve data based on a date field (string) in the format YYYYWW. I want to filter the data so I retrieve the current month, previous month, the month before that and older values - using 4 different datasets.
For example:
201547 = November 2015
How do I create a calculated member which converts a string into the month name and year? I would like to use this value to filter out the data from the cube. Is there any other way of doing this?
:]1
Try adding a calculated field and set it to this expression:
=MonthName(Month(DateAdd("ww",CInt(MID("201547",5,LEN("201547")))
,DateSerial(CInt(LEFT("201547",4)),1,1)))) & " " & LEFT("201547",4)
Then use that field to filter your report.
Replace the string for the field returning "201547"
=MonthName(Month(DateAdd("ww",
CInt(MID(Fields!YearWeek.Value,5,LEN(Fields!YearWeek.Value)))
,DateSerial(CInt(LEFT(Fields!YearWeek.Value,4)),1,1))))
& " " & LEFT(Fields!YearWeek.Value,4)
UPDATE: MDX script for querying a calulated member with the required format:
I've tested with the following MDX:
WITH MEMBER [Measures].[month&year] AS
'FORMAT(DateAdd("ww",STRTOVALUE(MID("201547",5,LEN("201547"))) ,CDATE(DateSerial(LEFT("201547",4),1,1))),"MMMM") + " " + LEFT("201547",4)'
SELECT { [month&year] } ON COLUMNS
FROM [Your Cube]
To use it in your query replace "201547" for [Dimension].[Attribute].MemberValue that corresponds to your cube structure:
WITH MEMBER [Measures].[month&year] as
'FORMAT(DateAdd("ww",STRTOVALUE(MID([Dimension].[Attribute].MemberValue,5,LEN([Dimension].[Attribute].MemberValue)))
,CDATE(DateSerial(LEFT([Dimension].[Attribute].MemberValue,4),1,1))),"MMMM") + " " + LEFT([Dimension].[Attribute].MemberValue,4)'
SELECT
{ [Dimension].[Attribute].[Attribute] }
{[month&year]} ON COLUMNS
FROM [Your Cube]
Once you have this field in your dataset you can easily filter the values using a parameter.
Let me know if this can help you.
Using a combination of the resources found out on the internet you can find the information you require.
First you need to find the first day of the first week of the year. This has already been determined by this answer as
DateAdd("d",
(-1) * (CInt(New DateTime(Year(Now), 1, 1).DayOfWeek) +
IIf(CInt(New DateTime(Year(Now), 1, 1).DayOfWeek) < DayOfWeek.Monday,
7,
0)
) + 1,
New DateTime(Year(Now), 1, 1))
This needs to be adapted for any year - not just the current one by finding the year from the string as follows
CInt(Left(Parameters!myDate.Value,4))
Now, as noted in this resource you can use a DateAdd to add the number of weeks of the year you have entered to the first date of the year
=(DateAdd(DateInterval.WeekOfYear,
CInt(Right(Parameters!myDate.Value,2))-1,
<<FIRST_DAY_OF_YEAR>>)
You then want to get the month name of the month of this date, using Month and MonthName. Smashing it all together would result in the following
=MonthName(Month(DateAdd(DateInterval.WeekOfYear,
CInt(Right(Parameters!myDate.Value,2))-1,
DateAdd("d",
(-1) * (CInt(New DateTime(CInt(Left(Parameters!myDate.Value,4)), 1, 1).DayOfWeek) +
IIf(CInt(New DateTime(CInt(Left(Parameters!myDate.Value,4)), 1, 1).DayOfWeek) < DayOfWeek.Monday,
7,
0)
) + 1,
New DateTime(CInt(Left(Parameters!myDate.Value,4)), 1, 1))
)))
This will get a Textual value of the month for the interval
Create a new placeholder in the same textbox and then repeat the above for finding the year
=Year(DateAdd(DateInterval.WeekOfYear, ...
(Remember to remove the last brace otherwise you’ll get a End of Statement expected error.)
This shoud give what you require, so
201501 = December 2014
201547 = November 2015
201553 = December 2015
201622 = May 2016
Let me know if this helps, or if you have further questions
Part 1: Establishing which day falls into which week
ISO-8601 declares the international standards for converting dates into week numbers, and vice-versa.
ISO standard weeks start on a Monday. A year can have either 52 or 53 weeks.
There are 4 ways of establishing when Week 1 starts:
- It is the first week with a majority (4 or more) of its days in January.
- Its first day is the Monday nearest to 1 January.
- It has 4 January in it. Hence the earliest possible dates are 29 December through 4 January, the latest 4 through 10 January.
- It has the year's first working day in it - i.e. excluding Saturdays, Sundays and the 1st of January.
Part 2: Excel calculations
This answer assumes that the dates are being stored as 6-character strings.
First determine the year and what day of the week it starts on.
1) Strip the first four characters from the string.`
=left(<cell containing the year+week string>, 4)
2) Prepend "1/1/" to it.
="1/1/"&left(<cell containing the year+week string>, 4)
3) Convert this string to an Excel date
=datevalue(<the last result>)
4) Determine the Weekday of the 1st day of that year.
=weekday(<the last result>)
5) Calculate the Excel date of the first Monday.
=if((<the last result>-4)<0,<the result of step 3>+8-<the last result>, <the result of step 3>+1-<the last result>)
6) Work out how many weeks on from that we are:
=right(<original 6 char year/week string>,2)
7) Convert 6) to a number
=value(<last result>)
8) Add that to the Monday of Week 1
=<result of step 5>+<result of step 7>*7
9) Convert this to a month number
=month(<last result>)
10) Convert this to a month name
=choose(<last result>,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")
11) Add the year back on
=<lastresult>&" "&<result of 1>
This gives the month number which contains the Monday of the current numbered week.
If cell A1 contains '201547
then cell B1 should contain:
=CHOOSE(MONTH(IF((WEEKDAY(DATEVALUE("1/1/"&LEFT(A1,4)))-4)<0,DATEVALUE("1/1/"&LEFT(A1,4))+1-WEEKDAY(DATEVALUE("1/1/"&LEFT(A1,4))),DATEVALUE("1/1/"&LEFT(A1,4))+8-WEEKDAY(DATEVALUE("1/1/"&LEFT(A1,4))))+7*(VALUE(RIGHT(A1,2)))),"January","February","March","April","May","June","July","August","September","October","November","December")&" "&LEFT(A1,4)
If A1 contains 201547 then B1 returns "November 2015".
Further considerations:
You may want to make it more sophisicated, so that a month ending in a Monday does not have the week commencing with its final Monday counted as part of the month. More generally, that week only counts as part of a month is 3 or more of the days are in the month. Or 3 or more of its working days are part of the month.
As it is, I would sort with the calculated month and year as primary key and the YYYYMM code as secondary key.

Using T-SQL DATEADD and DATEDIFF function

I am trying to understand the code that someone wrong to implement the following requirements in calculating a date:
The derived JE_ACCTNG_DT is set equal to the last day of the prior month of the current year when the feed is created and sent.
For example, if the current year is 2013, the JE_ACCTNG_DT will be the following based on the quarter the feed is sent:
JE_ACCTNG_DT = 3/31/2013 for 1st quarter feed sent on 4/1/2013
JE_ACCTNG_DT = 6/30/2013 for 2nd quarter feed sent on 7/1/2013
JE_ACCTNG_DT = 9/30/2013 for the 3rd quarter feed sent on 10/1/2013
JE_ACCTNG_DT = 12/31/2013 for the 4th quarter feed sent on 1/1/2014
I see that someone implemented the code like this and it seems to give the right answer for the examples given in the requirements, if the datetime result is converted to a date data type (the time portion is dropped)
DECLARE #FEED_DT datetime
SET #FEED_DT = '4/1/2013'
select DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#FEED_DT),0))
SET #FEED_DT = '7/1/2013'
select DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#FEED_DT),0))
--2013-06-30 23:59:59.000
SET #FEED_DT = '10/1/2013'
select DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#FEED_DT),0))
--2013-09-30 23:59:59.000
SET #FEED_DT = '1/1/2014'
select DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#FEED_DT),0))
--2013-09-30 23:59:59.000
What is confusing me is that the DATEDIFF function accepts the following arguments: A datepart and two dates
DATEDIFF (datepart ,startdate ,enddate )
If you notice above, the code set the date part to "m" but passed zero for the 2nd parameter when a date is expected.
DECLARE #FEED_DT datetime = '4/1/2013'
DECLARE #DateAddResult as int
SET #DateAddResult = DATEDIFF(m,0,#FEED_DT)
select #DateAddResult
--1359
select DATEADD(s,-1,DATEADD(mm, #DateAddResult,0))
--2013-03-31 23:59:59.000
What is the code really doing? I would've expected an error. Did it coerce the 0 value to a date?
I'm thinking that what the requirements should really be is to calculate the last day of the previous quarter. This is how I would do it.
SELECT dateadd(day, -1, DATEADD(quarter,DATEDIFF(quarter,'1900/01/01', GETDATE()), '1900/01/01')) as FirstDayOfQuarter
Given this:
select DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#FEED_DT),0))
Let's take it one piece at a time...
DATEDIFF(m,0,#FEED_DT)
...calculates the number of whole integer months that has alapsed from date value zero (which is evaluated as 1-1-1900) to the date value started in the datetime field #FEED_DT. Let's say that this value is #ElapsedMonthsSinceZero and replace this variable into the original nested function, giving:
select DATEADD(s,-1,DATEADD(mm, #ElapsedMonthsSinceZero,0))
Now let's evaluate this part:
DATEADD(mm, #ElapsedMonthsSinceZero,0)
This adds the same number of months to the Date "zero", i.e. 1-1-1900, which brings you to the first day of the month of the date #FEED_DT. Essentially, what we did so far was calculate the whole number of months that have elapsed from date 0, truncating off any day beyond day 1 into the month. This gives us #FirstDayOfFEED_DT. Replacing that into the function gives us...
select DATEADD(s,-1,#FirstDayOfFEED_DT)
Now that we have the first day of the month of the date #FEED_DT, subtracting 1 gives us the last day of the previous month.
I guess I was just thrown off by the discrepancy where a value of 0 was passed instead of a date.
suppose you add some days in a new column and you hava a stored procedure of a parameter #date and you want to add current date to that parameter and get result in days.
DateAdd(days,#date,Getdate())