The wwJsonSerializer :: WriteData function attempts to convert all datetime values into a UTC date by adding the timezone value (of wherever the server might be located). This is a problem. In my case, all the dates within my SQL table are already stored in UTC. Thus, the resulting JSON is returning every datetime misstated by some number of hours.
My need is for the JsonSerializer to return the data as it is stored in the table.
~bob
Since FoxPro has no sense of UTC dates it assumes all dates are local dates. There's no way for wwJsonSerializer to tell the difference to really work around this generically so I think the only solution to this is to convert the dates before conversion.
You can use the FromUtcTime() function (in wwapi) to perform that for you. If it's in an object list you'll have to loop through and do it to each one before serialization.
+++ Rick ---
Rick,
The wwJsonSerializer :: WriteData function attempts to convert all datetime values into a UTC date by adding the timezone value (of wherever the server might be located). This is a problem. In my case, all the dates within my SQL table are already stored in UTC. Thus, the resulting JSON is returning every datetime misstated by some number of hours.
My need is for the JsonSerializer to return the data as it is stored in the table.
~bob
West Wind Technologies
Making waves on the Web
from Maui
So we now have:
*** If .t. doesn't convert dates to UTC first
*** Assumes that any date is already UTC formatted
AssumeUtcDates = .F.
in the class header and:
************************************************************************
* wwJsonSerializer :: WriteDate
****************************************
*** Function: Turns a date into an ISO formatted date value string
*** Assume: Input dates are assumed to be local dates
*** If you have UTC dates to start you'll need to convert
*** them to local dates first
*** Pass:
*** Return:
************************************************************************
FUNCTION WriteDate(lvValue)IF VARTYPE(lvValue) = "D"
lvValue = DTOT(lvValue)
ENDIF*** turn into UTC date
IF !THIS.AssumeUtcDates
lvValue = lvValue + (GetTimeZone() * 60)
ENDIFIF EMPTY(lvValue)
lvValue = {^1970-1-1 :}
ENDIF#IF wwVFPVersion > 8
this.cOutput = this.cOutput + '"' + TTOC(lvValue,3) + 'Z"'
RETURN
#ENDIF*** ISO Format
this.cOutput = this.cOutput + ;
'"' + TRANSFORM(YEAR(ldDateVal)) + "-" + ;
PADL(TRANSFORM(MONTH(ldDateVal)),2,"0") + "-" +;
PADL(TRANSFORM(Day(ldDateVal)),2,"0") + "T" +;
PADL(TRANSFORM(Hour(ldDateVal)),2,"0") + ":" +;
PADL(TRANSFORM(Minute(ldDateVal)),2,"0") + ":" +;
PADL(TRANSFORM(Sec(ldDateVal)),2,"0") + 'Z"'ENDFUNC
* wwJsonSerializer :: WriteDate
where the code skips generation if this flag is set to .T.
This will be in a future release so you can make this change now to get you around the problem you described here and it'll just be there in the next version.
+++ Rick ---
Rick,
The wwJsonSerializer :: WriteData function attempts to convert all datetime values into a UTC date by adding the timezone value (of wherever the server might be located). This is a problem. In my case, all the dates within my SQL table are already stored in UTC. Thus, the resulting JSON is returning every datetime misstated by some number of hours.
My need is for the JsonSerializer to return the data as it is stored in the table.
~bob
West Wind Technologies
Making waves on the Web
from Maui
Where should the "AssumeUtcDates = .F." declaration be placed so your next update does not overwrite it?
Also, did you see my "wwjsonserializer bug" posted on 10/8 within your ParseObjectJson()?
~bob
Ok, so I gave this a little more thought and I suppose it's worthy of adding a flag to the serializer.
So we now have:
*** If .t. doesn't convert dates to UTC first
*** Assumes that any date is already UTC formatted
AssumeUtcDates = .F.
in the class header and:
************************************************************************
* wwJsonSerializer :: WriteDate
****************************************
*** Function: Turns a date into an ISO formatted date value string
*** Assume: Input dates are assumed to be local dates
*** If you have UTC dates to start you'll need to convert
*** them to local dates first
*** Pass:
*** Return:
************************************************************************
FUNCTION WriteDate(lvValue)IF VARTYPE(lvValue) = "D"
lvValue = DTOT(lvValue)
ENDIF*** turn into UTC date
IF !THIS.AssumeUtcDates
lvValue = lvValue + (GetTimeZone() * 60)
ENDIFIF EMPTY(lvValue)
lvValue = {^1970-1-1 :}
ENDIF#IF wwVFPVersion > 8
this.cOutput = this.cOutput + '"' + TTOC(lvValue,3) + 'Z"'
RETURN
#ENDIF*** ISO Format
this.cOutput = this.cOutput + ;
'"' + TRANSFORM(YEAR(ldDateVal)) + "-" + ;
PADL(TRANSFORM(MONTH(ldDateVal)),2,"0") + "-" +;
PADL(TRANSFORM(Day(ldDateVal)),2,"0") + "T" +;
PADL(TRANSFORM(Hour(ldDateVal)),2,"0") + ":" +;
PADL(TRANSFORM(Minute(ldDateVal)),2,"0") + ":" +;
PADL(TRANSFORM(Sec(ldDateVal)),2,"0") + 'Z"'ENDFUNC
* wwJsonSerializer :: WriteDate
where the code skips generation if this flag is set to .T.
This will be in a future release so you can make this change now to get you around the problem you described here and it'll just be there in the next version.
+++ Rick ---
Rick,
The wwJsonSerializer :: WriteData function attempts to convert all datetime values into a UTC date by adding the timezone value (of wherever the server might be located). This is a problem. In my case, all the dates within my SQL table are already stored in UTC. Thus, the resulting JSON is returning every datetime misstated by some number of hours.
My need is for the JsonSerializer to return the data as it is stored in the table.
~bob
Can you also update the wwAPI functions: GetUtcTime and FromUtcTime to account for relative changes in the timezone? Like passing a date from 100 days ago while DST was in affect. I included some code below that uses the Windows API.
Thanks!
Bo
Ok, so I gave this a little more thought and I suppose it's worthy of adding a flag to the serializer.
So we now have:
*** If .t. doesn't convert dates to UTC first
*** Assumes that any date is already UTC formatted
AssumeUtcDates = .F.
...
This will be in a future release so you can make this change now to get you around the problem you described here and it'll just be there in the next version.
+++ Rick ---
** Today
?LocalToUtcTime(DATETIME())
** 150 days ago
?LocalToUtcTime(DATETIME()-(150*24*60*60))
************************************************************************
FUNCTION LocalToUtcTime(ltLocalTime)
****************************************
*** Function: Returns a UTC DateTime from a Local DateTime
*** Accounts for daylight saving time
************************************************************************
LOCAL lqUTCTime, lqLocalTime, ltUtcTimeDECLARE Long TzSpecificLocalTimeToSystemTime IN WIN32API ;
String lpTimeZone, ;
String lpLocalTime, ;
String @lpUniversalTime
lqLocalTime = VFPToSystemTime(ltLocalTime)
lqUTCTime = VFPToSystemTime()
ltUtcTime = {// ::}IF TzSpecificLocalTimeToSystemTime(NULL,lqLocalTime,@lqUTCTime) = 1
ltUtcTime = SystemTimeToVFP(lqUTCTime)
ENDIFRETURN ltUtcTime
ENDFUNC
************************************************************************
FUNCTION UtcToLocalTime(ltUTCTime)
****************************************
*** Function: Returns a Local DateTime from a UTC DateTime
*** Accounts for daylight saving time
************************************************************************
LOCAL lqUTCTime, lqLocalTime, ltLocalTimeDECLARE Long SystemTimeToTzSpecificLocalTime IN WIN32API ;
String lpTimeZone, ;
String lpUniversalTime, ;
String @lpLocalTime
lqUTCTime = VFPToSystemTime(ltUTCTime)
lqLocalTime = VFPToSystemTime()
ltLocalTime = {// ::}IF SystemTimeToTzSpecificLocalTime(NULL,lqUTCTime,@lqLocalTime) = 1
ltLocalTime = SystemTimeToVFP(lqLocalTime)
ENDIFRETURN ltLocalTime
ENDFUNC
************************************************************************
FUNCTION SYSTEMTIMEToVFP(lqSYSTEMTIME)
****************************************
*** Function: Returns a VFP DateTime from a SYSTEMTIME API structure (as a binary string)
************************************************************************
LOCAL ltVFPTime
ltVFPTime = DATETIME( ;
CTOBIN(SUBSTR(lqSYSTEMTIME, 1,2),"2rs"), ;
CTOBIN(SUBSTR(lqSYSTEMTIME, 3,2),"2rs"), ;
CTOBIN(SUBSTR(lqSYSTEMTIME, 7,2),"2rs"), ;
CTOBIN(SUBSTR(lqSYSTEMTIME, 9,2),"2rs"), ;
CTOBIN(SUBSTR(lqSYSTEMTIME,11,2),"2rs"), ;
CTOBIN(SUBSTR(lqSYSTEMTIME,13,2),"2rs"))
RETURN ltVFPTime
ENDFUNC
************************************************************************
FUNCTION VFPToSYSTEMTIME(ltVFPTime)
****************************************
*** Function: Returns a SYSTEMTIME API structure (as a binary string) from a VFP DateTime
************************************************************************
LOCAL lqSYSTEMTIMElqSYSTEMTIME = 0h0000+0h0000+0h0000+0h0000+0h0000+0h0000+0h0000+0h0000
IF NOT EMPTY(ltVFPTime)
lqSYSTEMTIME = STUFF(lqSYSTEMTIME, 1, 2, BINTOC(YEAR(ltVFPTime),"2rs"))
lqSYSTEMTIME = STUFF(lqSYSTEMTIME, 3, 2, BINTOC(MONTH(ltVFPTime),"2rs"))
lqSYSTEMTIME = STUFF(lqSYSTEMTIME, 5, 2, BINTOC(DOW(ltVFPTime),"2rs"))
lqSYSTEMTIME = STUFF(lqSYSTEMTIME, 7, 2, BINTOC(DAY(ltVFPTime),"2rs"))
lqSYSTEMTIME = STUFF(lqSYSTEMTIME, 9, 2, BINTOC(HOUR(ltVFPTime),"2rs"))
lqSYSTEMTIME = STUFF(lqSYSTEMTIME, 11, 2, BINTOC(MINUTE(ltVFPTime),"2rs"))
lqSYSTEMTIME = STUFF(lqSYSTEMTIME, 13, 2, BINTOC(SEC(ltVFPTime),"2rs"))
lqSYSTEMTIME = STUFF(lqSYSTEMTIME, 15, 2, BINTOC(0000,"2rs"))
ENDIFRETURN lqSYSTEMTIME
ENDFUNC