]> Creatis software - CreaPhase.git/blob - octave_packages/financial-0.4.0/datesplit.m
Add a useful package (from Source forge) for octave
[CreaPhase.git] / octave_packages / financial-0.4.0 / datesplit.m
1 ## Copyright (C) 2001 Bill Denney <bill@denney.ws>
2 ##
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
6 ## version.
7 ##
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
11 ## details.
12 ##
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/>.
15
16 ## -*- texinfo -*-
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.
22 ##
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}.
30 ##
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
59 ## @end multitable
60 ##
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.
67 ##
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.
75 ##
76 ## @seealso{date,clock,now,datestr,datenum,calendar,weekday} 
77 ## @end deftypefn
78
79 ## TODO:
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...
88
89 function [y, m, d, h, mi, s] = datesplit(ds, P)
90
91   persistent warned = false;
92   if (! warned)
93     warned = true;
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");
96   endif
97
98   if nargin < 2
99     P = [];
100   endif
101
102   today = datevec(now);
103
104   if (isempty(P))
105     P = today(1)-50;
106   endif
107
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"];
112
113   if (iscellstr(ds))
114     ds = ds{1};
115   endif
116   ds = tolower(deblank(ds));
117
118   if (nargin < 1)
119     error("datesplit: no input arguments");
120   elseif (nargin == 1)
121     fmt = [];
122   endif
123   %% we have to determine the format, this could be error prone
124
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);
128
129   ## format 21  mmm.dd.yyyy HH:MM:SS    e.g. Mar.03.1962 13:53:06
130   if (isempty(match))
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);
133   endif
134
135   ## format 31  yyyy-mm-dd HH:MM:SS     e.g. 2004-03-13 13:26:03
136   if (isempty(match))
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);
139   endif
140
141   ## format 30  yyyymmddTHHMMSS         e.g. 20470313T132603
142   if (isempty(match))
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);
145     ap = "NA";
146   endif
147
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
152   if (isempty(match))
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);
155
156     if (! isempty(match))
157       %% assume that it is as of today
158       y = today(1);
159       m = today(2);
160       d = today(3);
161     endif
162   endif
163
164   ## format  1  dd-mmm-yyyy             e.g. 07-Sep-2000
165   if (isempty(match))
166     [match, d, m, y] = \
167         of_regexp("^(3[01]|[012]?[0-9])[-./]([a-z]{3})[-./]([0-9]{4})$", ds);
168
169     if (! isempty(match))
170       %% assume the beginning of the day
171       h = 0;
172       mi = 0;
173       s = 0;
174       ap = "NA";
175     endif
176   endif
177
178   ## format 22  mmm.dd.yyyy             e.g. Mar.03.1962
179   if (isempty(match))
180     [match, m, d, y] = \
181         of_regexp("^([a-z]{3})[-./](3[01]|[012]?[0-9])[-./]([0-9]{4})$", ds);
182
183     if (! isempty(match))
184       %% assume the beginning of the day
185       h = 0;
186       mi = 0;
187       s = 0;
188       ap = "NA";
189     endif
190   endif
191
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
198   if (isempty(match))
199     [match, d, m, y] = \
200         of_regexp("^([0-9]{1,2}|[0-9]{4})[-./](3[01]|[012]?[0-9])[-./]([0-9]{1,2}|[0-9]{4})$", ds);
201
202     if (! isempty(match))
203       %% we have to determine if the date is unambiguous
204       d = str2num(d);
205       m = str2num(m);
206       y = str2num(y);
207
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
212           tmp = m;
213           m = d;
214           d = tmp;
215         elseif ((m < 13) && (d > 12))
216           %% it's fine
217         else
218           %% it's ambiguous
219           error(["datesplit: ambiguous date " ds]);
220         endif
221       elseif ((d == 0) || (d > 31))
222         %% the day and the year need to be switched
223         tmp = y;
224         y = d;
225         d = tmp;
226       else
227         %% it's ambiguous
228         error(["datesplit: ambiguous date " ds]);
229       endif
230
231       %% assume the beginning of the day
232       h = 0;
233       mi = 0;
234       s = 0;
235       ap = "NA";
236     endif
237
238   endif
239
240   ## format 29  yyyymmdd                e.g. 20470313
241   if (isempty(match))
242     [match, y, m, d] = \
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.
246
247     if (! isempty(match))
248       %% assume the beginning of the day
249       h = 0;
250       mi = 0;
251       s = 0;
252       ap = "NA";
253     endif
254   endif
255
256   ## format 17  QQ-YY                   e.g. Q3-00
257   ## format 27  QQ-YYYY                 e.g. Q4-2132
258   if (isempty(match))
259     [match, q, y] = \
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
263       q = str2num(q);
264       m = 3*q;
265       dayopts = [31 30 30 31];
266       d = dayopts(q);
267     
268       %% assume the end of the day
269       h = 23;
270       mi = 59;
271       s = 59;
272       ap = "NA";
273     endif
274   endif
275
276   ## format 28  mmmyyyy                 e.g. Mar2047
277   ## format 12  mmmyy                   e.g. Sep00
278   if (isempty(match))
279     [match, m, y] = \
280         of_regexp("^([a-z]{3})([0-9]{2}|[0-9]{4})$", ds);
281     if (! isempty(match))
282       %% assume the beginning of the month
283       d = 1;
284       h = 0;
285       mi = 0;
286       s = 0;
287       ap = "NA";
288     endif
289   endif
290
291   ## format  6  mm/dd                   e.g. 09/07
292   ## format 19  dd/mm                   e.g. 13/03
293   if (isempty(match))
294     [match, m, d] = \
295         of_regexp("^(3[01]|[012]?[0-9])[-./](3[01]|[012][0-9])$", ds);
296
297     if (! isempty(match))
298       m = str2num(m);
299       d = str2num(d);
300
301       %% we have to determine if the date is unambiguous
302       if ((m > 12) && (d < 13))
303         %% we're operating on mm/dd/yyyy
304         tmp = m;
305         m = d;
306         d = tmp;
307       elseif ((m < 13) && (d > 12))
308         %% it's fine
309       else
310         %% it's ambiguous
311         error(["datesplit: ambiguous date " ds]);
312       endif
313       %% assume this year and the beginning of the day
314       y = today(1);
315       h = 0;
316       mi = 0;
317       s = 0;
318       ap = "NA";
319     endif
320   endif
321
322   ## format 10  yyyy                    e.g. 2000
323   if (isempty(match))
324     [match, y] = of_regexp("^([0-9]{4})$", ds);
325
326     if (! isempty(match))
327       %% assume the beginning of the year
328       m = 1;
329       d = 1;
330       h = 0;
331       mi = 0;
332       s = 0;
333       ap = "NA";
334     endif
335   endif
336
337   ## format  3  mmm                     e.g. Sep
338   if (isempty(match))
339     m = strmatch(ds, tolower(__month_names));
340
341     if (! isempty(m))
342       match = 1;
343       %% assuming the beginning of the month, this year
344       y = today(1);
345       d = 1;
346       h = 0;
347       mi = 0;
348       s = 0;
349       ap = "NA";
350     endif
351   endif
352
353   ## format  8  ddd                     e.g. Thu
354   %% People shouldn't use this function for something like this
355
356   if (isempty(match))
357     %% you mean I did all that work, and you still didn't use a valid
358     %% date?  Darn you!
359     error(["datesplit: Unknown date format " ds]);
360   endif
361
362   if (! isempty(match))
363     if isempty(s)
364       s = 0;
365     elseif (ischar(s) && (1 == findstr(s,":")))
366       s = s(2:3);
367     endif
368     if isempty(ap)
369       ap = "NA";
370     endif
371   endif
372
373   %% start converting the date from characters to numbers
374   if (ischar(y))
375     y = str2num(y);
376     if (isempty(y))
377       error(["datesplit: Invalid year specification " y]);
378     endif
379   endif
380   %% Handle Y2K issues...
381   if (y < 100)
382     y = y + 1900;
383     if (y < P)
384       y = y + 100;
385     endif
386   endif
387
388   if (ischar(m))
389     m_num = str2num(m);
390     if (isempty(m_num))
391       m = strmatch(m, tolower(__month_names));
392     else
393       m = m_num;
394     endif
395     if (isempty(m))
396       error(["datesplit: Invalid month specification"]);
397     endif
398   endif
399
400   if (ischar(d))
401     d = str2num(d);
402     if (isempty(d))
403       error(["datesplit: Invalid day specification " d]);
404     endif
405   endif
406
407   if (ischar(h))
408     h = str2num(h);
409     if (isempty(h))
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."]);
414     endif
415     
416     if (strcmpi(ap, "PM") && (h < 12))
417       h = h + 12;
418     elseif (strcmpi(ap, "AM") && (h == 12))
419       h = 0;
420     endif
421   endif
422
423   if (ischar(mi))
424     mi = str2num(mi);
425     if (isempty(mi) || (mi > 59))
426       error(["datesplit: Invalid minute specification " mi]);
427     endif
428   endif
429
430   if (ischar(s))
431     s = str2num(s);
432     if (isempty(s) || (s > 59))
433       error(["datesplit: Invalid second specification " s]);
434     endif
435   endif
436
437   if (nargout <= 1)
438     y = [y, m, d, h, mi, s];
439   endif
440
441 endfunction
442
443 # wrapper around Octave's regexp
444 # compatible with octave-forge's regexp
445
446 function varargout = of_regexp(pattern,string)
447   [S, E, TE, M, T, NM]  = regexp(string,pattern);
448   varargout{1} = S;
449   
450   # return sub-strings if match
451   if (S)
452     for i=2:nargout
453       varargout{i} = T{1}{i-1};  
454     end    
455   else
456     for i=2:nargout
457       varargout{i} = [];
458     end        
459   end
460 endfunction
461
462
463 %!shared nowvec
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]);
474
475 ## Ambiguous
476 %!#assert (datevec("09/07/00"),[2000,9,7,0,0,0]);
477 %!#assert (datevec("09/13"),[nowvec(1),9,13,0,0,0]);
478
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]);
483
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]);
486
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]);