Conver Time from UTC to PST/PDT/CET and CEST with VBA - vba

I have a cloumn (Col. A) of the time and date for one years based on UTC time as the follow format (in fact i have 30 mins Intervals) from first of the year until end of the year:
01.01.2019 00:00
01.01.2019 00:30
.
.
.
31.12.2019 11:00
31.12.2019 11:30
Now I want to convert the time from UTC to CET when it's winter Time and CEST when is Summer time (I want to have the information in Col.B). I could do it with Excel Formula Easily but as my file is big enough I have to use VBA code.
Do have any suggetion How can I do it? My problem is on switching summer time to winter time via versa.

Regarding the time zone conversion itself, Wikipedia says (https://en.wikipedia.org/wiki/Central_European_Summer_Time#Period_of_observation):
Since 1996, European Summer Time has been observed between 01:00 UTC
(02:00 CET and 03:00 CEST) on the last Sunday of March, and 01:00 UTC
on the last Sunday of October
If I'm reading that correctly, a datetime can only be CET if it:
is before (<) the last Sunday of March at 01:00 UTC (i.e. when CEST begins)
or is on or after (>=) the last Sunday of October at 01:00 UTC (i.e. when CET begins)
It's not clear to me what your source data is like. (It looks like two datetimes delimited by a space, meaning each row actually contains a string. If this is true, you'll likely need to split the string into two strings along the delimiting space, then convert the two strings into two datetimes. This can probably be done with LEFT, RIGHT, DATEVALUE functions in VBA.)
To give you an example, I've filled range A1:A17520 of my worksheet "Sheet1" with datetimes at half-hour intervals (similar to what you've shown):
and I then use the code below to convert my values in column A (and put the converted values in column B of the same sheet):
Option Explicit
Private Sub ConvertUtcDatesInColumnAToCETorCEST()
Dim sourceSheet As Worksheet
Set sourceSheet = ThisWorkbook.Worksheets("Sheet1")
Dim lastSourceRow As Long
lastSourceRow = sourceSheet.Cells(sourceSheet.Rows.Count, "A").End(xlUp).Row
Dim datesFromSheet() As Variant
datesFromSheet = sourceSheet.Range("A1", sourceSheet.Cells(lastSourceRow, "A"))
Dim outputArray() As Date
ReDim outputArray(1 To UBound(datesFromSheet, 1), 1 To 1)
Dim rowIndex As Long
For rowIndex = LBound(datesFromSheet, 1) To UBound(datesFromSheet, 1)
outputArray(rowIndex, 1) = ConvertUtcDateTimeToCETorCEST(datesFromSheet(rowIndex, 1))
Next rowIndex
sourceSheet.Range("B1").Resize(UBound(outputArray, 1), UBound(outputArray, 2)).Value = outputArray
End Sub
Private Function LastSundayOfSomeMonthAndYear(ByVal someMonth As Long, ByVal someYear As Long) As Date
Dim lastDateInMonth As Date
lastDateInMonth = Application.EoMonth(DateSerial(someYear, someMonth, 1), 0)
Dim lastSundayInMonth As Date
LastSundayOfSomeMonthAndYear = lastDateInMonth - Weekday(lastDateInMonth, vbSunday) + 1
End Function
Private Function ConvertUtcDateTimeToCETorCEST(ByVal someUtcDateTime As Date) As Date
Dim startOfCESTinUTC As Date
startOfCESTinUTC = LastSundayOfSomeMonthAndYear(someMonth:=3, someYear:=Year(someUtcDateTime)) + TimeSerial(1, 0, 0)
Dim endOfCESTinUTC As Date
endOfCESTinUTC = LastSundayOfSomeMonthAndYear(someMonth:=10, someYear:=Year(someUtcDateTime)) + TimeSerial(1, 0, 0)
Dim isCET As Boolean
isCET = someUtcDateTime < startOfCESTinUTC Or someUtcDateTime >= endOfCESTinUTC ' Not sure if end is inclusive or exclusive
Dim hoursToAdd As Date
If isCET Then hoursToAdd = TimeSerial(2, 0, 0) Else hoursToAdd = TimeSerial(3, 0, 0)
ConvertUtcDateTimeToCETorCEST = someUtcDateTime + hoursToAdd
End Function
which gives me something like this:
Something to note is that this approach is quite inefficient for a few reasons:
The variables startOfCESTinUTC and endOfCESTinUTC are re-calculated every single time the function ConvertUtcDateTimeToCETorCEST is invoked (~17.5k times in my case). Since all of our datetimes are during the same year, the values of startOfCESTinUTC and endOfCESTinUTC will not change (so really the variables startOfCESTinUTC and endOfCESTinUTC should be determined just once, before the For loop).
If you know in advance and for sure that your data is sorted and is at half-hour intervals, you can do some maths and work out on which row CEST must begin on and end on. With that knowledge, you no longer need to check if the datetime is CET or CEST inside the For loop. If you use three For loops (loop 1: first row to row CET ends on, loop 2: row CEST starts on to row CEST ends on, loop 3: row CET starts on to last row), you can convert all values to CET (loop 1, loop 3) and CEST (loop 2) without actually checking what the value is. This makes the code more specialised (and faster), but also less reusable.
Modern computers are (relatively) fast. The code above converted 17.5k datetimes for me in <2 seconds on my machine. You may also want to check if the code handles the boundaries of CET and CEST correctly.

