Here's what I have so far. I was hoping to include a "batch change" feature, but I just didin't have the time to include here. 
The batch change would come in handy for me, because at one point, my camera timestamp was off by -1 hour -6 minutes. The batch change will adjust/change dates on all selected files by adding/subtracting the time offset you choose, in my case +1 hour + 6 minutes.
This was tested using images from a Canon A75 and Olympus D550. It may or may not work with other cameras, so if it doesn't work for you, let me know the model of your camera and I'll see if I can get it to work for you. 
Code: Select all
UseJPEGImageDecoder() 
;--> Window Constants
Enumeration 1
  #Window_Main
EndEnumeration
;--> Gadget Constants
Enumeration 
  ;--> These must be 0, 1, 2
  #Date_Modified_Write
  #Date_Taken_Write
  #Date_Digitized_Write
  ;--> These can be any number
  #Date_All_Write
  #ExplorerTree_0
  #Frame3D_Thumb
  #ImageGad_Thumb
  #Frame3D_Modified
  #Text_Modified_Read
  #Text_New_0
  #Frame3D_Taken
  #Text_Taken_Read
  #Text_New_1
  #Frame3D_Digitized
  #Text_Digitized_Read
  #Text_New_2
  #Text_Date
  #Frame3D_All
  #Text_New_3
  #Button_Write
  #StatusBar_0
EndEnumeration
;--> Image Constants
#Image_No = 0
#Image_Thumb = 1
;--> DateGadget constants
#DTM_FIRST = $1000
#DTM_SETFORMATA = #DTM_FIRST + 5
#DTM_GETSYSTEMTIME = #DTM_FIRST + 1
#DTM_SETSYSTEMTIME = #DTM_FIRST + 2
#GDT_VALID = 0
;--> Exif Tag constants
#Exif_DT_Modified   = $132  ; 306 
#Exif_Sub           = $8769 ; 34665
#Exif_DT_Taken      = $9003 ; 36867
#Exif_DT_Digitized  = $9004 ; 36868
#Exif_Thumb_Offset  = $201  ; 513
#Exif_Thumb_Length  = $202  ; 514
#LittleEndian       = $4949 ; 18761
;--> Image Header size and Array constants
#Header             = 12 
#ModifiedDate       = 0
#TakenDate          = 1
#DigitizedDate      = 2
;--> Globals
Global dates
;--> Structures
Structure EXIF
  eDate.s
  eOffset.l
