Consider the following golang
today := time.Now().Format("2006-01-02T03:04:05.9999999Z")
t, _ := time.Parse(time.RFC3339Nano, today)
Why isn’t that equivalent to the much simpler
t := time.Now()
?
I would like to confirm whether the above code is equivalent, as I believe it is, or why it isn’t, based on my current experience. I also provide additional context regarding what I’ve attempted.
My full repo code here in GitHub in the main.go
file. However, for the purpose of this question, let’s focus on the code snippet provided below.
sasToken, err := client.ListAccountSAS(
ctx, resourceGroupName, storageAccountName, armstorage.AccountSasParameters{
KeyToSign: to.Ptr("key1"),
SharedAccessExpiryTime: to.Ptr(func() time.Time {
t, _ := time.Parse(time.RFC3339Nano, tomorrow); return t }()),
Permissions: to.Ptr(armstorage.PermissionsR),
Protocols: to.Ptr(armstorage.HTTPProtocolHTTPSHTTP),
ResourceTypes: to.Ptr(armstorage.SignedResourceTypesS),
Services: to.Ptr(armstorage.ServicesB),
SharedAccessStartTime: to.Ptr(func() time.Time {
t, _ := time.Parse(time.RFC3339Nano, today); return t }()),
}, nil)
Currently, this code is functioning as expected. However, when I simplify the last line to t := time.Now()
, it results in an error (shown in Run job of Github Actions).
today 2023-09-18T07:54:42.6314723Z should be formatted as 2023-09-16T11:42:03.1567373Z
tomorrow 2023-09-19T07:54:42.6314723Z should be formattes as 2023-09-17T11:42:03.1567373Z
2023/09/18 07:54:43 POST https://management.azure.com/subscriptions/***/resourceGroups/go-azure-sdk/providers/Microsoft.Storage/storageAccounts/golangazure/ListAccountSas
--------------------------------------------------------------------------------
RESPONSE 400: 400 Bad Request
ERROR CODE: InvalidValuesForRequestParameters
--------------------------------------------------------------------------------
{
"error": {
"code": "InvalidValuesForRequestParameters",
"message": "Values for request parameters are invalid: signedStart."
}
}
--------------------------------------------------------------------------------
exit status 1
This has led me to question why the equivalence doesn’t hold, contrary to my initial expectation.
2
Answers
I now understand that - from the point of view of golang code - the root issue boils down to the concept of monotonic clock reading:
This inclusion of the monotonic clock reading is what can cause issues in certain scenarios, possibly due to some internal interpretation of the parameter. However, there is no such monotonic clock reading when using the
time.Parse
function.It's worth noting that because the monotonic clock reading has no meaning outside the current process, various functions and methods in Go's
time
package handle it differently. Functions liket.Round(d)
strip the monotonic clock reading from their results. This behavior is why the accepted answer works, as it leverages such functions to achieve the desired outcome.Edit
I've also located where the problem likely resides in the SDK source code. The issue lies in the time formats used by the SDK, which are as follows:
The crucial detail here is that the first time format contains only 7 decimal places. This explains why I had to use
Format("2006-01-02T03:04:05.9999999Z")
instead of the simplerFormat(time.RFC3339Nano)
.Their documentation about the access policy specifies the Accepted ISO 8601 UTC formats. It mentions:
Hence
Round(time.Microsecond)
would still work but attempting to round withRound(time.Nanosecond)
would fail.That API expect the time rounded to second. So try something like: