calcurse-4.8.1/0000755000175000017500000000000014432731065010333 500000000000000calcurse-4.8.1/COPYING0000644000175000017500000000252414415345513011311 00000000000000Copyright (c) 2004-2023 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. calcurse-4.8.1/config.h.in0000644000175000017500000001150614432731002012270 00000000000000/* config.h.in. Generated from configure.ac by autoheader. */ /* Define if building universal (internal helper macro) */ #undef AC_APPLE_UNIVERSAL_BUILD /* Define to 1 if you want memory debug. */ #undef CALCURSE_MEMORY_DEBUG /* Define to 1 if you do not want memory debug. */ #undef CALCURSE_MEMORY_DEBUG_DISABLED /* Define to 1 if translation of program messages to the user's native language is requested. */ #undef ENABLE_NLS /* Define to 1 if you have the Mac OS X function CFLocaleCopyCurrent in the CoreFoundation framework. */ #undef HAVE_CFLOCALECOPYCURRENT /* Define to 1 if you have the Mac OS X function CFPreferencesCopyAppValue in the CoreFoundation framework. */ #undef HAVE_CFPREFERENCESCOPYAPPVALUE /* Define to 1 if you have the header file. */ #undef HAVE_CTYPE_H /* Define to 1 if a SysV or X/Open compatible Curses library is present */ #undef HAVE_CURSES /* Define to 1 if library supports color (enhanced functions) */ #undef HAVE_CURSES_COLOR /* Define to 1 if library supports X/Open Enhanced functions */ #undef HAVE_CURSES_ENHANCED /* Define to 1 if is present */ #undef HAVE_CURSES_H /* Define to 1 if library supports certain obsolete features */ #undef HAVE_CURSES_OBSOLETE /* Define if the GNU dcgettext() function is already present or preinstalled. */ #undef HAVE_DCGETTEXT /* Define to 1 if you have the header file. */ #undef HAVE_ERRNO_H /* Define to 1 if you have the header file. */ #undef HAVE_FCNTL_H /* Define to 1 if you have the header file. */ #undef HAVE_GETOPT_H /* Define if the GNU gettext() function is already present or preinstalled. */ #undef HAVE_GETTEXT /* Define if you have the iconv() function and it works. */ #undef HAVE_ICONV /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H /* Define to 1 if you have the 'math' library (-lm). */ #undef HAVE_LIBMATH /* Define to 1 if you have the 'pthread' library (-pthread). */ #undef HAVE_LIBPTHREAD /* Define to 1 if you have the header file. */ #undef HAVE_LIMITS_H /* Define to 1 if you have the header file. */ #undef HAVE_LOCALE_H /* Define to 1 if you have the header file. */ #undef HAVE_MATH_H /* Define to 1 if the Ncurses library is present */ #undef HAVE_NCURSES /* Define to 1 if the NcursesW library is present */ #undef HAVE_NCURSESW /* Define to 1 if is present */ #undef HAVE_NCURSESW_CURSES_H /* Define to 1 if is present */ #undef HAVE_NCURSESW_H /* Define to 1 if is present */ #undef HAVE_NCURSES_CURSES_H /* Define to 1 if is present */ #undef HAVE_NCURSES_H /* Define to 1 if you have the header file. */ #undef HAVE_PATHS_H /* Define to 1 if you have the header file. */ #undef HAVE_PTHREAD_H /* Define to 1 if you have the header file. */ #undef HAVE_REGEX_H /* Define to 1 if you have the header file. */ #undef HAVE_SIGNAL_H /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H /* Define to 1 if you have the header file. */ #undef HAVE_STDIO_H /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H /* Define to 1 if you have the header file. */ #undef HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_WAIT_H /* Define to 1 if you have the header file. */ #undef HAVE_TIME_H /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H /* Name of package */ #undef PACKAGE /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT /* Define to the full name of this package. */ #undef PACKAGE_NAME /* Define to the full name and version of this package. */ #undef PACKAGE_STRING /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME /* Define to the home page for this package. */ #undef PACKAGE_URL /* Define to the version of this package. */ #undef PACKAGE_VERSION /* Define to 1 if all of the C90 standard headers exist (not just the ones required in a freestanding environment). This macro is provided for backward compatibility; new code need not use it. */ #undef STDC_HEADERS /* Version number of package */ #undef VERSION /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel). */ #if defined AC_APPLE_UNIVERSAL_BUILD # if defined __BIG_ENDIAN__ # define WORDS_BIGENDIAN 1 # endif #else # ifndef WORDS_BIGENDIAN # undef WORDS_BIGENDIAN # endif #endif calcurse-4.8.1/.version0000644000175000017500000000000614432731065011735 000000000000004.8.1 calcurse-4.8.1/compile0000755000175000017500000001635014432731003011626 00000000000000#! /bin/sh # Wrapper for compilers which do not understand '-c -o'. scriptversion=2018-03-07.03; # UTC # Copyright (C) 1999-2021 Free Software Foundation, Inc. # Written by Tom Tromey . # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # This file is maintained in Automake, please report # bugs to or send patches to # . nl=' ' # We need space, tab and new line, in precisely that order. Quoting is # there to prevent tools from complaining about whitespace usage. IFS=" "" $nl" file_conv= # func_file_conv build_file lazy # Convert a $build file to $host form and store it in $file # Currently only supports Windows hosts. If the determined conversion # type is listed in (the comma separated) LAZY, no conversion will # take place. func_file_conv () { file=$1 case $file in / | /[!/]*) # absolute file, and not a UNC file if test -z "$file_conv"; then # lazily determine how to convert abs files case `uname -s` in MINGW*) file_conv=mingw ;; CYGWIN* | MSYS*) file_conv=cygwin ;; *) file_conv=wine ;; esac fi case $file_conv/,$2, in *,$file_conv,*) ;; mingw/*) file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` ;; cygwin/* | msys/*) file=`cygpath -m "$file" || echo "$file"` ;; wine/*) file=`winepath -w "$file" || echo "$file"` ;; esac ;; esac } # func_cl_dashL linkdir # Make cl look for libraries in LINKDIR func_cl_dashL () { func_file_conv "$1" if test -z "$lib_path"; then lib_path=$file else lib_path="$lib_path;$file" fi linker_opts="$linker_opts -LIBPATH:$file" } # func_cl_dashl library # Do a library search-path lookup for cl func_cl_dashl () { lib=$1 found=no save_IFS=$IFS IFS=';' for dir in $lib_path $LIB do IFS=$save_IFS if $shared && test -f "$dir/$lib.dll.lib"; then found=yes lib=$dir/$lib.dll.lib break fi if test -f "$dir/$lib.lib"; then found=yes lib=$dir/$lib.lib break fi if test -f "$dir/lib$lib.a"; then found=yes lib=$dir/lib$lib.a break fi done IFS=$save_IFS if test "$found" != yes; then lib=$lib.lib fi } # func_cl_wrapper cl arg... # Adjust compile command to suit cl func_cl_wrapper () { # Assume a capable shell lib_path= shared=: linker_opts= for arg do if test -n "$eat"; then eat= else case $1 in -o) # configure might choose to run compile as 'compile cc -o foo foo.c'. eat=1 case $2 in *.o | *.[oO][bB][jJ]) func_file_conv "$2" set x "$@" -Fo"$file" shift ;; *) func_file_conv "$2" set x "$@" -Fe"$file" shift ;; esac ;; -I) eat=1 func_file_conv "$2" mingw set x "$@" -I"$file" shift ;; -I*) func_file_conv "${1#-I}" mingw set x "$@" -I"$file" shift ;; -l) eat=1 func_cl_dashl "$2" set x "$@" "$lib" shift ;; -l*) func_cl_dashl "${1#-l}" set x "$@" "$lib" shift ;; -L) eat=1 func_cl_dashL "$2" ;; -L*) func_cl_dashL "${1#-L}" ;; -static) shared=false ;; -Wl,*) arg=${1#-Wl,} save_ifs="$IFS"; IFS=',' for flag in $arg; do IFS="$save_ifs" linker_opts="$linker_opts $flag" done IFS="$save_ifs" ;; -Xlinker) eat=1 linker_opts="$linker_opts $2" ;; -*) set x "$@" "$1" shift ;; *.cc | *.CC | *.cxx | *.CXX | *.[cC]++) func_file_conv "$1" set x "$@" -Tp"$file" shift ;; *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO]) func_file_conv "$1" mingw set x "$@" "$file" shift ;; *) set x "$@" "$1" shift ;; esac fi shift done if test -n "$linker_opts"; then linker_opts="-link$linker_opts" fi exec "$@" $linker_opts exit 1 } eat= case $1 in '') echo "$0: No command. Try '$0 --help' for more information." 1>&2 exit 1; ;; -h | --h*) cat <<\EOF Usage: compile [--help] [--version] PROGRAM [ARGS] Wrapper for compilers which do not understand '-c -o'. Remove '-o dest.o' from ARGS, run PROGRAM with the remaining arguments, and rename the output as expected. If you are trying to build a whole package this is not the right script to run: please start by reading the file 'INSTALL'. Report bugs to . EOF exit $? ;; -v | --v*) echo "compile $scriptversion" exit $? ;; cl | *[/\\]cl | cl.exe | *[/\\]cl.exe | \ icl | *[/\\]icl | icl.exe | *[/\\]icl.exe ) func_cl_wrapper "$@" # Doesn't return... ;; esac ofile= cfile= for arg do if test -n "$eat"; then eat= else case $1 in -o) # configure might choose to run compile as 'compile cc -o foo foo.c'. # So we strip '-o arg' only if arg is an object. eat=1 case $2 in *.o | *.obj) ofile=$2 ;; *) set x "$@" -o "$2" shift ;; esac ;; *.c) cfile=$1 set x "$@" "$1" shift ;; *) set x "$@" "$1" shift ;; esac fi shift done if test -z "$ofile" || test -z "$cfile"; then # If no '-o' option was seen then we might have been invoked from a # pattern rule where we don't need one. That is ok -- this is a # normal compilation that the losing compiler can handle. If no # '.c' file was seen then we are probably linking. That is also # ok. exec "$@" fi # Name of file we expect compiler to create. cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` # Create the lock directory. # Note: use '[/\\:.-]' here to ensure that we don't use the same name # that we are using for the .o file. Also, base the name on the expected # object file name, since that is what matters with a parallel build. lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d while true; do if mkdir "$lockdir" >/dev/null 2>&1; then break fi sleep 1 done # FIXME: race condition here if user kills between mkdir and trap. trap "rmdir '$lockdir'; exit 1" 1 2 15 # Run the compile. "$@" ret=$? if test -f "$cofile"; then test "$cofile" = "$ofile" || mv "$cofile" "$ofile" elif test -f "${cofile}bj"; then test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" fi rmdir "$lockdir" exit $ret # Local Variables: # mode: shell-script # sh-indentation: 2 # eval: (add-hook 'before-save-hook 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" # time-stamp-end: "; # UTC" # End: calcurse-4.8.1/README.md0000644000175000017500000000460114415345513011533 00000000000000calcurse ======== [![Build and test](https://github.com/lfos/calcurse/actions/workflows/make.yml/badge.svg)](https://github.com/lfos/calcurse/actions/workflows/make.yml) [![Lint Python](https://github.com/lfos/calcurse/actions/workflows/lint_python.yml/badge.svg)](https://github.com/lfos/calcurse/actions/workflows/lint_python.yml) ![Demo](https://calcurse.org/images/demo.gif) Building -------- Install the following build dependencies. If your distro segments development files from core packages (i.e., \*-devel or \*-dev packages), you may need to install those as well: * gcc * autoconf-archive * automake * autopoint * asciidoc * gettext with development files * ncurses with development files If you are using a release tarball, the following commands can be used to build and install calcurse: $ ./configure $ make $ make install Note that `make install` needs to be run as root. When working on a Git checkout, you need to run `./autogen.sh` before `./configure`. Package Overview ---------------- * `build-aux`: auxiliary files for the build process * `contrib`: useful tools such as hooks or the CalDAV synchronization script * `doc`: detailed documentation in plain text and HTML * `po`: translations and i18n-related files * `scripts`: additional official scripts, such as `calcurse-upgrade` * `src`: the actual calcurse source files * `test`: test suite and test cases for calcurse Authors ------- calcurse was created by Frederic Culot in 2004. Since 2011, the project is maintained by Lukas Fleischer. Many core features added to calcurse since 2017 were designed and implemented by Lars Henriksen. Of course, there are numerous other contributors. Check the Git commit log and the `Thanks` section in the manual for a list of people who have contributed by reporting bugs, sending fixes, or suggesting improvements. Contributing and Donations -------------------------- Patches, bug reports and other requests are always welcome! You can submit them to one of our mailing lists (check the [patch submission guidelines](doc/submitting-patches.txt) for details) or via GitHub. We are also extremely grateful for donations which help us continue developing calcurse as open source software and are used to cover recurring costs, such as for our servers. You can use https://calcurse.org/donate/ for a one-time payment. If you prefer another form of donation, do not hesitate to contact us! calcurse-4.8.1/src/0000755000175000017500000000000014432731064011121 500000000000000calcurse-4.8.1/src/dmon.c0000644000175000017500000001404714415345513012151 00000000000000/* * Calcurse - text-based organizer * * Copyright (c) 2004-2023 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 "calcurse.h" #define DMON_SLEEP_TIME 60 #define DMON_LOG(...) do { \ if (dmon.log) \ io_fprintln (path_dmon_log, __VA_ARGS__); \ } while (0) #define DMON_ABRT(...) do { \ DMON_LOG (__VA_ARGS__); \ if (kill (getpid (), SIGINT) < 0) \ { \ DMON_LOG (_("Could not stop daemon properly: %s\n"), \ strerror (errno)); \ exit (EXIT_FAILURE); \ } \ } while (0) static unsigned data_loaded; static void dmon_sigs_hdlr(int sig) { if (sig == SIGUSR1) { want_reload = 1; return; } if (data_loaded) free_user_data(); DMON_LOG(_("terminated at %s with signal %d\n"), nowstr(), sig); if (unlink(path_dpid) != 0) { DMON_LOG(_("Could not remove daemon lock file: %s\n"), strerror(errno)); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } static unsigned daemonize(int status) { int fd; /* * Operate in the background: Daemonizing. * * First need to fork in order to become a child of the init process, * once the father exits. */ switch (fork()) { case -1: /* fork error */ EXIT(_("Could not fork: %s\n"), strerror(errno)); break; case 0: /* child */ break; default: /* parent */ exit(status); } /* * Process independency. * * Obtain a new process group and session in order to get detached from the * controlling terminal. */ if (setsid() == -1) { DMON_LOG(_("Could not detach from the controlling terminal: %s\n"), strerror(errno)); return 0; } /* * Change working directory to root directory, * to prevent filesystem unmounts. */ if (chdir("/") == -1) { DMON_LOG(_("Could not change working directory: %s\n"), strerror(errno)); return 0; } /* Redirect standard file descriptors to /dev/null. */ if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); if (fd > 2) close(fd); } /* Write access for the owner only. */ umask(0022); if (!sigs_set_hdlr(SIGINT, dmon_sigs_hdlr) || !sigs_set_hdlr(SIGTERM, dmon_sigs_hdlr) || !sigs_set_hdlr(SIGALRM, dmon_sigs_hdlr) || !sigs_set_hdlr(SIGQUIT, dmon_sigs_hdlr) || !sigs_set_hdlr(SIGUSR1, dmon_sigs_hdlr)) return 0; return 1; } void dmon_start(int parent_exit_status) { if (!daemonize(parent_exit_status)) DMON_ABRT(_("Cannot daemonize, aborting\n")); if (!io_dump_pid(path_dpid)) DMON_ABRT(_("Could not set lock file\n")); if (!io_file_exists(path_conf)) DMON_ABRT(_("Could not access \"%s\": %s\n"), path_conf, strerror(errno)); config_load(); if (!io_file_exists(path_apts)) DMON_ABRT(_("Could not access \"%s\": %s\n"), path_apts, strerror(errno)); apoint_llist_init(); recur_apoint_llist_init(); event_llist_init(); recur_event_llist_init(); todo_init_list(); io_load_app(NULL); data_loaded = 1; DMON_LOG(_("started at %s\n"), nowstr()); for (;;) { int left; if (want_reload) { want_reload = 0; io_reload_data(); notify_check_next_app(1); } if (!notify_get_next_bkgd()) DMON_ABRT(_("error loading next appointment\n")); left = notify_time_left(); if (left > 0 && left <= MAX(DMON_SLEEP_TIME, nbar.cntdwn) && notify_needs_reminder()) { DMON_LOG(_("launching notification at %s for: \"%s\"\n"), nowstr(), notify_app_txt()); if (!notify_launch_cmd()) DMON_LOG(_("error while sending notification\n")); } DMON_LOG(ngettext("sleeping at %s for %d second\n", "sleeping at %s for %d seconds\n", DMON_SLEEP_TIME), nowstr(), DMON_SLEEP_TIME); psleep(DMON_SLEEP_TIME); DMON_LOG(_("awakened at %s\n"), nowstr()); /* Reap the user-defined notifications. */ while (waitpid(0, NULL, WNOHANG) > 0) ; } } /* * Check if calcurse is running in background, and if yes, send a SIGINT * signal to stop it. */ void dmon_stop(void) { int dpid; dpid = io_get_pid(path_dpid); if (!dpid) return; if (kill((pid_t) dpid, SIGINT) < 0 && errno != ESRCH) EXIT(_("Could not stop calcurse daemon: %s\n"), strerror(errno)); } calcurse-4.8.1/src/utils.c0000644000175000017500000013407714415345513012362 00000000000000/* * Calcurse - text-based organizer * * Copyright (c) 2004-2023 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; } int modify_wday(int wday, int shift) { return (WEEKINDAYS + wday + shift) % WEEKINDAYS; } /* returns char* representing a wday, used for internal functions */ char *get_wday_default_string(int wday) { switch(wday) { case MONDAY: return "Monday"; break; case TUESDAY: return "Tuesday"; break; case WEDNESDAY: return "Wednesday"; break; case THURSDAY: return "Thursday"; break; case FRIDAY: return "Friday"; break; case SATURDAY: return "Saturday"; break; case SUNDAY: return "Sunday"; break; default: return "Sunday"; break; } } /* * 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: * ::= |