EndStructure
;--> Arrays
;--> This holds Exif data: The dates and their offsets
;--> We use this for writing new dates
Dim exifDate.EXIF(2)
;--> Procedure to reset Display
Procedure DisplayReset()
  SetGadgetState(#ImageGad_Thumb, UseImage(#Image_No))
  SetGadgetText(#Text_Modified_Read, "")
  SetGadgetText(#Text_Taken_Read, "")
  SetGadgetText(#Text_Digitized_Read, "")
  SetGadgetState(#Date_Modified_Write, 0)
  SetGadgetState(#Date_Taken_Write, 0)
  SetGadgetState(#Date_Digitized_Write, 0)
  DisableGadget(#Button_Write, 1)
  StatusBarText(#StatusBar_0, 0, "")
EndProcedure
;--> Procedure to set DateGadgets.
;--> PB doesn't support the seconds field so I use API
Procedure SetDate(gadget, date$)
  st.SYSTEMTIME
  st\wYear = Val(Left(date$, 4))
  st\wMonth = Val(Mid(date$, 6, 2))
  st\wDay = Val(Mid(date$, 9, 2))
  st\wHour = Val(Mid(date$, 12, 2))
  st\wMinute = Val(Mid(date$, 15, 2))
  st\wSecond = Val(Mid(date$, 18, 2))
  ;--> Set the text
  SendMessage_(GadgetID(gadget), #DTM_SETSYSTEMTIME, #GDT_VALID, st)
  ;--> Clear checkbox if All Dates is selected
  If GetGadgetState(#Date_All_Write) <> 0
    SetGadgetState(gadget, 0)
  EndIf
EndProcedure
;--> Procedure for reading Exif Dates
Procedure GetExif(ExifLoc)
  FileSeek(ExifLoc + #Header); + 2)
  exifEntries = ReadWord()
  For i = 1 To exifEntries
    tag = ReadWord() &$FFFF
    Select tag
      Case #Exif_DT_Taken
        ;--> We'll need to reset Loc after reading date string, so mark it.
        currentloc = Loc()
        ;--> By-pass tag type and length because we know it's
        ;--> Type = ASCII and Length = 20
        FileSeek(Loc() + 6)
        ;--> Get the offset to Date Taken string
        dateLoc = ReadLong()
        ;--> Move to that offset
        FileSeek(dateLoc + #Header)
        ;--> Put data into our Structure for future use
        exifDate(#TakenDate)\eOffset = Loc()
        exifDate(#TakenDate)\eDate = ReadString()
        ;--> Display Date Taken
        SetGadgetText(#Text_Taken_Read, exifDate(#TakenDate)\eDate)
        SetDate(#Date_Taken_Write, exifDate(#TakenDate)\eDate)
        dates = #True
        ;--> Move back to next tag
        FileSeek(currentloc + 10)
      Case #Exif_DT_Digitized
        ;--> We'll need to reset Loc after reading date string, so mark it.
        currentloc = Loc()
        ;--> By-pass tag type and length because we know it's
        ;--> Type = ASCII and Length = 20
        FileSeek(Loc() + 6)
        ;--> Get the offset to Date Digitized string
        dateLoc = ReadLong()
        ;--> Move to that offset
        FileSeek(dateLoc + #Header)
        ;--> Put data into our Structure for future use
        exifDate(#DigitizedDate)\eOffset = Loc()
        exifDate(#DigitizedDate)\eDate = ReadString()
        ;--> Display Date Digitized
        SetGadgetText(#Text_Digitized_Read, exifDate(#DigitizedDate)\eDate)
        SetDate(#Date_Digitized_Write, exifDate(#DigitizedDate)\eDate)
        dates = #True
        ;--> Move back to next tag
        FileSeek(currentloc + 10)
      Default
        ;--> We're skipping tags so move on to the next one
        FileSeek(Loc() + 10)
    EndSelect
  Next i
EndProcedure
;--> Proceure for reading thumbnail
Procedure GetThumb(thumbLoc)
  ;--> Go to thumbnail tags offset
  FileSeek(thumbLoc + #Header)
  ;--> get total entries
  entries = ReadWord()
  ;--> We're looking for thumbnail offset and size in bytes
  For i = 1 To entries
    tag = ReadWord() &$FFFF
    Select tag
      Case #Exif_Thumb_Offset
        ;--> Here's the starting offset for thumbnail
        FileSeek(Loc() + 6)
        thumbOffset = ReadLong()
      Case #Exif_Thumb_Length
        ;--> here's the length of thumbnail in bytes
        FileSeek(Loc() + 6)
        thumbLength = ReadLong()
      Default
        ;--> We're skipping some tags so move on to the next one
        FileSeek(Loc() + 10)
    EndSelect
  Next i
  FileSeek(thumbOffset + #Header)
  ;--> Read the thumbnail data into memory
  *thumb = AllocateMemory(thumbLength)
  ReadData(*thumb, thumbLength)
  ;--> Catch the thumbnail and put it into ImageGadget
  CatchImage(#Image_Thumb, *thumb)
  SetGadgetState(#ImageGad_Thumb, ImageID())
  FreeMemory(*thumb)
EndProcedure
Procedure GetInfo(jpg$, writeIt, newDateTime$) 
  OpenFile(0, jpg$) 
  ;--> Byte 0 of EXIF begins after JPEG header 
  FileSeek(#Header) 
  ;--> Bytes 0-1 is word order 18761 ($4949) is Intel and 19789 ($4D4D) is Motorola 
  byteOrder = ReadWord() 
  ;--> For now I only handle Little Endian
  If byteOrder = #LittleEndian 
    ; --> Bytes 2-3 is TIFF format, it's always 42 ($2A). If not, give up. 
    tifFormat = ReadWord()
    ;--> This is always $2A. If not, give up.
    If tifFormat = $2A 
      ;--> Bytes 4-7 is starting offset for IFD (Image File Directory) 
      ifd1 = ReadLong() 
      ;--> Move to start of IFD 
      FileSeek(ifd1 + #Header) 
      ;--> First 2 bytes of IFD is number of field entries 
      nFields = ReadWord() 
      ;--> Loop through all fields to find Date/Time stamp 
      For i = 1 To nFields 
        ;--> Bytes 0-1 contain the Tag for the field. 
        currentTag = ReadWord() &$FFFF
        Select currentTag
          Case #Exif_DT_Modified
            ;--> Bytes 2-3 contain the field Type. 
            ;--> We know this will be 2 (ASCII) For Date/Time 
            fieldType = ReadWord() 
            ;--> Bytes 4-7 contain the  Length of the field. 
            fieldLength = ReadLong() 
            ;--> We'll need to reset Loc after reading date string, so mark it.
            currentloc = Loc() 
            ;--> Bytes 8-11 contain a pointer to ASCII Date/Time 
            fieldValue = ReadLong() 
            ;--> Move to that pointer 
            FileSeek(fieldValue + #Header) 
            ;--> This is the start offset of Date/Time ASCII string 
            ;--> Put data into our Structure for future use
            exifDate(#ModifiedDate)\eOffset = Loc()
            exifDate(#ModifiedDate)\eDate = ReadString()
            ;--> Display date
            SetGadgetText(#Text_Modified_Read, exifDate(#ModifiedDate)\eDate)
            SetDate(#Date_Modified_Write, exifDate(#ModifiedDate)\eDate)
            dates = #True
            ;--> Go back to next tag
            FileSeek(currentloc + 4)
          Case #Exif_Sub
            ;--> Here's the offest to more Exif data tags
            FileSeek(Loc() + 6)
            exifStartLoc = ReadLong()
          Default
            ;--> Move to next field. Each field is 12 bytes. 
            ;--> currentTag (2 bytes) is current Loc() so we add 10 
            FileSeek(Loc() + 10) 
        EndSelect
      Next i
      ;--> Offset to thumbnail is after the last tag
      thumbStart = ReadLong()
      ;--> Go to Exif offset and get dates
      GetExif(exifStartLoc)
      ;--> Go to Thumbnail offset and read Thumbnail
      GetThumb(thumbStart)
      ;--> All done
      CloseFile(0) 
      exifResult = 1
    Else
      ;--> Wrong format, display Unavailable
      DisplayReset()
      exifResult = 0
    EndIf 
  Else
    ;--> Wrong byte order, display Unavailable
    DisplayReset()
    exifResult = 0
  EndIf
  ProcedureReturn exifResult
EndProcedure 
;--> Create image to display when no Exif found
CreateImage(#Image_No, 160, 120)
StartDrawing(ImageOutput())
DrawingMode(1)
FrontColor(255, 0, 0)
Locate(28, 50)
DrawText("Thumbnail n/a")
StopDrawing()
;--> Main window with gadgets
If OpenWindow(#Window_Main, 0, 0, 465, 500, #PB_Window_SystemMenu | #PB_Window_ScreenCentered, "Exif Dates") And CreateGadgetList(WindowID(#Window_Main))
  hSB = CreateStatusBar(#StatusBar_0, WindowID(#Window_Main))
  AddStatusBarField(WindowWidth())
  SendMessage_(hSB, #SB_GETRECT, 0, @sbRect.RECT)
  statusbarHeight = sbRect\bottom - sbRect\top
  ExplorerTreeGadget(#ExplorerTree_0, 0, 0, 220, 500 - statusbarHeight, "*.jpg;*.jpeg")
  SetWindowLong_(GadgetID(#ExplorerTree_0), #GWL_STYLE, GetWindowLong_(GadgetID(#ExplorerTree_0), #GWL_STYLE) | #TVS_SHOWSELALWAYS)
  ;--> Thumbnail
  Frame3DGadget(#Frame3D_Thumb, 230, 10, 225, 145, "Thumbnail")
  ImageGadget(#ImageGad_Thumb, 265, 25, 160, 120, UseImage(#Image_No))
  ;--> Date Modified
  Frame3DGadget(#Frame3D_Modified, 230, 160, 225, 75,"Date Modified")
  TextGadget(#Text_Modified_Read, 240, 180, 100, 22, "")
  TextGadget(#Text_New_0, 240, 210, 50, 22, "Change to")
  DateGadget(#Date_Modified_Write, 300, 205, 145, 22, "", Date(), #PB_Date_CheckBox|#PB_Date_UpDown)
  ;--> Date Taken
  Frame3DGadget(#Frame3D_Taken, 230, 240, 225, 75, "Date Taken")
  TextGadget(#Text_Taken_Read, 240, 260, 100, 22, "")
  TextGadget(#Text_New_1, 240, 290, 50, 22, "Change to")
  DateGadget(#Date_Taken_Write, 300, 285, 145, 22, "", Date(), #PB_Date_CheckBox | #PB_Date_UpDown)
  ;--> Date Digitized
  Frame3DGadget(#Frame3D_Digitized, 230, 320, 225, 75, "Date Digitized")
  TextGadget(#Text_Digitized_Read, 240, 340, 100, 22, "")
  TextGadget(#Text_New_2, 240, 370, 50, 22, "Change to")
  DateGadget(#Date_Digitized_Write, 300, 365, 145, 22, "", Date(), #PB_Date_CheckBox | #PB_Date_UpDown)
  ;--> All Dates
  Frame3DGadget(#Frame3D_All, 230, 400, 225, 45, "All Dates")
  TextGadget(#Text_New_3, 240, 420, 50, 22, "Change to")
  DateGadget(#Date_All_Write, 300, 415, 145, 22, "", Date(), #PB_Date_CheckBox | #PB_Date_UpDown)
  ;--> Write dates
  ButtonGadget(#Button_Write, 270, 455, 150, 20, "Write Selected Date(s)")
  DisableGadget(#Button_Write, 1)
  ;--> Clear checkboxes
  SetGadgetState(#Date_Modified_Write, 0)
  SetGadgetState(#Date_Taken_Write, 0)
  SetGadgetState(#Date_Digitized_Write, 0)
  SetGadgetState(#Date_All_Write, 0)
  ;--> Set format for DateGadgets
  dt$ = "yyyy':'MM':'dd HH':'mm':'ss"
  SendMessage_(GadgetID(#Date_Modified_Write), #DTM_SETFORMATA, 0, @dt$)
  SendMessage_(GadgetID(#Date_Taken_Write), #DTM_SETFORMATA, 0, @dt$)
  SendMessage_(GadgetID(#Date_Digitized_Write), #DTM_SETFORMATA, 0, @dt$)
  SendMessage_(GadgetID(#Date_All_Write), #DTM_SETFORMATA, 0, @dt$)
;-->Main Loop
quit = #False
  Repeat
    event = WaitWindowEvent()
    Select event
      Case #PB_Event_CloseWindow
        If EventWindowID() = #Window_Main
          quit = #True
        EndIf
      Case #PB_EventGadget
        Select EventGadgetID()
          Case #ExplorerTree_0
            currentFile$ = GetGadgetText(#ExplorerTree_0)
            fileExt$ = Left(LCase(GetExtensionPart(currentFile$)), 3)
            If EventType() = #PB_EventType_Change And fileExt$ = "jpg"
              dates = #False
              exif = GetInfo(currentFile$, 0, "") 
              If dates
                StatusBarText(#StatusBar_0, 0, currentFile$)
                DisableGadget(#Button_Write, 0)
              EndIf
            Else
              currentFile$ = ""
              DisplayReset()
            EndIf
          Case #Button_Write
            If currentFile$ And dates
              If OpenFile(0, currentFile$)
                For s = #Date_Modified_Write To #Date_Digitized_Write
                  If GetGadgetState(s) <> 0
                    newdate$ = GetGadgetText(s)
                    FileSeek(exifDate(s)\eOffset)
                    WriteString(newdate$)
                  ElseIf GetGadgetState(#Date_All_Write) <> 0
                    newdate$ = GetGadgetText(#Date_All_Write)
                    FileSeek(exifDate(s)\eOffset)
                    WriteString(newdate$)
                  EndIf
                Next s
                CloseFile(0)
              Else
                MessageRequester("Error", "Could not open requested file!", #MB_ICONEXCLAMATION)
              EndIf
            EndIf
            ;--> Sync date gadgets
            ;--> If All Dates is checked, dis-allow other DateGadgets being checked
          Case #Date_Modified_Write
            If GetGadgetState(#Date_All_Write) <> 0
              SetGadgetState(#Date_Modified_Write, 0)
            EndIf
          Case #Date_Taken_Write
            If GetGadgetState(#Date_All_Write) <> 0
              SetGadgetState(#Date_Taken_Write, 0)
            EndIf
          Case #Date_Digitized_Write
            If GetGadgetState(#Date_All_Write) <> 0
              SetGadgetState(#Date_Digitized_Write, 0)
            EndIf
          Case #Date_All_Write
            For g = #Date_Modified_Write To #Date_Digitized_Write
              If GetGadgetState(g) <> 0 
                SetGadgetState(g, 0)
              EndIf
              If GetGadgetState(#Date_All_Write) <> 0
                SetGadgetState(g, GetGadgetState(#Date_All_Write))
                SetDate(g, GetGadgetText(#Date_All_Write))
              EndIf
            Next g
        EndSelect
    EndSelect
  Until quit
EndIf
End