actorcat

Crypto Prices

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