Skip to content

Commit 8a2bcad

Browse files
committed
fix: normalize datetime format for Graph API event creation
Graph API requires dateTime without timezone offset when timeZone field is set. Added parseFlexibleDateTime helper that accepts multiple input formats (RFC3339, ISO, date+space+time) and strips the offset. Fixes HTTP 400 'unable to deserialize' on cal create.
1 parent 8bddfb1 commit 8a2bcad

1 file changed

Lines changed: 47 additions & 2 deletions

File tree

internal/cal/cal.go

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,40 @@ func List(cfg *config.Config, fromDate, toDate time.Time, search, account string
135135
return nil
136136
}
137137

138+
// parseFlexibleDateTime parses various datetime formats and converts to the configured timezone
139+
func parseFlexibleDateTime(input, timezoneName string) (string, error) {
140+
loc, err := time.LoadLocation(timezoneName)
141+
if err != nil {
142+
return "", fmt.Errorf("failed to load timezone %s: %w", timezoneName, err)
143+
}
144+
145+
// Try parsing various formats
146+
formats := []string{
147+
time.RFC3339, // "2026-03-04T10:00:00+01:00"
148+
"2006-01-02T15:04:05", // "2026-03-04T10:00:00"
149+
"2006-01-02 15:04", // "2026-03-04 10:00"
150+
}
151+
152+
var parsed time.Time
153+
for _, format := range formats {
154+
t, err := time.Parse(format, input)
155+
if err == nil {
156+
parsed = t
157+
break
158+
}
159+
}
160+
161+
if parsed.IsZero() {
162+
return "", fmt.Errorf("unable to parse datetime: %s", input)
163+
}
164+
165+
// Convert to configured timezone
166+
inZone := parsed.In(loc)
167+
168+
// Format without offset for Graph API
169+
return inZone.Format("2006-01-02T15:04:05.0000000"), nil
170+
}
171+
138172
// Create creates a new calendar event
139173
func Create(cfg *config.Config, account, subject, start, end, location, body string, attendees []string, force bool) error {
140174
// Check cross-tenant unless force is enabled
@@ -150,17 +184,28 @@ func Create(cfg *config.Config, account, subject, start, end, location, body str
150184
return err
151185
}
152186

187+
// Parse and convert datetimes to configured timezone
188+
startDateTime, err := parseFlexibleDateTime(start, cfg.Timezone)
189+
if err != nil {
190+
return fmt.Errorf("invalid start datetime: %w", err)
191+
}
192+
193+
endDateTime, err := parseFlexibleDateTime(end, cfg.Timezone)
194+
if err != nil {
195+
return fmt.Errorf("invalid end datetime: %w", err)
196+
}
197+
153198
// Create event
154199
client := graph.NewClient(token)
155200

156201
event := &graph.Event{
157202
Subject: subject,
158203
Start: graph.DateTime{
159-
DateTime: start,
204+
DateTime: startDateTime,
160205
TimeZone: cfg.Timezone,
161206
},
162207
End: graph.DateTime{
163-
DateTime: end,
208+
DateTime: endDateTime,
164209
TimeZone: cfg.Timezone,
165210
},
166211
}

0 commit comments

Comments
 (0)