skip to Main Content

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


  1. Chosen as BEST ANSWER

    I now understand that - from the point of view of golang code - the root issue boils down to the concept of monotonic clock reading:

    The Time returned by time.Now contains a 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 like t.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:

    var timeFormats = []string{
        "2006-01-02T15:04:05.0000000Z",
        TimeFormat,
        "2006-01-02T15:04Z",
        "2006-01-02",
    }
    

    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 simpler Format(time.RFC3339Nano).

    Their documentation about the access policy specifies the Accepted ISO 8601 UTC formats. It mentions:

    If the time value includes seconds, it may optionally also include up to 7 decimal digits of fractional seconds, following the pattern hh:mm:ss[.f{1,7}]. This pattern is supported by the Azure Storage APIs, tools, and client libraries.

    Hence Round(time.Microsecond) would still work but attempting to round with Round(time.Nanosecond) would fail.


  2. That API expect the time rounded to second. So try something like:

    ...
                SharedAccessExpiryTime: to.Ptr(expiryTime.Round(time.Second)),
    ...
                SharedAccessStartTime:  to.Ptr(startTime.Round(time.Second)),
    ...
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search