/* * Calcurse - text-based organizer * * Copyright (c) 2004-2020 calcurse Development Team * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Send your feedback or comments to : misc@calcurse.org * Calcurse home page : http://calcurse.org * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "calcurse.h" #include "sha1.h" #define FS_EXT_MAXLEN 64 enum format_specifier { FS_STARTDATE, FS_DURATION, FS_ENDDATE, FS_REMAINING, FS_MESSAGE, FS_NOTE, FS_NOTEFILE, FS_PRIORITY, FS_RAW, FS_HASH, FS_PSIGN, FS_EOF, FS_UNKNOWN }; /* General routine to exit calcurse properly. */ void exit_calcurse(int status) { int was_interactive; if (ui_mode == UI_CURSES) { notify_stop_main_thread(); ui_calendar_stop_date_thread(); io_stop_psave_thread(); clear(); wins_refresh(); endwin(); ui_mode = UI_CMDLINE; was_interactive = 1; } else { was_interactive = 0; } free_user_data(); keys_free(); mem_stats(); if (was_interactive) { if (unlink(path_cpid) != 0) EXIT(_("Could not remove calcurse lock file: %s\n"), strerror(errno)); if (dmon.enable) dmon_start(status); } exit(status); } void free_user_data(void) { unsigned i; day_free_vector(); event_llist_free(); apoint_llist_free(); recur_apoint_llist_free(); recur_event_llist_free(); for (i = 0; i <= REG_BLACK_HOLE; i++) ui_day_item_cut_free(i); todo_free_list(); notify_free_app(); } /* Function to exit on internal error. */ void fatalbox(const char *errmsg) { WINDOW *errwin; const char *label = _("/!\\ INTERNAL ERROR /!\\"); const char *reportmsg = _("Please report the following bug:"); const int WINROW = 10; const int WINCOL = col - 2; const int MSGLEN = WINCOL - 2; char msg[MSGLEN]; if (errmsg == NULL) return; strncpy(msg, errmsg, MSGLEN); msg[MSGLEN - 1] = '\0'; errwin = newwin(WINROW, WINCOL, (row - WINROW) / 2, (col - WINCOL) / 2); custom_apply_attr(errwin, ATTR_HIGHEST); box(errwin, 0, 0); wins_show(errwin, label); mvwaddstr(errwin, 3, 1, reportmsg); mvwaddstr(errwin, 5, (WINCOL - strlen(msg)) / 2, msg); custom_remove_attr(errwin, ATTR_HIGHEST); wins_wrefresh(errwin); keys_wait_for_any_key(errwin); delwin(errwin); wins_doupdate(); } void warnbox(const char *msg) { WINDOW *warnwin; const char *label = "/!\\"; const int WINROW = 10; const int WINCOL = col - 2; const int MSGLEN = WINCOL - 2; char displmsg[MSGLEN]; if (msg == NULL) return; strncpy(displmsg, msg, MSGLEN); displmsg[MSGLEN - 1] = '\0'; warnwin = newwin(WINROW, WINCOL, (row - WINROW) / 2, (col - WINCOL) / 2); custom_apply_attr(warnwin, ATTR_HIGHEST); box(warnwin, 0, 0); wins_show(warnwin, label); mvwaddstr(warnwin, 5, (WINCOL - strlen(displmsg)) / 2, displmsg); custom_remove_attr(warnwin, ATTR_HIGHEST); wins_wrefresh(warnwin); keys_wait_for_any_key(warnwin); delwin(warnwin); wins_doupdate(); } /* * Print a message in the status bar. * Message texts for first line and second line are to be provided. */ void status_mesg(const char *msg1, const char *msg2) { wins_erase_status_bar(); custom_apply_attr(win[STA].p, ATTR_HIGHEST); mvwaddstr(win[STA].p, 0, 0, msg1); mvwaddstr(win[STA].p, 1, 0, msg2); custom_remove_attr(win[STA].p, ATTR_HIGHEST); wins_wrefresh(win[STA].p); } /* * Prompts the user to make a choice between named alternatives. * * The available choices are described (in po-files) by a string of the form * "[ynp]". The first and last char are ignored (they are only here to * make the translators' life easier), and every other char indicates * a key the user is allowed to press. * * Returns the index of the key pressed by the user (starting from 1), * or -1 if the user doesn't want to answer (e.g. by escaping). */ int status_ask_choice(const char *message, const char choice[], int nb_choice) { /* Turn "[42w...Z]" into * "[4/2/w/.../Z]". */ char avail_choice[nb_choice * UTF8_MAXLEN + nb_choice + 1]; int ichoice[nb_choice]; int i, j, k, n, ch; avail_choice[0] = '['; for (n = 0, i = 1, j = 1; n < nb_choice; n++, i += k) { for (k = 0; k < UTF8_LENGTH(choice[i]); k++) { avail_choice[j] = choice[i + k]; j++; } avail_choice[j] = '/'; j++; } avail_choice[j - 1] = ']'; avail_choice[j] = '\0'; status_mesg(message, avail_choice); /* Convert the character choices to internal integer codes. */ for (n = 0, i = 1; n < nb_choice; n++, i += j) { j = UTF8_LENGTH(choice[i]); ichoice[n] = utf8_decode(choice + i) + (j > 1 ? KEY_MAX : 0); } for (;;) { ch = keys_wgetch(win[KEY].p); for (i = 0; i < nb_choice; i++) if (ch == ichoice[i]) return i + 1; if (ch == ESCAPE) return (-1); if (ch == RETURN) return (-2); if (resize) { resize = 0; wins_reset(); status_mesg(message, avail_choice); } } } /* * Prompts the user with a boolean question. * * Returns 1 if yes, 2 if no, and -1 otherwise */ int status_ask_bool(const char *msg) { return (status_ask_choice(msg, _("[yn]"), 2)); } /* * Prompts the user to make a choice between a number of alternatives. * * Returns the option chosen by the user (starting from 1), or -1 if * the user doesn't want to answer. */ int status_ask_simplechoice(const char *prefix, const char *choice[], int nb_choice) { int i; char *tmp; /* "(1) Choice1, (2) Choice2, (3) Choice3?" */ char choicestr[BUFSIZ]; /* Holds the characters to choose from ('1', '2', etc) */ char char_choice[nb_choice + 2]; /* No need to initialize first and last char. */ for (i = 1; i <= nb_choice; i++) char_choice[i] = '0' + i; strcpy(choicestr, prefix); for (i = 0; i < nb_choice; i++) { asprintf(&tmp, ((i + 1) == nb_choice) ? "(%d) %s?" : "(%d) %s, ", (i + 1), choice[i]); strcat(choicestr, tmp); mem_free(tmp); } return (status_ask_choice(choicestr, char_choice, nb_choice)); } /* Erase part of a window. */ void erase_window_part(WINDOW * win, int first_col, int first_row, int last_col, int last_row) { int c, r; for (r = first_row; r <= last_row; r++) { for (c = first_col; c <= last_col; c++) mvwaddstr(win, r, c, " "); } } /* draws a popup window */ WINDOW *popup(int pop_row, int pop_col, int pop_y, int pop_x, const char *title, const char *msg, int hint) { const char *any_key = _("Press any key to continue..."); WINDOW *popup_win; const int MSGXPOS = 5; popup_win = newwin(pop_row, pop_col, pop_y, pop_x); keypad(popup_win, TRUE); if (msg) mvwaddstr(popup_win, MSGXPOS, (pop_col - strlen(msg)) / 2, msg); custom_apply_attr(popup_win, ATTR_HIGHEST); box(popup_win, 0, 0); wins_show(popup_win, title); if (hint) mvwaddstr(popup_win, pop_row - 2, pop_col - (strlen(any_key) + 1), any_key); custom_remove_attr(popup_win, ATTR_HIGHEST); wins_wrefresh(popup_win); return popup_win; } /* prints in middle of a panel */ void print_in_middle(WINDOW * win, int starty, int startx, int width, const char *string) { int len = strlen(string); int x, y; win = win ? win : stdscr; getyx(win, y, x); x = startx ? startx : x; y = starty ? starty : y; width = width ? width : 80; x += (width - len) / 2; custom_apply_attr(win, ATTR_HIGHEST); mvwaddstr(win, y, x, string); custom_remove_attr(win, ATTR_HIGHEST); } /* checks if a string is only made of digits */ int is_all_digit(const char *string) { for (; *string; string++) { if (!isdigit((int)*string)) return 0; } return 1; } /* Given an item date expressed in seconds, return its start time in seconds. */ long get_item_time(time_t date) { return (long)(get_item_hour(date) * HOURINSEC + get_item_min(date) * MININSEC); } int get_item_hour(time_t date) { struct tm lt; localtime_r(&date, <); return lt.tm_hour; } int get_item_min(time_t date) { struct tm lt; localtime_r(&date, <); return lt.tm_min; } struct tm date2tm(struct date day, unsigned hour, unsigned min) { time_t t = now(); struct tm start; localtime_r(&t, &start); start.tm_mon = day.mm - 1; start.tm_mday = day.dd; start.tm_year = day.yyyy - 1900; start.tm_hour = hour; start.tm_min = min; start.tm_sec = 0; start.tm_isdst = -1; return start; } time_t date2sec(struct date day, unsigned hour, unsigned min) { struct tm start = date2tm(day, hour, min); time_t t = mktime(&start); EXIT_IF(t == -1, _("failure in mktime")); return t; } /* Return the (calcurse) date of a (Unix) time in seconds. */ struct date sec2date(time_t t) { struct tm tm; struct date d; localtime_r(&t, &tm); d.dd = tm.tm_mday; d.mm = tm.tm_mon + 1; d.yyyy = tm.tm_year + 1900; return d; } time_t tzdate2sec(struct date day, unsigned hour, unsigned min, char *tznew) { char *tzold; time_t t; if (!tznew) return date2sec(day, hour, min); tzold = getenv("TZ"); if (tzold) tzold = mem_strdup(tzold); setenv("TZ", tznew, 1); tzset(); t = date2sec(day, hour, min); if (tzold) { setenv("TZ", tzold, 1); mem_free(tzold); } else { unsetenv("TZ"); } tzset(); return t; } /* Compare two calcurse dates. */ int date_cmp(struct date *d1, struct date *d2) { if (d1->yyyy < d2->yyyy) return -1; if (d1->yyyy > d2->yyyy) return 1; if (d1->mm < d2->mm) return -1; if (d1->mm > d2->mm) return 1; if (d1->dd < d2->dd) return -1; if (d1->dd > d2->dd) return 1; return 0; } /* Compare two dates (without comparing times). */ int date_cmp_day(time_t d1, time_t d2) { struct tm lt1, lt2; localtime_r((time_t *)&d1, <1); localtime_r((time_t *)&d2, <2); if (lt1.tm_year < lt2.tm_year) return -1; if (lt1.tm_year > lt2.tm_year) return 1; if (lt1.tm_mon < lt2.tm_mon) return -1; if (lt1.tm_mon > lt2.tm_mon) return 1; if (lt1.tm_mday < lt2.tm_mday) return -1; if (lt1.tm_mday > lt2.tm_mday) return 1; return 0; } /* Generic function to format date. */ void date_sec2date_fmt(time_t sec, const char *fmt, char *datef) { #if ENABLE_NLS /* TODO: Find a better way to deal with localization and strftime(). */ char *locale_old = mem_strdup(setlocale(LC_ALL, NULL)); setlocale(LC_ALL, "C"); #endif struct tm lt; localtime_r(&sec, <); strftime(datef, BUFSIZ, fmt, <); #if ENABLE_NLS setlocale(LC_ALL, locale_old); mem_free(locale_old); #endif } /* Return a string containing the date, given a date in seconds. */ char *date_sec2date_str(time_t sec, const char *datefmt) { char *datestr = (char *)mem_calloc(BUFSIZ, sizeof(char)); date_sec2date_fmt(sec, datefmt, datestr); return datestr; } /* * Used to change date by adding a certain amount of days or months. * Returns 0 on success, 1 otherwise. */ int date_change(struct tm *date, int delta_month, int delta_day) { struct tm t; t = *date; t.tm_mon += delta_month; t.tm_mday += delta_day; t.tm_isdst = -1; if (mktime(&t) == -1) { return 1; } else { t.tm_isdst = -1; *date = t; return 0; } } /* * Used to change date by adding a certain amount of days or months. */ time_t date_sec_change(time_t date, int delta_month, int delta_day) { struct tm lt; time_t t; t = date; localtime_r(&t, <); lt.tm_mon += delta_month; lt.tm_mday += delta_day; lt.tm_isdst = -1; t = mktime(<); EXIT_IF(t == -1, _("failure in mktime")); return t; } /* * A date in seconds is updated with new day, month and year and returned. */ static time_t update_date_in_date(time_t date, int day, int month, int year) { struct tm lt; localtime_r(&date, <); lt.tm_mday = day; lt.tm_mon = month - 1; lt.tm_year = year - 1900; lt.tm_isdst = -1; date = mktime(<); EXIT_IF(date == -1, _("error in mktime")); return date; } /* * A date in seconds is updated with new hour and minutes and returned. */ time_t update_time_in_date(time_t date, unsigned hr, unsigned mn) { struct tm lt; localtime_r(&date, <); lt.tm_hour = hr; lt.tm_min = mn; lt.tm_sec = 0; lt.tm_isdst = -1; date = mktime(<); EXIT_IF(date == -1, _("error in mktime")); return date; } /* * Returns the date in seconds from year 1970. * If no date is entered, current date is chosen. */ time_t get_sec_date(struct date date) { struct tm ptrtime; time_t timer; char current_day[] = "dd "; char current_month[] = "mm "; char current_year[] = "yyyy "; if (date.yyyy == 0 && date.mm == 0 && date.dd == 0) { timer = time(NULL); localtime_r(&timer, &ptrtime); strftime(current_day, strlen(current_day), "%d", &ptrtime); strftime(current_month, strlen(current_month), "%m", &ptrtime); strftime(current_year, strlen(current_year), "%Y", &ptrtime); date.mm = atoi(current_month); date.dd = atoi(current_day); date.yyyy = atoi(current_year); } return date2sec(date, 0, 0); } long min2sec(unsigned minutes) { return minutes * MININSEC; } /* * Display a scroll bar when there are so many items that they * can not be displayed inside the corresponding panel. * Leave it out in the appointments panel in when multiple days mode. */ void draw_scrollbar(struct scrollwin *sw, int hilt) { if (sw == &lb_apt.sw && conf.multiple_days) return; int y = (conf.compact_panels ? 1 : 3); int h = sw->h - (conf.compact_panels ? 2 : 4); int sbar_h = MAX(h * h / sw->line_num, 1); int sbar_y = y + sw->line_off * (h - sbar_h) / (sw->line_num - h); int sbar_x = sw->w - 1; /* Redraw part of the border. */ if (hilt) custom_apply_attr(sw->win, ATTR_HIGHEST); mvwvline(sw->win, y, sbar_x, ACS_VLINE, h); if (hilt) custom_remove_attr(sw->win, ATTR_HIGHEST); /* Draw the scrollbar. */ if (hilt) custom_apply_attr(sw->win, ATTR_HIGHEST); wattron(sw->win, A_REVERSE); mvwvline(sw->win, sbar_y, sbar_x, ' ', sbar_h); wattroff(sw->win, A_REVERSE); if (hilt) custom_remove_attr(sw->win, ATTR_HIGHEST); } /* * Print an item (either an appointment, event, or todo) in a * popup window. This is useful if an item description is too * long to fit in its corresponding panel window. */ void item_in_popup(const char *a_start, const char *a_end, const char *msg, const char *pop_title) { WINDOW *popup_win, *pad; const int margin_left = 4, margin_top = 4; const int winl = row - 5, winw = col - margin_left; const int padl = winl - 2, padw = winw - margin_left; pad = newpad(padl, padw); popup_win = popup(winl, winw, 1, 2, pop_title, NULL, 1); if (a_start && a_end) { mvwprintw(popup_win, margin_top, margin_left, "- %s -> %s", a_start, a_end); } mvwaddstr(pad, 0, margin_left, msg); wmove(win[STA].p, 0, 0); pnoutrefresh(pad, 0, 0, margin_top + 2, margin_left, padl, winw); wins_doupdate(); keys_wait_for_any_key(popup_win); delwin(pad); delwin(popup_win); } /* Returns the beginning of current day in seconds from 1970. */ time_t get_today(void) { struct tm lt; time_t current_time; struct date day; current_time = time(NULL); localtime_r(¤t_time, <); day.mm = lt.tm_mon + 1; day.dd = lt.tm_mday; day.yyyy = lt.tm_year + 1900; return date2sec(day, 0, 0); } /* Returns the beginning of the selected day in the calendar. */ time_t get_slctd_day(void) { return date2sec(*ui_calendar_get_slctd_day(), 0, 0); } /* Returns the current time in seconds. */ time_t now(void) { return time(NULL); } char *nowstr(void) { struct tm lt; static char buf[BUFSIZ]; time_t t = now(); localtime_r(&t, <); strftime(buf, sizeof buf, "%a %b %d %T %Y", <); return buf; } /* Print the given option value with appropriate color. */ void print_bool_option_incolor(WINDOW * win, unsigned option, int pos_y, int pos_x) { int color = 0; const char *option_value; if (option == 1) { color = ATTR_TRUE; option_value = _("yes"); } else if (option == 0) { color = ATTR_FALSE; option_value = _("no"); } else { EXIT(_("option not defined")); } /* * Possibly nested custom_apply_attr() calls. Turn * custom_apply_attr(ATTR_HIGHEST) off explicitly, * while it may have other attributes besides the colour. */ custom_remove_attr(win, ATTR_HIGHEST); custom_apply_attr(win, color); mvwaddstr(win, pos_y, pos_x, option_value); custom_remove_attr(win, color); wnoutrefresh(win); wins_doupdate(); } /* * Get the name of the default directory for temporary files. */ const char *get_tempdir(void) { if (getenv("TMPDIR")) return getenv("TMPDIR"); #ifdef P_tmpdir else if (P_tmpdir) return P_tmpdir; #endif else return "/tmp"; } /* * Create a new unique file, and return a newly allocated string which contains * the random part of the file name. */ char *new_tempfile(const char *prefix) { char *fullname; int fd; FILE *file; if (prefix == NULL) return NULL; asprintf(&fullname, "%s.XXXXXX", prefix); if ((fd = mkstemp(fullname)) == -1 || (file = fdopen(fd, "w+")) == NULL) { if (fd != -1) { unlink(fullname); close(fd); } ERROR_MSG(_("temporary file \"%s\" could not be created"), fullname); mem_free(fullname); return NULL; } fclose(file); return fullname; } static void get_ymd(int *year, int *month, int *day, time_t t) { struct tm tm; localtime_r(&t, &tm); *day = tm.tm_mday; *month = tm.tm_mon + 1; *year = tm.tm_year + 1900; } static void get_weekday_ymd(int *year, int *month, int *day, int weekday) { time_t t = get_today(); struct tm tm; int delta; localtime_r(&t, &tm); delta = weekday - tm.tm_wday; t = date_sec_change(t, 0, delta > 0 ? delta : 7); localtime_r(&t, &tm); *day = tm.tm_mday; *month = tm.tm_mon + 1; *year = tm.tm_year + 1900; } /* * Check if a calcurse date is valid. */ int check_date(unsigned year, unsigned month, unsigned day) { return ((YEAR1902_2037 ? year >= 1902 && year <= 2037 : 1) && month >= 1 && month <= 12 && day >= 1 && day <= days[month - 1] + (month == 2 && ISLEAP(year)) ? 1 : 0); } /* * Check that a time in seconds is a valid calcurse date (ignoring hour:min:sec). */ int check_sec(time_t *time) { struct tm tm; localtime_r(time, &tm); return check_date(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); } /* * Convert a string containing a date into three integers containing the year, * month and day. * * If a pointer to a date structure containing the current date is passed as * last parameter ("slctd_date"), the function will accept several short forms, * e.g. "26" for the 26th of the current month/year or "3/1" for Mar 01 (or Jan * 03, depending on the date format) of the current year. If a null pointer is * passed, short forms won't be accepted at all. * * Returns 1 if sucessfully converted or 0 if the string is an invalid date. */ int parse_date(const char *date_string, enum datefmt datefmt, int *year, int *month, int *day, struct date *slctd_date) { const char sep = (datefmt == DATEFMT_ISO) ? '-' : '/'; const char *p; int in[3] = { 0, 0, 0 }, n = 0; int d, m, y; if (!date_string) return 0; if (!strcasecmp(date_string, "today")) { get_ymd(year, month, day, get_today()); return 1; } else if (!strcasecmp(date_string, "yesterday")) { get_ymd(year, month, day, date_sec_change(get_today(), 0, -1)); return 1; } else if (!strcasecmp(date_string, "tomorrow")) { get_ymd(year, month, day, date_sec_change(get_today(), 0, 1)); return 1; } else if (!strcasecmp(date_string, "now")) { get_ymd(year, month, day, now()); return 1; } else if (!strcasecmp(date_string, "sunday") || !strcasecmp(date_string, "sun")) { get_weekday_ymd(year, month, day, 0); return 1; } else if (!strcasecmp(date_string, "monday") || !strcasecmp(date_string, "mon")) { get_weekday_ymd(year, month, day, 1); return 1; } else if (!strcasecmp(date_string, "tuesday") || !strcasecmp(date_string, "tue")) { get_weekday_ymd(year, month, day, 2); return 1; } else if (!strcasecmp(date_string, "wednesday") || !strcasecmp(date_string, "wed")) { get_weekday_ymd(year, month, day, 3); return 1; } else if (!strcasecmp(date_string, "thursday") || !strcasecmp(date_string, "thu")) { get_weekday_ymd(year, month, day, 4); return 1; } else if (!strcasecmp(date_string, "friday") || !strcasecmp(date_string, "fri")) { get_weekday_ymd(year, month, day, 5); return 1; } else if (!strcasecmp(date_string, "saturday") || !strcasecmp(date_string, "sat")) { get_weekday_ymd(year, month, day, 6); return 1; } /* parse string into in[], read up to three integers */ for (p = date_string; *p; p++) { if (*p == sep) { if ((++n) > 2) return 0; } else if ((*p >= '0') && (*p <= '9')) { in[n] = in[n] * 10 + (int)(*p - '0'); } else { return 0; } } if ((!slctd_date && n < 2) || in[n] == 0) return 0; /* convert into day, month and year, depending on the date format */ switch (datefmt) { case DATEFMT_MMDDYYYY: m = (n >= 1) ? in[0] : 0; d = (n >= 1) ? in[1] : in[0]; y = in[2]; break; case DATEFMT_DDMMYYYY: d = in[0]; m = in[1]; y = in[2]; break; case DATEFMT_YYYYMMDD: case DATEFMT_ISO: y = (n >= 2) ? in[n - 2] : 0; m = (n >= 1) ? in[n - 1] : 0; d = in[n]; break; default: return 0; } if (slctd_date) { if (y > 0 && y < 100) { /* convert "YY" format into "YYYY" */ y += slctd_date->yyyy - slctd_date->yyyy % 100; } else if (n < 2) { /* set year and, optionally, month if short from is used */ y = slctd_date->yyyy; if (n < 1) m = slctd_date->mm; } } /* check if date is valid, take leap years into account */ if (!check_date(y, m, d)) return 0; if (year) *year = y; if (month) *month = m; if (day) *day = d; return 1; } int parse_date_interactive(const char *datestr, int *year, int *month, int *day) { return parse_date(datestr, conf.input_datefmt, year, month, day, ui_calendar_get_slctd_day()); } /* * Convert a date increment string into a number of days. * If start is non-zero, the final end time is validated. * * Allowed formats in lenient BNF: * ::= | * ::= [ w ][ d ] * Notes: * and are any integer >= 0. * must have at least one non-terminal. * * Returns 1 on success and 0 on failure. */ int parse_date_increment(const char *string, unsigned *days, time_t start) { enum { STATE_INITIAL, STATE_WWDD_DD, STATE_DONE } state = STATE_INITIAL; const char *p; unsigned in = 0, frac = 0, denom = 1; unsigned incr = 0; if (!string || *string == '\0') return 0; /* parse string using a simple state machine */ for (p = string; *p; p++) { if (state == STATE_DONE) { return 0; } else if ((*p >= '0') && (*p <= '9')) { in = in * 10 + (int)(*p - '0'); if (frac) denom *= 10; } else if (*p == '.') { if (frac) return 0; frac++; } else { switch (state) { case STATE_INITIAL: if (*p == 'w') { incr += in * WEEKINDAYS / denom; state = STATE_WWDD_DD; } else if (*p == 'd') { incr += in / denom; state = STATE_DONE; } else { return 0; } break; case STATE_WWDD_DD: if (*p == 'd') { incr += in / denom; state = STATE_DONE; } else { return 0; } break; default: break; } in = frac = 0; denom = 1; } } if (state == STATE_DONE && in > 0) return 0; incr += in; if (start) { /* wanted: start = start + incr * DAYINSEC */ long p; if (overflow_mul(incr, DAYINSEC, &p)) return 0; if (overflow_add(start, p, &start)) return 0; if (!check_sec(&start)) return 0; } *days = incr; return 1; } /* * Check if time is valid. */ int check_time(unsigned hours, unsigned minutes) { return (hours < DAYINHOURS && minutes < HOURINMIN); } /* * Converts a time string into hours and minutes. Short forms like "23:" * (23:00) or ":45" (0:45) are allowed as well as "2345". Note: the latter * clashes with date formats 0001 .. 0031 and must be picked up before * dates when parsing in parse_datetime. * * Returns 1 on success and 0 on failure. */ int parse_time(const char *string, unsigned *hour, unsigned *minute) { const char *p; unsigned in[2] = { 0, 0 }, n = 0; if (!string) return 0; /* parse string into in[], read up to two integers */ for (p = string; *p; p++) { if (*p == ':') { if ((++n) > 1) return 0; } else if (isdigit(*p)) { in[n] = in[n] * 10 + *p - '0'; } else { return 0; } } /* 24-hour format without ':' (hhmm)? */ if (n == 0 && strlen(string) == 4) { in[1] = in[0] % 100; in[0] = in[0] / 100; n = 1; } if (n != 1 || !check_time(in[0], in[1])) return 0; *hour = in[0]; *minute = in[1]; return 1; } /* * Converts a duration string into minutes. * If start time is non-zero, the final end time is validated. * * Allowed formats in lenient BNF: * ::= |