Thanks! I appreciate that code and I learned a few clever techniques from it!
At first I thought I could couple this with “ToISO8601Date” and make my own date object but then I realized it still needs all of the logic to convert between totalsecs and all of the year, month, day etc fields while accommodating various size months, leap years and other special cases… ugh…
FWIW Previously I benchmarked Xojo.date vs RB.date and the later is faster for my purposes…
Public Function ToISO8601Date(d as xojo.Core.Date) as text
// we'll form these as ISO 8601 format
// <date>T<time><TZ>
// where
// <dates> are stated as YYYY-MM-DD
// <times> are stated as HH:MM:SS.NNNN
// HH is hours (NOT 24 hour format)
// MM is minutes
// SS is seconds
// NNNN is nanoseconds
// <TZ> is formed as
// Z if the TZ is GMT (offset = 0)
// -hhmm if the tz is GMT- (offset < 0)
// +hhmm if the tz is GMT+ (offset > 0)
// hh is hours before GMT
// mm is minutes before GMT
// so we can handle offsets that are not whole or half hours
dim pieces() as Text
pieces.append d.Year.ToText(Xojo.Core.Locale.Raw, "0000")
pieces.append "-"
pieces.append d.Month.ToText(Xojo.Core.Locale.Raw, "00")
pieces.append "-"
pieces.append d.Day.ToText(Xojo.Core.Locale.Raw, "00")
pieces.append "T"
pieces.append d.Hour.ToText(Xojo.Core.Locale.Raw, "00")
pieces.append ":"
pieces.append d.Minute.ToText(Xojo.Core.Locale.Raw, "00")
pieces.append ":"
pieces.append d.Second.ToText(Xojo.Core.Locale.Raw, "00")
pieces.append "."
pieces.append d.Nanosecond.ToText(Xojo.Core.Locale.Raw, "0000")
dim hoursBeforeGMT as integer
dim secondsBeforeGMT as integer
hoursBeforeGMT = d.TimeZone.SecondsFromGMT \\ (3600)
secondsBeforeGMT = (d.TimeZone.SecondsFromGMT - (hoursBeforeGMT * (3600))) \\ 60
hoursBeforeGMT = abs(hoursBeforeGMT)
secondsBeforeGMT = abs(secondsBeforeGMT)
if d.TimeZone.SecondsFromGMT = 0 then
pieces.append "Z" // zulu time or gmt = 0
else
dim prefix as text = "+"
if d.TimeZone.SecondsFromGMT < 0 then
prefix = "-"
end if
pieces.append prefix
pieces.append hoursBeforeGMT.ToText(Xojo.Core.Locale.Raw, "00")
pieces.append secondsBeforeGMT.ToText(Xojo.Core.Locale.Raw, "00")
end if
return Text.Join(pieces,"")
End Function
Classic date also has caveats about the order you do things (like setting year then month then day) otherwise you can get weird rollovers if you do it in other orders
And there are a couple other gotcha’s
created a new desktop project
dropped a listbox on the window
took your code and stuffed it in a method
Public Function convertdate(s as string) as date
//Syslog date format: 2015-02-07T07:47:04.872175+00:00
dim DateStr as string = ReplaceB (s, "T", " ")
dim d as new date
d.GMTOffset = 0.0
d.SQLDateTime = DateStr
return d
End Function
the in the listbox open event I put
const kLimit = 10
const iLimit = 10000
for k as integer = 1 to kLimit
dim oldstart, newstart, oldend, newend as double
oldstart = Microseconds
for i as integer = 1 to iLimit
dim d as date = ConvertDate("2015-02-07T07:47:04.872175")
next
oldend = Microseconds
newstart = Microseconds
for i as integer = 1 to iLimit
dim d as xojo.core.date = FromISO8601Date("2015-02-07T07:47:04.872175Z")
next
newend = Microseconds
dim olddiff, newdiff as double
olddiff = oldend - oldstart
newdiff = newend - newstart
me.AddRow "old = " + str(olddiff,"00000000.0000") + " new = " + str(newdiff,"00000000.0000")
next
and those runs suggest my code is NOT quicker - I really didn’t expect it to be
old = 00177119.3440 new = 01523673.4921
old = 00179606.2590 new = 01531457.8109
old = 00189875.2260 new = 01611258.6799
old = 00195586.4709 new = 01679490.3530
old = 00196628.0620 new = 01549869.6760
old = 00188670.9550 new = 01527660.8311
old = 00179602.3750 new = 01553725.2560
old = 00177261.8450 new = 01553086.5740
old = 00178154.5540 new = 01556845.8679
old = 00188049.6189 new = 01576815.6350
[code] Sub ISODateTime(Extends aDate As Date, Assigns value As String)
if (Trim(value) <> “”) then
DIM aRegEx As NEW RegEx
aRegEx.SearchPattern = “^([\±]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))(T\s?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\±])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$”
DIM match As RegExMatch = aRegEx.Search(Trim(value))
if (match <> Nil) then
// set the GMT offset to the offset in the ISO date
if (match.SubExpressionCount > 21) then
aDate.GMTOffset = Val(match.SubExpressionString(21))
end if
for i as Integer = 0 to (match.SubExpressionCount)
select case i
case 1 // year
aDate.Year = Val(match.SubExpressionString(i))
case 5 // month
aDate.Month = Val(match.SubExpressionString(i))
case 7 // day
aDate.Day = Val(match.SubExpressionString(i))
case 15 // hour
aDate.Hour = Val(match.SubExpressionString(i))
case 16 // minutes
aDate.Minute = Val(match.SubExpressionString(i))
case 19 // seconds
aDate.Second = Val(match.SubExpressionString(i))
end select
next
// adjust the GMT offset for local time zone
DIM now As NEW Date
DIM localGMT As Double = now.GMTOffset
aDate.GMTOffset = localGMT
end if
end if
End Sub[/code]
Here is an extends method, for the old Date class, that will create an instance based on an ISO-8601… To use with the new immutable Date class, would require a small bit of rewriting, but shouldn’t be that hard…
took this method
plopped it in a module
changed driver code to
const kLimit = 10
const iLimit = 10000
for k as integer = 1 to kLimit
dim oldstart, newstart, oldend, newend as double
oldstart = Microseconds
for i as integer = 1 to iLimit
dim d as new date
d.ISODateTime = "2015-02-07T07:47:04.872175"
next
oldend = Microseconds
// newstart = Microseconds
// for i as integer = 1 to iLimit
// dim d as xojo.core.date = FromISO8601Date("2015-02-07T07:47:04.872175Z")
// next
// newend = Microseconds
dim olddiff, newdiff as double
olddiff = oldend - oldstart
// newdiff = newend - newstart
me.AddRow "old = " + str(olddiff,"00000000.0000") + " new = " + str(newdiff,"00000000.0000")
next
about 3x slower than joe’s code
old = 00540666.8180 new = 00000000.0000
old = 00526265.3419 new = 00000000.0000
old = 00534578.0790 new = 00000000.0000
old = 00545776.5730 new = 00000000.0000
old = 00550043.1011 new = 00000000.0000
old = 00526998.7090 new = 00000000.0000
old = 00521328.8260 new = 00000000.0000
old = 00525330.3170 new = 00000000.0000
old = 00535230.5740 new = 00000000.0000
old = 00537524.3340 new = 00000000.0000
And I’m sure each of these could be sped up
A real quick speed up is to simply not loop over all the search results since the specific matches are well known
Basically turn the for next loop into something like 6 if’s
if match.SubExpressionCount > 1 then aDate.Year = Val(match.SubExpressionString(1))
if match.SubExpressionCount > 5 then aDate.Month = Val(match.SubExpressionString(5))
if match.SubExpressionCount > 7 then aDate.Day = Val(match.SubExpressionString(7))
if match.SubExpressionCount > 15 then aDate.Hour = Val(match.SubExpressionString(15))
if match.SubExpressionCount > 16 then aDate.Minute = Val(match.SubExpressionString(16))
if match.SubExpressionCount > 19 then aDate.Second = Val(match.SubExpressionString(19))
This shaves of 70,000 microseconds over the iterations
Making the new regex be static shaves a tad more