Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@azure-tools/typespec-go"
---

Fix the example generator emitting incorrect code for `rfc7231` datatime.
2 changes: 1 addition & 1 deletion packages/typespec-go/docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,4 @@ Before you do, make sure to:

1. Format your code (`pnpm format` from the repo root, or check with `pnpm format:check`).
2. Rebuild and regenerate everything so the committed fixtures reflect your change.
3. Add a changelog entry with [Chronus](https://github.com/microsoft/chronus) by running `pnpm exec chronus add` from the repo root and following the prompts. This creates a change file under `.chronus/changes/` that you should commit with your PR.
3. Add a changelog entry with [Chronus](https://github.com/microsoft/chronus) by running `pnpm change add` from the repo root and following the prompts. This creates a change file under `.chronus/changes/` that you should commit with your PR.
1 change: 1 addition & 0 deletions packages/typespec-go/src/codegen/core/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ function getTimeValue(type: go.Time, value: any, imports?: ImportManager): strin
PlainTime: helpers.plainTimeFormat,
RFC1123: helpers.RFC1123Format,
RFC3339: helpers.RFC3339Format,
RFC7231: helpers.RFC1123Format,
};

if (type.format in formatMap) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Generated by `pnpm gen:scenario-suites`. Do not edit by hand.
import { resolvePath } from "@typespec/compiler";
import { describeScenarioFile } from "../scenario-runner.js";

describeScenarioFile(resolvePath(import.meta.dirname, "../scenarios/rfc7231-datetime-sample.md"));
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# A `utcDateTime` property encoded as `rfc7231` generates a sample that parses the value with `time.RFC1123`

```yaml
generate-samples: true
```

## TypeSpec

```tsp
@armProviderNamespace
namespace Microsoft.Test;

union ProvisioningState {
string,
Succeeded: "Succeeded",
Failed: "Failed",
Canceled: "Canceled",
}

model WidgetProperties {
description?: string;

@encode("rfc7231")
lastExecuted?: utcDateTime;

@visibility(Lifecycle.Read)
provisioningState?: ProvisioningState;
}

model Widget is TrackedResource<WidgetProperties> {
@path
@key("widgetName")
@segment("widgets")
name: string;
}

@armResourceOperations
interface Widgets {
put is ArmResourceCreateOrReplaceSync<Widget>;
}
```

## The example inputs

```json examples/Widgets_put.json
{
"operationId": "Widgets_put",
"title": "Widgets_put",
"parameters": {
"api-version": "2025-01-01",
"subscriptionId": "00000000-0000-0000-0000-000000000000",
"resourceGroupName": "myResourceGroup",
"widgetName": "myWidget",
"resource": {
"location": "eastus",
"properties": {
"description": "my widget",
"lastExecuted": "05 May 2021 12:00:23 GMT"
}
}
},
"responses": {
"200": {
"body": {
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.Test/widgets/myWidget",
"name": "myWidget",
"type": "Microsoft.Test/widgets",
"location": "eastus",
"properties": {
"description": "my widget",
"lastExecuted": "05 May 2021 12:00:23 GMT",
"provisioningState": "Succeeded"
}
}
}
}
}
```

## The generated sample parses the `rfc7231` datetime with `time.RFC1123`

```go widgets_client_example_test
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// Code generated by Microsoft (R) Go Code Generator. DO NOT EDIT.

package testmodule_test

import (
"context"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"log"
"testmodule"
"time"
)

// Generated from example definition: Widgets_put.json
func ExampleWidgetsClient_Put() {
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
log.Fatalf("failed to obtain a credential: %v", err)
}
ctx := context.Background()
clientFactory, err := testmodule.NewClientFactory("00000000-0000-0000-0000-000000000000", cred, nil)
if err != nil {
log.Fatalf("failed to create client: %v", err)
}
res, err := clientFactory.NewWidgetsClient().Put(ctx, "2025-01-01", "myResourceGroup", "myWidget", testmodule.Widget{
Location: to.Ptr("eastus"),
Properties: &testmodule.WidgetProperties{
Description: to.Ptr("my widget"),
LastExecuted: to.Ptr(func() time.Time { t, _ := time.Parse(time.RFC1123, "05 May 2021 12:00:23 GMT"); return t }()),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Callers should never need to manually parse time.Time into the wire format (we go to great lengths to not leak that implementation detail). Why do we need to do this here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the example value is in wire format: 05 May 2021 12:00:23 GMT, we need to manually parse it into time.Time. For sdk customers, they don't need this logic, just give the time.Time is ok.

I'm not sure if you mean that we should analyze the wire format in the code, and then generate unified code like to.Ptr(time.Date(2021, time.May, 5, 12, 0, 23, 0, time.UTC))

@jhendrixMSFT jhendrixMSFT Jul 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your example is correct.

The emitted code handles conversion to the correct wire format. All we should need to know is if we need to emit time.UTC or time.Local (the former should be the vast majority of cases.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI I'm going to fix this as part of #4956 which is in progress.

},
}, nil)
if err != nil {
log.Fatalf("failed to finish the request: %v", err)
}
// You could use response here. We use blank identifier for just demo purposes.
_ = res
// If the HTTP response code is 200 as defined in example definition, your response structure would look as follows. Please pay attention that all the values in the output are fake values for just demo purposes.
// res = testmodule.WidgetsClientPutResponse{
// Widget: testmodule.Widget{
// ID: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.Test/widgets/myWidget"),
// Name: to.Ptr("myWidget"),
// Type: to.Ptr("Microsoft.Test/widgets"),
// Location: to.Ptr("eastus"),
// Properties: &testmodule.WidgetProperties{
// Description: to.Ptr("my widget"),
// LastExecuted: to.Ptr(func() time.Time { t, _ := time.Parse(time.RFC1123, "05 May 2021 12:00:23 GMT"); return t}()),
// ProvisioningState: to.Ptr(testmodule.ProvisioningStateSucceeded),
// },
// },
// }
}
```
Loading