From 6727b479755d4e4fa807167de0e0a0be53db67f4 Mon Sep 17 00:00:00 2001 From: Jakub Date: Fri, 13 Mar 2026 12:17:26 +0100 Subject: [PATCH 1/2] fix(bitbucket): handle microsecond precision timestamps in PR extractor Bitbucket Cloud returns timestamps with 6-digit fractional seconds (e.g. 2025-09-15T08:53:36.337932+00:00). The PR extractor used time.Time directly for created_on/updated_on fields, causing a time.ParseError since Go's json.Unmarshal uses RFC3339 which does not accept microseconds. Fix: - Change BitbucketCreatedAt/BitbucketUpdatedAt in BitbucketApiPullRequest struct from time.Time to *common.Iso8601Time - Add microsecond format (6-digit fractional seconds) to DateTimeFormats in Iso8601Time, before the existing 3-digit format, so that ConvertStringToTime correctly parses these timestamps Fixes #8708 --- backend/core/models/common/iso8601time.go | 4 ++++ backend/plugins/bitbucket/tasks/pr_extractor.go | 9 ++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/backend/core/models/common/iso8601time.go b/backend/core/models/common/iso8601time.go index 8b49de2083a..a0570d00944 100644 --- a/backend/core/models/common/iso8601time.go +++ b/backend/core/models/common/iso8601time.go @@ -56,6 +56,10 @@ func init() { Matcher: regexp.MustCompile(`[+-][\d]{4}$`), Format: "2006-01-02T15:04:05-0700", }, + { + Matcher: regexp.MustCompile(`[\d]{6}[+-][\d]{2}:[\d]{2}$`), + Format: "2006-01-02T15:04:05.000000-07:00", + }, { Matcher: regexp.MustCompile(`[\d]{3}[+-][\d]{2}:[\d]{2}$`), Format: "2006-01-02T15:04:05.000-07:00", diff --git a/backend/plugins/bitbucket/tasks/pr_extractor.go b/backend/plugins/bitbucket/tasks/pr_extractor.go index 5edb75e284b..efb43b15da2 100644 --- a/backend/plugins/bitbucket/tasks/pr_extractor.go +++ b/backend/plugins/bitbucket/tasks/pr_extractor.go @@ -19,7 +19,6 @@ package tasks import ( "encoding/json" - "time" "github.com/apache/incubator-devlake/core/errors" "github.com/apache/incubator-devlake/core/models/common" @@ -57,8 +56,8 @@ type BitbucketApiPullRequest struct { } `json:"links"` ClosedBy *BitbucketAccountResponse `json:"closed_by"` Author *BitbucketAccountResponse `json:"author"` - BitbucketCreatedAt time.Time `json:"created_on"` - BitbucketUpdatedAt time.Time `json:"updated_on"` + BitbucketCreatedAt *common.Iso8601Time `json:"created_on"` + BitbucketUpdatedAt *common.Iso8601Time `json:"updated_on"` BaseRef *struct { Branch struct { Name string `json:"name"` @@ -174,8 +173,8 @@ func convertBitbucketPullRequest(pull *BitbucketApiPullRequest, connId uint64, r Url: pull.Links.Html.Href, Type: pull.Type, CommentCount: pull.CommentCount, - BitbucketCreatedAt: pull.BitbucketCreatedAt, - BitbucketUpdatedAt: pull.BitbucketUpdatedAt, + BitbucketCreatedAt: pull.BitbucketCreatedAt.ToTime(), + BitbucketUpdatedAt: pull.BitbucketUpdatedAt.ToTime(), } if pull.BaseRef != nil { if pull.BaseRef.Repo != nil { From 49312047c3a1e233551fc13a3af615d9d1f012ee Mon Sep 17 00:00:00 2001 From: Jakub Date: Fri, 13 Mar 2026 12:21:10 +0100 Subject: [PATCH 2/2] test(common): add test cases for millisecond and microsecond timestamps Adds ConvertStringToTime test cases for: - 3-digit millisecond precision (existing format) - 6-digit microsecond precision (Bitbucket Cloud format, fixes #8708) --- backend/core/models/common/iso8601time_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/core/models/common/iso8601time_test.go b/backend/core/models/common/iso8601time_test.go index b984a6632d9..4307345da7b 100644 --- a/backend/core/models/common/iso8601time_test.go +++ b/backend/core/models/common/iso8601time_test.go @@ -145,6 +145,18 @@ func TestConvertStringToTime(t *testing.T) { output: time.Date(2023, 3, 1, 0, 0, 0, 0, time.UTC), err: nil, }, + { + name: "Valid time string with milliseconds", + input: "2025-09-15T08:53:36.337+00:00", + output: time.Date(2025, 9, 15, 8, 53, 36, 337000000, time.UTC), + err: nil, + }, + { + name: "Valid time string with microseconds (Bitbucket Cloud format)", + input: "2025-09-15T08:53:36.337932+00:00", + output: time.Date(2025, 9, 15, 8, 53, 36, 337932000, time.UTC), + err: nil, + }, { name: "Invalid time string", input: "invalid",