Related

How to make an if else syntax with datetime (time only)

Need your help guys, how can I make an if else with time? I just need to make the time and minutes to round off.
for example: I put 9:23 in a textbox
if textbox1.text >= 9:00 And textbox1.text <= 9:30
textbox1.text = 9:00
end if
It will round off to 9:00, I just need the syntax on how the time will round off. Thank you 3000
Try this.
Dim datetime As DateTime = Convert.ToDateTime(textbox1.text)
Dim timeininteger As Integer = CInt(datetime.ToString("mm"))
If timeininteger >= 0 And timeininteger <= 30 Then
textbox1.text = datetime.ToString("HH:" & "00")
End If
Ok, so you see, 9:30 isn't time. Not to a computer, at least. It's a string. So you'll need to format it until it's time.
I notice that you didn't specify AM or PM, so I'll assume that you're using a 24h time format. Here we go:
'Convert the text from the TextBox1 to DateTime
Dim time As DateTime = Convert.ToDateTime(textbox1.text)
'Here's the result:
MessageBox.Show(time.ToShortTimeString)
'You can get only one component of a DateTime:
MessageBox.Show(time.Hour.ToString)
'So, yeah, you can totally do math with time now! Look:
If time.Minute > 30 Then
MessageBox.Show((time.Hour + 1).ToString)
End If
Now that you master time, use your newfound powers for the greater good!

ISO week number in VBScript or VBA

How can I get the ISO week number of some date in VBScript or VBA?
First, note that:
It is important to report the week year along with the week number, as the date's year could be different.
Several Windows components contain a bug for some years' last Monday.
In my experience the simplest, clearest and most robust way to compute this is:
Sub WeekNum(someDate, isoWeekYear, isoWeekNumber, isoWeekDay)
Dim nearestThursday
isoWeekDay = WeekDay(someDate, vbMonday)
nearestThursday = DateAdd("d", 4 - Int(isoWeekDay), someDate)
isoWeekYear = Year(nearestThursday)
isoWeekNumber = Int((nearestThursday - DateSerial(isoWeekYear, 1, 1)) / 7) + 1
End Sub
This also returns the ISO day of the week, counting from 1 for Mondays.
Enter any date into A1 cell, then run following code...
Range("A2").FormulaR1C1 = "=INT((R1C-DATE(YEAR(R1C-WEEKDAY(R1C-1)+4),1,3)+WEEKDAY(DATE(YEAR(R1C-WEEKDAY(R1C-1)+4),1,3))+5)/7)"
You can get it via DatePart() VBA function:
Sub IsoWeek()
Dim isoWeekNum As Byte
Dim myDate As Date
myDate = DateValue("01-01-2022")
isoWeekNum = DatePart("ww", myDate, vbMonday, vbFirstFourDays)
If isoWeekNum > 52 Then ' Bug check (to avoid the bug with Mondays)
If Format(myDate + 7, "ww", vbMonday, vbFirstFourDays) = 2 Then isoWeekNum = 1
End If
Debug.Print isoWeekNum
End Sub

