;+
;FUNCTION: time_parse
;PURPOSE:
;  Parse a string or array of strings into double precision seconds since 1970
;    (a)using user provided format code
; or (b)using flexible formatting and no code 
;
;INPUTS:
;  s : the input string or array of strings
;
;KEYWORDS:
;  tformat=tformat:  Format string such as "YYYY-MM-DD/hh:mm:ss" (Default)
;               the following tokens are recognized:
;                    YYYY  - 4 digit year
;                    yy    - 2 digit year (00-69 assumed to be 2000-2069, 70-99 assumed to be 1970-1999)
;                    MM    - 2 digit month
;                    DD    - 2 digit date
;                    hh    - 2 digit hour
;                    mm    - 2 digit minute
;                    ss    - 2 digit seconds
;                    .fff   - fractional seconds (can be repeated, e.g. .f,.ff,.fff,.ffff, etc... are all acceptable codes)
;                    MTH   - 3 character month
;                    DOY   - 3 character Day of Year
;                    TDIFF - 5 character, +hhmm or -hhmm different from UTC (sign required)
;               tformat is case sensitive!
;
; tdiff=tdiff: Offset in hours.  Array or scalar acceptable.
;              If your input times are not UTC and offset 
;              is not specified in the time string itself,
;              use this keyword.
;  
; MMDDYYYY=MMDDYYYY: handle dates in month/day/year format flexibly if tformat not specified
;             
;     
;Examples:
;
;NOTES:
;  #1 Some format combinations can conflict and may lead to unpredictable behavior. (e.g. "YYYY-MM-MTH") 
;  #2 Primarily intended as a helper routine for time_double and time_struct
;  #3 letter codes are case insensitive.
;  #4 Based heavily on str2time by Davin Larson.
; 
;$LastChangedBy: davin-mac $
;$LastChangedDate: 2019-03-04 13:43:53 -0800 (Mon, 04 Mar 2019) $
;$LastChangedRevision: 26755 $
;$URL: svn+ssh://thmsvn@ambrosia.ssl.berkeley.edu/repos/spdsoft/trunk/general/misc/time/time_parse.pro $
;-

function time_parse,s, tformat=tformat,tdiff=tdiff,MMDDYYYY=MMDDYYYY

  compile_opt idl2
  
  months= ['JAN','FEB','MAR','APR', 'MAY', 'JUN', 'JUL', 'AUG','SEP','OCT','NOV','DEC']
  
  ndim = size(s,/n_dimen)
  dim = size(s,/dim)
  if ndim gt 0 then begin
    str = replicate(time_struct(0d),dim)
  endif else begin
    str = time_struct(0d)
  endelse
  
  if undefined(tdiff) then begin
    tdiff = 0
  endif
  
  tdiff_sec = tdiff * 60. * 60.
  
  ;flexible formatting, allows arbitary punctuation, variable length fields, etc...
  ;primarily used for human entry and legacy support
  if undefined(tformat) then begin
    
    ;Davin's version.  Even though it loops and is a little opaque,
      ;this is faster than the vectorized versions using stregex
    bt = bindgen(256)
    bt[byte(':_-/,T')]= 32 ;create new ascii set where common separators are replaced with space
    
    year=0l & month=0l & date=0 & hour=0 & min=0 & fsec=0.d ;select output types for parse
    for i=0l,n_elements(s)-1l do begin
      if strlowcase(s[i]) eq 'now' then begin
        st = string(bt[byte(time_string(systime(1),prec=3))])
      endif else begin
        st = string(bt[byte(s[i])])+' 0 0 0 0 0 0'    ; remove separators and pad fields
      endelse
      if keyword_set(MMDDYYYY) then reads,st,month,date,year,hour,min,fsec  $
      else  reads,st,year,month,date,hour,min,fsec
  
      ;handle inputs of the form yyyymmdd hhmmss (only separator is space between date and time)
      if year gt 10000000l then begin
        hour = month
        date = year mod 100
        year = year/100
        month = year mod 100
        year = year/100
        min = hour mod 100
        hour = hour / 100
      endif
      
      ;handle two digit years
      if year lt 70  then year = year+2000
      if year lt 200 then year = year+1900

      ; month=0 or date=0 are invalid entries, replace with 1 
      month = month > 1
      date = date > 1
      
      str[i].year=year
      str[i].month=month
      str[i].date=date
      str[i].hour=hour
      str[i].min=min
      
      ;separate seconds and fractional seconds
      str[i].sec=fix(fsec)
      str[i].fsec=double(fsec) mod 1.0
      
    endfor
     
  endif else begin ;fixed formatting

    year = 0
    p = strpos(tformat,'yy')
    
    if p ge 0 then begin
      year = fix(strmid(s,p,2))
      year +=  1900*(year ge 70) +2000 *(year lt 70)
    endif
    
    p = strpos(tformat,'YYYY')
    if p ge 0 then begin
       year = fix(strmid(s,p,4))
    endif
    
    str.year = year
    
    p = strpos(tformat,'MM')
    if p ge 0 then begin
      str.month = fix(strmid(s,p,2))
    endif
    
    p = strpos(tformat,'MTH')
    
    for i = 0,11 do begin
      idx = where(months[i] eq strupcase(strmid(s,p,3)),c)
      if c gt 0 then begin
        str[idx].month = i+1
      endif
    endfor
    
    p = strpos(tformat,'DD')
    if p ge 0 then str.date = fix(strmid(s,p,2))
    
    p = strpos(tformat,'DOY')
    if p ge 0 then begin
      doy_to_month_date,str.year,fix(strmid(s,p,3)),month,date
      str.month = month
      str.date = date
    endif
    
    p = strpos(tformat,'hh')
    if p ge 0 then str.hour = fix(strmid(s,p,2))
    
    p = strpos(tformat,'mm')
    if p ge 0 then str.min = fix(strmid(s,p,2))
    
    p = strpos(tformat,'ss')
    if p ge 0 then str.sec = fix(strmid(s,p,2))
    
    token='.'
    repeat begin
      token = token +'f'
      p = strpos(tformat, token )
    endrep until strpos(tformat,token+'f') lt 0
    if p ge 0 then str.fsec = double(strmid(s,p,strlen(token)))
      
    p = strpos(tformat,'TDIFF')
    if p gt 0 then begin
      tdiff_hr = fix(strmid(s,p,3))
      tdiff_min = fix(strmid(s,p+3,2))
      tdiff_sec = tdiff_hr * 60. * 60. + tdiff_min * 60. 
    endif
    
  endelse
 
  if n_elements(tdiff_sec) gt 1 || tdiff_sec ne 0 then begin   
    return,time_struct(time_double(str) - tdiff_sec)
  endif else begin
    return,str
  endelse
    
end
