When a date is returned from our UTC server database Windows very helpfully changes the Date to a DateTime (say 23 June 2017 to 2017-06-23 00:00) and then makes an adjustment for the current Time Zone (to, say 2017-06-22 16:00)... For years we have used the code below to convert it back...
About four months ago users running Windows 7 (Windows 10 doesn't seem to be effected) that did not have 'Automatically adjust clock for Daylight Saving Time' checked, or live in a state like Arizona where that option is not available, noticed that reports where returning the date from the day before - our DB is returning the correct date but the conversion is no longer working correctly..
Any suggestions?
Thanks
Public Function LocalDateFormat(ByVal InputDate As Date) As String
Dim vDate As String = InputDate.ToString("d", System.Globalization.CultureInfo.InvariantCulture)
Dim LocalZone As TimeZone = TimeZone.CurrentTimeZone
Dim CurrentOffset As TimeSpan = LocalZone.GetUtcOffset(InputDate)
Dim DayLightSaving As Boolean = LocalZone.IsDaylightSavingTime(InputDate)
Dim CalculatedOffset As New DateTime(InputDate.Ticks, DateTimeKind.Local)
If CurrentOffset.CompareTo(TimeSpan.Zero) < 0 Then
CalculatedOffset -= LocalZone.GetUtcOffset(InputDate)
If DayLightSaving = True Then
CalculatedOffset = CalculatedOffset.AddHours(1)
End If
Else
CalculatedOffset += LocalZone.GetUtcOffset(InputDate)
If DayLightSaving = True Then
CalculatedOffset = CalculatedOffset.AddHours(-1)
End If
End If
InputDate = CalculatedOffset
Dim vCulture As String = System.Globalization.CultureInfo.CurrentCulture.ToString
Dim vReturnDate As String = ""
Select Case vCulture
Case "en-US"
vReturnDate = Format(InputDate, "MM/dd/yyyy")
Case "en-GB"
vReturnDate = Format(InputDate, "dd/MM/yyyy")
Case Else
vReturnDate = Format(InputDate, "dd/MM/yyyy")
End Select
Return vReturnDate
End Function
Your entire function can be re-written as:
Public Function LocalDateFormat(ByVal InputDate As Date) As String
Return InputDate.ToLocalTime().ToShortDateString()
End Function
With the code so small, you may want to rethink whether there's any benefit of having this in a function at all.
As far as why your existing function didn't work properly:
TimeZone.GetUtcOffset already factors in whether DST is in effect or not, though it has some problems with that that are described in the MSDN docs.
You shouldn't be using TimeZone any more at all. If you need to work with time zones, use TimeZoneInfo, or Noda Time. In this case, you don't need either of them, as you're just converting from UTC to local time, which is what DateTime.ToLocalTime does.
This code has some logic that appears to think one should subtract an hour for DST if the offset is positive. Sorry, but DST doesn't work that way - you always add an hour (unless you are in Lord Howe Island, Australia, where you add 30 minutes).
It also seems to think you should add the offset from UTC if its positive and subtract it if its negative. This is essentially an absolute value function, and also incorrect.
All the culture stuff is handled for you by formatting within the current culture. There's no need to do it by hand.
You say it works fine on Windows 10, but I'm sorry - this is bad code that will have errors wherever it is run. Just use the built-in APIs provided by the framework. Don't reinvent the wheel. :)
Related
So I know this question has been asked a couple of times over, but I believe my situation is a bit different (happy to be proven wrong of course!)
Here is the data flow: a user types a date in a date in a form. They then click a button. My macro then takes that date, runs it through the following function:
Function AddWeekDays(StartDate As Long, Days As Long) As Date
Dim i As Long
Dim d As Date
d = StartDate
i = 0
While i < Days
d = DateSerial(Year(d), Month(d), Day(d) + 1)
If Weekday(d, vbMonday) < 6 Then
i = i + 1
End If
Wend
AddWeekDays = d
End Function
Then it formats the date to change it from mm/dd/yyyy to dd/mm/yyyy in the following way:
Dim deadline As Date
Dim deadline_formatted As Date
Dim DateReceived As String
Dim DateConverted As Date
DateReceived = txt_DateReceived.Text
DateConverted = Format(DateReceived, "dd/mm/yyyy")
deadline = AddWeekDays(DateValue((CStr(DateConverted))), 9)
deadline_formatted = Format(deadline, "dd/mm/yyyy")
However, the deadline_formatted value is still coming out in the mm/dd/yyyy format.
As an example, when a user enters 01/05/2017 the program should return deadline_formatted = 12/05/2017, but it returns deadline_formatted = 05/12/2017
I have tried changing the variable type to string to see if that made a difference (it didn't), and have tried directly converting the deadline variable to the required format by using the following code:
deadline = Format(AddWeekDays(DateValue((CStr(DateConverted))), 9),"dd/mm/yyyy")
which still returns the incorrect format.
Can anybody out there suggest either:
How to fix the formatting issue to get the deadline_formatted into the format dd/mm/yyyy
OR
suggest a "workaround" to flip the "dd" with the "mm" (not ideal obviously, but if it works, it works!)
Thanks for any advice!
The best way to solve this issue is to actually change your computer's default date/time format to match the method used by the users. (In comments it is stated that the users are Australians but your company is US-based, so the default is probably currently set to be the USA's "mm/dd/yyyy" format.)
By ensuring that the computers date/time format is correct, it will allow you to process a date as a Date, and it can be stored in Excel cells as a Date, which then allows any of the Australian users to see it displayed as "dd/mm/yyyy" format while a USA-based colleague would see it displayed as "mm/dd/yyyy".
There is a financial risk to your company caused by forcing users to interact with software using an unfamiliar date system, as accidental entering of dates in the wrong formats can lead to major errors downstream, so it is in the company's best interest to allow you to change the settings to be relevant to the users.
It is not directly related to your problem, however I believe it might fix your issues. The manual calculation of adding week days might be the problem here.
There is a built in function to add workdays. You can include/exclude weekends/holidays. The following code replaces your above mentioned code.
Sub AddWeekDays()
Dim deadline As Date, deadline_formatted As Date
deadline = txt_DateReceived.Value
deadline_formatted = Format(Application.WorksheetFunction.WorkDay(deadline, 9), "dd/mm/yyyy")
'debug.print deadline_formatted
End Sub
the result to be String.
Dim deadline As Date
Dim deadline_formatted As String '<~~ change string
Dim DateReceived As String
Dim DateConverted As Date
txt_DateReceived = "01/05/2017"
DateReceived = txt_DateReceived
DateConverted = Format(DateReceived, "dd/mm/yyyy")
'deadline = AddWeekDays(DateValue((CStr(DateConverted))), 9)
deadline = AddWeekDays(CLng(DateConverted), 9) '<~~ change Long
deadline_formatted = Format(deadline, "dd/mm/yyyy")
I wouldn't bother about the regional settings. Instead, make sure that all dates are captured as Date() or Now() values (42123 or 42123.5555). On the output side such values can be presented in any format you wish.
To ensure that dates are entered correctly my preferred way is to use a date picker. If that can't be done make no rules for entering the date at all, working on the presumption that each user will know how to enter a date on his/her machine. Add a date check, like ISDATE(), which will catch some input errors but not all. You don't need to catch all. You only need to teach users how to input dates on their respective PCs.
With this line you don't need anything else.
Range("E:E").TextToColumns FieldInfo:=Array(0, xlDMYFormat)
'1.2.2019 -> 01/02/2019
'2,3,2019 -> 02/03/2019
'3-4-2019 -> 03/04/2019
'4*5*2019 -> 04/05/2019
'5_-6-*2019 -> 05/06/2019
'and so on
Of course you can change the format with
xlMDYFormat
I need to write a function which will convert 2 string parameters to dates and
return true/false after comparing the dates.
I have tried to implement this requirment by writing below mentioned code
Function CompareDate(StringDate1 As String, StringDate2 As String) As Boolean
Dim Date1 As Date
Dim Date2 As Date
Date1 = CDate(StringDate1)
Date2 = CDate(StringDate2)
If Date1 > Date2 Then
CompareDate = True
Else
CompareDate = False
End If
End Function
Dim test As Boolean
test = CompareDate("03-Mar-2016 02:43 PST", "01-Mar-2016 11:33 PST")
But I am getting "Type Mismatch" error at this line Date1 = CDate(StringDate1).
Any idea what needs to be modified to fix the issue.
Note : My function also needs to consider time and time zone while comparing dates.
try cdate(split(StringDate1," ")(0)) and (1) will give you the time to check also, when date1=date2? or you can replace the PST part
The parse problem is with the presence of a timezone. There are a lot of ways to manage this date time depending on what your endgame is but the most straightforward is just to drop off the timezone and let CDATE parse it. In the following code I assume that a) all of your times are in PST and b) there is a timezone on the end of every date. If these assumptions are wrong, modifications are necessary. If essential, managing multiple time zones could be done by converting everything to one time zone and, if desired, tracking what time zone it came from. But I don't know of any standard VBA module that knows the time zones so that would need an array from you.
Function CompareDate(strDate1 As String, strDate2 As String) As Boolean
Dim Date1 As Date
Dim Date2 As Date
Date1 = CDate(Left(strDate1, InStrRev(strDate1, " ")))
Date2 = CDate(Left(strDate2, InStrRev(strDate2, " ")))
CompareDate = (Date1 > Date2)
End Function
Maybe you could construct the timezone vs UTC offset array from the table here but I don't have time to process it right now. Pretty straightforward once you construct that array.
i think it would help if you formatted the date before doing CDate.
dateX = format(StringDate1, "mm/dd/yyyy")
You can also try somethign like this
dateX = cdate(format(stringdate1,"mm/dd/yyyy") - see that how works.
I don't have anythignt to test it with. If worst comes to worst, instead of passing the StringDate1 as string, pass it as Date and should be all set then. No need to do CDate in that case.
Function CompareDate(StringDate1 As Date, StringDate2 As Date) As Boolean
and Wherever you call this function CompareDate....do CDate then.. as such...
Call CompareDates(cdate(stringdate1), cdate(stringdate2))
The date portion gives you an integer value referring to the date, and the time portion gives you a fractional value for the time. So just add the two such as (using #Nathan_Sav code):
cdate(split(StringDate1," ")(0)) + cdate(split(StringDate1," ")(1))
Give this a try:
Function CompareDate(StringDate1 As String, StringDate2 As String) As Boolean
Dim Date1, Date2
Date1 = Split(StringDate1, " ")
Date2 = Split(StringDate2, " ")
CompareDate = IIf(DateDiff("d", Date1(0), Date2(0) > 0 And DateDiff("n", Date1(1), Date2(1)) > 0), True, False)
End Function
For the following code, I am receiving this error: "Conversion from String "10/22.2014 12:00:00 A10" to type 'Date' is not valid." Note - in comparison to the code, below - the error message's conversion of AM to A10.
What I Am Trying To Do
I am trying to give a user the ability to query a database for transactions that occurred, today. In order to do this, I need to specify the timestamp for the transaction, i.e. MM/dd/yyyy timestamp. I have consulted the MSDN documentation; however, am unable to get my code to function, properly.
By default, Date objects appear to drop their timestamp (this may be a result of the code I am working with, e.g. the casting); therefore, when specifying a date range of "today" (Today's Data - Today's Date), I am left with the default behaviour of the object: Today's Date 12:00:00 AM - Today's Date 12:00:00 AM. Regardless as to why this is happening, this is the problem with which I am left.
The objective: MM/dd/yyyy 12:00:00 AM - MM/dd/yy 11:59:59 PM (the day being the same).
My goal is to force a particular timestamp for a Date object (note this is not a DateTime object). By specifyiong the time range, I am able to grab all data from a database for today.
What I've Got
Below, is the code and, below that, the description (I've tried to condense the code as much as possible). You'll also note that this is only half of the code, i.e. the FromDate portion (presumably the format can be replicated for the ToDate:
Public Shared Function ToFromDate(ByVal aValue As Object) As Date
Dim Result As Date
Try
Result = CDate(aValue)
Catch ex As Exception
Result = Now
End Try
Result = CDate(String.Format("{0:MM/dd/yyyy 12:00:00 AM}", Result))
Return Result
End Function
The above code takes as an argument a DateTime, e.g. 10\10\2010 12:15:63 PM (and, for the purposes of my problem, the timestamp is included). Again, I am trying to take that Date with timestamp and change the time. Result receives aValue, casting the object as Date to "ensure" it is a date.
After Result receives a value (as when declaring a Date it is initialized to #12:00:00 AM#, interestingly enough), I attempt to CDate() a formatted String object. I have also attempted to remove the second cast, yet still receive the same error (the Result = CDate(String...) line throwing the error).
Question(s)
The main question: how do I appropriately cast a date to include a specified time?
The second, trivial question: what's with the # surrounding the Date? Is this a SQL 'thing'?
Here's my work around for the above not working, so far:
Dim Result As Date
Dim DateString As String = CStr(aValue)
Dim TestDateString As String = DateString.Substring(0, DateString.IndexOf("/"))
Dim NewDateString As String = ""
If TestDateString.Length = 2 Then
NewDateString = DateString.Substring(0, 10)
Else
NewDateString = DateString.Substring(0, 8)
End If
NewDateString = NewDateString + " 12:00:00 AM"
NewDateString = CObj("#" + NewDateString + "#")
Result = CDate(NewDateString)
Return Result
First, a date is the number of ticks since a point in time. Formatting it to a string and then converting to a date does nothing but spin the wheels of your CPU.
Because of culture issues, you should always create dates using NEW DATE(?,?,?,etc)
Second, the # is a vb6 way of creating dates (and MS Access) that is there for backwards compatibility.
Third, If you have a date (no time or as of midnight), and you want it to be as of say 6AM, you simply add the time you want. IE:
Dim d As Date = New Date(2014,1,1)
d = d.AddHours(6)
'Result: d = 1/1/2014 6:00:00 AM
Lastly, if you have a date and time and you want to remove the time, there are many ways but this is the one I like:
Dim d As Date = Now
d = New Date(d.Year, d.Month, d.Day)
Here is a peculiar result generated from this simple code:
Dim TodaysDate As Date = Date.Today ' Returns #8/12/2014#
Dim StringDate As String = TodaysDate ' Returns 12/08/2014
Dim AnotherStringDate As String = TodaysDate.ToString ' Returns 12/08/2014 00:00:00
What is going on? Why the string reversed the result, although I did not even used any conversion command?
Change Option Strict to On, then you get a compiler error instead of weird conversions. In general, a Date is not a String but it can be represented as one.
If i remember correctly VB uses helper methods with Option Strict Off to convert the type automatically which are sitting in the namespace Microsoft.VisualBasic.CompilerServices.
I've looked at the source-code (with ILSpy) and found a class StringType which has a method FromDate:
' Microsoft.VisualBasic.CompilerServices.StringType
Public Shared Function FromDate(Value As DateTime) As String
Dim ticks As Long = Value.TimeOfDay.Ticks
If ticks = Value.Ticks OrElse (Value.Year = 1899 AndAlso Value.Month = 12 AndAlso Value.Day = 30) Then
Return Value.ToString("T", Nothing)
End If
If ticks = 0L Then
Return Value.ToString("d", Nothing)
End If
Return Value.ToString("G", Nothing)
End Function
Since your Date was derived from Date.Today which is the current date without time, the code will be executed which starts with If ticks = 0L Then(ticks is from the time of the day).
This returns:
Value.ToString("d", Nothing)
which is the same as Date.ToShortDateString. The format is derived from your current culture since null is passed as CultureInfo. So obviously your current culture uses / as date separator.
You could also force this format by using:
Dim StringDate As String = TodaysDate.ToString("d")
StringDate = TodaysDate.ToShortdateString()
StringDate = TodaysDate.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture)
The last option also ensures that this format is used even if your current culture is different.
A date is stored internally as number. And a date/datetime variable doesn't really know or care what actual date it represents; it is just a number (decimal). The string representation is for our eyes only. When you convert a date/datetime variable to string type, it is converted in the format specified by our machine locale settings, unless we specifically tell it to convert it to some other format.
I am assuming that the date/time settings on your PC is in the dd/mm/yyyy (indian format) format rather than the default mm/dd/yyyy (US format).
So this is what is actually happening:
' You store today's date in a Date variable and it returns #8/12/2014# i.e. 12-Aug-2014
Dim TodaysDate As Date = Date.Today
' You convert it to string. Since you didn't sepecify the format, it picks up the fromat settings from your PC regional settings.
' Returns 12/08/2014 which means 12-Aug-2014 (in your locale)
' You didn't do .ToString, but that's implicit, unless you set OPTION STRICT ON, in which case it will force you to put .ToString
Dim StringDate As String = TodaysDate
' Same argument as above.
Dim AnotherStringDate As String = TodaysDate.ToString ' Returns 12/08/2014 00:00:00
As for the question why converting a date to a string changes the order? The answer is that it doesnt.
When you convert to string, your local culture format is used, as explained by Tim. That's not teh case with Date literals in VB.
Date literals - values between hashes such as #02/11/2014# - are always displayed in InvariantCulture or "m/d/y" pattern. This is likely because when you wish to create a date using a literal, you must specify the Day and Month in that format. See MSDN:
Date literals must be in the format #m/d/yyyy# or they are invalid.
So, it would make no sense to display the date to you in one pattern but require you to create them using a different pattern. This is just the way VB works (in part because the legacy functions have no notion of cultures beyond the local one in use). This is a VB-ism as seen in this VB vs C# date display in Visual Studio:
The culture is set to fr-FR.
But your strings ultimately represent the same date - they can make a roundtrip:
Dim TodaysDate As Date = Date.Today ' Returns #8/12/2014#
Dim AnotherStringDate As String = TodaysDate.ToString
Dim mydt As DateTime = DateTime.Parse(AnotherStringDate)
If mydt = TodaysDate Then
Console.Beep()
End If
It will beep because the same DateTime is created from the string even though it may display in the pattern of "d/m/y" for yur culture.
How can I compare the last characters using of month, day, and year or the completedate rather than using datetime, Example case is Textbox3 is greater than textbox9 because Textbox3 day = 26 and textbox9 day = 25.
Mycode:
'in my case I have 2 Textbox.
'Date format: hh.mm MM/DD/YYYY
'Textbox3= 02.02 03/26/2014
'TextBox9= 21.01 03/25/2014
If Val(Strings.Left(TextBox9.Text.Trim, 5)) < Val(Strings.Left(Textbox3 .Text.Trim, 5))Then
TimeError.ShowDialog()
End If
Really, the fastest, most reliable, and most effective way to do this is to parse the values into a DateTime. And taking a step back from there, the fastest, most effective way to get a date time from a textbox is to use a DateTimePicker control.
But if that's not an option, we can build on the code I gave you last time:
Dim temp1() As String = Textbox3.Text.Trim().Split(" .".ToCharArray())
Dim temp2() As String = Textbox9.Text.Trim().Split(" .".ToCharArray())
If DateTime.Parse(temp2(2)) < DateTime.Parse(temp1(2)) Then
TimeError.ShowDialog()
End If
I'll add that you probably want to also have code to compare the time values in the case where the date portions are equal. Given this as a starting point, you should be able to write that code on your own.
in vb.net you can comopare dates like this:
dim date1 as date = cdate(Textbox1.text)
dim date2 as date = Date.now()
if date1.date=date2.date then ....
and months like this
if date1.month=date2.month then ...