diff --git a/types.go b/types.go index 7d04dd2..c600aa4 100644 --- a/types.go +++ b/types.go @@ -9,6 +9,7 @@ import ( "regexp" "strconv" "strings" + "time" "unicode" ) @@ -384,6 +385,24 @@ func ParseDate(ddmmyy string) (Date, error) { return Date{true, dd, mm, yy}, nil } +// DateTime converts the provided Date and Time values to a standard UTC time.Time. +// The referenceYear parameter is used to determine the offset (century) for the two-digit year in Date. +// For example, if the referenceYear is 2024, the offset used is 2000; and the input date's year is prepended with 20. +// If referenceYear is 0, the current UTC year is used. +// If either Date or Time is not valid, DateTime returns the zero time.Time. +func DateTime(referenceYear int, d Date, t Time) time.Time { + if !d.Valid || !t.Valid { + return time.Time{} + } + if referenceYear == 0 { + referenceYear = time.Now().UTC().Year() + } + century := referenceYear / 100 * 100 // truncate the last two digits (year within century); keep first two digits of the full year + return time.Date(century+d.YY, time.Month(d.MM), d.DD, + t.Hour, t.Minute, t.Second, t.Millisecond*1e6, + time.UTC) +} + // LatDir returns the latitude direction symbol func LatDir(l float64) string { if l < 0.0 { diff --git a/types_test.go b/types_test.go index e261462..da630a1 100644 --- a/types_test.go +++ b/types_test.go @@ -3,6 +3,7 @@ package nmea import ( "fmt" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -220,6 +221,60 @@ func TestDateString(t *testing.T) { } } +func TestDateTime(t *testing.T) { + for i, testCase := range []struct { + refYear int + date Date + time Time + expect time.Time + }{ + { + refYear: 2024, + date: Date{DD: 1, MM: 2, YY: 3, Valid: true}, + time: Time{Hour: 1, Minute: 2, Second: 3, Millisecond: 4, Valid: true}, + expect: time.Date(2003, time.February, 1, 1, 2, 3, 4e6, time.UTC), + }, + { + refYear: 1999, + date: Date{DD: 1, MM: 2, YY: 3, Valid: true}, + time: Time{Hour: 1, Minute: 2, Second: 3, Millisecond: 4, Valid: true}, + expect: time.Date(1903, time.February, 1, 1, 2, 3, 4e6, time.UTC), + }, + { + refYear: 2025, + date: Date{DD: 23, MM: 7, YY: 24, Valid: true}, + time: Time{Hour: 18, Minute: 5, Second: 12, Millisecond: 1, Valid: true}, + expect: time.Date(2024, time.July, 23, 18, 5, 12, 1e6, time.UTC), + }, + // zero reference year; this test will fail starting in the year 3000 + { + refYear: 0, + date: Date{DD: 1, MM: 2, YY: 3, Valid: true}, + time: Time{Hour: 1, Minute: 2, Second: 3, Millisecond: 4, Valid: true}, + expect: time.Date(2003, time.February, 1, 1, 2, 3, 4e6, time.UTC), + }, + // invalid date + { + refYear: 2024, + date: Date{DD: 1, MM: 2, YY: 3}, + time: Time{Hour: 1, Minute: 2, Second: 3, Millisecond: 4, Valid: true}, + expect: time.Time{}, + }, + // invalid time + { + refYear: 2024, + date: Date{DD: 1, MM: 2, YY: 3, Valid: true}, + time: Time{Hour: 1, Minute: 2, Second: 3, Millisecond: 4}, + expect: time.Time{}, + }, + } { + actual := DateTime(testCase.refYear, testCase.date, testCase.time) + if !actual.Equal(testCase.expect) { + t.Fatalf("Test %d (refYear=%d date=%s time=%s): Expected %s but got %s", i, testCase.refYear, testCase.date, testCase.time, testCase.expect, actual) + } + } +} + func TestLatDir(t *testing.T) { tests := []struct { value float64