Advertisement
F0u4d

VBA Json Converter

May 22nd, 2022
1,935
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. ''
  2. ' VBA-JSON v2.3.1
  3. ' (c) Tim Hall - https://github.com/VBA-tools/VBA-JSON
  4. '
  5. ' JSON Converter for VBA
  6. '
  7. ' Errors:
  8. ' 10001 - JSON parse error
  9. '
  10. ' @class JsonConverter
  11. ' @license MIT (http://www.opensource.org/licenses/mit-license.php)
  12. '' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
  13. '
  14. ' Based originally on vba-json (with extensive changes)
  15. ' BSD license included below
  16. '
  17. ' JSONLib, http://code.google.com/p/vba-json/
  18. '
  19. ' Copyright (c) 2013, Ryo Yokoyama
  20. ' All rights reserved.
  21. '
  22. ' Redistribution and use in source and binary forms, with or without
  23. ' modification, are permitted provided that the following conditions are met:
  24. '     * Redistributions of source code must retain the above copyright
  25. '       notice, this list of conditions and the following disclaimer.
  26. '     * Redistributions in binary form must reproduce the above copyright
  27. '       notice, this list of conditions and the following disclaimer in the
  28. '       documentation and/or other materials provided with the distribution.
  29. '     * Neither the name of the <organization> nor the
  30. '       names of its contributors may be used to endorse or promote products
  31. '       derived from this software without specific prior written permission.
  32. '
  33. ' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  34. ' ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  35. ' WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  36. ' DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  37. ' DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  38. ' (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  39. ' LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  40. ' ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  41. ' (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  42. ' SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  43. ' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
  44. Option Explicit
  45.  
  46. ' === VBA-UTC Headers
  47. #If Mac Then
  48.  
  49. #If VBA7 Then
  50.  
  51. ' 64-bit Mac (2016)
  52. Private Declare PtrSafe Function utc_popen Lib "/usr/lib/libc.dylib" Alias "popen" _
  53.     (ByVal utc_Command As String, ByVal utc_Mode As String) As LongPtr
  54. Private Declare PtrSafe Function utc_pclose Lib "/usr/lib/libc.dylib" Alias "pclose" _
  55.     (ByVal utc_File As LongPtr) As LongPtr
  56. Private Declare PtrSafe Function utc_fread Lib "/usr/lib/libc.dylib" Alias "fread" _
  57.     (ByVal utc_Buffer As String, ByVal utc_Size As LongPtr, ByVal utc_Number As LongPtr, ByVal utc_File As LongPtr) As LongPtr
  58. Private Declare PtrSafe Function utc_feof Lib "/usr/lib/libc.dylib" Alias "feof" _
  59.     (ByVal utc_File As LongPtr) As LongPtr
  60.  
  61. #Else
  62.  
  63. ' 32-bit Mac
  64. Private Declare Function utc_popen Lib "libc.dylib" Alias "popen" _
  65.     (ByVal utc_Command As String, ByVal utc_Mode As String) As Long
  66. Private Declare Function utc_pclose Lib "libc.dylib" Alias "pclose" _
  67.     (ByVal utc_File As Long) As Long
  68. Private Declare Function utc_fread Lib "libc.dylib" Alias "fread" _
  69.     (ByVal utc_Buffer As String, ByVal utc_Size As Long, ByVal utc_Number As Long, ByVal utc_File As Long) As Long
  70. Private Declare Function utc_feof Lib "libc.dylib" Alias "feof" _
  71.     (ByVal utc_File As Long) As Long
  72.  
  73. #End If
  74.  
  75. #ElseIf VBA7 Then
  76.  
  77. ' http://msdn.microsoft.com/en-us/library/windows/desktop/ms724421.aspx
  78. ' http://msdn.microsoft.com/en-us/library/windows/desktop/ms724949.aspx
  79. ' http://msdn.microsoft.com/en-us/library/windows/desktop/ms725485.aspx
  80. Private Declare PtrSafe Function utc_GetTimeZoneInformation Lib "kernel32" Alias "GetTimeZoneInformation" _
  81.     (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION) As Long
  82. Private Declare PtrSafe Function utc_SystemTimeToTzSpecificLocalTime Lib "kernel32" Alias "SystemTimeToTzSpecificLocalTime" _
  83.     (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpUniversalTime As utc_SYSTEMTIME, utc_lpLocalTime As utc_SYSTEMTIME) As Long
  84. Private Declare PtrSafe Function utc_TzSpecificLocalTimeToSystemTime Lib "kernel32" Alias "TzSpecificLocalTimeToSystemTime" _
  85.     (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpLocalTime As utc_SYSTEMTIME, utc_lpUniversalTime As utc_SYSTEMTIME) As Long
  86.  
  87. #Else
  88.  
  89. Private Declare Function utc_GetTimeZoneInformation Lib "kernel32" Alias "GetTimeZoneInformation" _
  90.     (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION) As Long
  91. Private Declare Function utc_SystemTimeToTzSpecificLocalTime Lib "kernel32" Alias "SystemTimeToTzSpecificLocalTime" _
  92.     (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpUniversalTime As utc_SYSTEMTIME, utc_lpLocalTime As utc_SYSTEMTIME) As Long
  93. Private Declare Function utc_TzSpecificLocalTimeToSystemTime Lib "kernel32" Alias "TzSpecificLocalTimeToSystemTime" _
  94.     (utc_lpTimeZoneInformation As utc_TIME_ZONE_INFORMATION, utc_lpLocalTime As utc_SYSTEMTIME, utc_lpUniversalTime As utc_SYSTEMTIME) As Long
  95.  
  96. #End If
  97.  
  98. #If Mac Then
  99.  
  100. #If VBA7 Then
  101. Private Type utc_ShellResult
  102.     utc_Output As String
  103.     utc_ExitCode As LongPtr
  104. End Type
  105.  
  106. #Else
  107.  
  108. Private Type utc_ShellResult
  109.     utc_Output As String
  110.     utc_ExitCode As Long
  111. End Type
  112.  
  113. #End If
  114.  
  115. #Else
  116.  
  117. Private Type utc_SYSTEMTIME
  118.     utc_wYear As Integer
  119.     utc_wMonth As Integer
  120.     utc_wDayOfWeek As Integer
  121.     utc_wDay As Integer
  122.     utc_wHour As Integer
  123.     utc_wMinute As Integer
  124.     utc_wSecond As Integer
  125.     utc_wMilliseconds As Integer
  126. End Type
  127.  
  128. Private Type utc_TIME_ZONE_INFORMATION
  129.     utc_Bias As Long
  130.     utc_StandardName(0 To 31) As Integer
  131.     utc_StandardDate As utc_SYSTEMTIME
  132.     utc_StandardBias As Long
  133.     utc_DaylightName(0 To 31) As Integer
  134.     utc_DaylightDate As utc_SYSTEMTIME
  135.     utc_DaylightBias As Long
  136. End Type
  137.  
  138. #End If
  139. ' === End VBA-UTC
  140.  
  141. Private Type json_Options
  142.     ' VBA only stores 15 significant digits, so any numbers larger than that are truncated
  143.    ' This can lead to issues when BIGINT's are used (e.g. for Ids or Credit Cards), as they will be invalid above 15 digits
  144.    ' See: http://support.microsoft.com/kb/269370
  145.    '
  146.    ' By default, VBA-JSON will use String for numbers longer than 15 characters that contain only digits
  147.    ' to override set `JsonConverter.JsonOptions.UseDoubleForLargeNumbers = True`
  148.    UseDoubleForLargeNumbers As Boolean
  149.  
  150.     ' The JSON standard requires object keys to be quoted (" or '), use this option to allow unquoted keys
  151.    AllowUnquotedKeys As Boolean
  152.  
  153.     ' The solidus (/) is not required to be escaped, use this option to escape them as \/ in ConvertToJson
  154.    EscapeSolidus As Boolean
  155. End Type
  156. Public JsonOptions As json_Options
  157.  
  158. ' ============================================= '
  159. ' Public Methods
  160. ' ============================================= '
  161.  
  162. ''
  163. ' Convert JSON string to object (Dictionary/Collection)
  164. '
  165. ' @method ParseJson
  166. ' @param {String} json_String
  167. ' @return {Object} (Dictionary or Collection)
  168. ' @throws 10001 - JSON parse error
  169. ''
  170. Public Function ParseJson(ByVal JsonString As String) As Object
  171.     Dim json_Index As Long
  172.     json_Index = 1
  173.  
  174.     ' Remove vbCr, vbLf, and vbTab from json_String
  175.    JsonString = VBA.Replace(VBA.Replace(VBA.Replace(JsonString, VBA.vbCr, ""), VBA.vbLf, ""), VBA.vbTab, "")
  176.  
  177.     json_SkipSpaces JsonString, json_Index
  178.     Select Case VBA.Mid$(JsonString, json_Index, 1)
  179.     Case "{"
  180.         Set ParseJson = json_ParseObject(JsonString, json_Index)
  181.     Case "["
  182.         Set ParseJson = json_ParseArray(JsonString, json_Index)
  183.     Case Else
  184.         ' Error: Invalid JSON string
  185.        Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(JsonString, json_Index, "Expecting '{' or '['")
  186.     End Select
  187. End Function
  188.  
  189. ''
  190. ' Convert object (Dictionary/Collection/Array) to JSON
  191. '
  192. ' @method ConvertToJson
  193. ' @param {Variant} JsonValue (Dictionary, Collection, or Array)
  194. ' @param {Integer|String} Whitespace "Pretty" print json with given number of spaces per indentation (Integer) or given string
  195. ' @return {String}
  196. ''
  197. Public Function ConvertToJson(ByVal JsonValue As Variant, Optional ByVal Whitespace As Variant, Optional ByVal json_CurrentIndentation As Long = 0) As String
  198.     Dim json_Buffer As String
  199.     Dim json_BufferPosition As Long
  200.     Dim json_BufferLength As Long
  201.     Dim json_Index As Long
  202.     Dim json_LBound As Long
  203.     Dim json_UBound As Long
  204.     Dim json_IsFirstItem As Boolean
  205.     Dim json_Index2D As Long
  206.     Dim json_LBound2D As Long
  207.     Dim json_UBound2D As Long
  208.     Dim json_IsFirstItem2D As Boolean
  209.     Dim json_Key As Variant
  210.     Dim json_Value As Variant
  211.     Dim json_DateStr As String
  212.     Dim json_Converted As String
  213.     Dim json_SkipItem As Boolean
  214.     Dim json_PrettyPrint As Boolean
  215.     Dim json_Indentation As String
  216.     Dim json_InnerIndentation As String
  217.  
  218.     json_LBound = -1
  219.     json_UBound = -1
  220.     json_IsFirstItem = True
  221.     json_LBound2D = -1
  222.     json_UBound2D = -1
  223.     json_IsFirstItem2D = True
  224.     json_PrettyPrint = Not IsMissing(Whitespace)
  225.  
  226.     Select Case VBA.VarType(JsonValue)
  227.     Case VBA.vbNull
  228.         ConvertToJson = "null"
  229.     Case VBA.vbDate
  230.         ' Date
  231.        json_DateStr = ConvertToIso(VBA.CDate(JsonValue))
  232.  
  233.         ConvertToJson = """" & json_DateStr & """"
  234.     Case VBA.vbString
  235.         ' String (or large number encoded as string)
  236.        If Not JsonOptions.UseDoubleForLargeNumbers And json_StringIsLargeNumber(JsonValue) Then
  237.             ConvertToJson = JsonValue
  238.         Else
  239.             ConvertToJson = """" & json_Encode(JsonValue) & """"
  240.         End If
  241.     Case VBA.vbBoolean
  242.         If JsonValue Then
  243.             ConvertToJson = "true"
  244.         Else
  245.             ConvertToJson = "false"
  246.         End If
  247.     Case VBA.vbArray To VBA.vbArray + VBA.vbByte
  248.         If json_PrettyPrint Then
  249.             If VBA.VarType(Whitespace) = VBA.vbString Then
  250.                 json_Indentation = VBA.String$(json_CurrentIndentation + 1, Whitespace)
  251.                 json_InnerIndentation = VBA.String$(json_CurrentIndentation + 2, Whitespace)
  252.             Else
  253.                 json_Indentation = VBA.Space$((json_CurrentIndentation + 1) * Whitespace)
  254.                 json_InnerIndentation = VBA.Space$((json_CurrentIndentation + 2) * Whitespace)
  255.             End If
  256.         End If
  257.  
  258.         ' Array
  259.        json_BufferAppend json_Buffer, "[", json_BufferPosition, json_BufferLength
  260.  
  261.         On Error Resume Next
  262.  
  263.         json_LBound = LBound(JsonValue, 1)
  264.         json_UBound = UBound(JsonValue, 1)
  265.         json_LBound2D = LBound(JsonValue, 2)
  266.         json_UBound2D = UBound(JsonValue, 2)
  267.  
  268.         If json_LBound >= 0 And json_UBound >= 0 Then
  269.             For json_Index = json_LBound To json_UBound
  270.                 If json_IsFirstItem Then
  271.                     json_IsFirstItem = False
  272.                 Else
  273.                     ' Append comma to previous line
  274.                    json_BufferAppend json_Buffer, ",", json_BufferPosition, json_BufferLength
  275.                 End If
  276.  
  277.                 If json_LBound2D >= 0 And json_UBound2D >= 0 Then
  278.                     ' 2D Array
  279.                    If json_PrettyPrint Then
  280.                         json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength
  281.                     End If
  282.                     json_BufferAppend json_Buffer, json_Indentation & "[", json_BufferPosition, json_BufferLength
  283.  
  284.                     For json_Index2D = json_LBound2D To json_UBound2D
  285.                         If json_IsFirstItem2D Then
  286.                             json_IsFirstItem2D = False
  287.                         Else
  288.                             json_BufferAppend json_Buffer, ",", json_BufferPosition, json_BufferLength
  289.                         End If
  290.  
  291.                         json_Converted = ConvertToJson(JsonValue(json_Index, json_Index2D), Whitespace, json_CurrentIndentation + 2)
  292.  
  293.                         ' For Arrays/Collections, undefined (Empty/Nothing) is treated as null
  294.                        If json_Converted = "" Then
  295.                             ' (nest to only check if converted = "")
  296.                            If json_IsUndefined(JsonValue(json_Index, json_Index2D)) Then
  297.                                 json_Converted = "null"
  298.                             End If
  299.                         End If
  300.  
  301.                         If json_PrettyPrint Then
  302.                             json_Converted = vbNewLine & json_InnerIndentation & json_Converted
  303.                         End If
  304.  
  305.                         json_BufferAppend json_Buffer, json_Converted, json_BufferPosition, json_BufferLength
  306.                     Next json_Index2D
  307.  
  308.                     If json_PrettyPrint Then
  309.                         json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength
  310.                     End If
  311.  
  312.                     json_BufferAppend json_Buffer, json_Indentation & "]", json_BufferPosition, json_BufferLength
  313.                     json_IsFirstItem2D = True
  314.                 Else
  315.                     ' 1D Array
  316.                    json_Converted = ConvertToJson(JsonValue(json_Index), Whitespace, json_CurrentIndentation + 1)
  317.  
  318.                     ' For Arrays/Collections, undefined (Empty/Nothing) is treated as null
  319.                    If json_Converted = "" Then
  320.                         ' (nest to only check if converted = "")
  321.                        If json_IsUndefined(JsonValue(json_Index)) Then
  322.                             json_Converted = "null"
  323.                         End If
  324.                     End If
  325.  
  326.                     If json_PrettyPrint Then
  327.                         json_Converted = vbNewLine & json_Indentation & json_Converted
  328.                     End If
  329.  
  330.                     json_BufferAppend json_Buffer, json_Converted, json_BufferPosition, json_BufferLength
  331.                 End If
  332.             Next json_Index
  333.         End If
  334.  
  335.         On Error GoTo 0
  336.  
  337.         If json_PrettyPrint Then
  338.             json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength
  339.  
  340.             If VBA.VarType(Whitespace) = VBA.vbString Then
  341.                 json_Indentation = VBA.String$(json_CurrentIndentation, Whitespace)
  342.             Else
  343.                 json_Indentation = VBA.Space$(json_CurrentIndentation * Whitespace)
  344.             End If
  345.         End If
  346.  
  347.         json_BufferAppend json_Buffer, json_Indentation & "]", json_BufferPosition, json_BufferLength
  348.  
  349.         ConvertToJson = json_BufferToString(json_Buffer, json_BufferPosition)
  350.  
  351.     ' Dictionary or Collection
  352.    Case VBA.vbObject
  353.         If json_PrettyPrint Then
  354.             If VBA.VarType(Whitespace) = VBA.vbString Then
  355.                 json_Indentation = VBA.String$(json_CurrentIndentation + 1, Whitespace)
  356.             Else
  357.                 json_Indentation = VBA.Space$((json_CurrentIndentation + 1) * Whitespace)
  358.             End If
  359.         End If
  360.  
  361.         ' Dictionary
  362.        If VBA.TypeName(JsonValue) = "Dictionary" Then
  363.             json_BufferAppend json_Buffer, "{", json_BufferPosition, json_BufferLength
  364.             For Each json_Key In JsonValue.Keys
  365.                 ' For Objects, undefined (Empty/Nothing) is not added to object
  366.                json_Converted = ConvertToJson(JsonValue(json_Key), Whitespace, json_CurrentIndentation + 1)
  367.                 If json_Converted = "" Then
  368.                     json_SkipItem = json_IsUndefined(JsonValue(json_Key))
  369.                 Else
  370.                     json_SkipItem = False
  371.                 End If
  372.  
  373.                 If Not json_SkipItem Then
  374.                     If json_IsFirstItem Then
  375.                         json_IsFirstItem = False
  376.                     Else
  377.                         json_BufferAppend json_Buffer, ",", json_BufferPosition, json_BufferLength
  378.                     End If
  379.  
  380.                     If json_PrettyPrint Then
  381.                         json_Converted = vbNewLine & json_Indentation & """" & json_Key & """: " & json_Converted
  382.                     Else
  383.                         json_Converted = """" & json_Key & """:" & json_Converted
  384.                     End If
  385.  
  386.                     json_BufferAppend json_Buffer, json_Converted, json_BufferPosition, json_BufferLength
  387.                 End If
  388.             Next json_Key
  389.  
  390.             If json_PrettyPrint Then
  391.                 json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength
  392.  
  393.                 If VBA.VarType(Whitespace) = VBA.vbString Then
  394.                     json_Indentation = VBA.String$(json_CurrentIndentation, Whitespace)
  395.                 Else
  396.                     json_Indentation = VBA.Space$(json_CurrentIndentation * Whitespace)
  397.                 End If
  398.             End If
  399.  
  400.             json_BufferAppend json_Buffer, json_Indentation & "}", json_BufferPosition, json_BufferLength
  401.  
  402.         ' Collection
  403.        ElseIf VBA.TypeName(JsonValue) = "Collection" Then
  404.             json_BufferAppend json_Buffer, "[", json_BufferPosition, json_BufferLength
  405.             For Each json_Value In JsonValue
  406.                 If json_IsFirstItem Then
  407.                     json_IsFirstItem = False
  408.                 Else
  409.                     json_BufferAppend json_Buffer, ",", json_BufferPosition, json_BufferLength
  410.                 End If
  411.  
  412.                 json_Converted = ConvertToJson(json_Value, Whitespace, json_CurrentIndentation + 1)
  413.  
  414.                 ' For Arrays/Collections, undefined (Empty/Nothing) is treated as null
  415.                If json_Converted = "" Then
  416.                     ' (nest to only check if converted = "")
  417.                    If json_IsUndefined(json_Value) Then
  418.                         json_Converted = "null"
  419.                     End If
  420.                 End If
  421.  
  422.                 If json_PrettyPrint Then
  423.                     json_Converted = vbNewLine & json_Indentation & json_Converted
  424.                 End If
  425.  
  426.                 json_BufferAppend json_Buffer, json_Converted, json_BufferPosition, json_BufferLength
  427.             Next json_Value
  428.  
  429.             If json_PrettyPrint Then
  430.                 json_BufferAppend json_Buffer, vbNewLine, json_BufferPosition, json_BufferLength
  431.  
  432.                 If VBA.VarType(Whitespace) = VBA.vbString Then
  433.                     json_Indentation = VBA.String$(json_CurrentIndentation, Whitespace)
  434.                 Else
  435.                     json_Indentation = VBA.Space$(json_CurrentIndentation * Whitespace)
  436.                 End If
  437.             End If
  438.  
  439.             json_BufferAppend json_Buffer, json_Indentation & "]", json_BufferPosition, json_BufferLength
  440.         End If
  441.  
  442.         ConvertToJson = json_BufferToString(json_Buffer, json_BufferPosition)
  443.     Case VBA.vbInteger, VBA.vbLong, VBA.vbSingle, VBA.vbDouble, VBA.vbCurrency, VBA.vbDecimal
  444.         ' Number (use decimals for numbers)
  445.        ConvertToJson = VBA.Replace(JsonValue, ",", ".")
  446.     Case Else
  447.         ' vbEmpty, vbError, vbDataObject, vbByte, vbUserDefinedType
  448.        ' Use VBA's built-in to-string
  449.        On Error Resume Next
  450.         ConvertToJson = JsonValue
  451.         On Error GoTo 0
  452.     End Select
  453. End Function
  454.  
  455. ' ============================================= '
  456. ' Private Functions
  457. ' ============================================= '
  458.  
  459. Private Function json_ParseObject(json_String As String, ByRef json_Index As Long) As Dictionary
  460.     Dim json_Key As String
  461.     Dim json_NextChar As String
  462.  
  463.     Set json_ParseObject = New Dictionary
  464.     json_SkipSpaces json_String, json_Index
  465.     If VBA.Mid$(json_String, json_Index, 1) <> "{" Then
  466.         Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting '{'")
  467.     Else
  468.         json_Index = json_Index + 1
  469.  
  470.         Do
  471.             json_SkipSpaces json_String, json_Index
  472.             If VBA.Mid$(json_String, json_Index, 1) = "}" Then
  473.                 json_Index = json_Index + 1
  474.                 Exit Function
  475.             ElseIf VBA.Mid$(json_String, json_Index, 1) = "," Then
  476.                 json_Index = json_Index + 1
  477.                 json_SkipSpaces json_String, json_Index
  478.             End If
  479.  
  480.             json_Key = json_ParseKey(json_String, json_Index)
  481.             json_NextChar = json_Peek(json_String, json_Index)
  482.             If json_NextChar = "[" Or json_NextChar = "{" Then
  483.                 Set json_ParseObject.Item(json_Key) = json_ParseValue(json_String, json_Index)
  484.             Else
  485.                 json_ParseObject.Item(json_Key) = json_ParseValue(json_String, json_Index)
  486.             End If
  487.         Loop
  488.     End If
  489. End Function
  490.  
  491. Private Function json_ParseArray(json_String As String, ByRef json_Index As Long) As Collection
  492.     Set json_ParseArray = New Collection
  493.  
  494.     json_SkipSpaces json_String, json_Index
  495.     If VBA.Mid$(json_String, json_Index, 1) <> "[" Then
  496.         Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting '['")
  497.     Else
  498.         json_Index = json_Index + 1
  499.  
  500.         Do
  501.             json_SkipSpaces json_String, json_Index
  502.             If VBA.Mid$(json_String, json_Index, 1) = "]" Then
  503.                 json_Index = json_Index + 1
  504.                 Exit Function
  505.             ElseIf VBA.Mid$(json_String, json_Index, 1) = "," Then
  506.                 json_Index = json_Index + 1
  507.                 json_SkipSpaces json_String, json_Index
  508.             End If
  509.  
  510.             json_ParseArray.Add json_ParseValue(json_String, json_Index)
  511.         Loop
  512.     End If
  513. End Function
  514.  
  515. Private Function json_ParseValue(json_String As String, ByRef json_Index As Long) As Variant
  516.     json_SkipSpaces json_String, json_Index
  517.     Select Case VBA.Mid$(json_String, json_Index, 1)
  518.     Case "{"
  519.         Set json_ParseValue = json_ParseObject(json_String, json_Index)
  520.     Case "["
  521.         Set json_ParseValue = json_ParseArray(json_String, json_Index)
  522.     Case """", "'"
  523.         json_ParseValue = json_ParseString(json_String, json_Index)
  524.     Case Else
  525.         If VBA.Mid$(json_String, json_Index, 4) = "true" Then
  526.             json_ParseValue = True
  527.             json_Index = json_Index + 4
  528.         ElseIf VBA.Mid$(json_String, json_Index, 5) = "false" Then
  529.             json_ParseValue = False
  530.             json_Index = json_Index + 5
  531.         ElseIf VBA.Mid$(json_String, json_Index, 4) = "null" Then
  532.             json_ParseValue = Null
  533.             json_Index = json_Index + 4
  534.         ElseIf VBA.InStr("+-0123456789", VBA.Mid$(json_String, json_Index, 1)) Then
  535.             json_ParseValue = json_ParseNumber(json_String, json_Index)
  536.         Else
  537.             Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting 'STRING', 'NUMBER', null, true, false, '{', or '['")
  538.         End If
  539.     End Select
  540. End Function
  541.  
  542. Private Function json_ParseString(json_String As String, ByRef json_Index As Long) As String
  543.     Dim json_Quote As String
  544.     Dim json_Char As String
  545.     Dim json_Code As String
  546.     Dim json_Buffer As String
  547.     Dim json_BufferPosition As Long
  548.     Dim json_BufferLength As Long
  549.  
  550.     json_SkipSpaces json_String, json_Index
  551.  
  552.     ' Store opening quote to look for matching closing quote
  553.    json_Quote = VBA.Mid$(json_String, json_Index, 1)
  554.     json_Index = json_Index + 1
  555.  
  556.     Do While json_Index > 0 And json_Index <= Len(json_String)
  557.         json_Char = VBA.Mid$(json_String, json_Index, 1)
  558.  
  559.         Select Case json_Char
  560.         Case "\"
  561.             ' Escaped string, \\, or \/
  562.            json_Index = json_Index + 1
  563.             json_Char = VBA.Mid$(json_String, json_Index, 1)
  564.  
  565.             Select Case json_Char
  566.             Case """", "\", "/", "'"
  567.                 json_BufferAppend json_Buffer, json_Char, json_BufferPosition, json_BufferLength
  568.                 json_Index = json_Index + 1
  569.             Case "b"
  570.                 json_BufferAppend json_Buffer, vbBack, json_BufferPosition, json_BufferLength
  571.                 json_Index = json_Index + 1
  572.             Case "f"
  573.                 json_BufferAppend json_Buffer, vbFormFeed, json_BufferPosition, json_BufferLength
  574.                 json_Index = json_Index + 1
  575.             Case "n"
  576.                 json_BufferAppend json_Buffer, vbCrLf, json_BufferPosition, json_BufferLength
  577.                 json_Index = json_Index + 1
  578.             Case "r"
  579.                 json_BufferAppend json_Buffer, vbCr, json_BufferPosition, json_BufferLength
  580.                 json_Index = json_Index + 1
  581.             Case "t"
  582.                 json_BufferAppend json_Buffer, vbTab, json_BufferPosition, json_BufferLength
  583.                 json_Index = json_Index + 1
  584.             Case "u"
  585.                 ' Unicode character escape (e.g. \u00a9 = Copyright)
  586.                json_Index = json_Index + 1
  587.                 json_Code = VBA.Mid$(json_String, json_Index, 4)
  588.                 json_BufferAppend json_Buffer, VBA.ChrW(VBA.Val("&h" + json_Code)), json_BufferPosition, json_BufferLength
  589.                 json_Index = json_Index + 4
  590.             End Select
  591.         Case json_Quote
  592.             json_ParseString = json_BufferToString(json_Buffer, json_BufferPosition)
  593.             json_Index = json_Index + 1
  594.             Exit Function
  595.         Case Else
  596.             json_BufferAppend json_Buffer, json_Char, json_BufferPosition, json_BufferLength
  597.             json_Index = json_Index + 1
  598.         End Select
  599.     Loop
  600. End Function
  601.  
  602. Private Function json_ParseNumber(json_String As String, ByRef json_Index As Long) As Variant
  603.     Dim json_Char As String
  604.     Dim json_Value As String
  605.     Dim json_IsLargeNumber As Boolean
  606.  
  607.     json_SkipSpaces json_String, json_Index
  608.  
  609.     Do While json_Index > 0 And json_Index <= Len(json_String)
  610.         json_Char = VBA.Mid$(json_String, json_Index, 1)
  611.  
  612.         If VBA.InStr("+-0123456789.eE", json_Char) Then
  613.             ' Unlikely to have massive number, so use simple append rather than buffer here
  614.            json_Value = json_Value & json_Char
  615.             json_Index = json_Index + 1
  616.         Else
  617.             ' Excel only stores 15 significant digits, so any numbers larger than that are truncated
  618.            ' This can lead to issues when BIGINT's are used (e.g. for Ids or Credit Cards), as they will be invalid above 15 digits
  619.            ' See: http://support.microsoft.com/kb/269370
  620.            '
  621.            ' Fix: Parse -> String, Convert -> String longer than 15/16 characters containing only numbers and decimal points -> Number
  622.            ' (decimal doesn't factor into significant digit count, so if present check for 15 digits + decimal = 16)
  623.            json_IsLargeNumber = IIf(InStr(json_Value, "."), Len(json_Value) >= 17, Len(json_Value) >= 16)
  624.             If Not JsonOptions.UseDoubleForLargeNumbers And json_IsLargeNumber Then
  625.                 json_ParseNumber = json_Value
  626.             Else
  627.                 ' VBA.Val does not use regional settings, so guard for comma is not needed
  628.                json_ParseNumber = VBA.Val(json_Value)
  629.             End If
  630.             Exit Function
  631.         End If
  632.     Loop
  633. End Function
  634.  
  635. Private Function json_ParseKey(json_String As String, ByRef json_Index As Long) As String
  636.     ' Parse key with single or double quotes
  637.    If VBA.Mid$(json_String, json_Index, 1) = """" Or VBA.Mid$(json_String, json_Index, 1) = "'" Then
  638.         json_ParseKey = json_ParseString(json_String, json_Index)
  639.     ElseIf JsonOptions.AllowUnquotedKeys Then
  640.         Dim json_Char As String
  641.         Do While json_Index > 0 And json_Index <= Len(json_String)
  642.             json_Char = VBA.Mid$(json_String, json_Index, 1)
  643.             If (json_Char <> " ") And (json_Char <> ":") Then
  644.                 json_ParseKey = json_ParseKey & json_Char
  645.                 json_Index = json_Index + 1
  646.             Else
  647.                 Exit Do
  648.             End If
  649.         Loop
  650.     Else
  651.         Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting '""' or '''")
  652.     End If
  653.  
  654.     ' Check for colon and skip if present or throw if not present
  655.    json_SkipSpaces json_String, json_Index
  656.     If VBA.Mid$(json_String, json_Index, 1) <> ":" Then
  657.         Err.Raise 10001, "JSONConverter", json_ParseErrorMessage(json_String, json_Index, "Expecting ':'")
  658.     Else
  659.         json_Index = json_Index + 1
  660.     End If
  661. End Function
  662.  
  663. Private Function json_IsUndefined(ByVal json_Value As Variant) As Boolean
  664.     ' Empty / Nothing -> undefined
  665.    Select Case VBA.VarType(json_Value)
  666.     Case VBA.vbEmpty
  667.         json_IsUndefined = True
  668.     Case VBA.vbObject
  669.         Select Case VBA.TypeName(json_Value)
  670.         Case "Empty", "Nothing"
  671.             json_IsUndefined = True
  672.         End Select
  673.     End Select
  674. End Function
  675.  
  676. Private Function json_Encode(ByVal json_Text As Variant) As String
  677.     ' Reference: http://www.ietf.org/rfc/rfc4627.txt
  678.    ' Escape: ", \, /, backspace, form feed, line feed, carriage return, tab
  679.    Dim json_Index As Long
  680.     Dim json_Char As String
  681.     Dim json_AscCode As Long
  682.     Dim json_Buffer As String
  683.     Dim json_BufferPosition As Long
  684.     Dim json_BufferLength As Long
  685.  
  686.     For json_Index = 1 To VBA.Len(json_Text)
  687.         json_Char = VBA.Mid$(json_Text, json_Index, 1)
  688.         json_AscCode = VBA.AscW(json_Char)
  689.  
  690.         ' When AscW returns a negative number, it returns the twos complement form of that number.
  691.        ' To convert the twos complement notation into normal binary notation, add 0xFFF to the return result.
  692.        ' https://support.microsoft.com/en-us/kb/272138
  693.        If json_AscCode < 0 Then
  694.             json_AscCode = json_AscCode + 65536
  695.         End If
  696.  
  697.         ' From spec, ", \, and control characters must be escaped (solidus is optional)
  698.  
  699.         Select Case json_AscCode
  700.         Case 34
  701.             ' " -> 34 -> \"
  702.            json_Char = "\"""
  703.         Case 92
  704.             ' \ -> 92 -> \\
  705.            json_Char = "\\"
  706.         Case 47
  707.             ' / -> 47 -> \/ (optional)
  708.            If JsonOptions.EscapeSolidus Then
  709.                 json_Char = "\/"
  710.             End If
  711.         Case 8
  712.             ' backspace -> 8 -> \b
  713.            json_Char = "\b"
  714.         Case 12
  715.             ' form feed -> 12 -> \f
  716.            json_Char = "\f"
  717.         Case 10
  718.             ' line feed -> 10 -> \n
  719.            json_Char = "\n"
  720.         Case 13
  721.             ' carriage return -> 13 -> \r
  722.            json_Char = "\r"
  723.         Case 9
  724.             ' tab -> 9 -> \t
  725.            json_Char = "\t"
  726.         Case 0 To 31, 127 To 65535
  727.             ' Non-ascii characters -> convert to 4-digit hex
  728.            json_Char = "\u" & VBA.Right$("0000" & VBA.Hex$(json_AscCode), 4)
  729.         End Select
  730.  
  731.         json_BufferAppend json_Buffer, json_Char, json_BufferPosition, json_BufferLength
  732.     Next json_Index
  733.  
  734.     json_Encode = json_BufferToString(json_Buffer, json_BufferPosition)
  735. End Function
  736.  
  737. Private Function json_Peek(json_String As String, ByVal json_Index As Long, Optional json_NumberOfCharacters As Long = 1) As String
  738.     ' "Peek" at the next number of characters without incrementing json_Index (ByVal instead of ByRef)
  739.    json_SkipSpaces json_String, json_Index
  740.     json_Peek = VBA.Mid$(json_String, json_Index, json_NumberOfCharacters)
  741. End Function
  742.  
  743. Private Sub json_SkipSpaces(json_String As String, ByRef json_Index As Long)
  744.     ' Increment index to skip over spaces
  745.    Do While json_Index > 0 And json_Index <= VBA.Len(json_String) And VBA.Mid$(json_String, json_Index, 1) = " "
  746.         json_Index = json_Index + 1
  747.     Loop
  748. End Sub
  749.  
  750. Private Function json_StringIsLargeNumber(json_String As Variant) As Boolean
  751.     ' Check if the given string is considered a "large number"
  752.    ' (See json_ParseNumber)
  753.  
  754.     Dim json_Length As Long
  755.     Dim json_CharIndex As Long
  756.     json_Length = VBA.Len(json_String)
  757.  
  758.     ' Length with be at least 16 characters and assume will be less than 100 characters
  759.    If json_Length >= 16 And json_Length <= 100 Then
  760.         Dim json_CharCode As String
  761.  
  762.         json_StringIsLargeNumber = True
  763.  
  764.         For json_CharIndex = 1 To json_Length
  765.             json_CharCode = VBA.Asc(VBA.Mid$(json_String, json_CharIndex, 1))
  766.             Select Case json_CharCode
  767.             ' Look for .|0-9|E|e
  768.            Case 46, 48 To 57, 69, 101
  769.                 ' Continue through characters
  770.            Case Else
  771.                 json_StringIsLargeNumber = False
  772.                 Exit Function
  773.             End Select
  774.         Next json_CharIndex
  775.     End If
  776. End Function
  777.  
  778. Private Function json_ParseErrorMessage(json_String As String, ByRef json_Index As Long, ErrorMessage As String)
  779.     ' Provide detailed parse error message, including details of where and what occurred
  780.    '
  781.    ' Example:
  782.    ' Error parsing JSON:
  783.    ' {"abcde":True}
  784.    '          ^
  785.    ' Expecting 'STRING', 'NUMBER', null, true, false, '{', or '['
  786.  
  787.     Dim json_StartIndex As Long
  788.     Dim json_StopIndex As Long
  789.  
  790.     ' Include 10 characters before and after error (if possible)
  791.    json_StartIndex = json_Index - 10
  792.     json_StopIndex = json_Index + 10
  793.     If json_StartIndex <= 0 Then
  794.         json_StartIndex = 1
  795.     End If
  796.     If json_StopIndex > VBA.Len(json_String) Then
  797.         json_StopIndex = VBA.Len(json_String)
  798.     End If
  799.  
  800.     json_ParseErrorMessage = "Error parsing JSON:" & VBA.vbNewLine & _
  801.                              VBA.Mid$(json_String, json_StartIndex, json_StopIndex - json_StartIndex + 1) & VBA.vbNewLine & _
  802.                              VBA.Space$(json_Index - json_StartIndex) & "^" & VBA.vbNewLine & _
  803.                              ErrorMessage
  804. End Function
  805.  
  806. Private Sub json_BufferAppend(ByRef json_Buffer As String, _
  807.                               ByRef json_Append As Variant, _
  808.                               ByRef json_BufferPosition As Long, _
  809.                               ByRef json_BufferLength As Long)
  810.     ' VBA can be slow to append strings due to allocating a new string for each append
  811.    ' Instead of using the traditional append, allocate a large empty string and then copy string at append position
  812.    '
  813.    ' Example:
  814.    ' Buffer: "abc  "
  815.    ' Append: "def"
  816.    ' Buffer Position: 3
  817.    ' Buffer Length: 5
  818.    '
  819.    ' Buffer position + Append length > Buffer length -> Append chunk of blank space to buffer
  820.    ' Buffer: "abc       "
  821.    ' Buffer Length: 10
  822.    '
  823.    ' Put "def" into buffer at position 3 (0-based)
  824.    ' Buffer: "abcdef    "
  825.    '
  826.    ' Approach based on cStringBuilder from vbAccelerator
  827.    ' http://www.vbaccelerator.com/home/VB/Code/Techniques/RunTime_Debug_Tracing/VB6_Tracer_Utility_zip_cStringBuilder_cls.asp
  828.    '
  829.    ' and clsStringAppend from Philip Swannell
  830.    ' https://github.com/VBA-tools/VBA-JSON/pull/82
  831.  
  832.     Dim json_AppendLength As Long
  833.     Dim json_LengthPlusPosition As Long
  834.  
  835.     json_AppendLength = VBA.Len(json_Append)
  836.     json_LengthPlusPosition = json_AppendLength + json_BufferPosition
  837.  
  838.     If json_LengthPlusPosition > json_BufferLength Then
  839.         ' Appending would overflow buffer, add chunk
  840.        ' (double buffer length or append length, whichever is bigger)
  841.        Dim json_AddedLength As Long
  842.         json_AddedLength = IIf(json_AppendLength > json_BufferLength, json_AppendLength, json_BufferLength)
  843.  
  844.         json_Buffer = json_Buffer & VBA.Space$(json_AddedLength)
  845.         json_BufferLength = json_BufferLength + json_AddedLength
  846.     End If
  847.  
  848.     ' Note: Namespacing with VBA.Mid$ doesn't work properly here, throwing compile error:
  849.    ' Function call on left-hand side of assignment must return Variant or Object
  850.    Mid$(json_Buffer, json_BufferPosition + 1, json_AppendLength) = CStr(json_Append)
  851.     json_BufferPosition = json_BufferPosition + json_AppendLength
  852. End Sub
  853.  
  854. Private Function json_BufferToString(ByRef json_Buffer As String, ByVal json_BufferPosition As Long) As String
  855.     If json_BufferPosition > 0 Then
  856.         json_BufferToString = VBA.Left$(json_Buffer, json_BufferPosition)
  857.     End If
  858. End Function
  859.  
  860. ''
  861. ' VBA-UTC v1.0.6
  862. ' (c) Tim Hall - https://github.com/VBA-tools/VBA-UtcConverter
  863. '
  864. ' UTC/ISO 8601 Converter for VBA
  865. '
  866. ' Errors:
  867. ' 10011 - UTC parsing error
  868. ' 10012 - UTC conversion error
  869. ' 10013 - ISO 8601 parsing error
  870. ' 10014 - ISO 8601 conversion error
  871. '
  872. ' @module UtcConverter
  873. ' @license MIT (http://www.opensource.org/licenses/mit-license.php)
  874. '' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ '
  875.  
  876. ' (Declarations moved to top)
  877.  
  878. ' ============================================= '
  879. ' Public Methods
  880. ' ============================================= '
  881.  
  882. ''
  883. ' Parse UTC date to local date
  884. '
  885. ' @method ParseUtc
  886. ' @param {Date} UtcDate
  887. ' @return {Date} Local date
  888. ' @throws 10011 - UTC parsing error
  889. ''
  890. Public Function ParseUtc(utc_UtcDate As Date) As Date
  891.     On Error GoTo utc_ErrorHandling
  892.  
  893. #If Mac Then
  894.     ParseUtc = utc_ConvertDate(utc_UtcDate)
  895. #Else
  896.     Dim utc_TimeZoneInfo As utc_TIME_ZONE_INFORMATION
  897.     Dim utc_LocalDate As utc_SYSTEMTIME
  898.  
  899.     utc_GetTimeZoneInformation utc_TimeZoneInfo
  900.     utc_SystemTimeToTzSpecificLocalTime utc_TimeZoneInfo, utc_DateToSystemTime(utc_UtcDate), utc_LocalDate
  901.  
  902.     ParseUtc = utc_SystemTimeToDate(utc_LocalDate)
  903. #End If
  904.  
  905.     Exit Function
  906.  
  907. utc_ErrorHandling:
  908.     Err.Raise 10011, "UtcConverter.ParseUtc", "UTC parsing error: " & Err.Number & " - " & Err.Description
  909. End Function
  910.  
  911. ''
  912. ' Convert local date to UTC date
  913. '
  914. ' @method ConvertToUrc
  915. ' @param {Date} utc_LocalDate
  916. ' @return {Date} UTC date
  917. ' @throws 10012 - UTC conversion error
  918. ''
  919. Public Function ConvertToUtc(utc_LocalDate As Date) As Date
  920.     On Error GoTo utc_ErrorHandling
  921.  
  922. #If Mac Then
  923.     ConvertToUtc = utc_ConvertDate(utc_LocalDate, utc_ConvertToUtc:=True)
  924. #Else
  925.     Dim utc_TimeZoneInfo As utc_TIME_ZONE_INFORMATION
  926.     Dim utc_UtcDate As utc_SYSTEMTIME
  927.  
  928.     utc_GetTimeZoneInformation utc_TimeZoneInfo
  929.     utc_TzSpecificLocalTimeToSystemTime utc_TimeZoneInfo, utc_DateToSystemTime(utc_LocalDate), utc_UtcDate
  930.  
  931.     ConvertToUtc = utc_SystemTimeToDate(utc_UtcDate)
  932. #End If
  933.  
  934.     Exit Function
  935.  
  936. utc_ErrorHandling:
  937.     Err.Raise 10012, "UtcConverter.ConvertToUtc", "UTC conversion error: " & Err.Number & " - " & Err.Description
  938. End Function
  939.  
  940. ''
  941. ' Parse ISO 8601 date string to local date
  942. '
  943. ' @method ParseIso
  944. ' @param {Date} utc_IsoString
  945. ' @return {Date} Local date
  946. ' @throws 10013 - ISO 8601 parsing error
  947. ''
  948. Public Function ParseIso(utc_IsoString As String) As Date
  949.     On Error GoTo utc_ErrorHandling
  950.  
  951.     Dim utc_Parts() As String
  952.     Dim utc_DateParts() As String
  953.     Dim utc_TimeParts() As String
  954.     Dim utc_OffsetIndex As Long
  955.     Dim utc_HasOffset As Boolean
  956.     Dim utc_NegativeOffset As Boolean
  957.     Dim utc_OffsetParts() As String
  958.     Dim utc_Offset As Date
  959.  
  960.     utc_Parts = VBA.Split(utc_IsoString, "T")
  961.     utc_DateParts = VBA.Split(utc_Parts(0), "-")
  962.     ParseIso = VBA.DateSerial(VBA.CInt(utc_DateParts(0)), VBA.CInt(utc_DateParts(1)), VBA.CInt(utc_DateParts(2)))
  963.  
  964.     If UBound(utc_Parts) > 0 Then
  965.         If VBA.InStr(utc_Parts(1), "Z") Then
  966.             utc_TimeParts = VBA.Split(VBA.Replace(utc_Parts(1), "Z", ""), ":")
  967.         Else
  968.             utc_OffsetIndex = VBA.InStr(1, utc_Parts(1), "+")
  969.             If utc_OffsetIndex = 0 Then
  970.                 utc_NegativeOffset = True
  971.                 utc_OffsetIndex = VBA.InStr(1, utc_Parts(1), "-")
  972.             End If
  973.  
  974.             If utc_OffsetIndex > 0 Then
  975.                 utc_HasOffset = True
  976.                 utc_TimeParts = VBA.Split(VBA.Left$(utc_Parts(1), utc_OffsetIndex - 1), ":")
  977.                 utc_OffsetParts = VBA.Split(VBA.Right$(utc_Parts(1), Len(utc_Parts(1)) - utc_OffsetIndex), ":")
  978.  
  979.                 Select Case UBound(utc_OffsetParts)
  980.                 Case 0
  981.                     utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), 0, 0)
  982.                 Case 1
  983.                     utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), VBA.CInt(utc_OffsetParts(1)), 0)
  984.                 Case 2
  985.                     ' VBA.Val does not use regional settings, use for seconds to avoid decimal/comma issues
  986.                    utc_Offset = TimeSerial(VBA.CInt(utc_OffsetParts(0)), VBA.CInt(utc_OffsetParts(1)), Int(VBA.Val(utc_OffsetParts(2))))
  987.                 End Select
  988.  
  989.                 If utc_NegativeOffset Then: utc_Offset = -utc_Offset
  990.             Else
  991.                 utc_TimeParts = VBA.Split(utc_Parts(1), ":")
  992.             End If
  993.         End If
  994.  
  995.         Select Case UBound(utc_TimeParts)
  996.         Case 0
  997.             ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), 0, 0)
  998.         Case 1
  999.             ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), VBA.CInt(utc_TimeParts(1)), 0)
  1000.         Case 2
  1001.             ' VBA.Val does not use regional settings, use for seconds to avoid decimal/comma issues
  1002.            ParseIso = ParseIso + VBA.TimeSerial(VBA.CInt(utc_TimeParts(0)), VBA.CInt(utc_TimeParts(1)), Int(VBA.Val(utc_TimeParts(2))))
  1003.         End Select
  1004.  
  1005.         ParseIso = ParseUtc(ParseIso)
  1006.  
  1007.         If utc_HasOffset Then
  1008.             ParseIso = ParseIso - utc_Offset
  1009.         End If
  1010.     End If
  1011.  
  1012.     Exit Function
  1013.  
  1014. utc_ErrorHandling:
  1015.     Err.Raise 10013, "UtcConverter.ParseIso", "ISO 8601 parsing error for " & utc_IsoString & ": " & Err.Number & " - " & Err.Description
  1016. End Function
  1017.  
  1018. ''
  1019. ' Convert local date to ISO 8601 string
  1020. '
  1021. ' @method ConvertToIso
  1022. ' @param {Date} utc_LocalDate
  1023. ' @return {Date} ISO 8601 string
  1024. ' @throws 10014 - ISO 8601 conversion error
  1025. ''
  1026. Public Function ConvertToIso(utc_LocalDate As Date) As String
  1027.     On Error GoTo utc_ErrorHandling
  1028.  
  1029.     ConvertToIso = VBA.Format$(ConvertToUtc(utc_LocalDate), "yyyy-mm-ddTHH:mm:ss.000Z")
  1030.  
  1031.     Exit Function
  1032.  
  1033. utc_ErrorHandling:
  1034.     Err.Raise 10014, "UtcConverter.ConvertToIso", "ISO 8601 conversion error: " & Err.Number & " - " & Err.Description
  1035. End Function
  1036.  
  1037. ' ============================================= '
  1038. ' Private Functions
  1039. ' ============================================= '
  1040.  
  1041. #If Mac Then
  1042.  
  1043. Private Function utc_ConvertDate(utc_Value As Date, Optional utc_ConvertToUtc As Boolean = False) As Date
  1044.     Dim utc_ShellCommand As String
  1045.     Dim utc_Result As utc_ShellResult
  1046.     Dim utc_Parts() As String
  1047.     Dim utc_DateParts() As String
  1048.     Dim utc_TimeParts() As String
  1049.  
  1050.     If utc_ConvertToUtc Then
  1051.         utc_ShellCommand = "date -ur `date -jf '%Y-%m-%d %H:%M:%S' " & _
  1052.             "'" & VBA.Format$(utc_Value, "yyyy-mm-dd HH:mm:ss") & "' " & _
  1053.             " +'%s'` +'%Y-%m-%d %H:%M:%S'"
  1054.     Else
  1055.         utc_ShellCommand = "date -jf '%Y-%m-%d %H:%M:%S %z' " & _
  1056.             "'" & VBA.Format$(utc_Value, "yyyy-mm-dd HH:mm:ss") & " +0000' " & _
  1057.             "+'%Y-%m-%d %H:%M:%S'"
  1058.     End If
  1059.  
  1060.     utc_Result = utc_ExecuteInShell(utc_ShellCommand)
  1061.  
  1062.     If utc_Result.utc_Output = "" Then
  1063.         Err.Raise 10015, "UtcConverter.utc_ConvertDate", "'date' command failed"
  1064.     Else
  1065.         utc_Parts = Split(utc_Result.utc_Output, " ")
  1066.         utc_DateParts = Split(utc_Parts(0), "-")
  1067.         utc_TimeParts = Split(utc_Parts(1), ":")
  1068.  
  1069.         utc_ConvertDate = DateSerial(utc_DateParts(0), utc_DateParts(1), utc_DateParts(2)) + _
  1070.             TimeSerial(utc_TimeParts(0), utc_TimeParts(1), utc_TimeParts(2))
  1071.     End If
  1072. End Function
  1073.  
  1074. Private Function utc_ExecuteInShell(utc_ShellCommand As String) As utc_ShellResult
  1075. #If VBA7 Then
  1076.     Dim utc_File As LongPtr
  1077.     Dim utc_Read As LongPtr
  1078. #Else
  1079.     Dim utc_File As Long
  1080.     Dim utc_Read As Long
  1081. #End If
  1082.  
  1083.     Dim utc_Chunk As String
  1084.  
  1085.     On Error GoTo utc_ErrorHandling
  1086.     utc_File = utc_popen(utc_ShellCommand, "r")
  1087.  
  1088.     If utc_File = 0 Then: Exit Function
  1089.  
  1090.     Do While utc_feof(utc_File) = 0
  1091.         utc_Chunk = VBA.Space$(50)
  1092.         utc_Read = CLng(utc_fread(utc_Chunk, 1, Len(utc_Chunk) - 1, utc_File))
  1093.         If utc_Read > 0 Then
  1094.             utc_Chunk = VBA.Left$(utc_Chunk, CLng(utc_Read))
  1095.             utc_ExecuteInShell.utc_Output = utc_ExecuteInShell.utc_Output & utc_Chunk
  1096.         End If
  1097.     Loop
  1098.  
  1099. utc_ErrorHandling:
  1100.     utc_ExecuteInShell.utc_ExitCode = CLng(utc_pclose(utc_File))
  1101. End Function
  1102.  
  1103. #Else
  1104.  
  1105. Private Function utc_DateToSystemTime(utc_Value As Date) As utc_SYSTEMTIME
  1106.     utc_DateToSystemTime.utc_wYear = VBA.Year(utc_Value)
  1107.     utc_DateToSystemTime.utc_wMonth = VBA.Month(utc_Value)
  1108.     utc_DateToSystemTime.utc_wDay = VBA.Day(utc_Value)
  1109.     utc_DateToSystemTime.utc_wHour = VBA.Hour(utc_Value)
  1110.     utc_DateToSystemTime.utc_wMinute = VBA.Minute(utc_Value)
  1111.     utc_DateToSystemTime.utc_wSecond = VBA.Second(utc_Value)
  1112.     utc_DateToSystemTime.utc_wMilliseconds = 0
  1113. End Function
  1114.  
  1115. Private Function utc_SystemTimeToDate(utc_Value As utc_SYSTEMTIME) As Date
  1116.     utc_SystemTimeToDate = DateSerial(utc_Value.utc_wYear, utc_Value.utc_wMonth, utc_Value.utc_wDay) + _
  1117.         TimeSerial(utc_Value.utc_wHour, utc_Value.utc_wMinute, utc_Value.utc_wSecond)
  1118. End Function
  1119.  
  1120. #End If
  1121.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement