GnmFunc: make this a GObject.
[gnumeric.git] / plugins / fn-date / functions.c
blob0917c73882f9fe7c1bba8b6ad5fe4ee51691ef9d
1 /*
2 * fn-date.c: Built in date functions.
4 * Authors:
5 * Miguel de Icaza (miguel@gnu.org)
6 * Jukka-Pekka Iivonen (iivonen@iki.fi)
7 * Morten Welinder <terra@gnome.org>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, see <https://www.gnu.org/licenses/>.
22 #include <gnumeric-config.h>
23 #include <gnumeric.h>
24 #include <func.h>
26 #include <parse-util.h>
27 #include <cell.h>
28 #include <gnm-datetime.h>
29 #include <value.h>
30 #include <mathfunc.h>
31 #include <gnm-format.h>
32 #include <workbook.h>
33 #include <sheet.h>
34 #include <collect.h>
35 #include <gnm-i18n.h>
37 #include <math.h>
38 #include <string.h>
39 #include <stdlib.h>
41 #include <goffice/goffice.h>
42 #include <gnm-plugin.h>
44 GNM_PLUGIN_MODULE_HEADER;
46 #define DAY_SECONDS (3600*24)
47 #define DATE_CONV(ep) sheet_date_conv ((ep)->sheet)
49 static GnmValue *
50 make_date (GnmValue *res)
52 value_set_fmt (res, go_format_default_date ());
53 return res;
56 static int
57 value_get_basis (const GnmValue *v, int defalt)
59 if (v) {
60 gnm_float b = value_get_as_float (v);
62 if (b < 0 || b >= 6)
63 return -1;
64 return (int)b;
65 } else
66 return defalt;
69 /***************************************************************************/
71 static GnmFuncHelp const help_date[] = {
72 { GNM_FUNC_HELP_NAME, F_("DATE:create a date serial value")},
73 { GNM_FUNC_HELP_ARG, F_("year:year of date")},
74 { GNM_FUNC_HELP_ARG, F_("month:month of year")},
75 { GNM_FUNC_HELP_ARG, F_("day:day of month")},
76 { GNM_FUNC_HELP_DESCRIPTION, F_("The DATE function creates date serial values. 1-Jan-1900 is serial value 1, 2-Jan-1900 is serial value 2, and so on. For compatibility reasons, a serial value is reserved for the non-existing date 29-Feb-1900.") },
77 { GNM_FUNC_HELP_NOTE, F_("If @{month} or @{day} is less than 1 or too big, then the year and/or month will be adjusted.") },
78 { GNM_FUNC_HELP_NOTE, F_("For spreadsheets created with the Mac version of Excel, serial 1 is 1-Jan-1904.") },
79 { GNM_FUNC_HELP_EXAMPLES, "=DATE(2008,1,1)" },
80 { GNM_FUNC_HELP_EXAMPLES, "=DATE(2008,13,1)" },
81 { GNM_FUNC_HELP_EXAMPLES, "=DATE(2008,1,-10)" },
82 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
83 { GNM_FUNC_HELP_SEEALSO, "TODAY,YEAR,MONTH,DAY"},
84 { GNM_FUNC_HELP_END }
87 static GnmValue *
88 gnumeric_date (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
90 gnm_float year = value_get_as_float (argv [0]);
91 gnm_float month = value_get_as_float (argv [1]);
92 gnm_float day = value_get_as_float (argv [2]);
93 GDate date;
94 GODateConventions const *conv = DATE_CONV (ei->pos);
96 if (year < 0 || year >= 10000)
97 goto error;
98 if (!gnm_datetime_allow_negative () && year < 1900)
99 year += 1900; /* Excel compatibility. */
100 else if (year < 1000)
101 year += 1900; /* Somewhat more sane. */
103 /* This uses floor and not trunc on purpose. */
104 month = gnm_floor (month);
105 if (gnm_abs (month) > 120000) /* Actual number not critical. */
106 goto error;
108 /* This uses floor and not trunc on purpose. */
109 day = gnm_floor (day);
110 if (day < -32768 || day >= 32768)
111 day = 32767; /* Absurd, but yes. */
113 g_date_clear (&date, 1);
115 g_date_set_dmy (&date, 1, 1, (int)year);
116 gnm_date_add_months (&date, (int)month - 1);
117 gnm_date_add_days (&date, (int)day - 1);
119 if (!g_date_valid (&date) ||
120 g_date_get_year (&date) < (gnm_datetime_allow_negative ()
121 ? 1582
122 : go_date_convention_base (conv)) ||
123 g_date_get_year (&date) >= 11900)
124 goto error;
126 return make_date (value_new_int (go_date_g_to_serial (&date, conv)));
128 error:
129 return value_new_error_NUM (ei->pos);
132 /***************************************************************************/
134 static GnmFuncHelp const help_unix2date[] = {
135 { GNM_FUNC_HELP_NAME, F_("UNIX2DATE:date value corresponding to the Unix timestamp @{t}")},
136 { GNM_FUNC_HELP_ARG, F_("t:Unix time stamp")},
137 { GNM_FUNC_HELP_DESCRIPTION, F_("The UNIX2DATE function translates Unix timestamps into the corresponding date. A Unix timestamp is the number of seconds since midnight (0:00) of January 1st, 1970 GMT.") },
138 { GNM_FUNC_HELP_EXAMPLES, "=UNIX2DATE(1000000000)" },
139 { GNM_FUNC_HELP_SEEALSO, "DATE2UNIX,DATE"},
140 { GNM_FUNC_HELP_END }
143 static GnmValue *
144 gnumeric_unix2date (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
146 gnm_float futime = value_get_as_float (argv [0]);
147 time_t utime = (time_t)futime;
148 gnm_float serial;
150 /* Check for overflow. */
151 if (gnm_abs (futime - utime) >= 1.0)
152 return value_new_error_VALUE (ei->pos);
154 serial = go_date_timet_to_serial_raw (utime, DATE_CONV (ei->pos));
155 if (serial == G_MAXINT)
156 return value_new_error_VALUE (ei->pos);
158 return make_date (value_new_float (serial +
159 (futime - utime) / DAY_SECONDS));
162 /***************************************************************************/
164 static GnmFuncHelp const help_date2unix[] = {
165 { GNM_FUNC_HELP_NAME, F_("DATE2UNIX:the Unix timestamp corresponding to a date @{d}") },
166 { GNM_FUNC_HELP_ARG, F_("d:date")},
167 { GNM_FUNC_HELP_DESCRIPTION, F_("The DATE2UNIX function translates a date into a Unix timestamp. A Unix timestamp is the number of seconds since midnight (0:00) of January 1st, 1970 GMT.") },
168 { GNM_FUNC_HELP_EXAMPLES, "=DATE2UNIX(DATE(2000,1,1))" },
169 { GNM_FUNC_HELP_SEEALSO, "UNIX2DATE,DATE"},
170 { GNM_FUNC_HELP_END }
173 static GnmValue *
174 gnumeric_date2unix (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
176 gnm_float fserial = value_get_as_float (argv [0]);
177 int serial = (int)fserial;
178 time_t utime = go_date_serial_to_timet (serial, DATE_CONV (ei->pos));
180 /* Check for overflow. */
181 if (gnm_abs (fserial - serial) >= 1.0 || utime == (time_t)-1)
182 return value_new_error_VALUE (ei->pos);
184 return value_new_int (utime +
185 gnm_fake_round (DAY_SECONDS * (fserial - serial)));
188 /***************************************************************************/
190 static GnmFuncHelp const help_datevalue[] = {
191 { GNM_FUNC_HELP_NAME, F_("DATEVALUE:the date part of a date and time serial value")},
192 { GNM_FUNC_HELP_ARG, F_("serial:date and time serial value")},
193 { GNM_FUNC_HELP_DESCRIPTION, F_("DATEVALUE returns the date serial value part of a date and time serial value.") },
194 { GNM_FUNC_HELP_EXAMPLES, "=DATEVALUE(NOW())" },
195 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
196 { GNM_FUNC_HELP_SEEALSO, "TIMEVALUE,DATE"},
197 { GNM_FUNC_HELP_END }
200 static GnmValue *
201 gnumeric_datevalue (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
203 return value_new_int (datetime_value_to_serial (argv[0], DATE_CONV (ei->pos)));
206 /***************************************************************************/
208 static GnmFuncHelp const help_datedif[] = {
209 { GNM_FUNC_HELP_NAME, F_("DATEDIF:difference between dates") },
210 { GNM_FUNC_HELP_ARG, F_("start_date:starting date serial value")},
211 { GNM_FUNC_HELP_ARG, F_("end_date:ending date serial value")},
212 { GNM_FUNC_HELP_ARG, F_("interval:counting unit")},
213 { GNM_FUNC_HELP_DESCRIPTION, F_("DATEDIF returns the distance from @{start_date} to @{end_date} according to the unit specified by @{interval}.") },
214 { GNM_FUNC_HELP_NOTE, F_("If @{interval} is \"y\", \"m\", or \"d\" then the distance is measured in complete years, months, or days respectively.") },
215 { GNM_FUNC_HELP_NOTE, F_("If @{interval} is \"ym\" or \"yd\" then the distance is measured in complete months or days, respectively, but excluding any difference in years.") },
216 { GNM_FUNC_HELP_NOTE, F_("If @{interval} is \"md\" then the distance is measured in complete days but excluding any difference in months.") },
217 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
218 { GNM_FUNC_HELP_EXAMPLES, "=DATEDIF(DATE(2003,2,3),DATE(2007,4,2),\"m\")" },
219 { GNM_FUNC_HELP_EXAMPLES, "=DATEDIF(DATE(2000,4,30),DATE(2003,8,4),\"d\")" },
220 { GNM_FUNC_HELP_EXAMPLES, "=DATEDIF(DATE(2000,4,30),DATE(2003,8,4),\"y\")" },
221 { GNM_FUNC_HELP_SEEALSO, "DAYS360"},
222 { GNM_FUNC_HELP_END }
225 static int
226 datedif_opt_ym (GDate *gdate1, GDate *gdate2)
228 g_assert (g_date_valid (gdate1));
229 g_assert (g_date_valid (gdate2));
231 return go_date_g_months_between (gdate1, gdate2) % 12;
234 static int
235 datedif_opt_yd (GDate *gdate1, GDate *gdate2, int excel_compat)
237 g_assert (g_date_valid (gdate1));
238 g_assert (g_date_valid (gdate2));
240 g_date_get_day (gdate1);
242 gnm_date_add_years (gdate1,
243 go_date_g_years_between (gdate1, gdate2));
244 /* according to glib.h, feb 29 turns to feb 28 if necessary */
246 if (excel_compat) {
247 int new_year1, new_year2;
249 /* treat all years divisible by four as leap years: */
250 /* this is clearly wrong, but it's what Excel does. */
251 /* (I use 2004 here since it is clearly a leap year.) */
252 new_year1 = 2004 + (g_date_get_year (gdate1) & 0x3);
253 new_year2 = new_year1 + (g_date_get_year (gdate2) -
254 g_date_get_year (gdate1));
255 g_date_set_year (gdate1, new_year1);
256 g_date_set_year (gdate2, new_year2);
259 static gboolean need_warning = TRUE;
260 if (need_warning) {
261 g_warning("datedif is known to differ from "
262 "Excel for some values.");
263 need_warning = FALSE;
268 return g_date_days_between (gdate1, gdate2);
271 static int
272 datedif_opt_md (GDate *gdate1, GDate *gdate2, gboolean excel_compat)
274 int day;
276 g_assert (g_date_valid (gdate1));
277 g_assert (g_date_valid (gdate2));
279 day = g_date_get_day (gdate1);
281 gnm_date_add_months (gdate1,
282 go_date_g_months_between (gdate1, gdate2));
283 /* according to glib.h, days>28 decrease if necessary */
285 if (excel_compat) {
286 int new_year1, new_year2;
288 /* treat all years divisible by four as leap years: */
289 /* this is clearly wrong, but it's what Excel does. */
290 /* (I use 2004 here since it is clearly a leap year.) */
291 new_year1 = 2004 + (g_date_get_year (gdate1) & 0x3);
292 new_year2 = new_year1 + (g_date_get_year (gdate2) -
293 g_date_get_year (gdate1));
294 g_date_set_year (gdate1, new_year1);
295 g_date_set_year (gdate2, new_year2);
297 /* add back the days if they were decreased by
298 gnm_date_add_months */
299 /* ( i feel this is inferior because it reports e.g.:
300 datedif(1/31/95,3/1/95,"d") == -2 ) */
301 gnm_date_add_days (gdate1,
302 day - g_date_get_day (gdate1));
305 return g_date_days_between (gdate1, gdate2);
308 static GnmValue *
309 gnumeric_datedif (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
311 int date1, date2;
312 char const *opt;
313 GDate d1, d2;
314 GODateConventions const *conv = DATE_CONV (ei->pos);
316 date1 = gnm_floor (value_get_as_float (argv [0]));
317 date2 = gnm_floor (value_get_as_float (argv [1]));
318 opt = value_peek_string (argv[2]);
320 if (date1 > date2)
321 return value_new_error_NUM (ei->pos);
323 go_date_serial_to_g (&d1, date1, conv);
324 go_date_serial_to_g (&d2, date2, conv);
325 if (!g_date_valid (&d1) || !g_date_valid (&d2))
326 return value_new_error_VALUE (ei->pos);
328 if (!strcmp (opt, "d"))
329 return value_new_int (g_date_get_julian (&d2) -
330 g_date_get_julian (&d1));
331 else if (!strcmp (opt, "m"))
332 return value_new_int (go_date_g_months_between (&d1, &d2));
333 else if (!strcmp (opt, "y"))
334 return value_new_int (go_date_g_years_between (&d1, &d2));
335 else if (!strcmp (opt, "ym"))
336 return value_new_int (datedif_opt_ym (&d1, &d2));
337 else if (!strcmp (opt, "yd"))
338 return value_new_int (datedif_opt_yd (&d1, &d2, TRUE));
339 else if (!strcmp (opt, "md"))
340 return value_new_int (datedif_opt_md (&d1, &d2, TRUE));
341 else
342 return value_new_error_VALUE (ei->pos);
345 /***************************************************************************/
347 static GnmFuncHelp const help_edate[] = {
348 { GNM_FUNC_HELP_NAME, F_("EDATE:adjust a date by a number of months") },
349 { GNM_FUNC_HELP_ARG, F_("date:date serial value")},
350 { GNM_FUNC_HELP_ARG, F_("months:signed number of months")},
351 { GNM_FUNC_HELP_DESCRIPTION, F_("EDATE returns @{date} moved forward or backward the number of months specified by @{months}.") },
352 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
353 { GNM_FUNC_HELP_EXAMPLES, "=EDATE(DATE(2001,12,30),2)" },
354 { GNM_FUNC_HELP_SEEALSO, "DATE"},
355 { GNM_FUNC_HELP_END }
358 static GnmValue *
359 gnumeric_edate (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
361 GODateConventions const *conv = DATE_CONV (ei->pos);
362 gnm_float serial = value_get_as_float (argv[0]);
363 gnm_float months = value_get_as_float (argv[1]);
364 GDate date;
366 if (serial < 0 || serial > INT_MAX)
367 return value_new_error_NUM (ei->pos);
368 if (months > INT_MAX / 2 || -months > INT_MAX / 2)
369 return value_new_error_NUM (ei->pos);
371 go_date_serial_to_g (&date, (int)serial, conv);
372 gnm_date_add_months (&date, (int)months);
374 if (!g_date_valid (&date) ||
375 g_date_get_year (&date) < 1900 ||
376 g_date_get_year (&date) > 9999)
377 return value_new_error_NUM (ei->pos);
379 return make_date (value_new_int (go_date_g_to_serial (&date, conv)));
382 /***************************************************************************/
384 static GnmFuncHelp const help_today[] = {
385 { GNM_FUNC_HELP_NAME, F_("TODAY:the date serial value of today") },
386 { GNM_FUNC_HELP_DESCRIPTION, F_("The TODAY function returns the date serial value of the day it is computed. Recomputing on a later date will produce a different value.") },
387 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
388 { GNM_FUNC_HELP_EXAMPLES, "=TODAY()" },
389 { GNM_FUNC_HELP_SEEALSO, "DATE"},
390 { GNM_FUNC_HELP_END }
393 static GnmValue *
394 gnumeric_today (GnmFuncEvalInfo *ei, G_GNUC_UNUSED GnmValue const * const *argv)
396 return make_date (value_new_int (go_date_timet_to_serial (time (NULL), DATE_CONV (ei->pos))));
399 /***************************************************************************/
401 static GnmFuncHelp const help_now[] = {
402 { GNM_FUNC_HELP_NAME, F_("NOW:the date and time serial value of the current time") },
403 { GNM_FUNC_HELP_DESCRIPTION, F_("The NOW function returns the date and time serial value of the moment it is computed. Recomputing later will produce a different value.") },
404 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
405 { GNM_FUNC_HELP_EXAMPLES, "=NOW()" },
406 { GNM_FUNC_HELP_SEEALSO, "DATE"},
407 { GNM_FUNC_HELP_END }
410 static GnmValue *
411 gnumeric_now (GnmFuncEvalInfo *ei, G_GNUC_UNUSED GnmValue const * const *argv)
413 return value_new_float (go_date_timet_to_serial_raw (time (NULL), DATE_CONV (ei->pos)));
416 /***************************************************************************/
418 static GnmFuncHelp const help_time[] = {
419 { GNM_FUNC_HELP_NAME, F_("TIME:create a time serial value")},
420 { GNM_FUNC_HELP_ARG, F_("hour:hour of the day")},
421 { GNM_FUNC_HELP_ARG, F_("minute:minute within the hour")},
422 { GNM_FUNC_HELP_ARG, F_("second:second within the minute")},
423 { GNM_FUNC_HELP_DESCRIPTION, F_("The TIME function computes the fractional day after midnight at the time given by @{hour}, @{minute}, and @{second}.") },
424 { GNM_FUNC_HELP_NOTE, F_("While the return value is automatically formatted to look like a time between 0:00 and 24:00, "
425 "the underlying serial time value is a number between 0 and 1.")},
426 { GNM_FUNC_HELP_NOTE, F_("If any of @{hour}, @{minute}, and @{second} is negative, #NUM! is returned")},
427 { GNM_FUNC_HELP_EXAMPLES, "=TIME(12,30,2)" },
428 { GNM_FUNC_HELP_EXAMPLES, "=TIME(25,100,18)" },
429 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
430 { GNM_FUNC_HELP_SEEALSO, "ODF.TIME,HOUR,MINUTE,SECOND"},
431 { GNM_FUNC_HELP_END }
434 static GnmValue *
435 gnumeric_time (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
437 gnm_float hours, minutes, seconds;
438 gnm_float time;
440 hours = gnm_fmod (value_get_as_float (argv [0]), 24);
441 minutes = value_get_as_float (argv [1]);
442 seconds = value_get_as_float (argv [2]);
444 if (hours < 0 || minutes < 0 || seconds < 0)
445 return value_new_error_NUM (ei->pos);
447 time = (hours * 3600 + minutes * 60 + seconds) / DAY_SECONDS;
448 time -= gnm_fake_floor (time);
450 return value_new_float (time);
453 /***************************************************************************/
455 static GnmFuncHelp const help_odf_time[] = {
456 { GNM_FUNC_HELP_NAME, F_("ODF.TIME:create a time serial value")},
457 { GNM_FUNC_HELP_ARG, F_("hour:hour")},
458 { GNM_FUNC_HELP_ARG, F_("minute:minute")},
459 { GNM_FUNC_HELP_ARG, F_("second:second")},
460 { GNM_FUNC_HELP_DESCRIPTION, F_("The ODF.TIME function computes the time given by @{hour}, @{minute}, and @{second} as a fraction of a day.") },
461 { GNM_FUNC_HELP_NOTE, F_("While the return value is automatically formatted to look like a time between 0:00 and 24:00, "
462 "the underlying serial time value can be any number.")},
463 { GNM_FUNC_HELP_EXAMPLES, "=ODF.TIME(12,30,2)" },
464 { GNM_FUNC_HELP_EXAMPLES, "=ODF.TIME(25,100,-18)" },
465 { GNM_FUNC_HELP_ODF, F_("This function is OpenFormula compatible.") },
466 { GNM_FUNC_HELP_SEEALSO, "TIME,HOUR,MINUTE,SECOND"},
467 { GNM_FUNC_HELP_END }
470 static GnmValue *
471 gnumeric_odf_time (G_GNUC_UNUSED GnmFuncEvalInfo *ei, GnmValue const * const *argv)
473 gnm_float hours, minutes, seconds;
475 hours = value_get_as_float (argv [0]);
476 minutes = value_get_as_float (argv [1]);
477 seconds = value_get_as_float (argv [2]);
479 return make_date (value_new_float ((hours * 3600 + minutes * 60 + seconds) /
480 DAY_SECONDS));
483 /***************************************************************************/
485 static GnmFuncHelp const help_timevalue[] = {
486 { GNM_FUNC_HELP_NAME, F_("TIMEVALUE:the time part of a date and time serial value")},
487 { GNM_FUNC_HELP_ARG, F_("serial:date and time serial value")},
488 { GNM_FUNC_HELP_DESCRIPTION, F_("TIMEVALUE returns the time-of-day part of a date and time serial value.") },
489 { GNM_FUNC_HELP_EXAMPLES, "=TIMEVALUE(NOW())" },
490 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
491 { GNM_FUNC_HELP_SEEALSO, "DATEVALUE,TIME"},
492 { GNM_FUNC_HELP_END }
495 static GnmValue *
496 gnumeric_timevalue (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
498 gnm_float raw = datetime_value_to_serial_raw (argv[0], DATE_CONV (ei->pos));
499 return value_new_float (raw - (int)raw);
502 /***************************************************************************/
504 static GnmFuncHelp const help_hour[] = {
505 { GNM_FUNC_HELP_NAME, F_("HOUR:compute hour part of fractional day")},
506 { GNM_FUNC_HELP_ARG, F_("time:time of day as fractional day")},
507 { GNM_FUNC_HELP_DESCRIPTION, F_("The HOUR function computes the hour part of the fractional day given by @{time}.") },
508 { GNM_FUNC_HELP_EXAMPLES, "=HOUR(TIME(12,30,2))" },
509 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
510 { GNM_FUNC_HELP_SEEALSO, "TIME,MINUTE,SECOND"},
511 { GNM_FUNC_HELP_END }
514 static GnmValue *
515 gnumeric_hour (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
517 int secs = datetime_value_to_seconds (argv[0], DATE_CONV (ei->pos));
519 if (secs < 0)
520 return value_new_error_NUM (ei->pos);
521 else
522 return value_new_int (secs / 3600);
525 /***************************************************************************/
527 static GnmFuncHelp const help_minute[] = {
528 { GNM_FUNC_HELP_NAME, F_("MINUTE:compute minute part of fractional day")},
529 { GNM_FUNC_HELP_ARG, F_("time:time of day as fractional day")},
530 { GNM_FUNC_HELP_DESCRIPTION, F_("The MINUTE function computes the minute part of the fractional day given by @{time}.") },
531 { GNM_FUNC_HELP_EXAMPLES, "=MINUTE(TIME(12,30,2))" },
532 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
533 { GNM_FUNC_HELP_SEEALSO, "TIME,HOUR,SECOND"},
534 { GNM_FUNC_HELP_END }
537 static GnmValue *
538 gnumeric_minute (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
540 int secs = datetime_value_to_seconds (argv[0], DATE_CONV (ei->pos));
542 if (secs < 0)
543 return value_new_error_NUM (ei->pos);
544 else
545 return value_new_int (secs / 60 % 60);
548 /***************************************************************************/
550 static GnmFuncHelp const help_second[] = {
551 { GNM_FUNC_HELP_NAME, F_("SECOND:compute seconds part of fractional day")},
552 { GNM_FUNC_HELP_ARG, F_("time:time of day as fractional day")},
553 { GNM_FUNC_HELP_DESCRIPTION, F_("The SECOND function computes the seconds part of the fractional day given by @{time}.") },
554 { GNM_FUNC_HELP_EXAMPLES, "=SECOND(TIME(12,30,2))" },
555 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
556 { GNM_FUNC_HELP_SEEALSO, "TIME,HOUR,MINUTE"},
557 { GNM_FUNC_HELP_END }
560 static GnmValue *
561 gnumeric_second (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
563 int secs = datetime_value_to_seconds (argv[0], DATE_CONV (ei->pos));
565 if (secs < 0)
566 return value_new_error_NUM (ei->pos);
567 else
568 return value_new_int (secs % 60);
571 /***************************************************************************/
573 static GnmFuncHelp const help_year[] = {
574 { GNM_FUNC_HELP_NAME, F_("YEAR:the year part of a date serial value") },
575 { GNM_FUNC_HELP_ARG, F_("date:date serial value")},
576 { GNM_FUNC_HELP_DESCRIPTION, F_("The YEAR function returns the year part of @{date}.") },
577 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
578 { GNM_FUNC_HELP_EXAMPLES, "=YEAR(TODAY())" },
579 { GNM_FUNC_HELP_EXAMPLES, "=YEAR(DATE(1940,4,9))" },
580 { GNM_FUNC_HELP_SEEALSO, "DATE,MONTH,DAY"},
581 { GNM_FUNC_HELP_END }
584 static GnmValue *
585 gnumeric_year (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
587 GDate date;
589 if (datetime_value_to_g (&date, argv[0], DATE_CONV (ei->pos)))
590 return value_new_int (g_date_get_year (&date));
591 else
592 return value_new_error_NUM (ei->pos);
595 /***************************************************************************/
597 static GnmFuncHelp const help_month[] = {
598 { GNM_FUNC_HELP_NAME, F_("MONTH:the month part of a date serial value") },
599 { GNM_FUNC_HELP_ARG, F_("date:date serial value")},
600 { GNM_FUNC_HELP_DESCRIPTION, F_("The MONTH function returns the month part of @{date}.") },
601 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
602 { GNM_FUNC_HELP_EXAMPLES, "=MONTH(TODAY())" },
603 { GNM_FUNC_HELP_EXAMPLES, "=MONTH(DATE(1940,4,9))" },
604 { GNM_FUNC_HELP_SEEALSO, "DATE,YEAR,DAY"},
605 { GNM_FUNC_HELP_END }
608 static GnmValue *
609 gnumeric_month (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
611 GDate date;
613 if (datetime_value_to_g (&date, argv[0], DATE_CONV (ei->pos)))
614 return value_new_int (g_date_get_month (&date));
615 else
616 return value_new_error_NUM (ei->pos);
619 /***************************************************************************/
621 static GnmFuncHelp const help_day[] = {
622 { GNM_FUNC_HELP_NAME, F_("DAY:the day-of-month part of a date serial value") },
623 { GNM_FUNC_HELP_ARG, F_("date:date serial value")},
624 { GNM_FUNC_HELP_DESCRIPTION, F_("The DAY function returns the day-of-month part of @{date}.") },
625 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
626 { GNM_FUNC_HELP_EXAMPLES, "=DAY(TODAY())" },
627 { GNM_FUNC_HELP_EXAMPLES, "=DAY(DATE(1940,4,9))" },
628 { GNM_FUNC_HELP_SEEALSO, "DATE,YEAR,MONTH"},
629 { GNM_FUNC_HELP_END }
632 static GnmValue *
633 gnumeric_day (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
635 GDate date;
637 if (datetime_value_to_g (&date, argv[0], DATE_CONV (ei->pos)))
638 return value_new_int (g_date_get_day (&date));
639 else
640 return value_new_error_NUM (ei->pos);
643 /***************************************************************************/
645 static GnmFuncHelp const help_weekday[] = {
646 { GNM_FUNC_HELP_NAME, F_("WEEKDAY:day-of-week") },
647 { GNM_FUNC_HELP_ARG, F_("date:date serial value")},
648 { GNM_FUNC_HELP_ARG, F_("method:numbering system, defaults to 1")},
649 { GNM_FUNC_HELP_DESCRIPTION, F_("The WEEKDAY function returns the day-of-week of @{date}. The value of @{method} determines how days are numbered; it defaults to 1.") },
650 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 1, then Sunday is 1, Monday is 2, etc.") },
651 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 2, then Monday is 1, Tuesday is 2, etc.") },
652 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 3, then Monday is 0, Tuesday is 1, etc.") },
653 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 11, then Monday is 1, Tuesday is 2, etc.") },
654 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 12, then Tuesday is 1, Wednesday is 2, etc.") },
655 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 13, then Wednesday is 1, Thursday is 2, etc.") },
656 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 14, then Thursday is 1, Friday is 2, etc.") },
657 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 15, then Friday is 1, Saturday is 2, etc.") },
658 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 16, then Saturday is 1, Sunday is 2, etc.") },
659 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 17, then Sunday is 1, Monday is 2, etc.") },
660 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
661 { GNM_FUNC_HELP_EXAMPLES, "=WEEKDAY(DATE(1940,4,9))" },
662 { GNM_FUNC_HELP_SEEALSO, "DATE,ISOWEEKNUM"},
663 { GNM_FUNC_HELP_END }
666 static GnmValue *
667 gnumeric_weekday (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
669 GDate date;
670 int res;
671 gnm_float method = argv[1] ? value_get_as_float (argv[1]) : 1;
673 if (method < 1 || method >= G_MAXINT)
674 return value_new_error_NUM (ei->pos);
676 if (!datetime_value_to_g (&date, argv[0], DATE_CONV (ei->pos)))
677 return value_new_error_NUM (ei->pos);
679 switch ((int)method) {
680 case 1:
681 case 17:
682 res = (g_date_get_weekday (&date) % 7) + 1;
683 break;
684 case 2:
685 case 11:
686 res = (g_date_get_weekday (&date) + 6) % 7 + 1;
687 break;
688 case 3:
689 res = (g_date_get_weekday (&date) + 6) % 7;
690 break;
691 case 12:
692 res = (g_date_get_weekday (&date) + 5) % 7 + 1;
693 break;
694 case 13:
695 res = (g_date_get_weekday (&date) + 4) % 7 + 1;
696 break;
697 case 14:
698 res = (g_date_get_weekday (&date) + 3) % 7 + 1;
699 break;
700 case 15:
701 res = (g_date_get_weekday (&date) + 2) % 7 + 1;
702 break;
703 case 16:
704 res = (g_date_get_weekday (&date) + 1) % 7 + 1;
705 break;
706 default:
707 return value_new_error_NUM (ei->pos);
710 return value_new_int (res);
713 /***************************************************************************/
715 static GnmFuncHelp const help_days360[] = {
716 { GNM_FUNC_HELP_NAME, F_("DAYS360:days between dates") },
717 { GNM_FUNC_HELP_ARG, F_("start_date:starting date serial value")},
718 { GNM_FUNC_HELP_ARG, F_("end_date:ending date serial value")},
719 { GNM_FUNC_HELP_ARG, F_("method:counting method")},
720 { GNM_FUNC_HELP_DESCRIPTION, F_("DAYS360 returns the number of days from @{start_date} to @{end_date}.") },
721 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 0, the default, the MS Excel (tm) US method will be used. This is a somewhat complicated industry standard method where the last day of February is considered to be the 30th day of the month, but only for @{start_date}.") },
722 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 1, the European method will be used. In this case, if the day of the month is 31 it will be considered as 30") },
723 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 2, a saner version of the US method is used in which both dates get the same February treatment.") },
724 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
725 { GNM_FUNC_HELP_EXAMPLES, "=DAYS360(DATE(2003,2,3),DATE(2007,4,2))" },
726 { GNM_FUNC_HELP_SEEALSO, "DATEDIF"},
727 { GNM_FUNC_HELP_END }
730 static GnmValue *
731 gnumeric_days360 (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
733 go_basis_t basis;
734 GDate date1, date2;
735 GODateConventions const *date_conv = DATE_CONV (ei->pos);
736 gnm_float serial1 = datetime_value_to_serial (argv[0], date_conv);
737 gnm_float serial2 = datetime_value_to_serial (argv[1], date_conv);
738 gnm_float method = argv[2] ? gnm_floor (value_get_as_float (argv[2])) : 0;
740 switch ((int)method) {
741 case 0: basis = GO_BASIS_MSRB_30_360; break;
742 default:
743 case 1: basis = GO_BASIS_30E_360; break;
744 case 2: basis = GO_BASIS_MSRB_30_360_SYM; break;
747 go_date_serial_to_g (&date1, serial1, date_conv);
748 go_date_serial_to_g (&date2, serial2, date_conv);
749 if (!g_date_valid (&date1) || !g_date_valid (&date2))
750 return value_new_error_VALUE (ei->pos);
752 return value_new_int (go_date_days_between_basis (&date1, &date2, basis));
755 /***************************************************************************/
757 static GnmFuncHelp const help_eomonth[] = {
758 { GNM_FUNC_HELP_NAME, F_("EOMONTH:end of month") },
759 { GNM_FUNC_HELP_ARG, F_("date:date serial value")},
760 { GNM_FUNC_HELP_ARG, F_("months:signed number of months")},
761 { GNM_FUNC_HELP_DESCRIPTION, F_("EOMONTH returns the date serial value of the end of the month specified by @{date} adjusted forward or backward the number of months specified by @{months}.") },
762 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible.") },
763 { GNM_FUNC_HELP_EXAMPLES, "=EOMONTH(DATE(2001,12,14),2)" },
764 { GNM_FUNC_HELP_SEEALSO, "EDATE"},
765 { GNM_FUNC_HELP_END }
768 static GnmValue *
769 gnumeric_eomonth (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
771 gnm_float months = argv[1] ? value_get_as_float (argv[1]) : 0;
772 GDate date;
773 GODateConventions const *conv = DATE_CONV (ei->pos);
775 datetime_value_to_g (&date, argv[0], conv);
776 if (!g_date_valid (&date))
777 return value_new_error_VALUE (ei->pos);
779 if (months > INT_MAX / 2 || -months > INT_MAX / 2)
780 return value_new_error_NUM (ei->pos);
782 gnm_date_add_months (&date, (int)months);
783 if (!g_date_valid (&date) ||
784 g_date_get_year (&date) < 1900 ||
785 g_date_get_year (&date) > 9999)
786 return value_new_error_NUM (ei->pos);
788 g_date_set_day (&date,
789 g_date_get_days_in_month (g_date_get_month (&date),
790 g_date_get_year (&date)));
792 return make_date (value_new_int (go_date_g_to_serial (&date, conv)));
795 /***************************************************************************/
797 static GnmFuncHelp const help_workday[] = {
798 { GNM_FUNC_HELP_NAME, F_("WORKDAY:add working days") },
799 { GNM_FUNC_HELP_ARG, F_("date:date serial value")},
800 { GNM_FUNC_HELP_ARG, F_("days:number of days to add")},
801 { GNM_FUNC_HELP_ARG, F_("holidays:array of holidays")},
802 { GNM_FUNC_HELP_ARG, F_("weekend:array of 0s and 1s, indicating whether a weekday "
803 "(S, M, T, W, T, F, S) is on the weekend, defaults to {1,0,0,0,0,0,1}")},
804 { GNM_FUNC_HELP_DESCRIPTION, F_("WORKDAY adjusts @{date} by @{days} skipping over weekends and @{holidays} in the process.") },
805 { GNM_FUNC_HELP_NOTE, F_("@{days} may be negative.") },
806 { GNM_FUNC_HELP_NOTE, F_("If an entry of @{weekend} is non-zero, the corresponding weekday is not a work day.")},
807 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible if the last argument is omitted.") },
808 { GNM_FUNC_HELP_ODF, F_("This function is OpenFormula compatible.") },
809 { GNM_FUNC_HELP_EXAMPLES, "=WORKDAY(DATE(2001,12,14),2)" },
810 { GNM_FUNC_HELP_EXAMPLES, "=WORKDAY(DATE(2001,12,14),2,,{0,0,0,0,0,1,1})" },
811 { GNM_FUNC_HELP_SEEALSO, "NETWORKDAYS"},
812 { GNM_FUNC_HELP_END }
815 static gint
816 float_compare (gnm_float const *a, gnm_float const *b)
818 if (*a < *b)
819 return -1;
820 else if (*a == *b)
821 return 0;
822 else
823 return 1;
826 static GnmValue *
827 gnumeric_workday (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
829 GDate date;
830 GODateConventions const *conv = DATE_CONV (ei->pos);
831 gnm_float days = value_get_as_float (argv[1]);
832 int idays;
833 gnm_float *holidays = NULL;
834 gnm_float *weekends = NULL;
835 gnm_float const default_weekends[] = {1.,0.,0.,0.,0.,0.,1.};
836 int nholidays, nweekends, n_non_weekend = 0;
837 GDateWeekday weekday;
838 int serial;
839 int i;
841 datetime_value_to_g (&date, argv[0], conv);
842 if (!g_date_valid (&date))
843 goto bad;
845 if (days > INT_MAX / 2 || -days > INT_MAX / 2)
846 return value_new_error_NUM (ei->pos);
847 idays = (int)days;
849 if (argv[3]) {
850 GnmValue *result = NULL;
852 weekends = collect_floats_value (argv[3], ei->pos,
853 COLLECT_COERCE_STRINGS |
854 COLLECT_ZEROONE_BOOLS |
855 COLLECT_IGNORE_BLANKS,
856 &nweekends, &result);
857 if (result)
858 return result;
859 if (nweekends != 7)
860 goto bad;
862 } else {
863 weekends = (gnm_float *)default_weekends;
864 nweekends = 7;
867 for (i = 0; i < 7; i++)
868 if (weekends[i] == 0)
869 n_non_weekend++;
870 if (n_non_weekend == 0 && idays != 0)
871 goto bad;
872 if (n_non_weekend == 0 && idays == 0) {
873 if (weekends != default_weekends)
874 g_free (weekends);
875 return make_date (value_new_int (go_date_g_to_serial (&date, conv)));
878 if (argv[2]) {
879 int j;
880 GDate hol;
881 GnmValue *result = NULL;
883 holidays = collect_floats_value (argv[2], ei->pos,
884 COLLECT_COERCE_STRINGS |
885 COLLECT_IGNORE_BOOLS |
886 COLLECT_IGNORE_BLANKS,
887 &nholidays, &result);
888 if (result) {
889 if (weekends != default_weekends)
890 g_free (weekends);
891 return result;
893 qsort (holidays, nholidays, sizeof (holidays[0]), (void *) &float_compare);
895 for (i = j = 0; i < nholidays; i++) {
896 gnm_float s = holidays[i];
897 int hserial;
898 if (s < 0 || s > INT_MAX)
899 goto bad;
900 hserial = (int)s;
901 if (j > 0 && hserial == holidays[j - 1])
902 continue; /* Dupe */
903 go_date_serial_to_g (&hol, hserial, conv);
904 if (!g_date_valid (&hol))
905 goto bad;
906 if (weekends[g_date_get_weekday (&hol) % 7] != 0.)
907 continue;
908 holidays[j++] = hserial;
910 nholidays = j;
911 } else {
912 holidays = NULL;
913 nholidays = 0;
917 weekday = g_date_get_weekday (&date);
919 if (idays > 0) {
920 int h = 0;
921 guint diff = 0;
922 int old_serial;
924 weekday = weekday % 7;
925 while (weekends[weekday]) {
926 weekday = (weekday > 0) ? (weekday - 1) : G_DATE_SATURDAY;
927 diff++;
929 g_date_subtract_days (&date, diff);
930 old_serial = go_date_g_to_serial (&date, conv);
932 while (idays > 0) {
933 int dm_part_week = idays % n_non_weekend;
934 int ds = idays / n_non_weekend * 7;
936 g_date_add_days (&date, ds);
938 while (dm_part_week) {
939 g_date_add_days (&date, 1);
940 weekday = (weekday + 1) % 7;
941 if (!weekends[weekday])
942 dm_part_week--;
945 serial = go_date_g_to_serial (&date, conv);
947 * we may have passed holidays.
949 idays = 0;
950 while (h < nholidays && holidays[h] <= serial) {
951 if (holidays[h] > old_serial)
952 idays++;
953 h++;
955 old_serial = serial;
957 } else if (idays < 0) {
958 int h = nholidays - 1;
959 guint diff = 0;
960 int old_serial;
962 weekday = weekday % 7;
963 while (weekends[weekday]) {
964 weekday = (weekday + 1) % 7;
965 diff++;
967 g_date_add_days (&date, diff);
968 old_serial = go_date_g_to_serial (&date, conv);
970 idays = -idays;
971 while (idays > 0) {
972 int dm_part_week = idays % n_non_weekend;
973 int ds = idays / n_non_weekend * 7;
975 g_date_subtract_days (&date, ds);
977 while (dm_part_week) {
978 g_date_subtract_days (&date, 1);
979 weekday = (weekday > 0) ? (weekday - 1)
980 : G_DATE_SATURDAY;
981 if (!weekends[weekday])
982 dm_part_week--;
985 serial = go_date_g_to_serial (&date, conv);
987 * we may have passed holidays.
989 idays = 0;
990 while (h >= 0 && holidays[h] >= serial) {
991 if (holidays[h] < old_serial)
992 idays++;
993 h--;
995 old_serial = serial;
997 } else serial = go_date_g_to_serial (&date, conv);
999 if (weekends != default_weekends)
1000 g_free (weekends);
1001 g_free (holidays);
1003 go_date_serial_to_g (&date, serial, conv);
1004 if (!g_date_valid (&date) ||
1005 g_date_get_year (&date) < 1900 ||
1006 g_date_get_year (&date) > 9999)
1007 return value_new_error_NUM (ei->pos);
1009 return make_date (value_new_int (go_date_g_to_serial (&date, conv)));
1011 bad:
1012 if (weekends != default_weekends)
1013 g_free (weekends);
1014 g_free (holidays);
1015 return value_new_error_VALUE (ei->pos);
1018 /**************************************************************************
1019 networkdays:
1021 in OpenFormula 1.2:
1022 The optional 4th parameter Workdays can be used to specify a different definition for the standard
1023 work week by passing in a list of numbers which define which days of the week are workdays
1024 (indicated by 0) or not (indicated by non-zero) in order Sunday, Monday,...,Saturday. So, the
1025 default definition of the work week excludes Saturday and Sunday and is: {1;0;0;0;0;0;1}. To
1026 define the work week as excluding Friday and Saturday, the third parameter would be:
1027 {0;0;0;0;0;1;1}.
1029 In the implementation, we are using g_date_get_weekday which returns
1030 typedef enum
1032 G_DATE_BAD_WEEKDAY = 0,
1033 G_DATE_MONDAY = 1,
1034 G_DATE_TUESDAY = 2,
1035 G_DATE_WEDNESDAY = 3,
1036 G_DATE_THURSDAY = 4,
1037 G_DATE_FRIDAY = 5,
1038 G_DATE_SATURDAY = 6,
1039 G_DATE_SUNDAY = 7
1040 } GDateWeekday;
1041 Since Sunday here is 7 rather than a 0 we need to make appropriate adjustments.
1043 ***************************************************************************/
1044 static GnmFuncHelp const help_networkdays[] = {
1045 { GNM_FUNC_HELP_NAME, F_("NETWORKDAYS:number of workdays in range") },
1046 { GNM_FUNC_HELP_ARG, F_("start_date:starting date serial value")},
1047 { GNM_FUNC_HELP_ARG, F_("end_date:ending date serial value")},
1048 { GNM_FUNC_HELP_ARG, F_("holidays:array of holidays")},
1049 { GNM_FUNC_HELP_ARG, F_("weekend:array of 0s and 1s, indicating whether a weekday "
1050 "(S, M, T, W, T, F, S) is on the weekend, defaults to {1,0,0,0,0,0,1}")},
1051 { GNM_FUNC_HELP_DESCRIPTION, F_("NETWORKDAYS calculates the number of days from @{start_date} to @{end_date} "
1052 "skipping weekends and @{holidays} in the process.") },
1053 { GNM_FUNC_HELP_NOTE, F_("If an entry of @{weekend} is non-zero, the corresponding weekday is not a work day.")},
1054 { GNM_FUNC_HELP_EXCEL, F_("This function is Excel compatible if the last argument is omitted.") },
1055 { GNM_FUNC_HELP_ODF, F_("This function is OpenFormula compatible.") },
1056 { GNM_FUNC_HELP_EXAMPLES, "=NETWORKDAYS(DATE(2001,1,2),DATE(2001,2,15))" },
1057 { GNM_FUNC_HELP_EXAMPLES, "=NETWORKDAYS(DATE(2001,1,2),DATE(2001,2,15),,{0, 0, 0, 1, 1, 0, 0})" },
1058 { GNM_FUNC_HELP_SEEALSO, "WORKDAY"},
1059 { GNM_FUNC_HELP_END }
1062 static int
1063 networkdays_calc (GDate start_date, int start_serial, int end_serial,
1064 int n_non_weekend, gnm_float *weekends, int nholidays, gnm_float *holidays)
1066 int res = 0;
1067 int old_start_serial = start_serial;
1068 GDateWeekday weekday;
1069 int i, weeks;
1070 int h = 0;
1072 weekday = g_date_get_weekday (&start_date);
1073 if (weekday == G_DATE_BAD_WEEKDAY)
1074 return -1;
1075 if (weekday == G_DATE_SUNDAY)
1076 weekday = 0;
1078 weeks = (end_serial - start_serial)/7;
1079 start_serial = start_serial + weeks * 7;
1080 res = weeks * n_non_weekend;
1082 for (i = start_serial; i <= end_serial; i++) {
1083 if (!weekends[weekday])
1084 res++;
1085 weekday = (weekday + 1) % 7;
1089 * we may have included holidays.
1092 while (h < nholidays && holidays[h] <= end_serial) {
1093 if (holidays[h] >= old_start_serial)
1094 res--;
1095 h++;
1098 return res;
1101 static GnmValue *
1102 gnumeric_networkdays (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
1104 int start_serial;
1105 int end_serial;
1106 int res, total_res = 0;
1107 GDate start_date, trouble_mar, trouble_feb, end_date;
1108 GODateConventions const *conv = DATE_CONV (ei->pos);
1109 gnm_float *holidays = NULL;
1110 gnm_float *weekends = NULL;
1111 gnm_float const default_weekends[] = {1.,0.,0.,0.,0.,0.,1.};
1112 int nholidays, nweekends, n_non_weekend = 0;
1113 int i;
1114 GDateWeekday weekday;
1115 gboolean includes_bad_day = FALSE;
1117 /* Check the date period*/
1119 start_serial = datetime_value_to_serial (argv[0], conv);
1120 end_serial = datetime_value_to_serial (argv[1], conv);
1122 /* Swap if necessary */
1123 if (start_serial > end_serial) {
1124 int tmp = start_serial;
1125 start_serial = end_serial;
1126 end_serial = tmp;
1129 /* Make sure that start and end serial are valid */
1130 if (start_serial <= 0 || end_serial <= 0)
1131 return value_new_error_NUM (ei->pos);
1133 go_date_serial_to_g (&start_date, start_serial, conv);
1134 if (!g_date_valid (&start_date))
1135 goto bad;
1136 go_date_serial_to_g (&end_date, end_serial, conv);
1137 if (!g_date_valid (&end_date))
1138 goto bad;
1140 g_date_set_dmy (&trouble_mar, 1, 3, 1900);
1141 if (g_date_compare (&start_date, &trouble_mar) < 0) {
1142 g_date_set_dmy (&trouble_feb, 28, 2, 1900);
1143 includes_bad_day = (!g_date_valid (&end_date) || g_date_compare (&trouble_feb, &end_date) < 0);
1146 /* get the weekend info */
1148 if (argv[3]) {
1149 GnmValue *result = NULL;
1151 weekends = collect_floats_value (argv[3], ei->pos,
1152 COLLECT_COERCE_STRINGS |
1153 COLLECT_ZEROONE_BOOLS |
1154 COLLECT_IGNORE_BLANKS,
1155 &nweekends, &result);
1156 if (result)
1157 return result;
1158 if (nweekends != 7)
1159 goto bad;
1161 } else {
1162 weekends = (gnm_float *)default_weekends;
1163 nweekends = 7;
1166 /* If everything is a weekend we know the answer already */
1168 for (i = 0; i < 7; i++)
1169 if (weekends[i] == 0)
1170 n_non_weekend++;
1171 if (n_non_weekend == 0) {
1172 if (weekends != default_weekends)
1173 g_free (weekends);
1174 return value_new_int (0);
1177 /* Now get the holiday info */
1179 if (argv[2]) {
1180 int j;
1181 GDate hol;
1182 GnmValue *result = NULL;
1184 holidays = collect_floats_value (argv[2], ei->pos,
1185 COLLECT_COERCE_STRINGS |
1186 COLLECT_IGNORE_BOOLS |
1187 COLLECT_IGNORE_BLANKS,
1188 &nholidays, &result);
1189 if (result) {
1190 if (weekends != default_weekends)
1191 g_free (weekends);
1192 return result;
1194 qsort (holidays, nholidays, sizeof (holidays[0]), (void *) &float_compare);
1196 for (i = j = 0; i < nholidays; i++) {
1197 gnm_float s = holidays[i];
1198 int hserial;
1199 if (s < 0 || s > INT_MAX)
1200 goto bad;
1201 hserial = (int)s;
1202 if (j > 0 && hserial == holidays[j - 1])
1203 continue; /* Dupe */
1204 go_date_serial_to_g (&hol, hserial, conv);
1205 if (!g_date_valid (&hol))
1206 goto bad;
1207 weekday = g_date_get_weekday (&hol);
1208 if (weekday == G_DATE_BAD_WEEKDAY)
1209 goto bad;
1210 if (weekday == G_DATE_SUNDAY)
1211 weekday = 0;
1212 /* We skip holidays that are on the weekend */
1213 if (weekends[weekday] != 0.)
1214 continue;
1215 holidays[j++] = hserial;
1217 nholidays = j;
1218 } else {
1219 holidays = NULL;
1220 nholidays = 0;
1223 if (includes_bad_day) {
1224 total_res = networkdays_calc (start_date, start_serial,
1225 go_date_g_to_serial (&trouble_feb, conv),
1226 n_non_weekend, weekends, nholidays, holidays);
1227 if (total_res < 0)
1228 goto bad;
1229 res = networkdays_calc (trouble_mar, go_date_g_to_serial (&trouble_mar, conv),
1230 end_serial,
1231 n_non_weekend, weekends, nholidays, holidays);
1232 if (res < 0)
1233 goto bad;
1234 total_res += res;
1235 } else {
1236 total_res = networkdays_calc (start_date, start_serial, end_serial,
1237 n_non_weekend, weekends, nholidays, holidays);
1238 if (total_res < 0)
1239 goto bad;
1242 if (weekends != default_weekends)
1243 g_free (weekends);
1244 g_free (holidays);
1246 return value_new_int (total_res);
1248 bad:
1249 if (weekends != default_weekends)
1250 g_free (weekends);
1251 g_free (holidays);
1252 return value_new_error_VALUE (ei->pos);
1255 /***************************************************************************/
1257 static GnmFuncHelp const help_isoweeknum[] = {
1258 { GNM_FUNC_HELP_NAME, F_("ISOWEEKNUM:ISO week number")},
1259 { GNM_FUNC_HELP_ARG, F_("date:date serial value")},
1260 { GNM_FUNC_HELP_DESCRIPTION, F_("ISOWEEKNUM calculates the week number according to the ISO 8601 standard. Weeks start on Mondays and week 1 contains the first Thursday of the year.") },
1261 { GNM_FUNC_HELP_NOTE, F_("January 1 of a year is sometimes in week 52 or 53 of the previous year. Similarly, December 31 is sometimes in week 1 of the following year.") },
1262 { GNM_FUNC_HELP_EXAMPLES, "=ISOWEEKNUM(DATE(2000,1,1))" },
1263 { GNM_FUNC_HELP_EXAMPLES, "=ISOWEEKNUM(DATE(2008,1,1))" },
1264 { GNM_FUNC_HELP_SEEALSO, "ISOYEAR,WEEKNUM"},
1265 { GNM_FUNC_HELP_END }
1268 static GnmValue *
1269 gnumeric_isoweeknum (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
1271 GDate date;
1272 datetime_value_to_g (&date, argv[0], DATE_CONV (ei->pos));
1273 if (!g_date_valid (&date))
1274 return value_new_error_VALUE (ei->pos);
1276 return value_new_int (go_date_weeknum (&date, GO_WEEKNUM_METHOD_ISO));
1279 /***************************************************************************/
1281 static GnmFuncHelp const help_isoyear[] = {
1282 { GNM_FUNC_HELP_NAME, F_("ISOYEAR:year corresponding to the ISO week number")},
1283 { GNM_FUNC_HELP_ARG, F_("date:date serial value")},
1284 { GNM_FUNC_HELP_DESCRIPTION, F_("ISOYEAR calculates the year to go with week number according to the ISO 8601 standard.") },
1285 { GNM_FUNC_HELP_NOTE, F_("January 1 of a year is sometimes in week 52 or 53 of the previous year. Similarly, December 31 is sometimes in week 1 of the following year.") },
1286 { GNM_FUNC_HELP_EXAMPLES, "=ISOYEAR(DATE(2000,1,1))" },
1287 { GNM_FUNC_HELP_EXAMPLES, "=ISOYEAR(DATE(2008,1,1))" },
1288 { GNM_FUNC_HELP_SEEALSO, "ISOWEEKNUM,YEAR"},
1289 { GNM_FUNC_HELP_END }
1292 static GnmValue *
1293 gnumeric_isoyear (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
1295 GDate date;
1296 int year;
1297 int month;
1298 int isoweeknum;
1300 datetime_value_to_g (&date, argv[0], DATE_CONV (ei->pos));
1301 if (!g_date_valid (&date))
1302 return value_new_error_VALUE (ei->pos);
1304 isoweeknum = go_date_weeknum (&date, GO_WEEKNUM_METHOD_ISO);
1305 year = g_date_get_year (&date);
1306 month = g_date_get_month (&date);
1307 if (isoweeknum >= 52 && month == G_DATE_JANUARY)
1308 year--;
1309 else if (isoweeknum == 1 && month == G_DATE_DECEMBER)
1310 year++;
1312 return value_new_int (year);
1315 /***************************************************************************/
1317 static GnmFuncHelp const help_weeknum[] = {
1318 { GNM_FUNC_HELP_NAME, F_("WEEKNUM:week number")},
1319 { GNM_FUNC_HELP_ARG, F_("date:date serial value")},
1320 { GNM_FUNC_HELP_ARG, F_("method:numbering system, defaults to 1")},
1321 { GNM_FUNC_HELP_DESCRIPTION, F_("WEEKNUM calculates the week number according to @{method} which defaults to 1.") },
1322 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 1, then weeks start on Sundays and January 1 is in week 1.") },
1323 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 2, then weeks start on Mondays and January 1 is in week 1.") },
1324 { GNM_FUNC_HELP_NOTE, F_("If @{method} is 150, then the ISO 8601 numbering is used.") },
1325 { GNM_FUNC_HELP_EXAMPLES, "=WEEKNUM(DATE(2000,1,1))" },
1326 { GNM_FUNC_HELP_EXAMPLES, "=WEEKNUM(DATE(2008,1,1))" },
1327 { GNM_FUNC_HELP_SEEALSO, "ISOWEEKNUM"},
1328 { GNM_FUNC_HELP_END }
1331 static GnmValue *
1332 gnumeric_weeknum (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
1334 GDate date;
1335 gnm_float method = argv[1] ? gnm_floor (value_get_as_float (argv[1])) : 1;
1336 int m;
1338 if (method == 1)
1339 m = GO_WEEKNUM_METHOD_SUNDAY;
1340 else if (method == 2)
1341 m = GO_WEEKNUM_METHOD_MONDAY;
1342 else if (method == 150 || method == 21)
1343 m = GO_WEEKNUM_METHOD_ISO;
1344 else
1345 return value_new_error_VALUE (ei->pos);
1347 datetime_value_to_g (&date, argv[0], DATE_CONV (ei->pos));
1348 if (!g_date_valid (&date))
1349 return value_new_error_VALUE (ei->pos);
1351 return value_new_int (go_date_weeknum (&date, m));
1354 /***************************************************************************/
1356 static GnmFuncHelp const help_yearfrac[] = {
1357 { GNM_FUNC_HELP_NAME, F_("YEARFRAC:fractional number of years between dates")},
1358 { GNM_FUNC_HELP_ARG, F_("start_date:starting date serial value")},
1359 { GNM_FUNC_HELP_ARG, F_("end_date:ending date serial value")},
1360 { GNM_FUNC_HELP_ARG, F_("basis:calendar basis")},
1361 { GNM_FUNC_HELP_DESCRIPTION, F_("YEARFRAC calculates the number of days from @{start_date} to @{end_date} according to the calendar specified by @{basis}, which defaults to 0, and expresses the result as a fractional number of years.") },
1362 GNM_DATE_BASIS_HELP
1363 { GNM_FUNC_HELP_SEEALSO, "DATE"},
1364 { GNM_FUNC_HELP_EXAMPLES, "=YEARFRAC(DATE(2000,1,1),DATE(2001,4,1))" },
1365 { GNM_FUNC_HELP_END }
1368 static GnmValue *
1369 gnumeric_yearfrac (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
1371 GODateConventions const *conv = DATE_CONV (ei->pos);
1372 GDate start_date, end_date;
1373 int basis = value_get_basis (argv[2], GO_BASIS_MSRB_30_360);
1375 if (basis < 0 || basis > 4 ||
1376 !datetime_value_to_g (&start_date, argv[0], conv) ||
1377 !datetime_value_to_g (&end_date, argv[1], conv))
1378 return value_new_error_NUM (ei->pos);
1380 return value_new_float (yearfrac (&start_date, &end_date, basis));
1383 /***************************************************************************/
1385 static GnmFuncHelp const help_days[] = {
1386 { GNM_FUNC_HELP_NAME, F_("DAYS:difference between dates in days") },
1387 { GNM_FUNC_HELP_ARG, F_("end_date:ending date serial value")},
1388 { GNM_FUNC_HELP_ARG, F_("start_date:starting date serial value")},
1389 { GNM_FUNC_HELP_DESCRIPTION, F_("DAYS returns the positive or negative number of days from @{start_date} to @{end_date}.") },
1390 { GNM_FUNC_HELP_ODF, F_("This function is OpenFormula compatible.") },
1391 { GNM_FUNC_HELP_EXAMPLES, "=DAYS(DATE(2003,2,3),DATE(2007,4,2))" },
1392 { GNM_FUNC_HELP_EXAMPLES, "=DAYS(DATE(2007,4,2),DATE(2003,2,3))" },
1393 { GNM_FUNC_HELP_EXAMPLES, "=DAYS(DATE(1900,2,28),DATE(1900,3,1))" },
1394 { GNM_FUNC_HELP_SEEALSO, "DATEDIF"},
1395 { GNM_FUNC_HELP_END }
1399 static GnmValue *
1400 gnumeric_days (GnmFuncEvalInfo *ei, GnmValue const * const *argv)
1402 int date1, date2;
1403 GDate d1, d2;
1404 GODateConventions const *conv = DATE_CONV (ei->pos);
1406 date1 = gnm_floor (value_get_as_float (argv [0]));
1407 date2 = gnm_floor (value_get_as_float (argv [1]));
1409 go_date_serial_to_g (&d1, date1, conv);
1410 go_date_serial_to_g (&d2, date2, conv);
1412 return value_new_int (g_date_days_between (&d2, &d1));
1415 /***************************************************************************/
1417 GnmFuncDescriptor const datetime_functions[] = {
1418 { "date", "fff", help_date,
1419 gnumeric_date, NULL,
1420 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_DATE,
1421 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1422 { "datevalue", "f", help_datevalue,
1423 gnumeric_datevalue, NULL,
1424 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1425 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1426 { "datedif", "ffs", help_datedif,
1427 gnumeric_datedif, NULL,
1428 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1429 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1430 { "day", "f", help_day,
1431 gnumeric_day, NULL,
1432 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1433 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1434 { "days360", "ff|f", help_days360,
1435 gnumeric_days360, NULL,
1436 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1437 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1438 { "edate", "ff", help_edate,
1439 gnumeric_edate, NULL,
1440 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_DATE,
1441 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1442 { "eomonth", "f|f", help_eomonth,
1443 gnumeric_eomonth, NULL,
1444 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_DATE,
1445 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1446 { "hour", "f", help_hour,
1447 gnumeric_hour, NULL,
1448 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1449 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1450 { "minute", "f", help_minute,
1451 gnumeric_minute, NULL,
1452 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1453 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1454 { "month", "f", help_month,
1455 gnumeric_month, NULL,
1456 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1457 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1458 { "networkdays", "ff|?A",
1459 help_networkdays, gnumeric_networkdays, NULL,
1460 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1461 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1462 { "now", "", help_now,
1463 gnumeric_now, NULL,
1464 GNM_FUNC_VOLATILE + GNM_FUNC_AUTO_TIME,
1465 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1466 { "odf.time", "fff", help_odf_time,
1467 gnumeric_odf_time, NULL,
1468 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_TIME,
1469 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_NO_TESTSUITE },
1470 { "second", "f", help_second,
1471 gnumeric_second, NULL,
1472 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1473 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1474 { "time", "fff", help_time,
1475 gnumeric_time, NULL,
1476 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_TIME,
1477 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1478 { "timevalue", "f", help_timevalue,
1479 gnumeric_timevalue, NULL,
1480 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1481 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1482 { "today", "", help_today,
1483 gnumeric_today, NULL,
1484 GNM_FUNC_VOLATILE + GNM_FUNC_AUTO_DATE,
1485 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1486 { "weekday", "f|f", help_weekday,
1487 gnumeric_weekday, NULL,
1488 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1489 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1490 { "weeknum", "f|f", help_weeknum,
1491 gnumeric_weeknum, NULL,
1492 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1493 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1494 { "workday", "ff|?A", help_workday,
1495 gnumeric_workday, NULL,
1496 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_DATE,
1497 GNM_FUNC_IMPL_STATUS_SUBSET, GNM_FUNC_TEST_STATUS_BASIC },
1498 { "year", "f", help_year,
1499 gnumeric_year, NULL,
1500 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1501 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_BASIC },
1502 { "yearfrac", "ff|f", help_yearfrac,
1503 gnumeric_yearfrac, NULL,
1504 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1505 GNM_FUNC_IMPL_STATUS_COMPLETE, GNM_FUNC_TEST_STATUS_NO_TESTSUITE },
1507 { "unix2date", "f", help_unix2date,
1508 gnumeric_unix2date, NULL,
1509 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_DATE,
1510 GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC, GNM_FUNC_TEST_STATUS_NO_TESTSUITE },
1511 { "date2unix", "f", help_date2unix,
1512 gnumeric_date2unix, NULL,
1513 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1514 GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC, GNM_FUNC_TEST_STATUS_NO_TESTSUITE },
1515 { "isoweeknum", "f", help_isoweeknum,
1516 gnumeric_isoweeknum, NULL,
1517 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1518 GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC, GNM_FUNC_TEST_STATUS_NO_TESTSUITE },
1519 { "isoyear", "f", help_isoyear,
1520 gnumeric_isoyear, NULL,
1521 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1522 GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC, GNM_FUNC_TEST_STATUS_NO_TESTSUITE },
1523 { "days", "ff",
1524 help_days, gnumeric_days, NULL,
1525 GNM_FUNC_SIMPLE + GNM_FUNC_AUTO_UNITLESS,
1526 GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC, GNM_FUNC_TEST_STATUS_NO_TESTSUITE },
1527 {NULL}