I'm surprised that I can't find any existing solutions to this online but I just need an SQL function that returns an ISO standard week number (i.e. the start of week 1 is always the first Monday of the year).
None of the DatePart function options consistently return the correct result. I had thought the option "vbFirstFourDays - Start with the first week that has at least four days in the new year." but testing it for today (12th Jan) returns week 3, not week 2 (my expression is DatePart("ww",Now(),2) )
This year ISO week 1 starts on 4th Jan, next Year the 2nd Jan and last year it was the 5th of Jan.
Many thanks
The DatePart function does indeed calculate the ISO-8601 week number almost* correctly when it uses vbMonday for the firstdayofweek argument and vbFirstFourDays for the firstweekofyear argument, e.g.,
DatePart("ww", Date(), vbMonday, vbFirstFourDays)
or, when used directly in an Access query
DatePart("ww", Date(), 2, 2)
* Note that the bug documented here has apparently never been fixed, so the following Mondays in the 21st century are reported as being in week 53 when according to ISO-8601 they should be in week 1 of the following year:
2003-12-29
2007-12-31
2019-12-30
2031-12-29
2035-12-31
2047-12-30
2059-12-29
2063-12-31
2075-12-30
2087-12-29
2091-12-31
Just to follow on from Gord Thompson, Microsoft have provided a workaround which returns the correct ISO week in all circumstances. It simply changes week 53 to week 1. Simply place this in a VBA Module and then you'll be able to use the function in Excel/Access.
Public Function ISOWeek(MyDate As Date) As Integer
ISOWeek = Format(MyDate, "ww", vbMonday, vbFirstFourDays)
If ISOWeek > 52 Then
If Format(MyDate + 7, "ww", vbMonday, vbFirstFourDays) = 2 Then ISOWeek = 1
End If
End Function
There are more problems with the ISO weeknumbers than just the 2 week digits returned by DatePart.
January 1st on a Friday should be in week 53 of the previous year
December 31 on a Monday should be in week 1 of the next year
A lot of businesses in Europe use a four digit number to show year and week together. In those cases:
Friday #01/01/2021# should be shown as week "2153"
Monday #12/31/2018# should be shown as week "1901"
I have created 2 wrappers around the DatePart function to add the correct year and show the right weeknumber in case DatePart is in error.
Public Function ISO_YYWW(dat As Date) As String ' ISO 8601 / NEN 2772
Dim ww As Integer
ww = CInt(ISO_WW(dat))
If ww >= 52 And Val(Format(dat, "ww")) <= 2 Then
ISO_YYWW = Format(((Year(dat) - 1) Mod 100), "00") & Format(ww, "00")
ElseIf ww = 1 And Month(dat) = 12 Then
ISO_YYWW = Format(((Year(dat) + 1) Mod 100), "00") & Format(ww, "00")
Else
ISO_YYWW = Format(dat, "YY") & Format(ww, "00")
End If
End Function
Public Function ISO_WW(dat As Date) As String ' ISO 8601 / NEN 2772
If Format(dat, "DD-MM") = "31-12" And DatePart("W", dat, vbMonday, vbFirstFourDays) = 1 Then ' 31-dec on a monday
ISO_WW = "01"
ElseIf Format(dat, "DD-MM") = "30-12" And DatePart("W", dat, vbMonday, vbFirstFourDays) = 1 Then ' 30-dec on a monday
ISO_WW = "01"
ElseIf Format(dat, "DD-MM") = "29-12" And DatePart("W", dat, vbMonday, vbFirstFourDays) = 1 Then ' 29-dec on a monday
ISO_WW = "01"
Else
ISO_WW = Format(DatePart("ww", dat, 2, 2), "00")
End If
End Function
I have tested from 1970 to 2021 and found no problems using this code
Sub test()
Dim dat As Date, yy As Integer, f As Integer
f = FreeFile
Open CodeDb.Name & ".txt" For Output As #f
Print #f, "date", "day", "ISO_WW / DOW", "ISO_YYWW"
For yy = 1970 To 2021
For dat = CDate("31/12/" & yy) - 7 To CDate("31/12/" & yy) + 7
If ISO_WW(dat) >= 52 Or ISO_WW(dat) = "53" Or Val(ISO_WW(dat)) = 1 Then
Print #f, Format(dat, "yyyy-mm-dd"), Format(dat, "DDD"), ISO_WW(dat) & " / " & DatePart("W", dat, vbMonday, vbFirstFourDays), ISO_YYWW(dat)
End If
Next dat
Print #f, ""
Next yy
Close #f
End Sub
Related
In Microsoft Access I am using the DateDiff formula in a text box on a form to calculate a person's age. The user types the date of birth and another text box called “Age” calculates and displays the age based on that date of birth and today’s date. But for some reason the age is incorrect. Here is the formula I am using to determining age.
=DateDiff("yyyy",[txtDoB1],Date()) 'today is 2/12/2021
=DateDiff("yyyy", #5/24/1979#, #2/12/2021#) 'this has the dates manually typed in
The DateDiff formula returns 42 as the age. That is not correct. It should be 41. Why is the DateDiff formula resulting in an incorrect age? What am I doing wrong?
The following solution using DateDiff is from Microsoft. I have used it and it works well. The link is: https://learn.microsoft.com/en-us/office/vba/access/Concepts/Date-Time/calculate-age
Function Age(varBirthDate As Variant) As Integer
Dim varAge As Variant
If IsNull(varBirthDate) Then Age = 0: Exit Function
varAge = DateDiff("yyyy", varBirthDate, Now)
If Date < DateSerial(Year(Now), Month(varBirthDate), _
Day(varBirthDate)) Then
varAge = varAge - 1
End If
Age = CInt(varAge)
End Function
The DateDiff formula does not calculate the difference between years very well. If you use "yyyy" in the DateDiff formula then it only uses the year portion of the 2 dates provided in the formula to calculate the difference in years. This leads to undesirable results. In your example the DateDiff formula would take 2021 from 2/12/2021 and subtract it by 1979 from 5/24/1979. Or in other words, 2021 - 1979 = 42.
Instead try using the below formula.
=Int((Date()-[txtDoB1])/365.25) '365.25 compensates for leap years
=Int((#2/11/2021#-#5/24/1979#)/365.25)
DateDiff returns the difference in calendar years.
For calculating age correct for any dates, DateAdd must be used:
' Returns the difference in full years from DateOfBirth to current date,
' optionally to another date.
' Returns zero if AnotherDate is earlier than DateOfBirth.
'
' Calculates correctly for:
' leap years
' dates of 29. February
' date/time values with embedded time values
' any date/time value of data type Date
'
' DateAdd() is used for check for month end of February as it correctly
' returns Feb. 28th when adding a count of years to dates of Feb. 29th
' when the resulting year is a common year.
'
' 2015-11-24. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function Age( _
ByVal DateOfBirth As Date, _
Optional ByVal AnotherDate As Variant) _
As Integer
Dim ThisDate As Date
Dim Years As Integer
If IsDate(AnotherDate) Then
ThisDate = CDate(AnotherDate)
Else
ThisDate = Date
End If
' Find difference in calendar years.
Years = DateDiff("yyyy", DateOfBirth, ThisDate)
If Years > 0 Then
' Decrease by 1 if current date is earlier than birthday of current year
' using DateDiff to ignore a time portion of DateOfBirth.
If DateDiff("d", ThisDate, DateAdd("yyyy", Years, DateOfBirth)) > 0 Then
Years = Years - 1
End If
ElseIf Years < 0 Then
Years = 0
End If
Age = Years
End Function
I would like to get weeknumbers in ms access according to:
https://www.kalender-365.nl/kalender-2021.html
My expression:
test: DatePart("ww",#01/01/2021#,2,2)
This returns 1 instead of week 53. What can I do to return 53?
syntax:
DatePart(datepart, date, firstdayofweek, firstweekofyear)
2 = Monday (firstdayofweek)
2 = Use the first week in the year that has at least 4 days (firstdayofyear)
You may need the matching ISO year as well:
Public Const MaxWeekValue As Integer = 53
Public Const MinWeekValue As Integer = 1
Public Const MaxMonthValue As Integer = 12
Public Const MinMonthValue As Integer = 1
' Returns the ISO 8601 week of a date.
' The related ISO year is returned by ref.
'
' 2016-01-06. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function Week( _
ByVal Date1 As Date, _
Optional ByRef IsoYear As Integer) _
As Integer
Dim Month As Integer
Dim Interval As String
Dim Result As Integer
Interval = "ww"
Month = VBA.Month(Date1)
' Initially, set the ISO year to the calendar year.
IsoYear = VBA.Year(Date1)
Result = DatePart(Interval, Date1, vbMonday, vbFirstFourDays)
If Result = MaxWeekValue Then
If DatePart(Interval, DateAdd(Interval, 1, Date1), vbMonday, vbFirstFourDays) = MinWeekValue Then
' OK. The next week is the first week of the following year.
Else
' This is really the first week of the next ISO year.
' Correct for DatePart bug.
Result = MinWeekValue
End If
End If
' Adjust year where week number belongs to next or previous year.
If Month = MinMonthValue Then
If Result >= MaxWeekValue - 1 Then
' This is an early date of January belonging to the last week of the previous ISO year.
IsoYear = IsoYear - 1
End If
ElseIf Month = MaxMonthValue Then
If Result = MinWeekValue Then
' This is a late date of December belonging to the first week of the next ISO year.
IsoYear = IsoYear + 1
End If
End If
' IsoYear is returned by reference.
Week = Result
End Function
I think i have a simple problem, i just cant figure it out. I have a table with ID, Date and Value
I want to select the NEWEST value based on criteria of week and year. Meaning i only have the year and week to find the newest value.
if you do the following
SELECT TOP 1 Value from tbl WHERE year(Date)<=year and format(date,"WW")<= weeknumber
you get a problem. because if the year is 2020 and the week is 30. then if there is a value from the 31/12/2019 it wont return it because format(date,"WW") is greater than the week.
Example: dateformat=dd/mm/yyyy
ID Date Value
1 15/01/2019 15
2 31/12/2019 18
3 15/04/2020 19
if the week is 5 and the year is 2020
the result of the sql should be 18 since that is the newest value before the week and year. But the query i wrote above returns 15, which makes sence because of the week of 31/12/2019>5 and therefore wont be returned.
But how do i do this correctly?
As this probably is ISO 8601 week numbering, the year is not the calendar year but the ISO 8601 year, which native VBA knows nothing about, thus a custom function is needed:
' First day of the week.
WeekStart = DateYearWeek(5, 2020, vbMonday)
' WeekStart -> 2020-01-27
The function is not that convoluted:
' Returns the date of Monday for the ISO 8601 week of IsoYear and Week.
' Optionally, returns the date of any other weekday of that week.
'
' 2017-05-03. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function DateYearWeek( _
ByVal IsoWeek As Integer, _
Optional ByVal IsoYear As Integer, _
Optional ByVal DayOfWeek As VbDayOfWeek = VbDayOfWeek.vbMonday) _
As Date
Dim WeekDate As Date
Dim ResultDate As Date
If IsoYear = 0 Then
IsoYear = Year(Date)
End If
' Validate parameters.
If Not IsWeekday(DayOfWeek) Then
' Don't accept invalid values for DayOfWeek.
Err.Raise DtError.dtInvalidProcedureCallOrArgument
Exit Function
End If
If Not IsWeek(IsoWeek, IsoYear) Then
' A valid week number must be passed.
Err.Raise DtError.dtInvalidProcedureCallOrArgument
Exit Function
End If
WeekDate = DateAdd(IntervalSetting(dtWeek), IsoWeek - 1, DateFirstWeekYear(IsoYear))
ResultDate = DateThisWeekPrimo(WeekDate, DayOfWeek)
DateYearWeek = ResultDate
End Function
but - as you can see - it calls some helper functions, which again call other functions, which will be too much to post here.
I can upload it somewhere, if you feel this will provide a solution for you.
There is no simple work-around. On the other hand, once held in a module, the code is simple to implement - as you can see.
I have an Access form that requires users to enter a [Start Date] and [End Date]. Before saving the record, I would like to validate these fields to ensure that the period entered does not cross the 30th of June in any given year.
In my head I am thinking that the procedure would check the [Start Date] first, if it is between January 1 and June 30, then [End Date] must be less than or equal to June 30 of that year. If [Start Date] is between July 1 and December 31, then the [End Date] must be less than or equal to June 30 of the following year.
I'm am not sure how to express this in VBA so any assistance would be greatly appreciated.
I think it may be something like this:
Public Function IsDateValid(dtStart As Date, dtEnd As Date) As Boolean
Dim dtDeadline As Date
dtDeadline = DateSerial(Year(dtStart), 6, 30)
If dtStart > dtDeadline Then
dtDeadline = DateSerial(Year(dtStart) + 1, 6, 30)
End If
IsDateValid = dtEnd < dtDeadline
End Function
You could follow this method:
Dim StartDate As Date
Dim EndDate As Date
Dim CheckDate As Date
Dim OK As Boolean
' Pass values of StartDate and EndDate here.
' StartDate = ?
' EndDate = ?
' Find June 30th of the current or the next year of StartDate.
CheckDate = DateSerial(Year(StartDate) + (Month(StartDate) - 1) \ 6, 6, 30)
If DateDiff("d", EndDate, CheckDate) >= 0 Then
' EndDate falls before or on CheckDate.
OK = True
End If
' Return value of OK.
Please note the the backslash performs an integer division.
How can I find the age of a person from a query on a given date from the date of birth.
I want to run a query that will return the age of everyone in a table, on any date of the following year calculated from their date of birth.
I want to return the age in years they will be, say on the 01/09(September) of the next year.
Todays date is 02/09/2015
If the date of birth is 09/10/2006 the answer is 9
If the date of birth is 28/01/2006 the answer is 10
Any ideas?? Anyone?
UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(date_in_table) gives you the difference in seconds. Now just convert to years.
You can use a Expression in the Control source of a textbox on a form
Current Date: 01/01/2015
Table1:
|Birth_Date |
|01/01/1995 | = 20
|01/01/1994 | = 21
Expression:
=DateDiff("yyyy",[BirthDate],Date())
The age will update every time you open the form
or
VBA(will need a button)
Sub Bottun1_Click()
Me.MyTextBox = DateDiff("yyyy", Me.BirthDate, Date)
End Sub
DateDiff only returns the difference in calendar years.
For a true age as of today, use a function like this:
Public Function AgeSimple( _
ByVal datDateOfBirth As Date) _
As Integer
' Returns the difference in full years from datDateOfBirth to current date.
'
' Calculates correctly for:
' leap years
' dates of 29. February
' date/time values with embedded time values
'
' DateAdd() is used for check for month end of February as it correctly
' returns Feb. 28. when adding a count of years to dates of Feb. 29.
' when the resulting year is a common year.
' After an idea of Markus G. Fischer.
'
' 2007-06-26. Cactus Data ApS, CPH.
Dim datToday As Date
Dim intAge As Integer
Dim intYears As Integer
datToday = Date
' Find difference in calendar years.
intYears = DateDiff("yyyy", datDateOfBirth, datToday)
If intYears > 0 Then
' Decrease by 1 if current date is earlier than birthday of current year
' using DateDiff to ignore a time portion of datDateOfBirth.
intAge = intYears - Abs(DateDiff("d", datToday, DateAdd("yyyy", intYears, datDateOfBirth)) > 0)
End If
AgeSimple = intAge
End Function
For any combo of dates, use this function:
Public Function Years( _
ByVal datDate1 As Date, _
ByVal datDate2 As Date, _
Optional ByVal booLinear As Boolean) _
As Integer
' Returns the difference in full years between datDate1 and datDate2.
'
' Calculates correctly for:
' negative differences
' leap years
' dates of 29. February
' date/time values with embedded time values
' negative date/time values (prior to 1899-12-29)
'
' Optionally returns negative counts rounded down to provide a
' linear sequence of year counts.
' For a given datDate1, if datDate2 is decreased step wise one year from
' returning a positive count to returning a negative count, one or two
' occurrences of count zero will be returned.
' If booLinear is False, the sequence will be:
' 3, 2, 1, 0, 0, -1, -2
' If booLinear is True, the sequence will be:
' 3, 2, 1, 0, -1, -2, -3
'
' If booLinear is False, reversing datDate1 and datDate2 will return
' results of same absolute Value, only the sign will change.
' This behaviour mimics that of Fix().
' If booLinear is True, reversing datDate1 and datDate2 will return
' results where the negative count is offset by -1.
' This behaviour mimics that of Int().
' DateAdd() is used for check for month end of February as it correctly
' returns Feb. 28. when adding a count of years to dates of Feb. 29.
' when the resulting year is a common year.
'
' 2000-11-03. Cactus Data ApS, CPH.
' 2000-12-16. Leap year correction modified to be symmetrical.
' Calculation of intDaysDiff simplified.
' Renamed from YearsDiff() to Years().
' 2000-12-18. Introduced cbytMonthDaysMax.
' 2007-06-22. Version 2. Complete rewrite.
' Check for month end of February performed with DateAdd()
' after idea of Markus G. Fischer.
Dim intDiff As Integer
Dim intSign As Integer
Dim intYears As Integer
' Find difference in calendar years.
intYears = DateDiff("yyyy", datDate1, datDate2)
' For positive resp. negative intervals, check if the second date
' falls before, on, or after the crossing date for a full 12 months period
' while at the same time correcting for February 29. of leap years.
If DateDiff("d", datDate1, datDate2) > 0 Then
intSign = Sgn(DateDiff("d", DateAdd("yyyy", intYears, datDate1), datDate2))
intDiff = Abs(intSign < 0)
Else
intSign = Sgn(DateDiff("d", DateAdd("yyyy", -intYears, datDate2), datDate1))
If intSign <> 0 Then
' Offset negative count of years to continuous sequence if requested.
intDiff = Abs(booLinear)
End If
intDiff = intDiff - Abs(intSign < 0)
End If
' Return count of years as count of full 12 months periods.
Years = intYears - intDiff
End Function