huma icon indicating copy to clipboard operation
huma copied to clipboard

Date response format is returned as date-time

Open pgr0ss opened this issue 8 months ago • 1 comments

I have an API which specifies that the response is a date, not a date-time. Since go doesn't have a date type, I'm using time.Time but want it to return a date like 2025-03-11. I have configured my response object like this:

Date time.Time `json:"date" format:"date"`

And the docs recognize that it's a date type:

Image

But the API still returns the full timestamp without errors or warnings that I can see:

> curl localhost:8080/test
{"$schema":"http://localhost:8080/schemas/GreetingOutputBody.json","date":"2025-03-11T12:38:07.655129-07:00"}

Full code (adapted from https://huma.rocks/tutorial/your-first-api/, using huma v2.31.0):

package main

import (
	"context"
	"net/http"
	"time"

	"github.com/danielgtaylor/huma/v2"
	"github.com/danielgtaylor/huma/v2/adapters/humachi"
	"github.com/go-chi/chi/v5"

	_ "github.com/danielgtaylor/huma/v2/formats/cbor"
)

type GreetingOutput struct {
	Body struct {
		Date time.Time `json:"date" format:"date"`
	}
}

func main() {
	router := chi.NewMux()
	api := humachi.New(router, huma.DefaultConfig("My API", "1.0.0"))

	huma.Get(api, "/test", func(ctx context.Context, input *struct{}) (*GreetingOutput, error) {
		resp := &GreetingOutput{}
		resp.Body.Date = time.Now()
		return resp, nil
	})

	err := http.ListenAndServe("127.0.0.1:8080", router)
	if err != nil {
		panic(err)
	}
}

Is this a bug? Or is there something else I need to do to properly format (and/or validate) the response? Thanks!

pgr0ss avatar Mar 11 '25 19:03 pgr0ss

@pgr0ss thanks for opening an issue for this! It isn't actually a Huma thing, but has to do with how Golang serializes time.Time structs, while the format tag is just informational in the generated OpenAPI. Unfortunately there is no field tag to tell Golang's JSON marshaler what format to use, you just always get ISO8601 output by default. You have two options:

  1. Create a custom Date type which serializes how you want by implementing encoding.TextMarshaler.
  2. Use a string and set it in your handler code.

https://go.dev/play/p/_lHajInSNiC

Option 1:

type Date time.Time

func (d Date) MarshalText() ([]byte, error) {
	return []byte(time.Time(d).Format("2006-01-02")), nil
}

func (d *Date) TransformSchema(r huma.Registry, s *huma.Schema) *huma.Schema {
	s.Format = "date"
	return s
}

type DateResponse struct {
	Body struct {
		Date Date `json:"date"`
	}
}

huma.Get(api, "/date1", func(ctx context.Context, input *struct{}) (*DateResponse, error) {
	resp := &DateResponse{}
	resp.Body.Date = Date(time.Now())
	return resp, nil
})

Option 2:

type DateStringResponse struct {
	Body struct {
		Date string `json:"date" format:"date"`
	}
}

huma.Get(api, "/date2", func(ctx context.Context, input *struct{}) (*DateStringResponse, error) {
	resp := &DateStringResponse{}
	resp.Body.Date = time.Now().Format("2006-01-02")
	return resp, nil
})

Hope that helps!

danielgtaylor avatar Mar 15 '25 19:03 danielgtaylor