package calendar import ( "context" "encoding/json" "fmt" "net/http" "strings" "time" ) type Holiday struct { Date string `json:"date"` Name string `json:"name"` Type string `json:"type"` } type Provider interface { NationalHolidays(ctx context.Context, year int) ([]Holiday, error) } type CommemorativeProvider interface { CommemorativeDates(ctx context.Context, year int) ([]ExternalCommemorativeDate, error) } type ExternalCommemorativeDate struct { Date string Name string Description string SourceID string } type BrasilAPIProvider struct { baseURL string client *http.Client } type FeriadosBrasilProvider struct { baseURL string client *http.Client } func NewFeriadosBrasilProvider() FeriadosBrasilProvider { return FeriadosBrasilProvider{ baseURL: "https://raw.githubusercontent.com/joaopbini/feriados-brasil/master/dados/comemorativas/json", client: &http.Client{ Timeout: 10 * time.Second, }, } } type feriadosBrasilCommemorativeDate struct { ID string `json:"id"` Data string `json:"data"` Nome string `json:"nome"` Tipo string `json:"tipo"` Descricao string `json:"descricao"` UF string `json:"uf"` CodigoIBGE *int `json:"codigo_ibge"` } func (p FeriadosBrasilProvider) CommemorativeDates(ctx context.Context, year int) ([]ExternalCommemorativeDate, error) { url := fmt.Sprintf("%s/%d.json", p.baseURL, year) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err } resp, err := p.client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("feriados-brasil returned status %d", resp.StatusCode) } var records []feriadosBrasilCommemorativeDate if err := json.NewDecoder(resp.Body).Decode(&records); err != nil { return nil, err } dates := make([]ExternalCommemorativeDate, 0, len(records)) for _, record := range records { if strings.ToUpper(strings.TrimSpace(record.Tipo)) != "COMEMORATIVA" { continue } parsed, err := time.Parse("02/01/2006", strings.TrimSpace(record.Data)) if err != nil { continue } dates = append(dates, ExternalCommemorativeDate{ Date: parsed.Format("2006-01-02"), Name: strings.TrimSpace(record.Nome), Description: strings.TrimSpace(record.Descricao), SourceID: strings.TrimSpace(record.ID), }) } return dates, nil } func NewBrasilAPIProvider(baseURL string) BrasilAPIProvider { return BrasilAPIProvider{ baseURL: baseURL, client: &http.Client{ Timeout: 10 * time.Second, }, } } func (p BrasilAPIProvider) NationalHolidays(ctx context.Context, year int) ([]Holiday, error) { url := fmt.Sprintf("%s/api/feriados/v1/%d", p.baseURL, year) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err } resp, err := p.client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("brasilapi returned status %d", resp.StatusCode) } var holidays []Holiday if err := json.NewDecoder(resp.Body).Decode(&holidays); err != nil { return nil, err } return holidays, nil }