Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions pkg/postgres/time/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
Copyright The CloudNativePG Contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package time contains some useful functions to manage
// timestamps in the same representation that PostgreSQL uses.
package time
33 changes: 33 additions & 0 deletions pkg/postgres/time/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
Copyright The CloudNativePG Contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package time

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.

func TestPostgresVersion(t *testing.T) {
RegisterFailHandler(Fail)

RunSpecs(t, "Time")
}
80 changes: 80 additions & 0 deletions pkg/postgres/time/time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
Copyright The CloudNativePG Contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package time

import (
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ConvertToPostgresFormat converts timestamps to PostgreSQL time format, if needed.
// e.g. "2006-01-02T15:04:05Z07:00" --> "2006-01-02 15:04:05.000000Z07:00"
// If the conversion fails, the input timestamp is returned as it is.
func ConvertToPostgresFormat(timestamp string) string {
if t, err := time.Parse(metav1.RFC3339Micro, timestamp); err == nil {
return t.Format("2006-01-02 15:04:05.000000Z07:00")
}

if t, err := time.Parse(time.RFC3339, timestamp); err == nil {
return t.Format("2006-01-02 15:04:05.000000Z07:00")
}

return timestamp
}

// GetCurrentTimestamp returns the current timestamp as a string in RFC3339Micro format
func GetCurrentTimestamp() string {
t := time.Now()
return t.Format(metav1.RFC3339Micro)
}

// GetCurrentTimestampWithFormat returns the current timestamp as a string with the specified format
func GetCurrentTimestampWithFormat(format string) string {
t := time.Now()
return t.Format(format)
}

// DifferenceBetweenTimestamps returns the time.Duration difference between two timestamps strings in time.RFC3339.
func DifferenceBetweenTimestamps(first, second string) (time.Duration, error) {
parsedTimestamp, err := time.Parse(metav1.RFC3339Micro, first)
if err != nil {
return 0, err
}

parsedTimestampTwo, err := time.Parse(metav1.RFC3339Micro, second)
if err != nil {
return 0, err
}

return parsedTimestamp.Sub(parsedTimestampTwo), nil
}

// ToCompactISO8601 converts a time.Time into a compacted version of the ISO8601 timestamp,
// removing any separators for brevity.
//
// For example:
//
// Given: 2022-01-02 15:04:05 (UTC)
// Returns: 20220102150405
//
// This compact format is useful for generating concise, yet human-readable timestamps that
// can serve as suffixes for backup-related objects or any other contexts where space or
// character count might be a concern.
func ToCompactISO8601(t time.Time) string {
return t.Format("20060102150405")
}
88 changes: 88 additions & 0 deletions pkg/postgres/time/time_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
Copyright The CloudNativePG Contributors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package time

import (
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Time conversion", func() {
It("properly works given a string in RFC3339 format", func() {
res := ConvertToPostgresFormat("2021-09-01T10:22:47+03:00")
Expect(res).To(BeEquivalentTo("2021-09-01 10:22:47.000000+03:00"))
})
It("return same input string if not in RFC3339 format", func() {
res := ConvertToPostgresFormat("2001-09-29 01:02:03")
Expect(res).To(BeEquivalentTo("2001-09-29 01:02:03"))
})
})

var _ = Describe("Parsing targetTime", func() {
It("should calculate correctly the difference between two timestamps", func() {
By("having the first time bigger than the second", func() {
time1 := "2022-07-06T13:11:09.000000Z"
time2 := "2022-07-06T13:11:07.000000Z"
expectedSecondDifference := float64(2)
difference, err := DifferenceBetweenTimestamps(time1, time2)
Expect(err).ToNot(HaveOccurred())
Expect(difference.Seconds()).To(Equal(expectedSecondDifference))
})
By("having the first time smaller than the second", func() {
time1 := "2022-07-06T13:11:07.000000Z"
time2 := "2022-07-06T13:11:09.000000Z"
expectedSecondDifference := float64(-2)
difference, err := DifferenceBetweenTimestamps(time1, time2)
Expect(err).ToNot(HaveOccurred())
Expect(difference.Seconds()).To(Equal(expectedSecondDifference))
})
By("having first or second time wrong", func() {
time1 := "2022-07-06T13:12:09.000000Z"

_, err := DifferenceBetweenTimestamps(time1, "")
Expect(err).To(HaveOccurred())

_, err = DifferenceBetweenTimestamps("", time1)
Expect(err).To(HaveOccurred())
})
})

It("should be RFC3339Micro format", func() {
time1 := GetCurrentTimestamp()

_, err := time.Parse(metav1.RFC3339Micro, time1)
Expect(err).ToNot(HaveOccurred())
})
})

var _ = Describe("ToCompactISO8601", func() {
It("should return a string in the expected format for a given time", func() {
testTime := time.Date(2022, 0o1, 0o2, 15, 0o4, 0o5, 0, time.UTC)
compactISO8601 := ToCompactISO8601(testTime)
Expect(compactISO8601).To(Equal("20220102150405"))
})

It("should return a string of length 14", func() {
testTime := time.Now()
compactISO8601 := ToCompactISO8601(testTime)
Expect(compactISO8601).To(HaveLen(14))
})
})