1 ## Copyright (C) 2001 Bill Denney <bill@denney.ws>
3 ## This program is free software; you can redistribute it and/or modify it under
4 ## the terms of the GNU General Public License as published by the Free Software
5 ## Foundation; either version 3 of the License, or (at your option) any later
8 ## This program is distributed in the hope that it will be useful, but WITHOUT
9 ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 ## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 ## You should have received a copy of the GNU General Public License along with
14 ## this program; if not, see <http://www.gnu.org/licenses/>.
17 ## @deftypefn {Function File} {Y =} datesplit(date, P)
18 ## @deftypefnx {Function File} {[Y,M,D,h,m,s] =} datesplit(date, P)
19 ## Split a date string into the Year, Month, Day, hour, minute, and
20 ## second. This routine tries to be as forgiving as possible to the
21 ## date input while requiring that the date is not ambiguous.
23 ## Anywhere possible where it would not be ambiguous, efforts were made
24 ## to make times possible with seconds and AM/PM as optional. Also,
25 ## along the same lines, where possible, commas were allowed with
26 ## spaces, and the year/month/day separators were allowed as period (.),
27 ## slash (/), and dash (-). Not all format possibilities are shown in
28 ## the following table, but a date like @code{dd-mmm-yyyy HH:MM:SS} is
29 ## parsed just as well as @code{d/mmm.yyyy, ,H:MM, AM}.
31 ## Supported @code{date} formats include (the same as datestr):
32 ## @multitable @columnfractions 0.1 0.45 0.45
33 ## @item @strong{Code} @tab @strong{Format} @tab @strong{Example}
34 ## @item 0 @tab dd-mmm-yyyy HH:MM:SS @tab 07-Sep-2000 15:38:09
35 ## @item 1 @tab dd-mmm-yyyy @tab 07-Sep-2000
36 ## @item 2 @tab mm/dd/yy @tab 09/07/00
37 ## @item 3 @tab mmm @tab Sep
38 ## @item 6 @tab mm/dd @tab 09/13
39 ## @item 10 @tab yyyy @tab 2000
40 ## @item 12 @tab mmmyy @tab Sep00
41 ## @item 13 @tab HH:MM:SS @tab 15:38:09
42 ## @item 14 @tab HH:MM:SS PM @tab 03:38:09 PM
43 ## @item 15 @tab HH:MM @tab 15:38
44 ## @item 16 @tab HH:MM PM @tab 03:38 PM
45 ## @item 17 @tab QQ-YY @tab Q3-00
46 ## @item 19 @tab dd/mm @tab 13/03
47 ## @item 20 @tab dd/mm/yy @tab 13/03/95
48 ## @item 21 @tab mmm.dd.yyyy HH:MM:SS @tab Mar.03.1962 13:53:06
49 ## @item 22 @tab mmm.dd.yyyy @tab Mar.03.1962
50 ## @item 23 @tab mm/dd/yyyy @tab 03/13/1962
51 ## @item 24 @tab dd/mm/yyyy @tab 12/03/1962
52 ## @item 25 @tab yy/mm/dd @tab 95/03/13
53 ## @item 26 @tab yyyy/mm/dd @tab 1995/03/13
54 ## @item 27 @tab QQ-YYYY @tab Q4-2132
55 ## @item 28 @tab mmmyyyy @tab Mar2047
56 ## @item 29 @tab yyyymmdd @tab 20470313
57 ## @item 30 @tab yyyymmddTHHMMSS @tab 20470313T132603
58 ## @item 31 @tab yyyy-mm-dd HH:MM:SS @tab 1047-03-13 13:26:03
61 ## The parameter @code{P} is needed to convert date strings with 2 digit
62 ## years into dates with 4 digit years. 2 digit years are assumed to be
63 ## between @code{P} and @code{P+99}. If @code{P} is not given then the
64 ## current year - 50 is used, so that dates are centered on the present.
65 ## For birthdates, you would want @code{P} to be current year - 99. For
66 ## appointments, you would want @code{P} to be current year.
68 ## This function makes no strong attempt to verify the accuracy of the
69 ## numbers that it returns in that it doesn't (currently) check to see
70 ## that you're not trying to use the date Feb 30. When applicable, it
71 ## tries to make your input work, though. It will try to determine if
72 ## you're using the date "03/13/95" that the date is "March 13, 1995",
73 ## but if there is doubt, datesplit will return an error instead of
74 ## trying to guess the wrong value.
76 ## @seealso{date,clock,now,datestr,datenum,calendar,weekday}
80 ## * Some formats are ambiguous. Allow the user to specify the format
81 ## to remove ambiguity.
82 ## * Validate the dates.
83 ## * Possible bug (after dates are validated): There are times where
84 ## the year is assumed, Feb 29 may be a valid date, but with the
85 ## assumed year, it may become invalid.
86 ## * Internationalize. Not all months use the English system.
87 ## * Vectorize. That requires vectorization of regexp though...
89 function [y, m, d, h, mi, s] = datesplit(ds, P)
91 persistent warned = false;
94 warning ("Octave:deprecated-function",
95 "`datesplit' has been deprecated in favor of `datevec' from Octave core. This function will be removed from future versions of the `financial' package");
102 today = datevec(now);
108 global __month_names = ["Jan";"Feb";"Mar";"Apr";"May";"Jun";...
109 "Jul";"Aug";"Sep";"Oct";"Nov";"Dec"];
110 global __day_names = ["Sun";"Mon";"Tue";"Wed";"Thu";"Fri";"Sat"];
111 global __time_names = ["AM";"PM"];
116 ds = tolower(deblank(ds));
119 error("datesplit: no input arguments");
123 %% we have to determine the format, this could be error prone
125 ## format 0 dd-mmm-yyyy HH:MM:SS e.g. 07-Sep-2000 15:38:09
126 [match, d, m, y, h, mi, s, ap] = \
127 of_regexp("^(3[01]|[0-2]?[0-9])[-./]([a-z]{3})[-./]([0-9]{4})[, ]+(2[0-3]|[01]?[0-9]):([0-5][0-9])(:[0-5][0-9]|)[, ]*([ap]m|)$", ds);
129 ## format 21 mmm.dd.yyyy HH:MM:SS e.g. Mar.03.1962 13:53:06
131 [match, m, d, y, h, mi, s, ap] = \
132 of_regexp("^([a-z]{3})[-./](3[01]|[0-2]?[0-9])[-./]([0-9]{4})[, ]+(2[0-3]|[01]?[0-9]):([0-5][0-9])(:[0-5][0-9]|)[, ]*([ap]m|)$", ds);
135 ## format 31 yyyy-mm-dd HH:MM:SS e.g. 2004-03-13 13:26:03
137 [match, y, m, d, h, mi, s, ap] = \
138 of_regexp("^([0-9]{4})[-./](1[012]|0?[0-9])[-./](3[01]|[0-2]?[0-9])[, ]+(2[0-3]|[01]?[0-9]):([0-5][0-9])(:[0-5][0-9]|)[, ]*([ap]m|)$", ds);
141 ## format 30 yyyymmddTHHMMSS e.g. 20470313T132603
143 [match, y, m, d, h, mi, s] = \
144 of_regexp("^([0-9]{4})(1[012]|0[0-9])(3[01]|[012][0-9])t(2[0-3]|[01][0-9])([0-5][0-9])([0-5][0-9])$", ds);
148 ## format 13 HH:MM:SS e.g. 15:38:09
149 ## format 14 HH:MM:SS PM e.g. 03:38:09 PM
150 ## format 15 HH:MM e.g. 15:38
151 ## format 16 HH:MM PM e.g. 03:38 PM
153 [match, h, mi, s, ap] = \
154 of_regexp("^(2[0-3]|[01]?[0-9]):([0-5][0-9])(:[0-5][0-9]|)[, ]*([ap]m|)$", ds);
156 if (! isempty(match))
157 %% assume that it is as of today
164 ## format 1 dd-mmm-yyyy e.g. 07-Sep-2000
167 of_regexp("^(3[01]|[012]?[0-9])[-./]([a-z]{3})[-./]([0-9]{4})$", ds);
169 if (! isempty(match))
170 %% assume the beginning of the day
178 ## format 22 mmm.dd.yyyy e.g. Mar.03.1962
181 of_regexp("^([a-z]{3})[-./](3[01]|[012]?[0-9])[-./]([0-9]{4})$", ds);
183 if (! isempty(match))
184 %% assume the beginning of the day
192 ## format 2 mm/dd/yy e.g. 09/07/00
193 ## format 23 mm/dd/yyyy e.g. 03/13/1962
194 ## format 20 dd/mm/yy e.g. 13/03/95
195 ## format 24 dd/mm/yyyy e.g. 12/03/1962
196 ## format 25 yy/mm/dd e.g. 95/03/13
197 ## format 26 yyyy/mm/dd e.g. 1995/03/13
200 of_regexp("^([0-9]{1,2}|[0-9]{4})[-./](3[01]|[012]?[0-9])[-./]([0-9]{1,2}|[0-9]{4})$", ds);
202 if (! isempty(match))
203 %% we have to determine if the date is unambiguous
208 if ((y == 0) || (y > 31))
209 %% we've got the year correct
210 if ((m > 12) && (d < 13))
211 %% we're operating on mm/dd/yyyy
215 elseif ((m < 13) && (d > 12))
219 error(["datesplit: ambiguous date " ds]);
221 elseif ((d == 0) || (d > 31))
222 %% the day and the year need to be switched
228 error(["datesplit: ambiguous date " ds]);
231 %% assume the beginning of the day
240 ## format 29 yyyymmdd e.g. 20470313
243 of_regexp("^([0-9]{4})(1[012]|0?[0-9])(3[01]|[012][0-9])$", ds);
244 %% I've never seen a date that has the year first that was not
245 %% yyyy/mm/dd, so I'm going to assume that it's unambiguous.
247 if (! isempty(match))
248 %% assume the beginning of the day
256 ## format 17 QQ-YY e.g. Q3-00
257 ## format 27 QQ-YYYY e.g. Q4-2132
260 of_regexp("^q([1-4])[-./]([0-9]{2}|[0-9]{4})$", ds);
261 if (! isempty(match))
262 %% Assume that it's the end of the quarter
265 dayopts = [31 30 30 31];
268 %% assume the end of the day
276 ## format 28 mmmyyyy e.g. Mar2047
277 ## format 12 mmmyy e.g. Sep00
280 of_regexp("^([a-z]{3})([0-9]{2}|[0-9]{4})$", ds);
281 if (! isempty(match))
282 %% assume the beginning of the month
291 ## format 6 mm/dd e.g. 09/07
292 ## format 19 dd/mm e.g. 13/03
295 of_regexp("^(3[01]|[012]?[0-9])[-./](3[01]|[012][0-9])$", ds);
297 if (! isempty(match))
301 %% we have to determine if the date is unambiguous
302 if ((m > 12) && (d < 13))
303 %% we're operating on mm/dd/yyyy
307 elseif ((m < 13) && (d > 12))
311 error(["datesplit: ambiguous date " ds]);
313 %% assume this year and the beginning of the day
322 ## format 10 yyyy e.g. 2000
324 [match, y] = of_regexp("^([0-9]{4})$", ds);
326 if (! isempty(match))
327 %% assume the beginning of the year
337 ## format 3 mmm e.g. Sep
339 m = strmatch(ds, tolower(__month_names));
343 %% assuming the beginning of the month, this year
353 ## format 8 ddd e.g. Thu
354 %% People shouldn't use this function for something like this
357 %% you mean I did all that work, and you still didn't use a valid
359 error(["datesplit: Unknown date format " ds]);
362 if (! isempty(match))
365 elseif (ischar(s) && (1 == findstr(s,":")))
373 %% start converting the date from characters to numbers
377 error(["datesplit: Invalid year specification " y]);
380 %% Handle Y2K issues...
391 m = strmatch(m, tolower(__month_names));
396 error(["datesplit: Invalid month specification"]);
403 error(["datesplit: Invalid day specification " d]);
410 error(["datesplit: Invalid hour specification " h]);
411 elseif ((ap(2) == "M") && (h > 12))
412 error(["datesplit: Invalid hour specification, AM or PM specified but"
413 "hour is greater than 12."]);
416 if (strcmpi(ap, "PM") && (h < 12))
418 elseif (strcmpi(ap, "AM") && (h == 12))
425 if (isempty(mi) || (mi > 59))
426 error(["datesplit: Invalid minute specification " mi]);
432 if (isempty(s) || (s > 59))
433 error(["datesplit: Invalid second specification " s]);
438 y = [y, m, d, h, mi, s];
443 # wrapper around Octave's regexp
444 # compatible with octave-forge's regexp
446 function varargout = of_regexp(pattern,string)
447 [S, E, TE, M, T, NM] = regexp(string,pattern);
450 # return sub-strings if match
453 varargout{i} = T{1}{i-1};
464 %! nowvec=datevec(now); % Some tests could fail around midnight!
465 %!assert (datevec("07-Sep-2000 15:38:09"),[2000,9,7,15,38,9]);
466 %!assert (datevec("07-Sep-2000"),[2000,9,7,0,0,0]);
467 %!assert (datevec("1 Jan 2000"),[2000,1,1,0,0,0]);
468 %!assert (datevec("Sep00"),[2000,9,1,0,0,0]);
469 %!assert (datevec("15:38:09"),[nowvec(1:3),15,38,9]);
470 %!assert (datevec("03:38:09 PM"),[nowvec(1:3),15,38,9]);
471 %!assert (datevec("15:38"),[nowvec(1:3),15,38,0]);
472 %!assert (datevec("3:38 PM"),[nowvec(1:3),15,38,0]);
473 %!assert (datevec("03/13/1962"),[1962,3,13,0,0,0]);
476 %!#assert (datevec("09/07/00"),[2000,9,7,0,0,0]);
477 %!#assert (datevec("09/13"),[nowvec(1),9,13,0,0,0]);
479 ## Not supported in octave version of datevec
480 %!#assert (datevec("Sep"),[nowvec(1),9,1,0,0,0]);
481 %!#assert (datevec("Q3-00"),[2000,9,30,23,59,59]);
482 %!#assert (datevec("Q4-2132"),[2132,12,31,23,59,59]);
484 ## This should be a standard string (without or without the time)
485 %!assert (datevec("Mar.03.1962 13:53:06","mmm.dd.yyyy HH:MM:SS"),[1962,3,3,13,53,6]);
487 ## No longer a predefined string to parse the date in octave version
488 %!assert (datevec("1995/03/13","yyyy/mm/dd"),[1995,3,13,0,0,0]);
489 %!assert (datevec("Mar2047","mmmyyyy"),[2047,3,1,0,0,0]);
490 %!assert (datevec("20470313","yyyymmdd"),[2047,3,13,0,0,0]);
491 %!assert (datevec("20470313T132603","yyyymmddTHHMMSS"),[2047,3,13,13,26,3]);
492 %!assert (datevec("1047-03-13 13:26:03","yyyy-mm-dd HH:MM:SS"),[1047,3,13,13,26,3]);