changeset 13848:40e32fe44aaa

Ugrade time functions to accept millisecond format string FFF (Bug #34586) * datestr.m: Add millisecond FFF format. Change numerical formats 21,22,29 to match Matlab. Remove unused persistent variables. Vectorize some for loops. Use strrep rather than regexp where possible for speed. * datevec.m: Add millisecond FFF format. Use strrep rather than regexp where possible for speed. * datenum.m: Update docstring. Use modern coding style. Only calculate second output argument if requested. Allow cellstr inputs.
author Rik <octave@nomad.inbox5.com>
date Wed, 09 Nov 2011 14:49:09 -0800
parents 9fc597693b0b
children b4b8e525dee0
files NEWS scripts/time/datenum.m scripts/time/datestr.m scripts/time/datevec.m
diffstat 4 files changed, 214 insertions(+), 224 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Tue Nov 08 19:59:37 2011 -0500
+++ b/NEWS	Wed Nov 09 14:49:09 2011 -0800
@@ -65,6 +65,12 @@
                Default options are "Qt Qbb Qc Qx" for 4D and higher
     voronoi  : No default arguments
 
+ ** Date/Time functions updated.
+    Millisecond support with FFF format string now supported.
+
+    datestr : Numerical formats 21, 22, 29 changed to match Matlab.
+              Now accepts cellstr inputs.
+
  ** Octave warning IDs updated
     "empty-list-elements" : removed
     "fortran-indexing"    : removed
--- a/scripts/time/datenum.m	Tue Nov 08 19:59:37 2011 -0500
+++ b/scripts/time/datenum.m	Wed Nov 09 14:49:09 2011 -0800
@@ -17,16 +17,25 @@
 ## <http://www.gnu.org/licenses/>.
 
 ## -*- texinfo -*-
-## @deftypefn  {Function File} {} datenum (@var{year}, @var{month}, @var{day})
-## @deftypefnx {Function File} {} datenum (@var{year}, @var{month}, @var{day}, @var{hour})
-## @deftypefnx {Function File} {} datenum (@var{year}, @var{month}, @var{day}, @var{hour}, @var{minute})
-## @deftypefnx {Function File} {} datenum (@var{year}, @var{month}, @var{day}, @var{hour}, @var{minute}, @var{second})
-## @deftypefnx {Function File} {} datenum ("date")
-## @deftypefnx {Function File} {} datenum ("date", @var{p})
-## Return the specified local time as a day number, with Jan 1, 0000
-## being day 1.  By this reckoning, Jan 1, 1970 is day number 719529.
-## The fractional portion, @var{p}, corresponds to the portion of the
-## specified day.
+## @deftypefn  {Function File} {@var{days} =} datenum (@var{datevec})
+## @deftypefnx {Function File} {@var{days} =} datenum (@var{year}, @var{month}, @var{day})
+## @deftypefnx {Function File} {@var{days} =} datenum (@var{year}, @var{month}, @var{day}, @var{hour})
+## @deftypefnx {Function File} {@var{days} =} datenum (@var{year}, @var{month}, @var{day}, @var{hour}, @var{minute})
+## @deftypefnx {Function File} {@var{days} =} datenum (@var{year}, @var{month}, @var{day}, @var{hour}, @var{minute}, @var{second})
+## @deftypefnx {Function File} {@var{days} =} datenum ("datestr")
+## @deftypefnx {Function File} {@var{days} =} datenum ("datestr", @var{p})
+## @deftypefnx {Function File} {[@var{days}, @var{secs}] =} datenum (@dots{})
+## Return the date/time input as a serial day number, with Jan 1, 0000
+## being day 1.  The fractional portion of @var{days} corresponds to the time
+## on the given day.  The input may be a date vector (see @code{datevec}), 
+## datestr (see @code{datestr}), or directly specified as input.
+##
+## When processing datestrings, @var{p} is the year at the start of the century
+## to which two-digit years will be referenced.  If not specified, it defaults
+## to the current year minus 50.
+##
+## The optional output @var{secs} holds the time on the specified day with greater
+## precision than @var{days}.
 ##
 ## Notes:
 ##
@@ -52,48 +61,43 @@
 ##
 ## @strong{Caution:} this function does not attempt to handle Julian
 ## calendars so dates before Octave 15, 1582 are wrong by as much
-## as eleven days.  Also be aware that only Roman Catholic countries
+## as eleven days.  Also, be aware that only Roman Catholic countries
 ## adopted the calendar in 1582.  It took until 1924 for it to be
 ## adopted everywhere.  See the Wikipedia entry on the Gregorian
 ## calendar for more details.
 ##
 ## @strong{Warning:} leap seconds are ignored.  A table of leap seconds
 ## is available on the Wikipedia entry for leap seconds.
-## @seealso{date, clock, now, datestr, datevec, calendar, weekday}
+## @seealso{datestr, datevec, date, clock, now, calendar, weekday}
 ## @end deftypefn
 
 ## Algorithm: Peter Baum (http://vsg.cape.com/~pbaum/date/date0.htm)
 ## Author: pkienzle <pkienzle@users.sf.net>
 
-function [days, secs] = datenum (year, month, day, hour, minute, second)
+function [days, secs] = datenum (year, month = [], day = [], hour = 0, minute = 0, second = 0)
 
   ## Days until start of month assuming year starts March 1.
   persistent monthstart = [306; 337; 0; 31; 61; 92; 122; 153; 184; 214; 245; 275];
 
-  if (nargin == 0 || (nargin > 2  && ischar (year)) || nargin > 6)
+  if (nargin == 0 || nargin > 6 || 
+     (nargin > 2 && (ischar (year) || iscellstr (year))))
     print_usage ();
   endif
-  if (ischar (year))
-    if (nargin < 2)
-      month = [];
-    endif
+
+  if (ischar (year) || iscellstr (year))
     [year, month, day, hour, minute, second] = datevec (year, month);
   else
-    if (nargin < 6) second = 0; endif
-    if (nargin < 5) minute = 0; endif
-    if (nargin < 4) hour = 0; endif
     if (nargin == 1)
       nc = columns (year);
       if (nc > 6 || nc < 3)
         error ("datenum: expected date vector containing [YEAR, MONTH, DAY, HOUR, MINUTE, SECOND]");
       endif
-      second = minute = hour = 0;
       if (nc >= 6) second = year(:,6); endif
       if (nc >= 5) minute = year(:,5); endif
-      if (nc >= 4) hour = year(:,4); endif
-      day = year(:,3);
+      if (nc >= 4) hour   = year(:,4); endif
+      day   = year(:,3);
       month = year(:,2);
-      year = year(:,1);
+      year  = year(:,1);
     endif
   endif
 
@@ -120,30 +124,32 @@
   day += 365*year + floor (year/4) - floor (year/100) + floor (year/400);
 
   ## Add fraction representing current second of the day.
-  days = day + (hour+(minute+second/60)/60)/24;
+  days = day + (hour + (minute + second/60)/60)/24;
 
   ## Output seconds if asked so that etime can be more accurate
-  secs = 86400*day + hour*3600 + minute*60 + second;
+  if (isargout (2))
+    secs = day*86400 + hour*3600 + minute*60 + second;
+  endif
 
 endfunction
 
+
 %!shared part
 %! part = 0.514623842592593;
-%!assert(datenum(2001,5,19), 730990)
-%!assert(datenum([1417,6,12]), 517712)
-%!assert(datenum([2001,5,19;1417,6,12]), [730990;517712])
-%!assert(datenum(2001,5,19,12,21,3.5), 730990+part, eps)
-%!assert(datenum([1417,6,12,12,21,3.5]), 517712+part, eps)
+%!assert (datenum (2001,5,19), 730990)
+%!assert (datenum ([1417,6,12]), 517712)
+%!assert (datenum ([2001,5,19;1417,6,12]), [730990;517712])
+%!assert (datenum (2001,5,19,12,21,3.5), 730990+part, eps)
+%!assert (datenum ([1417,6,12,12,21,3.5]), 517712+part, eps)
 ## Test vector inputs
 %!test
 %! t = [2001,5,19,12,21,3.5; 1417,6,12,12,21,3.5];
 %! n = [730990; 517712] + part;
-%! assert(datenum(t), n, 2*eps);
-## Make sure that the vectors can have either orientation
-%!test
-%! t = [2001,5,19,12,21,3.5; 1417,6,12,12,21,3.5]';
-%! n = [730990 517712] + part;
-%! assert(datenum(t(1,:), t(2,:), t(3,:), t(4,:), t(5,:), t(6,:)), n, 2*eps);
+%! assert (datenum (t), n, 2*eps);
+%! ## Check that vectors can have either orientation
+%! t = t';
+%! n = n';
+%! assert (datenum (t(1,:), t(2,:), t(3,:), t(4,:), t(5,:), t(6,:)), n, 2*eps);
 
 ## Test mixed vectors and scalars
 %!assert (datenum([2008;2009], 1, 1), [datenum(2008, 1, 1);datenum(2009, 1, 1)]);
@@ -159,3 +165,14 @@
 %!assert (datenum([2008 2009], [1 2], 1), [datenum(2008, 1, 1) datenum(2009, 2, 1)]);
 %!assert (datenum([2008 2009], 1, [1 2]), [datenum(2008, 1, 1) datenum(2009, 1, 2)]);
 %!assert (datenum(2008, [1 2], [1 2]), [datenum(2008, 1, 1) datenum(2008, 2, 2)]);
+## Test string and cellstr inputs
+%!assert (datenum ("5/19/2001"), 730990)
+%!assert (datenum ({"5/19/2001"}), 730990)
+%!assert (datenum (char ("5/19/2001", "6/6/1944")), [730990; 710189])
+%!assert (datenum ({"5/19/2001", "6/6/1944"}), [730990; 710189])
+
+%% Test input validation
+%!error datenum ()
+%!error datenum (1,2,3,4,5,6,7)
+%!error datenum ([1, 2])
+%!error datenum ([1,2,3,4,5,6,7])
--- a/scripts/time/datestr.m	Tue Nov 08 19:59:37 2011 -0500
+++ b/scripts/time/datestr.m	Wed Nov 09 14:49:09 2011 -0800
@@ -89,6 +89,8 @@
 ## @item      @tab and not padded with zeros otherwise          @tab 9:00 AM
 ## @item MM   @tab Minute of hour (padded with zeros)           @tab 10:05
 ## @item SS   @tab Second of minute (padded with zeros)         @tab 10:05:03
+## @item FFF  @tab Milliseconds of second (padded with zeros)   @tab 10:05:03.012
+## @item AM   @tab Use 12-hour time format                      @tab 11:30 AM
 ## @item PM   @tab Use 12-hour time format                      @tab 11:30 PM
 ## @end multitable
 ##
@@ -98,10 +100,10 @@
 ##
 ## If @var{p} is nor specified, it defaults to the current year minus 50.
 ##
-## If a matrix or cell array of dates is given, a vector of date strings is
-## returned.
+## If a matrix or cell array of dates is given, a column vector of date strings
+## is returned.
 ##
-## @seealso{datenum, datevec, date, clock, now, datetick}
+## @seealso{datenum, datevec, date, clock, now}
 ## @end deftypefn
 
 ## FIXME: parse arbitrary code strings.
@@ -126,22 +128,21 @@
 ## Created: 10 October 2001 (CVS)
 ## Adapted-By: William Poetra Yoga Hadisoeseno <williampoetra@gmail.com>
 
-function retval = datestr (date, f, p)
+function retval = datestr (date, f = [], p = [])
 
-  persistent dateform names_mmmm names_mmm names_m names_dddd names_ddd names_d;
+  persistent dateform names_mmmm names_m names_d;
 
   if (isempty (dateform))
-
     dateform = cell (32, 1);
-    dateform{1} = "dd-mmm-yyyy HH:MM:SS";
-    dateform{2} = "dd-mmm-yyyy";
-    dateform{3} = "mm/dd/yy";
-    dateform{4} = "mmm";
-    dateform{5} = "m";
-    dateform{6} = "mm";
-    dateform{7} = "mm/dd";
-    dateform{8} = "dd";
-    dateform{9} = "ddd";
+    dateform{1}  = "dd-mmm-yyyy HH:MM:SS";
+    dateform{2}  = "dd-mmm-yyyy";
+    dateform{3}  = "mm/dd/yy";
+    dateform{4}  = "mmm";
+    dateform{5}  = "m";
+    dateform{6}  = "mm";
+    dateform{7}  = "mm/dd";
+    dateform{8}  = "dd";
+    dateform{9}  = "ddd";
     dateform{10} = "d";
     dateform{11} = "yyyy";
     dateform{12} = "yy";
@@ -154,68 +155,50 @@
     dateform{19} = "QQ";
     dateform{20} = "dd/mm";
     dateform{21} = "dd/mm/yy";
-    dateform{22} = "mmm.dd.yyyy HH:MM:SS";
-    dateform{23} = "mmm.dd.yyyy";
+    dateform{22} = "mmm.dd,yyyy HH:MM:SS";
+    dateform{23} = "mmm.dd,yyyy";
     dateform{24} = "mm/dd/yyyy";
     dateform{25} = "dd/mm/yyyy";
     dateform{26} = "yy/mm/dd";
     dateform{27} = "yyyy/mm/dd";
     dateform{28} = "QQ-YYYY";
     dateform{29} = "mmmyyyy";
-    dateform{30} = "yyyymmdd";
+    dateform{30} = "yyyy-mm-dd";
     dateform{31} = "yyyymmddTHHMMSS";
     dateform{32} = "yyyy-mm-dd HH:MM:SS";
 
-    names_m = {"J"; "F"; "M"; "A"; "M"; "J"; "J"; "A"; "S"; "O"; "N"; "D"};
-
-    names_d = {"S"; "M"; "T"; "W"; "T"; "F"; "S"};
-
+    names_m = {"J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"};
+    names_d = {"S", "M", "T", "W", "T", "F", "S"};
   endif
 
   if (nargin < 1 || nargin > 3)
     print_usage ();
   endif
 
-  if (nargin < 2)
-    f = [];
-  endif
-  if (nargin < 3)
-    p = [];
-  endif
-
-  if (ischar (date))
-    t = date;
-    date = cell (1);
-    date{1} = t;
-  endif
-
-  ## Guess, so we might be wrong.
-  if (iscell (date) || columns (date) != 6)
+  ## Guess input type.  We might be wrong.
+  if (ischar (date) || iscellstr (date) || columns (date) != 6)
     v = datevec (date, p);
   else
     v = [];
     if (columns (date) == 6)
       ## Make sure that the input really is a datevec.
       maxdatevec = [Inf, 12, 31, 23, 59, 60];
-      for i = 1:numel (maxdatevec)
-        if (any (date(:,i) > maxdatevec(i))
-            || (i != 6 && any (floor (date(:,i)) != date(:,i))))
-          v = datevec (date, p);
-          break;
-        endif
-      endfor
+      if (any (max (date, 1) > maxdatevec) ||
+          any (date(:,1:5) != floor (date(:,1:5))))
+        v = datevec (date, p);
+      endif
     endif
     if (isempty (v))
       v = date;
     endif
   endif
 
-  for i = 1:(rows (v))
+  retval = [];
+  for i = 1 : rows (v)
 
-    if (isempty (f) || f == -1)
+    if (isempty (f))
       if (v(i,4:6) == 0)
         f = 1;
-        ## elseif (v(i,1:3) == [0, 1, 1])
       elseif (v(i,1:3) == [-1, 12, 31])
         f = 16;
       else
@@ -230,7 +213,8 @@
     endif
 
     df_orig = df;
-    df = regexprep (df, '[AP]M', "%p");
+    df = strrep (df, 'AM', "%p");
+    df = strrep (df, 'PM', "%p");
     if (strcmp (df, df_orig))
       ## PM not set.
       df = strrep (df, "HH", "%H");
@@ -266,9 +250,11 @@
 
     df = strrep (df, "MM", "%M");
 
-    df = strrep (df, "SS", "%S");
+    df = regexprep (df, '[Ss][Ss]', "%S");
 
-    df = regexprep (df, '[Qq][Qq]', sprintf ("Q%d", fix ((v(i,2) + 2) / 3)));
+    df = strrep (df, "FFF", sprintf ("%03d", 1000 * (v(i,6) - fix (v(i,6)))));
+
+    df = strrep (df, 'QQ', sprintf ("Q%d", fix ((v(i,2) + 2) / 3)));
 
     vi = v(i,:);
     tm.year = vi(1) - 1900;
@@ -278,7 +264,7 @@
     tm.min = vi(5);
     sec = vi(6);
     tm.sec = fix (sec);
-    tm.usec = fix (rem (sec, 1) * 1e6);
+    tm.usec = fix ((sec - tm.sec) * 1e6);
     tm.wday = wday - 1;
     ## FIXME -- Do we need YDAY and DST?  How should they be computed?
     ## We don't want to use "localtime (mktime (tm))" because that
@@ -288,61 +274,64 @@
 
     str = strftime (df, tm);
 
-    if (i == 1)
-      retval = str;
-    else
-      retval = [retval; str];
-    endif
+    retval = [retval; str];
 
   endfor
 
 endfunction
 
-# simple tests
+
+## demos
+%!demo
+%! ## Current date and time in default format
+%! datestr (now ())
+%!demo
+%! ## Current date (integer portion of datenum)
+%! datestr (fix (now ()))
+%!demo
+%! ## Current time (fractional portion of datenum)
+%! datestr (rem (now (), 1))
+
 %!shared testtime
 %! testtime = [2005.0000, 12.0000, 18.0000, 2.0000, 33.0000, 17.3822];
-%!assert(datestr(testtime,0),"18-Dec-2005 02:33:17");
-%!assert(datestr(testtime,1),"18-Dec-2005");
-%!assert(datestr(testtime,2),"12/18/05");
-%!assert(datestr(testtime,3),"Dec");
-%!assert(datestr(testtime,4),"D");
-%!assert(datestr(testtime,5),"12");
-%!assert(datestr(testtime,6),"12/18");
-%!assert(datestr(testtime,7),"18");
-%!assert(datestr(testtime,8),"Sun");
-%!assert(datestr(testtime,9),"S");
-%!assert(datestr(testtime,10),"2005");
-%!assert(datestr(testtime,11),"05");
-%!assert(datestr(testtime,12),"Dec05");
-%!assert(datestr(testtime,13),"02:33:17");
-%!assert(datestr(testtime,14)," 2:33:17 AM");
-%!assert(datestr(testtime,15),"02:33");
-%!assert(datestr(testtime,16)," 2:33 AM");
-%!assert(datestr(testtime,17),"Q4-05");
-%!assert(datestr(testtime,18),"Q4");
-%!assert(datestr(testtime,19),"18/12");
-%!assert(datestr(testtime,20),"18/12/05");
-%!assert(datestr(testtime,21),"Dec.18.2005 02:33:17");
-%!assert(datestr(testtime,22),"Dec.18.2005");
-%!assert(datestr(testtime,23),"12/18/2005");
-%!assert(datestr(testtime,24),"18/12/2005");
-%!assert(datestr(testtime,25),"05/12/18");
-%!assert(datestr(testtime,26),"2005/12/18");
-%!assert(datestr(testtime,27),"Q4-2005");
-%!assert(datestr(testtime,28),"Dec2005");
-%!assert(datestr(testtime,29),"20051218");
-%!assert(datestr(testtime,30),"20051218T023317");
-%!assert(datestr(testtime,31),"2005-12-18 02:33:17");
-%!assert(datestr(testtime+[0 0 3 0 0 0],"dddd"),"Wednesday")
-## avoid the bug where someone happens to give a vector of datenums that
-## happens to be 6 wide
-%!assert(datestr(733452.933:733457.933), ["14-Feb-2008 22:23:31";"15-Feb-2008 22:23:31";"16-Feb-2008 22:23:31";"17-Feb-2008 22:23:31";"18-Feb-2008 22:23:31";"19-Feb-2008 22:23:31"])
-%!assert (datestr ([1944, 6, 6, 6, 30, 0], 0), "06-Jun-1944 06:30:00");
+%!assert (datestr (testtime,0), "18-Dec-2005 02:33:17")
+%!assert (datestr (testtime,1), "18-Dec-2005")
+%!assert (datestr (testtime,2), "12/18/05")
+%!assert (datestr (testtime,3), "Dec")
+%!assert (datestr (testtime,4), "D")
+%!assert (datestr (testtime,5), "12")
+%!assert (datestr (testtime,6), "12/18")
+%!assert (datestr (testtime,7), "18")
+%!assert (datestr (testtime,8), "Sun")
+%!assert (datestr (testtime,9), "S")
+%!assert (datestr (testtime,10), "2005")
+%!assert (datestr (testtime,11), "05")
+%!assert (datestr (testtime,12), "Dec05")
+%!assert (datestr (testtime,13), "02:33:17")
+%!assert (datestr (testtime,14), " 2:33:17 AM")
+%!assert (datestr (testtime,15), "02:33")
+%!assert (datestr (testtime,16), " 2:33 AM")
+%!assert (datestr (testtime,17), "Q4-05")
+%!assert (datestr (testtime,18), "Q4")
+%!assert (datestr (testtime,19), "18/12")
+%!assert (datestr (testtime,20), "18/12/05")
+%!assert (datestr (testtime,21), "Dec.18,2005 02:33:17")
+%!assert (datestr (testtime,22), "Dec.18,2005")
+%!assert (datestr (testtime,23), "12/18/2005")
+%!assert (datestr (testtime,24), "18/12/2005")
+%!assert (datestr (testtime,25), "05/12/18")
+%!assert (datestr (testtime,26), "2005/12/18")
+%!assert (datestr (testtime,27), "Q4-2005")
+%!assert (datestr (testtime,28), "Dec2005")
+%!assert (datestr (testtime,29), "2005-12-18")
+%!assert (datestr (testtime,30), "20051218T023317")
+%!assert (datestr (testtime,31), "2005-12-18 02:33:17")
+%!assert (datestr (testtime+[0 0 3 0 0 0], "dddd"), "Wednesday")
+## Test possible bug where input is a vector of datenums that is exactly 6 wide
+%!assert (datestr ([1944, 6, 6, 6, 30, 0], 0), "06-Jun-1944 06:30:00")
+## Test fractional millisecond time extension
+%!assert (datestr (testtime, "HH:MM:SS:FFF"), "02:33:17:382")
 
-# demos
-%!demo
-%! datestr (now ())
-%!demo
-%! datestr (rem (now (), 1))
-%!demo
-%! datestr (floor (now ()))
+%% Test input validation
+%!error datestr ()
+%!error datestr (1, 2, 3, 4)
--- a/scripts/time/datevec.m	Tue Nov 08 19:59:37 2011 -0500
+++ b/scripts/time/datevec.m	Wed Nov 09 14:49:09 2011 -0800
@@ -31,9 +31,9 @@
 ## @var{f} is the format string used to interpret date strings
 ## (see @code{datestr}).
 ##
-## @var{p} is the year at the start of the century in which two-digit years
-## are to be interpreted in.  If not specified, it defaults to the current
-## year minus 50.
+## @var{p} is the year at the start of the century to which two-digit years
+## will be referenced.  If not specified, it defaults to the current year
+## minus 50.
 ## @seealso{datenum, datestr, date, clock, now}
 ## @end deftypefn
 
@@ -46,13 +46,15 @@
 
 ## The function __date_str2vec__ is based on datesplit by Bill Denney.
 
-function [y, m, d, h, mi, s] = datevec (date, varargin)
+function [y, m, d, h, mi, s] = datevec (date, f = [], p = [])
 
   persistent std_formats nfmt;
 
   if (isempty (std_formats))
     std_formats = cell ();
     nfmt = 0;
+    ## These formats are specified by Matlab to be parsed
+    ## The '# XX' refers to the datestr numerical format code
     std_formats{++nfmt} = "dd-mmm-yyyy HH:MM:SS";   # 0
     std_formats{++nfmt} = "dd-mmm-yyyy";            # 1
     std_formats{++nfmt} = "mm/dd/yy";               # 2
@@ -62,6 +64,8 @@
     std_formats{++nfmt} = "HH:MM";                  # 15
     std_formats{++nfmt} = "HH:MM PM";               # 16
     std_formats{++nfmt} = "mm/dd/yyyy";             # 23
+
+    ## These are other formats that Octave tries
     std_formats{++nfmt} = "mmm-dd-yyyy HH:MM:SS";
     std_formats{++nfmt} = "mmm-dd-yyyy";
     std_formats{++nfmt} = "dd mmm yyyy HH:MM:SS";
@@ -72,8 +76,6 @@
     std_formats{++nfmt} = "dd.mmm.yyyy";
     std_formats{++nfmt} = "mmm.dd.yyyy HH:MM:SS";
     std_formats{++nfmt} = "mmm.dd.yyyy";
-
-    ## Custom formats.
     std_formats{++nfmt} = "mmmyy";                  # 12
     std_formats{++nfmt} = "mm/dd/yyyy HH:MM";
   endif
@@ -82,22 +84,14 @@
     print_usage ();
   endif
 
-  switch (nargin)
-  case 1
+  if (ischar (date))
+    date = cellstr (date);
+  endif
+
+  if (isnumeric (f))
+    p = f;
     f = [];
-    p = [];
-  case 2
-    if (ischar (varargin{1}))
-      f = varargin{1};
-      p = [];
-    else
-      f = [];
-      p = varargin{1};
-    endif
-  case 3
-      f = varargin{1};
-      p = varargin{2};
-  endswitch
+  endif
 
   if (isempty (f))
     f = -1;
@@ -107,10 +101,6 @@
     p = (localtime (time ())).year + 1900 - 50;
   endif
 
-  if (ischar (date))
-    date = cellstr (date);
-  endif
-
   if (iscell (date))
 
     nd = numel (date);
@@ -132,7 +122,7 @@
         endif
       endfor
     else
-      ## Decipher the format string just once for sake of speed.
+      ## Decipher the format string just once for speed.
       [f, rY, ry, fy, fm, fd, fh, fmi, fs] = __date_vfmt2sfmt__ (f);
       for k = 1:nd
         [found y(k) m(k) d(k) h(k) mi(k) s(k)] = __date_str2vec__ (date{k}, p, f, rY, ry, fy, fm, fd, fh, fmi, fs);
@@ -142,7 +132,7 @@
       endfor
     endif
 
-  else
+  else   # datenum input
 
     date = date(:);
 
@@ -187,38 +177,7 @@
 function [f, rY, ry, fy, fm, fd, fh, fmi, fs] = __date_vfmt2sfmt__ (f)
 
   ## Play safe with percent signs.
-  f = strrep(f, "%", "%%");
-
-  ## Dates to lowercase (note: we cannot convert MM to mm).
-  f = strrep (f, "YYYY", "yyyy");
-  f = strrep (f, "YY", "yy");
-  f = strrep (f, "QQ", "qq");
-  f = strrep (f, "MMMM", "mmmm");
-  f = strrep (f, "MMM", "mmm");
-  f = strrep (f, "DDDD", "dddd");
-  f = strrep (f, "DDD", "ddd");
-  f = strrep (f, "DD", "dd");
-  ## Times to uppercase (also cannot convert mm to MM).
-  f = strrep (f, "hh", "HH");
-  f = strrep (f, "ss", "SS");
-  f = strrep (f, "pm", "PM");
-  f = strrep (f, "am", "AM");
-
-  ## Right now, the format string may only contain these tokens:
-  ##
-  ## yyyy   4 digit year
-  ## yy     2 digit year
-  ## mmmm   month name, full
-  ## mmm    month name, abbreviated
-  ## mm     month number
-  ## dddd   weekday name, full
-  ## ddd    weekday name, abbreviated
-  ## dd     date
-  ## HH     hour
-  ## MM     minutes
-  ## SS     seconds
-  ## PM     AM/PM
-  ## AM     AM/PM
+  f = strrep (f, "%", "%%");
 
   if (! isempty (strfind (f, "PM")) || ! isempty (strfind (f, "AM")))
     ampm = true;
@@ -227,14 +186,14 @@
   endif
 
   ## Date part.
-  f = strrep (f, "yyyy", "%Y");
-  f = strrep (f, "yy", "%y");
+  f = regexprep (f, '[Yy][Yy][Yy][Yy]', "%Y");
+  f = regexprep (f, '[Yy][Yy]', "%y");
   f = strrep (f, "mmmm", "%B");
   f = strrep (f, "mmm", "%b");
   f = strrep (f, "mm", "%m");
-  f = strrep (f, "dddd", "%A");
-  f = strrep (f, "ddd", "%a");
-  f = strrep (f, "dd", "%d");
+  f = regexprep (f, '[Dd][Dd][Dd][Dd]', "%A");
+  f = regexprep (f, '[Dd][Dd][Dd]', "%a");
+  f = regexprep (f, '[Dd][Dd]', "%d");
 
   ## Time part.
   if (ampm)
@@ -245,7 +204,7 @@
     f = strrep (f, "HH", "%H");
   endif
   f = strrep (f, "MM", "%M");
-  f = strrep (f, "SS", "%S");
+  f = regexprep (f, '[Ss][Ss]', "%S");
 
   rY = rindex (f, "%Y");
   ry = rindex (f, "%y");
@@ -263,12 +222,25 @@
 
 function [found, y, m, d, h, mi, s] = __date_str2vec__ (ds, p, f, rY, ry, fy, fm, fd, fh, fmi, fs)
 
-  [tm, nc] = strptime (ds, f);
-
-  if (nc == size (ds, 2) + 1)
+  idx = strfind (f, "FFF");
+  if (! isempty (idx)) 
+    ## Kludge to handle FFF millisecond format since strptime does not
+    f(idx:idx+2) = []; 
+    [~, nc] = strptime (ds, f);
+    if (nc > 0)
+      msec = ds(nc:min(nc+2, end)); 
+      f = [f(1:idx-1) msec f(idx:end)]; 
+      [tm, nc] = strptime (ds, f);
+      tm.usec = 1000 * str2double (msec);
+    endif
+  else
+    [tm, nc] = strptime (ds, f);
+  endif
+  
+  if (nc == columns (ds) + 1)
+    found = true;
     y = tm.year + 1900; m = tm.mon + 1; d = tm.mday;
     h = tm.hour; mi = tm.min; s = tm.sec + tm.usec / 1e6;
-    found = true;
     if (rY < ry)
       if (y > 1999)
         y -= 2000;
@@ -289,7 +261,6 @@
       tmp = localtime (time ());
       y = tmp.year + 1900;
     elseif (fy && fm && ! fd)
-      tmp = localtime (time ());
       d = 1;
     endif
     if (! fh && ! fmi && ! fs)
@@ -304,23 +275,30 @@
 
 endfunction
 
+
+%!demo
+%! ## Current date and time
+%! datevec (now ())
+
 %!shared nowvec
 %! nowvec = datevec (now); # Some tests could fail around midnight!
-# tests for standard formats: 0, 1, 2, 6, 13, 14, 15, 16, 23
-%!assert(datevec("07-Sep-2000 15:38:09"),[2000,9,7,15,38,9]);
-%!assert(datevec("07-Sep-2000"),[2000,9,7,0,0,0]);
-%!assert(datevec("09/07/00"),[2000,9,7,0,0,0]);
-%!assert(datevec("09/13"),[nowvec(1),9,13,0,0,0]);
-%!assert(datevec("15:38:09"),[nowvec(1:3),15,38,9]);
-%!assert(datevec("3:38:09 PM"),[nowvec(1:3),15,38,9]);
-%!assert(datevec("15:38"),[nowvec(1:3),15,38,0]);
-%!assert(datevec("03:38 PM"),[nowvec(1:3),15,38,0]);
-%!assert(datevec("03/13/1962"),[1962,3,13,0,0,0]);
-# other tests
-%!assert(all(datenum(datevec([-1e4:1e4]))==[-1e4:1e4]'))
+%!# tests for standard formats: 0, 1, 2, 6, 13, 14, 15, 16, 23
+%!assert (datevec ("07-Sep-2000 15:38:09"), [2000,9,7,15,38,9])
+%!assert (datevec ("07-Sep-2000"), [2000,9,7,0,0,0])
+%!assert (datevec ("09/07/00"), [2000,9,7,0,0,0])
+%!assert (datevec ("09/13"), [nowvec(1),9,13,0,0,0])
+%!assert (datevec ("15:38:09"), [nowvec(1:3),15,38,9])
+%!assert (datevec ("3:38:09 PM"), [nowvec(1:3),15,38,9])
+%!assert (datevec ("15:38"), [nowvec(1:3),15,38,0])
+%!assert (datevec ("03:38 PM"), [nowvec(1:3),15,38,0])
+%!assert (datevec ("03/13/1962"), [1962,3,13,0,0,0])
+
+%% Test millisecond format FFF
+%!assert (datevec ("15:38:21.25", "HH:MM:SS.FFF"), [nowvec(1:3),15,38,21.025])
+
+# Other tests
+%!assert (datenum (datevec ([-1e4:1e4])), [-1e4:1e4]')
 %!test
 %! t = linspace (-2e5, 2e5, 10993);
 %! assert (all (abs (datenum (datevec (t)) - t') < 1e-5));
-# demos
-%!demo
-%! datevec (now ())
+