diff --git a/v3/common/utility.go b/v3/common/utility.go index 65165bf..239b156 100644 --- a/v3/common/utility.go +++ b/v3/common/utility.go @@ -3,11 +3,18 @@ package exifcommon import ( "bytes" "fmt" + "reflect" + "strconv" + "strings" "time" "github.com/dsoprea/go-logging" ) +var ( + timeType = reflect.TypeOf(time.Time{}) +) + // DumpBytes prints a list of hex-encoded bytes. func DumpBytes(data []byte) { fmt.Printf("DUMP: ") @@ -77,3 +84,61 @@ func ExifFullTimestampString(t time.Time) (fullTimestampPhrase string) { return fmt.Sprintf("%04d:%02d:%02d %02d:%02d:%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) } + +// ParseExifFullTimestamp parses dates like "2018:11:30 13:01:49" into a UTC +// `time.Time` struct. +func ParseExifFullTimestamp(fullTimestampPhrase string) (timestamp time.Time, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + parts := strings.Split(fullTimestampPhrase, " ") + datestampValue, timestampValue := parts[0], parts[1] + + dateParts := strings.Split(datestampValue, ":") + + year, err := strconv.ParseUint(dateParts[0], 10, 16) + if err != nil { + log.Panicf("could not parse year") + } + + month, err := strconv.ParseUint(dateParts[1], 10, 8) + if err != nil { + log.Panicf("could not parse month") + } + + day, err := strconv.ParseUint(dateParts[2], 10, 8) + if err != nil { + log.Panicf("could not parse day") + } + + timeParts := strings.Split(timestampValue, ":") + + hour, err := strconv.ParseUint(timeParts[0], 10, 8) + if err != nil { + log.Panicf("could not parse hour") + } + + minute, err := strconv.ParseUint(timeParts[1], 10, 8) + if err != nil { + log.Panicf("could not parse minute") + } + + second, err := strconv.ParseUint(timeParts[2], 10, 8) + if err != nil { + log.Panicf("could not parse second") + } + + timestamp = time.Date(int(year), time.Month(month), int(day), int(hour), int(minute), int(second), 0, time.UTC) + return timestamp, nil +} + +// IsTime returns true if the value is a `time.Time`. +func IsTime(v interface{}) bool { + + // TODO(dustin): Add test + + return reflect.TypeOf(v) == timeType +} diff --git a/v3/common/utility_test.go b/v3/common/utility_test.go index d53dcdb..42b0c35 100644 --- a/v3/common/utility_test.go +++ b/v3/common/utility_test.go @@ -1,8 +1,11 @@ package exifcommon import ( + "fmt" "testing" - // "github.com/dsoprea/go-logging" + "time" + + "github.com/dsoprea/go-logging" ) func TestDumpBytes(t *testing.T) { @@ -26,3 +29,52 @@ func TestDumpBytesClauseToString(t *testing.T) { t.Fatalf("Stringified clause is not correct: [%s]", s) } } + +func TestExifFullTimestampString(t *testing.T) { + originalPhrase := "2018:11:30 13:01:49" + + timestamp, err := ParseExifFullTimestamp(originalPhrase) + log.PanicIf(err) + + restoredPhrase := ExifFullTimestampString(timestamp) + if restoredPhrase != originalPhrase { + t.Fatalf("Final phrase [%s] does not equal original phrase [%s]", restoredPhrase, originalPhrase) + } +} + +func ExampleExifFullTimestampString() { + originalPhrase := "2018:11:30 13:01:49" + + timestamp, err := ParseExifFullTimestamp(originalPhrase) + log.PanicIf(err) + + restoredPhrase := ExifFullTimestampString(timestamp) + fmt.Printf("To EXIF timestamp: [%s]\n", restoredPhrase) + + // Output: + // To EXIF timestamp: [2018:11:30 13:01:49] +} + +func TestParseExifFullTimestamp(t *testing.T) { + timestamp, err := ParseExifFullTimestamp("2018:11:30 13:01:49") + log.PanicIf(err) + + actual := timestamp.Format(time.RFC3339) + expected := "2018-11-30T13:01:49Z" + + if actual != expected { + t.Fatalf("time not formatted correctly: [%s] != [%s]", actual, expected) + } +} + +func ExampleParseExifFullTimestamp() { + originalPhrase := "2018:11:30 13:01:49" + + timestamp, err := ParseExifFullTimestamp(originalPhrase) + log.PanicIf(err) + + fmt.Printf("To Go timestamp: [%s]\n", timestamp.Format(time.RFC3339)) + + // Output: + // To Go timestamp: [2018-11-30T13:01:49Z] +} diff --git a/v3/tags.go b/v3/tags.go index f53d9ce..d0dfb1e 100644 --- a/v3/tags.go +++ b/v3/tags.go @@ -114,7 +114,7 @@ func (it *IndexedTag) Is(ifdPath string, id uint16) bool { // GetEncodingType returns the largest type that this tag's value can occupy. func (it *IndexedTag) GetEncodingType(value interface{}) exifcommon.TagTypePrimitive { // For convenience, we handle encoding a `time.Time` directly. - if IsTime(value) == true { + if exifcommon.IsTime(value) == true { // Timestamps are encoded as ASCII. value = "" } diff --git a/v3/utility.go b/v3/utility.go index 7a5ff26..fe31082 100644 --- a/v3/utility.go +++ b/v3/utility.go @@ -2,12 +2,7 @@ package exif import ( "fmt" - "io" "math" - "reflect" - "strconv" - "strings" - "time" "github.com/dsoprea/go-logging" @@ -19,69 +14,6 @@ var ( utilityLogger = log.NewLogger("exif.utility") ) -var ( - timeType = reflect.TypeOf(time.Time{}) -) - -// ParseExifFullTimestamp parses dates like "2018:11:30 13:01:49" into a UTC -// `time.Time` struct. -func ParseExifFullTimestamp(fullTimestampPhrase string) (timestamp time.Time, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - parts := strings.Split(fullTimestampPhrase, " ") - datestampValue, timestampValue := parts[0], parts[1] - - dateParts := strings.Split(datestampValue, ":") - - year, err := strconv.ParseUint(dateParts[0], 10, 16) - if err != nil { - log.Panicf("could not parse year") - } - - month, err := strconv.ParseUint(dateParts[1], 10, 8) - if err != nil { - log.Panicf("could not parse month") - } - - day, err := strconv.ParseUint(dateParts[2], 10, 8) - if err != nil { - log.Panicf("could not parse day") - } - - timeParts := strings.Split(timestampValue, ":") - - hour, err := strconv.ParseUint(timeParts[0], 10, 8) - if err != nil { - log.Panicf("could not parse hour") - } - - minute, err := strconv.ParseUint(timeParts[1], 10, 8) - if err != nil { - log.Panicf("could not parse minute") - } - - second, err := strconv.ParseUint(timeParts[2], 10, 8) - if err != nil { - log.Panicf("could not parse second") - } - - timestamp = time.Date(int(year), time.Month(month), int(day), int(hour), int(minute), int(second), 0, time.UTC) - return timestamp, nil -} - -// ExifFullTimestampString produces a string like "2018:11:30 13:01:49" from a -// `time.Time` struct. It will attempt to convert to UTC first. -func ExifFullTimestampString(t time.Time) (fullTimestampPhrase string) { - - // RELEASE(dustin): Dump this for the next release. It duplicates the same function now in exifcommon. - - return exifcommon.ExifFullTimestampString(t) -} - // ExifTag is one simple representation of a tag in a flat list of all of them. type ExifTag struct { // IfdPath is the fully-qualified IFD path (even though it is not named as @@ -231,16 +163,3 @@ func GpsDegreesEquals(gi1, gi2 GpsDegrees) bool { return true } - -// IsTime returns true if the value is a `time.Time`. -func IsTime(v interface{}) bool { - return reflect.TypeOf(v) == timeType -} - -// TODO(dustin): !! For debugging -func mustGetCurrentOffset(s io.Seeker) int64 { - offset, err := s.Seek(0, io.SeekCurrent) - log.PanicIf(err) - - return offset -} diff --git a/v3/utility_test.go b/v3/utility_test.go index e5d7eb7..76ebdd7 100644 --- a/v3/utility_test.go +++ b/v3/utility_test.go @@ -1,62 +1,9 @@ package exif import ( - "fmt" "testing" - "time" - - "github.com/dsoprea/go-logging" ) -func TestParseExifFullTimestamp(t *testing.T) { - timestamp, err := ParseExifFullTimestamp("2018:11:30 13:01:49") - log.PanicIf(err) - - actual := timestamp.Format(time.RFC3339) - expected := "2018-11-30T13:01:49Z" - - if actual != expected { - t.Fatalf("time not formatted correctly: [%s] != [%s]", actual, expected) - } -} - -func TestExifFullTimestampString(t *testing.T) { - originalPhrase := "2018:11:30 13:01:49" - - timestamp, err := ParseExifFullTimestamp(originalPhrase) - log.PanicIf(err) - - restoredPhrase := ExifFullTimestampString(timestamp) - if restoredPhrase != originalPhrase { - t.Fatalf("Final phrase [%s] does not equal original phrase [%s]", restoredPhrase, originalPhrase) - } -} - -func ExampleParseExifFullTimestamp() { - originalPhrase := "2018:11:30 13:01:49" - - timestamp, err := ParseExifFullTimestamp(originalPhrase) - log.PanicIf(err) - - fmt.Printf("To Go timestamp: [%s]\n", timestamp.Format(time.RFC3339)) - - // Output: - // To Go timestamp: [2018-11-30T13:01:49Z] -} - -func ExampleExifFullTimestampString() { - originalPhrase := "2018:11:30 13:01:49" - - timestamp, err := ParseExifFullTimestamp(originalPhrase) - log.PanicIf(err) - - restoredPhrase := ExifFullTimestampString(timestamp) - fmt.Printf("To EXIF timestamp: [%s]\n", restoredPhrase) - - // Output: - // To EXIF timestamp: [2018:11:30 13:01:49] -} - func TestGpsDegreesEquals_Equals(t *testing.T) { gi := GpsDegrees{ Orientation: 'A',