Get the last day of month

I want to get the last day of the month.
This is my code. If I want to debug it and compile it to the database it says it has an error in the syntax.
Public Function GetNowLast() As Date
Dim asdfh As Date
asdfh = DateValue("1." _
& IIf(Month(Date) + 1) > 12, Month(Date) + 1 - 12, Month(Date) + 1) _
&"."&IIf(Month(Date)+1)>12 , Year(Date)+1,Year(Date))
asdf = DateAdd("d", -1, asdf)
GetNowLast = asdf
End Function
GD Linuxman,
Let's focus on obtaining the result...:-)
See also: here
The comment by #Scott Craner is spot on ! Though strictly speaking there is no need to use the formatting. (Assuming you want to work with the 'Date' object)
To achieve what you want, setup the function as per below:
Function GetNowLast() as Date
dYear = Year(Now)
dMonth = Month(Now)
getDate = DateSerial(dYear, dMonth + 1, 0)
GetNowLast = getDate
End Function
You can call the function in your code as:
Sub findLastDayOfMonth()
lastDay = GetNowLast()
End Sub
Alternatively, and neater is likely:
Function GetNowLast(inputDate as Date) as Date
dYear = Year(inputDate)
dMonth = Month(inputDate)
getDate = DateSerial(dYear, dMonth + 1, 0)
GetNowLast = getDate
End Function
You can call that function and pass it an input parameter.
Sub findLastDayOfMonth()
lastDay = GetNowLast(Now()) 'Or any other date you would like to know the last day of the month of.
End Sub
See also this neat solution by #KekuSemau
Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim d1 As String
Set Rng = Range("A2")
d1 = Range("a2").Value2 'put a date in A2 Formatted as date(cell format)
Dim years
Dim months
Dim end_month
years = year(d1)
months = month(d1)
end_month = Day(DateSerial(years, months + 1, 1 - 1)) 'add one month and subtract one day from the first day of that month
MsgBox CStr(end_month), vbOKOnly, "Last day of the month"
End Sub
I realize this is a bit late into the conversation, but there is an already available worksheet function that gives the end of month date, EoMonth().
Pasting into the Immediate Window:
?Format(CDate(WorksheetFunction.EoMonth(Date, 0)), "dd")
Will return the last day of the month based on current date.
As a UDF, it makes sense to give it a default Argument:
Function LastDay(Optional DateUsed As Date) As String
If DateUsed = Null Then DateUsed = Date
LastDay = Format(CDate(WorksheetFunction.EoMonth(DateUsed, 0)), "dd")
Debug.Print LastDay
End Function
If you feed it Arguments, be sure that they are Date Literals (i.e. Enclosed with #s)
LastDay(#3/10#)
Result: 31
LastDay #2/11/2012#
Result: 29 '(A leap Year)
Note the output Data Type is String (not Date) and that the format of the date can be adjusted as needed (Ex: "mm/dd/yyyy" instead of "dd").
If the Date Data Type is needed, use:
Function LastDay(Optional DateUsed As Date) As Date
If DateUsed = 0 Then DateUsed = Date
LastDay = WorksheetFunction.EoMonth(DateUsed, 0)
Debug.Print CDate(LastDay)
End Function
I hope that helps someone.
In short, a great and straightforward approach is to find the first day of the following month and then move backward one day.
Make yourself a little function that does something like this:
Obtain the month and year in question (the one where you want the last day)
Use DateSerial to combine the month and the year, along with the day "1" to get the first day of the month in question.
Use DateAdd to add one month. This will get you the first day of the next month (which is one day after the date you really want).
Use DateAdd again to subtract (move back) one day. This will give you the last day of the month where you started.
Function eom(ByVal input_date As Date) As Date
' take the first day of the month from the input date, add one month,
' then back up one day
eom = DateAdd("d", -1, DateAdd("m", 1, DateSerial(Year(input_date), Month(input_date), 1)))
End Function
In Access VBA, you can call Excel's EOMonth worksheet function (or almost any of Excel's worksheet methods) is by binding to an Excel application object and a WorksheetFunction object, which can be accomplished in a few ways.
Calling Excel functions with an Late Bound object
The shortest method from Access VBA is with a single line of code using a late-bound object. This example returns the date of the last day of the current month:
MsgBox CreateObject("Excel.Application").WorksheetFunction.EOMonth(Now(), 0)
A more verbose method, as a function:
Function eoMonth_LateBound(dt As Date) As Date
Dim xl As Object
Set xl = CreateObject("Excel.Application")
eoMonth_LateBound = xl.WorksheetFunction.eomonth(dt, 0)
Set xl = Nothing
End Function
An issue with late-bound references is that VBA takes a second to bind the object each time the function is called. This can be avoided by using early binding.
Calling Excel functions with an Early Bound object
If the function is to be used repeatedly, it's more efficient to go with Early Binding and retain the object between calls, for example:
Go Tools > References and add a reference to "Microsoft Excel x.xx Object Library" (use whatever the newest version number is that you have installed).
Add this code in a new module:
Option Compare Database
Option Explicit
Dim xl As Excel.Application
Function eoMonth_EarlyBound(dt As Date) As Date
If xl Is Nothing Then Set xl = New Excel.Application
eoMonth_EarlyBound = xl.WorksheetFunction.eomonth(dt, 0)
End Function
Sub demo()
MsgBox eoMonth_EarlyBound(Now())
MsgBox eoMonth_EarlyBound("4/20/2001")
End Sub
Creating a WorksheetFunction object
If Excel's worksheet functions are to be used lots throughout your code, you could even create a WorksheetFunction object to simplify the calls. For example, this could be a simple way to join multiple strings with TEXTJOIN, or get a response from an API with WEBSERVICE:
Sub Examples()
'requires reference: "Microsoft Excel x.xx Object Library"
Dim xl As Excel.Application, wsf As Excel.WorksheetFunction
Set xl = New Excel.Application
Set wsf = xl.WorksheetFunction
'use EOMONTH to return last date of current month
Debug.Print CDate(wsf.eomonth(Now(), 0))
'use WEBSERVICE return your current IP address from a free JSON API
Debug.Print wsf.WebService("https://api.ipify.org")
'use TEXTJOIN to implode a bunch of values
Debug.Print wsf.TextJoin(" & ", True, "join", "this", , "and", , "that", "too")
'always tidy up your mess when finished playing with objects!
Set wsf = Nothing
Set xl = Nothing
End Sub
Note that these functions may require Excel 2016+ or Excel 365 (aka: Object Library 16.0+.)
Another method I used was:
nMonth = 2
nYear = 2021
lastDayOfMonth = DateSerial(nYear, nMonth + 1, 0)

Excel VBA yearfrac with mins and maxes

I'm trying to find the number of weeks between two dates in Excel VBA (with some min/max functionality in between), was getting Type Mismatch error (Run-time error '13') for the following line:
WeeksWorked = Application.WorksheetFunction.RoundDown _
(52 * Application.WorksheetFunction.YearFrac _
(Application.WorksheetFunction.Max(DOH, DateValue("Jan 1, 2012")), _
DateValue("Dec 31, 2012")), 0)
Anyone have any direction as to what I'm doing wrong, it would be greatly appreciated!
Not sure why do you need to use this in VBA, here is something you can try.
In Excel:
Assuming Start Date is in A1, End Date is in A2, then A3,
=(NETWORKINGDAYS(A1,A2))/5
Now that is in the perspective of business days, thus giving 5 day week. If you need 7 day week with regular days,
=WEEKNUM(A3)-WEEKNUM(A2)
The function WEEKNUM() in the Analysis Toolpack addin calculates the correct week number for a given date, if you are in the U.S. The user defined function below will calculate the correct week number depending on the national language settings on your computer.
If you still need to use VBA try this: (as Tim pointed out DateDiff pretty handy.) Or you can even use Evaluate to trigger WEEKNUM.
Option Explicit
Function numWeeks(startDate As Date, endDate As Date)
numWeeks = DateDiff("ww", startDate, endDate)
End Function
Using Evaluate on WEEKNUM:
Function numWeeks(startDate As Range, endDate As Range)
Dim s As Integer, t As Integer
s = Application.Evaluate("=WEEKNUM(" & startDate.Address & ",1)")
t = Application.Evaluate("=WEEKNUM(" & endDate.Address & ",1)")
numWeeks = t - s
End Function
Reference for Excel VBA DataTime Functions
As suggested in the comments you could just do:
debug.print DateDiff("ww", DateValue("Jan 1, 2012"), DateValue("Dec 31, 2012"))
If for some reason you wanted to roll your own you could truncate the quotient of:
| day1 - day2 |
---------------
7
Example code:
Sub test_numWeeks_fn()
Call numWeeks(DateValue("Jan 1, 2012"), DateValue("Dec 31, 2012"))
End Sub
Function numWeeks(d1 As Date, d2 As Date)
Dim numDays As Long
numDays = Abs(d1 - d2)
numWeeks = Int(numDays / 7)
Debug.Print numWeeks
End Function
Result:
52
Try below code :
Sub example()
Dim dob As Date
dob = #7/31/1986#
Dim todaydt As Date
todaydt = Date
Dim numWeek As Long
numWeek = DateDiff("ww", dob, todaydt) ' Difference in weeks
End Sub

Dates in For Loop in vb.net

In vb.net I have two data values as shown below:
Dim startp as datetime
Dim endp as datetime
I have a function called ProcessData(soemdate) which processes dataporting.
In VB.net is there a way I can do something like
For each day between startp and endp
ProcessData(soemdate)
Next
Thanks
Here is another way to do this.
Dim startP As DateTime = New DateTime(2009, 1, 1)
Dim endP As DateTime = New DateTime(2009, 2, 1)
Dim CurrD As DateTime = startP
While (CurrD <= endP)
ProcessData(CurrD)
Console.WriteLine(CurrD.ToShortDateString)
CurrD = CurrD.AddDays(1)
End While
For Each Day As DateTime in Enumerable.Range(0, (endp - startp).Days) _
.Select(Function(i) startp.AddDays(i))
ProcessData(Day)
Next Day
Adding to Joel Coehoorn's answer which I personally think should be the accepted answer as I always try to avoid While loops no matter how safe they may appear. For...Each is a much safer approach although the enumerable isn't very pretty in-line. You can however move it to a function to keep things more readable, plus you can re-use as needed.
For Each Day As DateTime In DateRange(StartDate, EndDate)
ProcessData(Day)
Console.WriteLine(Day.ToShortDateString)
Next
Public Shared Function DateRange(Start As DateTime, Thru As DateTime) As IEnumerable(Of Date)
Return Enumerable.Range(0, (Thru.Date - Start.Date).Days + 1).Select(Function(i) Start.AddDays(i))
End Function
I also added 1 to Enumerable range since as Joel had it, it wouldn't return the end date and in my situation I needed it to return all dates in the range including the start and end days.
Enumerable.Range is a sort of loop in itself that adds i days to the startdate advancing i with each call from in this case 0 to the difference between start and end days + 1. So the first time it's called you get the result of Start.AddDays(0), next you'll get Start.AddDays(1) and so on until the range is complete.
You can easily loop through each day if you convert your dates to OLE Automation Date OADate where the left portion represents the day and the right portion represents the time.
For example #06/19/2018#.ToOADate converts to 43270.0
For loopDate As Double = #06/19/2018#.ToOADate To #07/01/2018#.ToOADate
Dim thisDate As Date = DateTime.FromOADate(loopDate)
' Do your stuff here e.g. ProcessData(thisDate)
Next
Yes, you can use an accumulator date:
Dim Accumulator as DateTime
Accumulator = startp
While (Accumulator <= endp)
Accumulator = Accumulator.AddDays(1)
End While
Not tested, and I'm a C# programmer, so be easy if my syntax is wrong.
For those that come looking later, I had to add a +1 to the Range endpoint to get this to work for when the start and end were the same. Here is the code I used.
For Each Day As DateTime in Enumerable.Range(0, (endp - startp).Days + 1) .Select(Function(i) startp.AddDays(i))
'Do work here
Next Day
Set a calendar table with all dates and query values from there.
SQL:
Select Date as MyDate from tblCalendar Where Date >= StartDt And Date <= EndDate
.NET:
While Reader.read
process(MyDate)
End While