Compare commits

...

67 Commits

Author SHA1 Message Date
Dianne Skoll
4574b552b6 Update WHATSNEW. 2025-09-06 12:05:46 -04:00
Dianne Skoll
21e0940257 Add distro-beta target. 2025-09-06 12:02:20 -04:00
Dianne Skoll
7064893ff2 Reorder man page; add explanation of how omit interacts with time zone. 2025-09-06 11:08:34 -04:00
Dianne Skoll
f8781276e2 Clarify that OMITFUNC also sees dates in the TZ timezone. 2025-09-06 11:00:50 -04:00
Dianne Skoll
b02f61ea05 Make alignment.rem executable. 2025-09-05 18:33:59 -04:00
Dianne Skoll
60447d530d Add a small example. 2025-09-05 18:32:05 -04:00
Dianne Skoll
2835176ad3 Make "make test" run all tests by default. Add individual test-basic and test-tz makefile targets. 2025-09-05 17:46:25 -04:00
Dianne Skoll
b0784feb1a Bump version to 06.01.00. 2025-09-04 15:58:07 -04:00
Dianne Skoll
ef7021972e Add trigimetz and trigeventstarttz functions. 2025-09-04 15:36:27 -04:00
Dianne Skoll
e449fcdfcf Add tests for trigtz 2025-09-04 14:29:00 -04:00
Dianne Skoll
47a4456c20 Explicitly initialize LastTrigger.tz to NULL. 2025-09-04 14:26:35 -04:00
Dianne Skoll
e9ee7492f7 Check for setuid/setgid early. 2025-09-04 14:25:09 -04:00
Dianne Skoll
2baa0405f3 Document new JSON key pairs. 2025-09-04 13:54:34 -04:00
Dianne Skoll
3425530c1c Add Makefile for running tests from tests/ with "make test" 2025-09-04 11:00:03 -04:00
Dianne Skoll
eeae3298ef Nicer formatting. 2025-09-04 10:59:58 -04:00
Dianne Skoll
4237dc4a3f Allow empty string to be used in tzconvert to designate system default time zone. 2025-09-04 10:09:46 -04:00
Dianne Skoll
faf8947dda Properly cache stdin in interactive mode. 2025-09-04 00:15:21 -04:00
Dianne Skoll
41a3db3a37 Fix abort. 2025-09-03 23:14:58 -04:00
Dianne Skoll
f12bcf3fe6 Restructure test targets. 2025-09-03 22:36:46 -04:00
Dianne Skoll
f9261bb24f Better target name 2025-09-03 19:35:28 -04:00
Dianne Skoll
2ff9aedba5 Add test. 2025-09-03 19:34:48 -04:00
Dianne Skoll
1b793eff2e Hard-code datetimes so slight astronomical function variations don't mess up test. 2025-09-03 19:33:18 -04:00
Dianne Skoll
e4c41e0f45 More TZ tests. 2025-09-03 19:13:55 -04:00
Dianne Skoll
9ec1006804 Make an empty TZ (ie, TZ "") the same as no TZ. 2025-09-03 19:10:26 -04:00
Dianne Skoll
dd048c0557 Formatting. 2025-09-03 19:06:23 -04:00
Dianne Skoll
dce1f282f4 Improve Makefile. 2025-09-03 19:05:41 -04:00
Dianne Skoll
7120d07d84 Add testtz and test-all targets. 2025-09-03 19:03:59 -04:00
Dianne Skoll
127358db02 The official IANA name of UTC is "Universal". 2025-09-03 18:55:43 -04:00
Dianne Skoll
a89aaf18b6 Add another distclean target 2025-09-03 18:37:33 -04:00
Dianne Skoll
5734bd7524 Start adding TZ tests. 2025-09-03 18:36:13 -04:00
Dianne Skoll
dfabda7dee add trigtz() function 2025-09-03 14:19:31 -04:00
Dianne Skoll
da146d7989 Always output time_in_tz in JSON when needed. 2025-09-03 14:08:56 -04:00
Dianne Skoll
e147925462 Make tests pass; update remind conf mode. 2025-09-03 13:58:09 -04:00
Dianne Skoll
703907c94d Update JSON output. 2025-09-03 13:57:25 -04:00
Dianne Skoll
bcfee04cae Handle another time zone edge case. 2025-09-03 13:21:16 -04:00
Dianne Skoll
5f9e71f9eb More timezone fixification. 2025-09-03 13:05:54 -04:00
Dianne Skoll
272336226e Add timezone support to evaltrig. 2025-09-03 12:42:59 -04:00
Dianne Skoll
32f4b125b5 Seemingly (?) working time zone support!! 2025-09-03 12:35:51 -04:00
Dianne Skoll
7979a69cb9 First semi-sorta-working time zone adjustment. 2025-09-03 12:22:09 -04:00
Dianne Skoll
651368fedc More work on timezone support. 2025-09-03 11:35:21 -04:00
Dianne Skoll
fe002557cf New approach to handling time zone specs in REM command. 2025-09-03 11:02:01 -04:00
Dianne Skoll
d2785d909c Remember DSEToday and SysTime in Local* variables so we can switch timezones. 2025-09-03 10:36:30 -04:00
Dianne Skoll
c2345a240b Update docs to reflect multi-type system variables. 2025-09-02 09:39:32 -04:00
Dianne Skoll
5b6bf25a20 Document readline support. 2025-09-01 10:14:22 -04:00
Dianne Skoll
44f9f0f0d4 Better quotes. 2025-09-01 10:09:27 -04:00
Dianne Skoll
0416d6da9c Update to version 06.00.02. 2025-09-01 10:02:14 -04:00
Dianne Skoll
5d46df871f Clamp trigger() to "1 January 1990 AT 00:00" if UTC flag is used. 2025-08-28 13:30:21 -04:00
Dianne Skoll
c5d661124c Never return a year < 1990 from trigger() 2025-08-28 12:53:28 -04:00
Dianne Skoll
2f239f77a5 Default $ParseUntriggered to 0 instead of 1 2025-08-27 11:34:10 -04:00
Dianne Skoll
1ef05d3f85 Slight efficiency improvement. 2025-08-21 11:50:45 -04:00
Dianne Skoll
7c56aad791 FIx tests. 2025-08-21 11:49:16 -04:00
Dianne Skoll
5f0f68f508 Allow '-i$foo' to be interpreted as '-i$foo=0' 2025-08-20 10:27:22 -04:00
Dianne Skoll
71bfe31002 Make IS_INTERACTIVE() macro. 2025-08-19 21:39:31 -04:00
Dianne Skoll
4661b454c0 Don't add *everything* to history, LOL. 2025-08-19 21:32:26 -04:00
Dianne Skoll
d41520a04f Better example. 2025-08-19 15:54:03 -04:00
Dianne Skoll
0206e538e0 Avoid -Wunused-result warning. 2025-08-19 15:22:05 -04:00
Dianne Skoll
6689cbfda3 Explicitly set latitude and longitude in test files. 2025-08-19 15:04:03 -04:00
Dianne Skoll
5baf102bfe Update WHATSNEW. 2025-08-19 12:43:00 -04:00
Dianne Skoll
c9002d5b54 Bump version to 06.00.01 2025-08-19 12:38:38 -04:00
Dianne Skoll
ac3ee7e22b Use constant-time DBufLen instead of O(n) strlen() 2025-08-19 10:21:01 -04:00
Dianne Skoll
f366037b8d Truncate over-long variable name in DUMP. 2025-08-19 10:13:09 -04:00
Dianne Skoll
a46488a50d Remove incorrect assignment that could cause a segfault. 2025-08-19 09:58:04 -04:00
Dianne Skoll
f91a1a2d65 If we don't have readline, don't declare unused variable. 2025-08-18 21:05:56 -04:00
Dianne Skoll
2ac8fb50e1 Fix typos. 2025-08-18 21:01:48 -04:00
Dianne Skoll
7df826f635 Add whole line at a time to history buffer. 2025-08-18 20:35:10 -04:00
Dianne Skoll
f8ce7b51da Change prompt depending on whether or not we're ignoring lines. 2025-08-18 16:26:58 -04:00
Dianne Skoll
35ee94ca6b If stdin and stdout are TTYs, use readline() where possible. 2025-08-18 16:20:45 -04:00
37 changed files with 2329 additions and 1362 deletions

11
.gitignore vendored
View File

@@ -4,12 +4,12 @@
.gitignore
MYMETA.json
MYMETA.yml
Makefile
TAGS
autom4te.cache
blib/
config.log
config.status
gmon.out
man/rem.1
man/rem2ps.1
man/remind.1
@@ -18,6 +18,7 @@ pm_to_blib
rem2html/Makefile
rem2html/rem2html
rem2html/rem2html.1
rem2pdf/Makefile
rem2pdf/Makefile.PL
rem2pdf/Makefile.old
rem2pdf/Makefile.top
@@ -30,8 +31,8 @@ src/rem2ps
src/remind
src/test-*.out
src/version.h
tests/test.out
www/Makefile
gmon.out
tests/once.timestamp
src/xlat.c
tests/once.timestamp
tests/test.out
tests/tz.out
www/Makefile

View File

@@ -39,14 +39,21 @@ install-stripped:
@$(MAKE) -C rem2html install
@$(MAKE) -C rem2pdf -f Makefile.top install INSTALL_BASE=$(INSTALL_BASE)
test:
@$(MAKE) -C src -s test
test: test-basic test-tz
test-tz:
@$(MAKE) -C src -s all
@$(MAKE) -C src -s test-tz
test-basic:
@$(MAKE) -C src -s all
@$(MAKE) -C src -s test-basic
cppcheck:
@$(MAKE) -C src cppcheck
distclean: clean
-rm -f config.cache config.log config.status src/Makefile src/version.h src/config.h tests/test.out www/Makefile rem2pdf/Makefile.top rem2pdf/Makefile.old rem2pdf/Makefile rem2pdf/Makefile.PL rem2pdf/bin/rem2pdf rem2html/rem2html
-rm -f config.cache config.log config.status src/Makefile src/version.h src/config.h tests/test.out tests/tz.out www/Makefile rem2pdf/Makefile.top rem2pdf/Makefile.old rem2pdf/Makefile rem2pdf/Makefile.PL rem2pdf/bin/rem2pdf rem2html/rem2html
-rm -f man/rem.1 man/rem2ps.1 man/remind.1 man/tkremind.1 scripts/tkremind
-rm -rf autom4te.cache rem2html/Makefile rem2html/rem2html.1

View File

@@ -9,7 +9,14 @@ the GNU General Public License, Vesion 2.
### Remind and Rem2PS
**remind** and **rem2ps** have no prerequisites beyond the standard C
library and the standard math library.
library and the standard math library. **remind** will make use of
GNU Readline if you have it installed.
- On Debian-like systems, install GNU Readline as follows:
`apt install libreadline-dev`
- On RPM-based systems, you need `readline-devel`
### Rem2HTML and Rem2PDF

79
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.72 for remind 06.00.00.
# Generated by GNU Autoconf 2.72 for remind 06.01.00.
#
#
# Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation,
@@ -601,8 +601,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='remind'
PACKAGE_TARNAME='remind'
PACKAGE_VERSION='06.00.00'
PACKAGE_STRING='remind 06.00.00'
PACKAGE_VERSION='06.01.00'
PACKAGE_STRING='remind 06.01.00'
PACKAGE_BUGREPORT=''
PACKAGE_URL='https://dianne.skoll.ca/projects/remind/'
@@ -1258,7 +1258,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
'configure' configures remind 06.00.00 to adapt to many kinds of systems.
'configure' configures remind 06.01.00 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1320,7 +1320,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of remind 06.00.00:";;
short | recursive ) echo "Configuration of remind 06.01.00:";;
esac
cat <<\_ACEOF
@@ -1408,7 +1408,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
remind configure 06.00.00
remind configure 06.01.00
generated by GNU Autoconf 2.72
Copyright (C) 2023 Free Software Foundation, Inc.
@@ -1871,7 +1871,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by remind $as_me 06.00.00, which was
It was created by remind $as_me 06.01.00, which was
generated by GNU Autoconf 2.72. Invocation command line was
$ $0$ac_configure_args_raw
@@ -2477,6 +2477,8 @@ as_fn_append ac_header_c_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H"
as_fn_append ac_header_c_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H"
as_fn_append ac_header_c_list " unistd.h unistd_h HAVE_UNISTD_H"
as_fn_append ac_header_c_list " sys/time.h sys_time_h HAVE_SYS_TIME_H"
as_fn_append ac_header_c_list " readline/readline.h readline_readline_h HAVE_READLINE_READLINE_H"
as_fn_append ac_header_c_list " readline/history.h readline_history_h HAVE_READLINE_HISTORY_H"
# Auxiliary files required by this configure script.
ac_aux_files="install-sh"
@@ -3941,6 +3943,57 @@ then :
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for readline in -lreadline" >&5
printf %s "checking for readline in -lreadline... " >&6; }
if test ${ac_cv_lib_readline_readline+y}
then :
printf %s "(cached) " >&6
else case e in #(
e) ac_check_lib_save_LIBS=$LIBS
LIBS="-lreadline $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply.
The 'extern "C"' is for builds by C++ compilers;
although this is not generally supported in C code supporting it here
has little cost and some practical benefit (sr 110532). */
#ifdef __cplusplus
extern "C"
#endif
char readline (void);
int
main (void)
{
return readline ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
ac_cv_lib_readline_readline=yes
else case e in #(
e) ac_cv_lib_readline_readline=no ;;
esac
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS ;;
esac
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_readline_readline" >&5
printf "%s\n" "$ac_cv_lib_readline_readline" >&6; }
if test "x$ac_cv_lib_readline_readline" = xyes
then :
printf "%s\n" "#define HAVE_LIBREADLINE 1" >>confdefs.h
LIBS="-lreadline $LIBS"
fi
ac_header= ac_cache=
for ac_item in $ac_header_c_list
do
@@ -3973,6 +4026,8 @@ fi
# The cast to long int works around a bug in the HP C Compiler
# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
# declarations like 'int a3[[(sizeof (unsigned char)) >= 0]];'.
@@ -4270,6 +4325,12 @@ then :
printf "%s\n" "#define HAVE_INOTIFY_INIT1 1" >>confdefs.h
fi
ac_fn_c_check_func "$LINENO" "readline" "ac_cv_func_readline"
if test "x$ac_cv_func_readline" = xyes
then :
printf "%s\n" "#define HAVE_READLINE 1" >>confdefs.h
fi
VERSION=$PACKAGE_VERSION
@@ -4787,7 +4848,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by remind $as_me 06.00.00, which was
This file was extended by remind $as_me 06.01.00, which was
generated by GNU Autoconf 2.72. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -4852,7 +4913,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
remind config.status 06.00.00
remind config.status 06.01.00
configured by $0, generated by GNU Autoconf 2.72,
with options \\"\$ac_cs_config\\"

View File

@@ -1,6 +1,6 @@
dnl Process this file with autoconf to produce a configure script.
AC_INIT(remind, 06.00.00, , , https://dianne.skoll.ca/projects/remind/)
AC_INIT(remind, 06.01.00, , , https://dianne.skoll.ca/projects/remind/)
AC_CONFIG_SRCDIR([src/queue.c])
cat <<'EOF'
@@ -30,7 +30,8 @@ AC_PATH_PROG([PERL], [perl])
dnl Checks for libraries.
AC_CHECK_LIB(m, sqrt)
AC_CHECK_HEADERS_ONCE([sys/time.h stdint.h])
AC_CHECK_LIB(readline, readline)
AC_CHECK_HEADERS_ONCE([sys/time.h stdint.h readline/readline.h readline/history.h])
dnl Integer sizes
AC_CHECK_SIZEOF(unsigned int)
@@ -84,7 +85,7 @@ if test "$?" != 0 ; then
echo "*** COULD NOT DETERMINE RELEASE DATE: docs/WHATSNEW is incorrect!"
exit 1
fi
AC_CHECK_FUNCS(strdup strcasecmp strncasecmp setenv unsetenv glob mbstowcs setlocale initgroups inotify_init1)
AC_CHECK_FUNCS(strdup strcasecmp strncasecmp setenv unsetenv glob mbstowcs setlocale initgroups inotify_init1 readline)
VERSION=$PACKAGE_VERSION
CONFIG_CMD="$0$ac_configure_args_raw"

View File

@@ -120,7 +120,7 @@
"PSFILE" "PUSH" "PUSH-FUNCS" "PUSH-VARS" "PUSH-OMIT-CONTEXT" "REM" "RETURN"
"RUN" "SATISFY" "SCAN" "SCANFROM" "SCHED" "SECOND" "SET"
"SKIP" "SPECIAL" "SYSINCLUDE" "TAG" "THIRD" "THROUGH" "TODO"
"TRANSLATE" "TRANS" "UNSET" "UNTIL" "WARN")
"TRANSLATE" "TRANS" "TZ" "UNSET" "UNTIL" "WARN")
#'(lambda (a b) (> (length a) (length b)))))
@@ -179,9 +179,9 @@
"realtoday" "rows" "sgn" "shell" "shellescape" "slide" "soleq"
"stdout" "strlen" "substr" "sunrise" "sunset" "time" "timepart"
"timezone" "today" "trig" "trigback" "trigbase" "trigcompletethrough" "trigdate" "trigdatetime"
"trigdelta" "trigduration" "trigeventduration" "trigeventstart"
"trigdelta" "trigduration" "trigeventduration" "trigeventstart" "trigeventstarttz"
"trigfrom" "trigger" "triginfo" "trigistodo" "trigmaxoverdue" "trigpriority" "trigrep"
"trigscanfrom" "trigtags" "trigtime" "trigtimedelta" "trigtimerep"
"trigscanfrom" "trigtags" "trigtime" "trigtimedelta" "trigtimerep" "trigtimetz" "trigtz"
"triguntil" "trigvalid" "typeof" "tzconvert" "upper" "utctolocal"
"value" "version" "weekno" "wkday" "wkdaynum" "year"
)

View File

@@ -1,5 +1,45 @@
CHANGES TO REMIND
* VERSION 6.1 Patch 0 - 2025-??-??
- MAJOR NEW FEATURE: remind: The TZ keyword lets you specify a time
zone for a REM command. All trigger calculations are performed in
the named time zone.
- BUG FIX: remind: If Remind was run interactively ("remind -c -" with
readline support enabled) it would not properly cache the input
file, but would keep asking for interactive input. This has been
fixed.
* VERSION 6.0 Patch 2 - 2025-09-01
- CHANGE: Default $ParseUntriggered to 0 instead of 1. The default
could cause spurious warnings such as "type mismatch" warnings for
untriggered reminders.
- TEST FIX: Explicitly set latitude and longitude in test files.
- BUG FIX: remind: Don't add lines to "readline" history unless they
are actually being read from standard input.
- BUG FIX: remind: On the command-line, make '-i$foo' behave the same
way as '-i$foo=0' as was documented in the man page.
- BUG FIX: remind: Clamp the output of trigger() to "1 January 1990 AT
00:00" if the UTC flag is used, even if it would actually produce a
result in 1989.
- MINOR FIX: remind: Avoid "unused result" compiler warning.
* VERSION 6.0 Patch 1 - 2025-08-19
- NEW FEATURE: remind: Add readline support if input is taken from stdin
(ie, "remind -"). This gives you full editing support if you run
Remind interactively.
- BUG FIX: remind: Fix buffer overflow in DUMPVARS command.
Thanks to Jochen Sprickerhof for helping me find this bug.
* VERSION 6.0 Patch 0 - 2025-08-18
- MAJOR NEW FEATURE: remind: Introduction of TODOs. These are similar

1
examples/alignment.rem Normal file → Executable file
View File

@@ -1,3 +1,4 @@
#!/usr/bin/env -S remind -@2
# Demo the columns() function
#
# Run as: remind -@2 alignment.rem

19
examples/tflag.rem Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env -S remind -@2
# This is a little Easter Egg that simply draws
# the Transgender Pride Flag. It's included in
# Remind because (1) the author is trans and (2)
# trans people are under attack in the USA and many
# other places. So this is a little show of support
# and resistance for trans people.
BANNER %
SET $AddBlankLines 0
REM SPECIAL COLOR 91 206 250 ██████████████████████████████████
REM SPECIAL COLOR 91 206 250 ██████████████████████████████████
REM SPECIAL COLOR 245 169 184 ██████████████████████████████████
REM SPECIAL COLOR 245 169 184 ██████████████████████████████████
REM SPECIAL COLOR 255 255 255 ██████████████████████████████████
REM SPECIAL COLOR 255 255 255 ██████████████████████████████████
REM SPECIAL COLOR 245 169 184 ██████████████████████████████████
REM SPECIAL COLOR 245 169 184 ██████████████████████████████████
REM SPECIAL COLOR 91 206 250 ██████████████████████████████████
REM SPECIAL COLOR 91 206 250 ██████████████████████████████████

View File

@@ -547,9 +547,17 @@ will produce the following \fBinfo\fR hash:
.PP
.RE
.TP
.B tz \fIzone\fR
If a non-empty TZ clause was present, this key will contain the time zone
name.
.TP
.B time \fIt\fR
If an AT clause was present, this key will contain the time of the AT clause
in minutes after midnight.
in minutes after midnight \fIin the system default time zone\fR.
.TP
.B time_in_tz \fIt\fR
If a TZ clause was present, this key will contain the time of the AT clause
in minutes after midnight \fIin the time zone specified by TZ\fR.
.TP
.B tdelta \fIn\fR
If a time delta (+n after an AT clause) was present, this key contains the
@@ -570,7 +578,12 @@ discussion of duration vs. event duration.
.TP
.B eventstart \fIdt\fR
If an AT clause was present, this key contains the event start time in
the format \fIYYYY-MM-DDTHH:MM\fR.
the format \fIYYYY-MM-DD\fRT\fIHH:MM\fR in the \fIsystem default time zone\fR.
.TP
.B eventstart_in_tz \fIdt\fR
If a TZ clause was present, this key containes the event start time in
the format \fIYYYY-MM-DD\fRT\fIHH:MM\fR in the \fItime zone specified
by TZ\fR.
.TP
.B back \fIn\fR
If the reminder contained a "back" clause (\-n or \-\-n), this key

View File

@@ -11,8 +11,9 @@ reminder or alarm can consist of a message sent to standard output, or
a program to be executed.
.PP
If \fIfilename\fR is specified as a single dash '-', then \fBRemind\fR
takes its input from standard input.
takes its input from standard input. In this case, if \fBRemind\fR
has been compiled against the GNU Readline library, it will use
Readline to give you an interactive line-editing interface.
.PP
If \fIfilename\fR happens to be a directory rather than a plain file,
then \fBRemind\fR reads all of the files (but not any subdirectories!)
@@ -692,6 +693,7 @@ Its syntax is:
[\fBSCANFROM\fR \fIscan_date\fR | \fBFROM\fR \fIstart_date\fR]
[\fBDURATION\fR \fIduration\fR]
[\fBTAG\fR \fItag\fR]
[\fBTZ\fR \fItimezone\fR]
[\fBINFO\fR "\fIinfo_string\fR"]
\fBMSG\fR | \fBMSF\fR | \fBRUN\fR | \fBCAL\fR | \fBSATISFY\fR |
\fBSPECIAL\fR \fIspecial\fR | \fBPS\fR | \fBPSFILE\fR
@@ -2205,6 +2207,81 @@ the first day of the month. The local \fBOMIT\fR keyword causes the
Finally, the \fBAFTER\fR keyword will keep moving the reminder forward
until it has passed any holidays specified with global \fBOMIT\fR
commands.
.PP
.SH TIMEZONE SUPPORT
.PP
The \fBREM\fR command supports an optional \fBTZ\fR keyword, which should
be followed by the \fIcase-sensitive\fR time zone name in which the
command is to be interpreted. Note that if you use the \fBTZ\fR keyword,
then you \fImust also\fR use an \fBAT\fR clause. Here are some examples:
.PP
.nf
REM Wednesday AT 14:00 TZ America/Toronto MSG 2PM Eastern (%2).
REM Wednesday AT 23:59 TZ America/Los_Angeles SATISFY [$Td == 13] MSG Foo %b %2.
.fi
.PP
Within a \fBSATISFY\fR clause and an \fBOMITFUNC\fR function, all
trigger functions and the trigger date are interpreted \fIin the time
zone specified in the \fBREM\fI command\fR. Outside the \fBREM\fR
command, however, trigger functions are adjusted to the local time
zone. If the local time zone is UTC and we feed \fBRemind\fR the
following file on 2025-09-04 UTC:
.PP
.nf
SET $AddBlankLines 0
BANNER %
REM Wednesday AT 14:00 TZ America/Toronto MSG 2PM Eastern (%2).
set a $T
set b $Tt
REM MSG a = [a], b = [b]
REM Wednesday AT 23:59 TZ America/Los_Angeles SATISFY [$Td == 13] MSG Foo %b %2.
set c $T
set d $Tt
REM MSG c = [c], d = [d]
.fi
.PP
Then the output is as follows:
.PP
.nf
a = 2025-09-10, b = 18:00
c = 2026-05-14, d = 06:59
.fi
.PP
That is because the trigger date of the first (Wednesday, 2025-09-10
at 14:00 Eastern time) is 2025-09-10 at 18:00 UTC. In the second case,
Wednesday, 13 May 2026 is the SATISFied trigger date, which is
adjusted to Thursday, 14 May 2026 at 06:59 UTC because of the
time zone adjustment.
.PP
If you use an invalid time zone name after the \fBTZ\fR keyword,
results are undefined. Unfortunately, \fBRemind\fR cannot diagnose
this error becase the C library \fBtzset()\fR function has no
error return.
.PP
In a reminder with the \fBTZ\fR keyword, OMIT dates are evaluated in
the specified time zone. Here's an example: Suppose the local time zone
is America/Toronto and you have this script:
.PP
.nf
# A Friday
OMIT 2025-09-05
# A Saturday
OMIT 2025-09-13
REM Saturday AT 01:00 TZ Europe/Amsterdam SKIP MSG Early Sat AM
.fi
.PP
On 2025-09-05 in the America/Toronto time zone, the reminder \fIwill\fR
trigger. Even though 2025-09-05 has been OMITted, the SKIP keyword
evaluates the date in the Europe/Amsterdam time zone, and 2025-09-06
(the trigger date) is \fInot\fR omitted.
.PP
Conversely, on 2025-09-12 in the America/Toronto time zone, the
reminder will \fInot\fR trigger. Even though 2025-09-12 is not OMITted,
2025-09-13 is, and that is the trigger date in the Europe/Amsterdam
time zone.
.PP
.SH THE DO, INCLUDE AND SYSINCLUDE COMMANDS
.PP
\fBRemind\fR allows you to include other files in your reminder script,
@@ -3194,25 +3271,26 @@ and ignore the attempt to set \fB$OnceFile\fR.
.RE
.TP
.B $ParseUntriggered
A flag indicating whether or not \fBRemind\fR should fully parse \fBREM\fR
statements that are not triggered. 0 means to skip parsing them and 1
(the default) means to parse them.
A flag indicating whether or not \fBRemind\fR should fully parse
\fBREM\fR statements that are not triggered. 0 (the default) means to
skip parsing them and 1 means to parse them.
.PP
.RS
For example, if we have the following \fBREM\fR statement:
.PP
.nf
REM 2020-01-01 MSG ["bad_expression" * 2]
REM 2020-01-01 MSG ["bad_expression" / 2]
.fi
.PP
Then by default, \fBRemind\fR will fully parse the line and issue
a "Type mismatch" error even if the reminder is not triggered. However,
if \fB$ParseUntriggered\fR is set to 0, then \fBRemind\fR will not
issue the error except on 2020-01-01, when the reminder is triggered.
Then if \fB$ParseUntriggered\fR is set to 1, \fBRemind\fR will fully
parse the line and issue a "Type mismatch" error even if the reminder
is not triggered. However, if \fB$ParseUntriggered\fR is set to 0,
the default, then \fBRemind\fR will not issue the error except on
2020-01-01, when the reminder is triggered.
.PP
Setting \fB$ParseUntriggered\fR to 0 may in some cases slightly
improve performance, at the risk of not catching errors until a
reminder is triggered.
Keeping \fB$ParseUntriggered\fR at 0 may slightly improve performance,
at the risk of not catching errors until a reminder is triggered. We recommend
leaving it set to 0.
.RE
.TP
.B $PrefixLineNo (read-only)
@@ -3271,10 +3349,10 @@ A directory path containing standard reminder scripts. Currently,
The value of \fB$SysInclude\fR is "@prefix@/share/remind" on
this installation.
.TP
.B $T (read-only, DATE type)
.B $T (read-only, DATE or INT type)
Equivalent to \fBtrigdate()\fR. (See BUILT-IN FUNCTIONS.)
.TP
.B $Tb (read-only, DATE type)
.B $Tb (read-only, DATE or INT type)
Equivalent to \fBtrigbase()\fR.
.TP
.B $Td (read-only)
@@ -3283,7 +3361,7 @@ Equivalent to \fBday(trigdate())\fR.
.B $Tm (read-only)
Equivalent to \fBmonnum(trigdate())\fR.
.TP
.B $Tu (read-only, DATE type)
.B $Tu (read-only, DATE or INT type)
Equivalent to \fBtriguntil()\fR.
.TP
.B $Tw (read-only)
@@ -3292,7 +3370,7 @@ Equivalent to \fBwkdaynum(trigdate())\fR.
.B $Ty (read-only)
Equivalent to \fByear(trigdate())\fR.
.TP
.B $Tt (read-only, TIME type)
.B $Tt (read-only, TIME or INT type)
Equivalent to \fBtrigtime()\fR.
.TP
.B $TimeSep (STRING type)
@@ -4701,6 +4779,11 @@ have an \fBAT\fR clause, returns the integer -1 (and differs from
\fBtrigdatetime()\fR in this respect.) See "MULTI-DAY EVENTS" for more
information.
.TP
.B trigeventstarttz()
Similar to \fBtrigeventstart()\fR but returns the DATETIME in the time
zone specified by a TZ clause, if one was present. If no TZ clause
was present, returns the same value as \fBtrigeventstart()\fR.
.TP
.B trigeventduration()
Returns a \fBTIME\fR representing the duration of the most recent
triggerable \fBREM\fR command that had an \fBAT\fR and a
@@ -4751,6 +4834,11 @@ command. Returns a positive integer N if the "repeat" is of the form
Similar to \fBtrigrep()\fR, but returns the repeat used in the \fBAT\fR clause
of a timed reminder.
.TP
.B trigtz()
If a \fBTZ\fR clause was used in the last \fBREM\fR or \fBIFTRIG\fR
command, returns the time zone name. Otherwise returns the empty
string.
.TP
.B trigduration()
Returns (as a TIME type) the \fBDURATION\fR parameter of a timed
reminder. If there is no \fBDURATION\fR parameter, returns the
@@ -4836,6 +4924,11 @@ Finally:
.PP
returns "30 November 1994 AT 22:00" for EST, which is 5 hours behind UTC.
The value for your time zone may differ.
.PP
\fBtrigger()\fR will \fInever\fR return a date earlier than "1 January 1990"
even if the UTC flag dictates that it should. So do not use it for reminders
before about 2 January 1990 in your local time zone. I do not anticipate this
restriction being a real problem.
.RE
.TP
.B trigtags()
@@ -4845,11 +4938,16 @@ were no TAGs. If there are multiple tags, they are each separated by
a single comma, not a comma and a space.
.TP
.B trigtime()
Returns the time of the last \fBREM\fR command with an \fBAT\fR
clause. If the last \fBREM\fR did not have an \fBAT\fR clause,
returns the integer 0. If a \fBREM\fR command has an \fBAT\fR clause
with a \fBDURATION\fR, then you can compute the end time as
\fBtrigtime() + trigduration()\fR.
Returns the time of the last \fBREM\fR command with an \fBAT\fR clause
\fIin the system default time zone\fR. If the last \fBREM\fR did not
have an \fBAT\fR clause, returns the integer 0. If a \fBREM\fR
command has an \fBAT\fR clause with a \fBDURATION\fR, then you can
compute the end time as \fBtrigtime() + trigduration()\fR.
.TP
.B trigtimetz()
Similar to \fBtrigtime()\fR but returns the time in the time zone specified
by a TZ clause, if one was present. If no TZ clause was present, returns
the same value as \fBtrigtime()\fR.
.TP
.B trigvalid()
Returns 1 if the value returned by \fBtrigdate()\fR is valid for the most
@@ -4866,11 +4964,13 @@ command can never be triggered:
Returns "STRING", "INT", "DATE", "TIME" or "DATETIME", depending on the type of \fIarg\fR.
.TP
.B tzconvert(q_datetime, s_srczone [,s_dstzone])
Converts \fBdatetime\fR from the time zone named by \fBsrczone\fR to the
time zone named by \fBdstzone\fR. If \fBdstzone\fR is omitted, the
default system time zone is used. The return value is a DATETIME. Time
zone names are system-dependent; consult your operating system for legal
values. Here is an example:
Converts \fBdatetime\fR from the time zone named by \fBsrczone\fR to
the time zone named by \fBdstzone\fR. If \fBsrczone\fR is the empty
string, then the default system time zone is used as the source zone.
If \fBdstzone\fR is omitted or is the empty string, the default system
time zone is used as the destination zone. The return value is a
DATETIME. Time zone names are system-dependent; consult your
operating system for legal values. Here is an example:
.PP
.nf
tzconvert('2007-07-08@01:14', "Canada/Eastern", "Canada/Pacific")

View File

@@ -40,9 +40,14 @@ REMINDOBJS= $(REMINDSRCS:.c=.o) $(XLATSRC:.c=.o)
all: remind rem2ps
test: all
test-basic: all
@sh ../tests/test-rem
test-tz: all
@sh ../tests/test-timezone-support
test: test-basic test-tz
.c.o:
@CC@ -c @CPPFLAGS@ @CFLAGS@ @DEFS@ $(CEXTRA) -DSYSDIR=$(datarootdir)/remind -I. -I$(srcdir) $<
@@ -117,6 +122,8 @@ beta-tgz:
gzip -f -v -9 remind-$(VERSION)-BETA-$(BETA).tar
gpg --detach-sign -u dianne@skoll.ca remind-$(VERSION)-BETA-$(BETA).tar.gz
distro-beta: beta-tgz
#---------------- Stuff after this added by "make depend" -----------------

View File

@@ -848,6 +848,9 @@ void ProduceCalendar(void)
if (CalMonths) {
FromDSE(DSEToday, &y, &m, &d);
DSEToday = DSE(y, m, 1);
LocalDSEToday = DSEToday;
LocalSysTime = 0;
SysTime = 0;
GenerateCalEntries(-1);
DidAMonth = 0;
if (PsCal == PSCAL_LEVEL3) {
@@ -862,8 +865,13 @@ void ProduceCalendar(void)
}
return;
} else {
if (MondayFirst) DSEToday -= (DSEToday%7);
else DSEToday -= ((DSEToday+1)%7);
if (MondayFirst) {
DSEToday -= (DSEToday%7);
LocalDSEToday -= (LocalDSEToday%7);
} else {
DSEToday -= ((DSEToday+1)%7);
LocalDSEToday -= ((LocalDSEToday+1)%7);
}
GenerateCalEntries(-1);
@@ -912,7 +920,7 @@ static void DoCalendarOneWeek(int nleft)
int y, m, d, done, i, l, wd;
char buf[128];
int LinesWritten = 0;
int OrigDse = DSEToday;
int OrigDse = LocalDSEToday;
InitMoonsAndShades();
/* Fill in the column entries */
@@ -920,6 +928,7 @@ static void DoCalendarOneWeek(int nleft)
ColToDay[i] = DayOf(DSEToday);
GenerateCalEntries(i);
DSEToday++;
LocalDSEToday++;
}
/* Figure out weekday of first column */
@@ -1205,6 +1214,7 @@ static int WriteCalendarRow(void)
GenerateCalEntries(i);
ColToDay[i] = DayOf(DSEToday);
DSEToday++;
LocalDSEToday++;
}
/* Output the entries */
@@ -1932,6 +1942,17 @@ static int DoCalRem(ParsePtr p, int col)
return r;
}
if (trig.tz != NULL && tim.ttime == NO_TIME) {
FreeTrig(&trig);
return E_TZ_NO_AT;
}
/* An empty string for time zone is just a missing time zone */
if (trig.tz != NULL && !*trig.tz) {
free( (void *) trig.tz);
trig.tz = NULL;
}
if (trig.typ == MSG_TYPE ||
trig.typ == CAL_TYPE ||
trig.typ == MSF_TYPE) {
@@ -1951,7 +1972,9 @@ static int DoCalRem(ParsePtr p, int col)
return E_EOLN;
}
if (trig.typ == SAT_TYPE) {
EnterTimezone(trig.tz);
r=DoSatRemind(&trig, &tim, p);
ExitTimezone(trig.tz);
if (r) {
if (r == E_CANT_TRIG && trig.maybe_uncomputable) {
r = OK;
@@ -2019,7 +2042,9 @@ static int DoCalRem(ParsePtr p, int col)
}
} else {
/* Calculate the trigger date */
EnterTimezone(trig.tz);
dse = ComputeTrigger(get_scanfrom(&trig), &trig, &tim, &r, 1);
ExitTimezone(trig.tz);
if (r) {
if (r == E_CANT_TRIG && trig.maybe_uncomputable) {
r = OK;
@@ -2029,6 +2054,9 @@ static int DoCalRem(ParsePtr p, int col)
}
}
/* Adjust trigger date/time to time zone */
dse = AdjustTriggerForTimeZone(&trig, dse, &tim);
/* Add to global OMITs if so indicated */
if (trig.addomit) {
r = AddGlobalOmit(dse);
@@ -2299,6 +2327,9 @@ static int DoCalRem(ParsePtr p, int col)
e->nonconst_expr = nonconst_expr;
e->if_depth = get_if_pointer() - get_base_if_pointer();
e->trig = trig;
if (e->trig.tz) {
e->trig.tz = StrDup(e->trig.tz);
}
e->tt = tim;
#ifdef REM_USE_WCHAR
e->wc_pos = NULL;
@@ -2395,6 +2426,9 @@ static void WriteSimpleEntryProtocol1(CalEntry const *e)
void WriteJSONTimeTrigger(TimeTrig const *tt)
{
PrintJSONKeyPairTime("time", tt->ttime);
if (tt->ttime != tt->ttime_orig) {
PrintJSONKeyPairTime("time_in_tz", tt->ttime_orig);
}
PrintJSONKeyPairTime("nexttime", tt->nexttime);
PrintJSONKeyPairInt("tdelta", tt->delta);
PrintJSONKeyPairInt("trep", tt->rep);
@@ -2526,6 +2560,10 @@ void WriteJSONTrigger(Trigger const *t, int include_tags)
PrintJSONKeyPairDate("from", t->from);
PrintJSONKeyPairInt("priority", t->priority);
PrintJSONKeyPairDateTime("eventstart", t->eventstart);
if (t->eventstart_orig != NO_TIME &&
t->eventstart_orig != t->eventstart) {
PrintJSONKeyPairDateTime("eventstart_in_tz", t->eventstart_orig);
}
if (t->eventduration != NO_TIME) {
PrintJSONKeyPairInt("eventduration", t->eventduration);
}
@@ -2547,6 +2585,7 @@ void WriteJSONTrigger(Trigger const *t, int include_tags)
}
PrintJSONKeyPairString("tags", DBufValue(&(t->tags)));
}
PrintJSONKeyPairString("tz", t->tz);
}
static void WriteSimpleEntryProtocol2(CalEntry *e)
@@ -2569,6 +2608,9 @@ static void WriteSimpleEntryProtocol2(CalEntry *e)
}
if (e->time != NO_TIME) {
PrintJSONKeyPairInt("time", e->time);
if (e->tt.ttime_orig != e->tt.ttime) {
PrintJSONKeyPairInt("time_in_tz", e->tt.ttime_orig);
}
if (e->tt.delta) {
PrintJSONKeyPairInt("tdelta", e->tt.delta);
}

View File

@@ -45,6 +45,9 @@
/* Define to 1 if you have the <stdlib.h> header file. */
#undef HAVE_STDLIB_H
/* Define to 1 if you have the `readline' function. */
#undef HAVE_READLINE
/* Define to 1 if you have the `strcasecmp' function. */
#undef HAVE_STRCASECMP
@@ -81,6 +84,12 @@
/* Define to 1 if you have the <wctype.h> header file. */
#undef HAVE_WCTYPE_H
/* Define to 1 if you have the <readline/history.h> header file. */
#undef HAVE_READLINE_HISTORY_H
/* Define to 1 if you have the <readline/readline.h> header file. */
#undef HAVE_READLINE_READLINE_H
/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT

View File

@@ -159,3 +159,15 @@
#else
#undef REM_USE_WCHAR
#endif
#if defined(HAVE_READLINE) && defined(HAVE_READLINE_READLINE_H)
#define USE_READLINE 1
#else
#undef USE_READLINE
#endif
#if defined(HAVE_READLINE) && defined(HAVE_READLINE_READLINE_H) && defined(HAVE_READLINE_HISTORY_H)
#define USE_READLINE_HISTORY 1
#else
#undef USE_READLINE_HISTORY
#endif

View File

@@ -22,8 +22,8 @@
/* The default values are initially set to the city hall in Ottawa, */
/* Ontario, Canada. */
/*---------------------------------------------------------------------*/
#define DEFAULT_LATITUDE 45.420556
#define DEFAULT_LONGITUDE -75.689722
#define DEFAULT_LATITUDE 45.42055555555555
#define DEFAULT_LONGITUDE -75.68944444444445
#define LOCATION "Ottawa"
/*---------------------------------------------------------------------*/
@@ -159,3 +159,15 @@
#else
#undef REM_USE_WCHAR
#endif
#if defined(HAVE_READLINE) && defined(HAVE_READLINE_READLINE_H)
#define USE_READLINE 1
#else
#undef USE_READLINE
#endif
#if defined(HAVE_READLINE) && defined(HAVE_READLINE_READLINE_H) && defined(HAVE_READLINE_HISTORY_H)
#define USE_READLINE_HISTORY 1
#else
#undef USE_READLINE_HISTORY
#endif

View File

@@ -18,6 +18,7 @@
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "types.h"
#include "globals.h"
@@ -32,6 +33,126 @@ static int ParseUntil (ParsePtr s, Trigger *t, int type);
static int ShouldTriggerBasedOnWarn (Trigger const *t, int dse, int *err);
static int ComputeTrigDuration(TimeTrig const *t);
static int CalledEnterTimezone = 0;
int AdjustTriggerForTimeZone(Trigger *trig, int dse, TimeTrig *tim)
{
int y, m, d, hour, minute;
int r;
struct tm tm;
if (!trig->tz || dse < 0) {
/* Already local time or did not compute trigger date - no adjustments needed */
return dse;
}
FromDSE(dse, &y, &m, &d);
hour = tim->ttime_orig / 60;
minute = tim->ttime_orig % 60;
r = tz_convert(y, m, d, hour, minute, trig->tz, LocalTimeZone, &tm);
if (r != 1) {
Wprint(tr("Error adjusting trigger to local time zone"));
return dse;
}
dse = DSE(tm.tm_year+1900, tm.tm_mon, tm.tm_mday);
tim->ttime = tm.tm_hour * 60 + tm.tm_min;
/* Adjust eventstart also */
trig->eventstart = dse * MINUTES_PER_DAY + tim->ttime;
SaveAllTriggerInfo(trig, tim, dse, tim->ttime, 1);
if (DebugFlag & DB_PRTTRIG) {
fprintf(ErrFp, "%s(%s): Trig(tz_adj %s) = %s, %d %s, %d AT %02d:%02d",
GetCurrentFilename(), line_range(LineNoStart, LineNo), trig->tz,
get_day_name(dse % 7), tm.tm_mday, get_month_name(tm.tm_mon),
1900 + tm.tm_year, tim->ttime / 60, tim->ttime % 60);
if (tim->duration != NO_TIME) {
fprintf(ErrFp, " DURATION %02d:%02d",
(tim->duration / 60),
(tim->duration % 60));
}
fprintf(ErrFp, "\n");
}
return dse;
}
void ExitTimezone(char const *tz)
{
if (!CalledEnterTimezone) {
fprintf(stderr, "ExitTimezone called without EnterTimezone!!!\n");
abort();
}
CalledEnterTimezone = 0;
if (!tz || !*tz) {
/* Nothing to do */
return;
}
/* Revert to our local time zone */
(void) tz_set_tz(LocalTimeZone);
DSEToday = LocalDSEToday;
SysTime = LocalSysTime;
FromDSE(DSEToday, &CurYear, &CurMon, &CurDay);
if (DebugFlag & DB_SWITCH_ZONE) {
fprintf(stderr, "TZ exit %s: %04d-%02d-%02d %02d:%02d\n", tz,
CurYear, CurMon+1, CurDay, SysTime / 3600, (SysTime/60) % 60);
}
return;
}
void EnterTimezone(char const *tz)
{
struct tm tm;
int y, m, d;
time_t t;
if (CalledEnterTimezone) {
fprintf(stderr, "EnterTimezone called twice in a row!!!\n");
abort();
}
CalledEnterTimezone = 1;
if (!tz || !*tz) {
/* Stay in local timezone */
return;
}
FromDSE(LocalDSEToday, &y, &m, &d);
tm.tm_sec = 0;
tm.tm_min = (LocalSysTime/60) % 60;
tm.tm_hour = LocalSysTime / 3600;
tm.tm_mday = d;
tm.tm_mon = m;
tm.tm_year = y - 1900;
tm.tm_wday = 0; /* Ignored by mktime */
tm.tm_yday = 0; /* Ignored by mktime */
tm.tm_isdst = -1; /* Information not available */
t = mktime(&tm); /* Convert local time to seconds */
/* Set target timezone */
(void) tz_set_tz(tz);
/* Update our variables */
(void) localtime_r(&t, &tm);
SysTime = tm.tm_min * 60 + (tm.tm_hour * 3600);
CurDay = tm.tm_mday;
CurMon = tm.tm_mon;
CurYear = tm.tm_year + 1900;
/* Adjust DSEToday back by a day if midnight in our time zone requires it */
if (SysTime < LocalSysTime) {
DSEToday--;
FromDSE(DSEToday, &CurYear, &CurMon, &CurDay);
}
if (DebugFlag & DB_SWITCH_ZONE) {
fprintf(stderr, "TZ enter %s: %04d-%02d-%02d %02d:%02d\n", tz,
CurYear, CurMon+1, CurDay, SysTime / 3600, (SysTime/60) % 60);
}
}
void remove_trailing_newlines(DynamicBuffer *buf)
{
char *s = (char *) DBufValue(buf) + DBufLen(buf) - 1;
@@ -287,6 +408,18 @@ int DoRem(ParsePtr p)
return r;
}
if (trig.tz != NULL && tim.ttime == NO_TIME) {
PurgeEchoLine("%s\n", CurLine);
FreeTrig(&trig);
return E_TZ_NO_AT;
}
/* An empty string for time zone is just a missing time zone */
if (trig.tz != NULL && !*trig.tz) {
free( (void *) trig.tz);
trig.tz = NULL;
}
if (trig.complete_through != NO_DATE && !trig.is_todo) {
PurgeEchoLine("%s\n", CurLine);
FreeTrig(&trig);
@@ -312,7 +445,9 @@ int DoRem(ParsePtr p)
PurgeEchoLine("%s\n", "#!P: Cannot purge SATISFY-type reminders");
}
PurgeEchoLine("%s\n", CurLine);
EnterTimezone(trig.tz);
r=DoSatRemind(&trig, &tim, p);
ExitTimezone(trig.tz);
if (r) {
if (r == E_CANT_TRIG && trig.maybe_uncomputable) {
r = OK;
@@ -372,7 +507,9 @@ int DoRem(ParsePtr p)
}
} else {
/* Calculate the trigger date */
EnterTimezone(trig.tz);
dse = ComputeTrigger(get_scanfrom(&trig), &trig, &tim, &r, 1);
ExitTimezone(trig.tz);
if (r) {
if (PurgeMode) {
if (!Hush) {
@@ -388,6 +525,9 @@ int DoRem(ParsePtr p)
}
}
/* Adjust trigger date/time to time zone */
dse = AdjustTriggerForTimeZone(&trig, dse, &tim);
/* Add to global OMITs if so indicated */
if (trig.addomit) {
r = AddGlobalOmit(dse);
@@ -437,6 +577,7 @@ int DoRem(ParsePtr p)
}
r = OK;
if (ShouldTriggerReminder(&trig, &tim, dse, &err)) {
/* Filter unwanted events/todos */
if (todo_filtered(&trig)) {
@@ -479,6 +620,9 @@ int DoRem(ParsePtr p)
}
if (tim.ttime != NO_TIME) {
PrintJSONKeyPairInt("time", tim.ttime);
if (tim.ttime_orig != tim.ttime) {
PrintJSONKeyPairInt("time_in_tz", tim.ttime_orig);
}
}
if (p->nonconst_expr) {
PrintJSONKeyPairInt("nonconst_expr", 1);
@@ -664,11 +808,13 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim)
trig->omitfunc[0] = 0;
trig->duration_days = 0;
trig->eventstart = NO_TIME;
trig->eventstart_orig = NO_TIME;
trig->eventduration = NO_TIME;
trig->maybe_uncomputable = 0;
DBufInit(&(trig->tags));
trig->passthru[0] = 0;
tim->ttime = NO_TIME;
tim->ttime_orig = NO_TIME;
tim->delta = DefaultTDelta;
tim->rep = NO_REP;
tim->duration = NO_TIME;
@@ -678,6 +824,7 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim)
trig->complete_through = NO_DATE;
trig->adj_for_last = 0;
trig->infos = NULL;
trig->tz = NULL;
int parsing = 1;
while(parsing) {
@@ -740,6 +887,7 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim)
trig->m = m;
trig->d = d;
tim->ttime = (tok.val % MINUTES_PER_DAY);
tim->ttime_orig = tim->ttime;
break;
case T_WkDay:
@@ -795,6 +943,7 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim)
DBufFree(&buf);
if (tim->ttime != NO_TIME) return E_TIME_TWICE;
tim->ttime = tok.val;
tim->ttime_orig = tok.val;
r = ParseTimeTrig(s, tim);
if (r) return r;
trig->duration_days = ComputeTrigDuration(tim);
@@ -951,6 +1100,22 @@ int ParseRem(ParsePtr s, Trigger *trig, TimeTrig *tim)
DBufFree(&buf);
break;
case T_Tz:
if (trig->tz) {
return E_TZ_SPECIFIED_TWICE;
}
r = ParseTokenOrQuotedString(s, &buf);
if (r != OK) {
return r;
}
trig->tz = StrDup(DBufValue(&buf));
if (!trig->tz) {
return E_NO_MEM;
}
DBufFree(&buf);
break;
case T_Info:
r = ParseQuotedString(s, &buf);
if (r != OK) {
@@ -1097,6 +1262,7 @@ static int ParseTimeTrig(ParsePtr s, TimeTrig *tim)
DBufFree(&buf);
if (tim->ttime != NO_TIME) return E_TIME_TWICE;
tim->ttime = tok.val;
tim->ttime_orig = tok.val;
break;
case T_Delta:
@@ -1817,6 +1983,7 @@ int DoSatRemind(Trigger *trig, TimeTrig *tt, ParsePtr p)
} else if (dse == start) {
if (tt->ttime != NO_TIME) {
trig->eventstart = MINUTES_PER_DAY * r + tt->ttime;
trig->eventstart_orig = trig->eventstart;
if (tt->duration != NO_TIME) {
trig->eventduration = tt->duration;
}

View File

@@ -132,6 +132,8 @@
#define E_COMPLETE_WITHOUT_TODO 108
#define E_MAX_OVERDUE_TWICE 109
#define E_MAX_OVERDUE_WITHOUT_TODO 110
#define E_TZ_SPECIFIED_TWICE 111
#define E_TZ_NO_AT 112
#ifdef MK_GLOBALS
#undef EXTERN
@@ -261,6 +263,8 @@ EXTERN char *ErrMsg[]
/* E_COMPLETE_WITHOUT_TODO */ "COMPLETE-THROUGH specified without TODO",
/* E_MAX_OVERDUE_TWICE */ "MAX-OVERDUE specified twice",
/* E_MAX_OVERDUE_WITHOUT_TODO */ "MAX-OVERDUE specified without TODO",
/* E_TZ_SPECIFIED_TWICE */ "TZ specified twice",
/* E_TZ_NO_AT */ "TZ specified for non-timed reminder",
}
#endif /* MK_GLOBALS */
;

View File

@@ -41,6 +41,13 @@
#include "globals.h"
#include "err.h"
#ifdef USE_READLINE
#include <readline/readline.h>
#endif
#ifdef USE_READLINE_HISTORY
#include <readline/history.h>
#endif
/* Convenient macros for closing files */
#define FCLOSE(fp) ((((fp)!=stdin)) ? (fclose(fp),(fp)=NULL) : ((fp)=NULL))
@@ -134,6 +141,9 @@ void InitFiles(void)
fprintf(ErrFp, "Unable to initialize filename hash table: Out of memory. Exiting.\n");
exit(1);
}
#ifdef USE_READLINE_HISTORY
using_history();
#endif
}
void SetCurrentFilename(char const *fname)
@@ -268,6 +278,8 @@ int ReadLine(void)
return ReadLineFromFile(0);
}
#define IS_INTERACTIVE() (fileno(fp) == STDIN_FILENO && isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
/***************************************************************/
/* */
/* ReadLineFromFile */
@@ -280,6 +292,11 @@ static int ReadLineFromFile(int use_pclose)
int l;
char copy_buffer[4096];
size_t n;
int force_eof = 0;
#ifdef USE_READLINE
int read_some = 0;
#endif
DynamicBuffer buf;
@@ -287,18 +304,56 @@ static int ReadLineFromFile(int use_pclose)
DBufFree(&LineBuffer);
LineNoStart = LineNo+1;
if (!fp) {
return E_EOF;
}
while(fp) {
#ifdef USE_READLINE
if (IS_INTERACTIVE()) {
char *lin;
if (should_ignore_line()) {
if (read_some) {
lin = readline("Rem...? ");
} else {
lin = readline("Remind? ");
}
} else {
if (read_some) {
lin = readline("Rem...> ");
} else {
lin = readline("Remind> ");
}
}
if (lin) {
read_some = 1;
DBufFree(&buf);
if (DBufPuts(&buf, lin) != OK) {
free(lin);
DBufFree(&buf);
return E_NO_MEM;
}
free(lin);
force_eof = 0;
} else {
force_eof = 1;
}
} else {
#endif
if (DBufGets(&buf, fp) != OK) {
DBufFree(&LineBuffer);
return E_NO_MEM;
}
#ifdef USE_READLINE
}
#endif
LineNo++;
if (ferror(fp)) {
DBufFree(&buf);
DBufFree(&LineBuffer);
return E_IO_ERR;
}
if (feof(fp)) {
if (feof(fp) || force_eof) {
if (use_pclose) {
PCLOSE(fp);
} else {
@@ -360,6 +415,11 @@ static int ReadLineFromFile(int use_pclose)
CurLine = DBufValue(&LineBuffer);
}
#ifdef USE_READLINE_HISTORY
if (fp && IS_INTERACTIVE()) {
add_history(CurLine);
}
#endif
got_a_fresh_line();
clear_callstack();
if (DebugFlag & DB_ECHO_LINE) OutputLine(ErrFp);
@@ -398,6 +458,7 @@ static int OpenFile(char const *fname)
fprintf(ErrFp, tr("Reading `%s': Found in cache"), fname);
fprintf(ErrFp, "\n");
}
fp = NULL;
CLine = h->cache;
SetCurrentFilename(fname);
LineNo = 0;
@@ -615,6 +676,7 @@ static int PopFile(void)
IncludeStruct *i;
pop_excess_ifs(FileName);
fp = NULL;
if (!IStackPtr) return E_EOF;
i = &IStack[IStackPtr-1];
@@ -627,17 +689,16 @@ static int PopFile(void)
RunDisabled = oldRunDisabled;
}
if (IStackPtr <= 1) {
if (TopLevel()) {
IStackPtr = 0;
return E_EOF;
}
IStackPtr--;
LineNo = i->LineNo;
LineNoStart = i->LineNoStart;
set_base_if_pointer(i->base_if_pointer);
CLine = i->CLine;
fp = NULL;
SetCurrentFilename(i->filename);
if (!i->ownedByMe) {
RunDisabled |= RUN_NOTOWNER;

View File

@@ -181,6 +181,7 @@ static int FTrigduration (func_info *);
static int FTriginfo (func_info *);
static int FTrigeventduration(func_info *);
static int FTrigeventstart (func_info *);
static int FTrigeventstarttz (func_info *);
static int FTrigfrom (func_info *);
static int FTrigger (func_info *);
static int FTrigistodo (func_info *);
@@ -190,8 +191,10 @@ static int FTrigrep (func_info *);
static int FTrigscanfrom (func_info *);
static int FTrigtags (func_info *);
static int FTrigtime (func_info *);
static int FTrigtimetz (func_info *);
static int FTrigtimedelta (func_info *);
static int FTrigtimerep (func_info *);
static int FTrigtz (func_info *);
static int FTriguntil (func_info *);
static int FTrigvalid (func_info *);
static int FTypeof (func_info *);
@@ -206,7 +209,6 @@ static int FWkdaynum (func_info *);
static int FYear (func_info *);
static int SunStuff (int rise, double cosz, int dse);
static int tz_set_tz (char const *tz);
/* Caches for extracting months, days, years from dates - may
improve performance slightly. */
@@ -357,6 +359,7 @@ BuiltinFunc Func[] = {
{ "trigduration", 0, 0, 0, FTrigduration, NULL },
{ "trigeventduration", 0, 0, 0, FTrigeventduration, NULL },
{ "trigeventstart", 0, 0, 0, FTrigeventstart, NULL },
{ "trigeventstarttz", 0, 0, 0, FTrigeventstarttz, NULL },
{ "trigfrom", 0, 0, 0, FTrigfrom, NULL },
{ "trigger", 1, 3, 0, FTrigger, NULL },
{ "triginfo", 1, 1, 0, FTriginfo, NULL },
@@ -369,6 +372,8 @@ BuiltinFunc Func[] = {
{ "trigtime", 0, 0, 0, FTrigtime, NULL },
{ "trigtimedelta",0, 0, 0, FTrigtimedelta, NULL },
{ "trigtimerep", 0, 0, 0, FTrigtimerep, NULL },
{ "trigtimetz", 0, 0, 0, FTrigtimetz, NULL },
{ "trigtz", 0, 0, 0, FTrigtz, NULL },
{ "triguntil", 0, 0, 0, FTriguntil, NULL },
{ "trigvalid", 0, 0, 0, FTrigvalid, NULL },
{ "typeof", 1, 1, 1, FTypeof, NULL },
@@ -1956,6 +1961,14 @@ static int FTrigtimerep(func_info *info)
return OK;
}
static int FTrigtz(func_info *info)
{
if (!LastTrigger.tz) {
return RetStrVal("", info);
}
return RetStrVal(LastTrigger.tz, info);
}
static int FTrigeventduration(func_info *info)
{
if (LastTrigger.eventduration == NO_TIME) {
@@ -1991,6 +2004,22 @@ static int FTrigeventstart(func_info *info)
return OK;
}
static int FTrigeventstarttz(func_info *info)
{
if (LastTrigger.eventstart == NO_TIME) {
RetVal.type = INT_TYPE;
RETVAL = -1;
} else {
RetVal.type = DATETIME_TYPE;
if (LastTrigger.eventstart_orig != NO_TIME) {
RETVAL = LastTrigger.eventstart_orig;
} else {
RETVAL = LastTrigger.eventstart;
}
}
return OK;
}
static int FTrigduration(func_info *info)
{
if (LastTimeTrig.duration == NO_TIME) {
@@ -2077,6 +2106,22 @@ static int FTrigtime(func_info *info)
return OK;
}
static int FTrigtimetz(func_info *info)
{
if (LastTriggerTime != NO_TIME) {
RetVal.type = TIME_TYPE;
if (LastTimeTrig.ttime_orig != NO_TIME) {
RETVAL = LastTimeTrig.ttime_orig;
} else {
RETVAL = LastTriggerTime;
}
} else {
RetVal.type = INT_TYPE;
RETVAL = 0;
}
return OK;
}
static int FTrigdatetime(func_info *info)
{
if (!LastTrigValid) {
@@ -2179,6 +2224,10 @@ static int FTrigger(func_info *info)
tim = ARGV(1);
if (ARGV(2)) {
UTCToLocal(date, tim, &date, &tim);
if (date < 0) {
date = 0;
tim = 0;
}
}
} else if (Nargs > 1) {
/* Date Time */
@@ -2193,6 +2242,10 @@ static int FTrigger(func_info *info)
ASSERT_TYPE(1, INT_TYPE);
if (ARGV(1)) {
UTCToLocal(date, tim, &date, &tim);
if (date < 0) {
date = 0;
tim = 0;
}
}
}
}
@@ -3871,7 +3924,7 @@ static void unsetenv(char const *varname)
/* Conversion between different timezones. */
/* */
/***************************************************************/
static int tz_set_tz(char const *tz)
int tz_set_tz(char const *tz)
{
int r;
if (tz == NULL) {
@@ -3884,7 +3937,7 @@ static int tz_set_tz(char const *tz)
return r;
}
static int tz_convert(int year, int month, int day,
int tz_convert(int year, int month, int day,
int hour, int minute,
char const *src_tz, char const *tgt_tz,
struct tm *tm)
@@ -3911,9 +3964,12 @@ static int tz_convert(int year, int month, int day,
old_tz = StrDup(old_tz);
if (!old_tz) return E_NO_MEM;
}
if (tgt_tz == NULL) {
if (tgt_tz == NULL || !*tgt_tz) {
tgt_tz = old_tz;
}
if (src_tz == NULL || !*src_tz) {
src_tz = old_tz;
}
/* set source TZ */
r = tz_set_tz(src_tz);
@@ -4209,25 +4265,32 @@ FEvalTrig(func_info *info)
DestroyParser(&p);
return r;
}
if (trig.tz != NULL && tim.ttime == NO_TIME) {
FreeTrig(&trig);
return E_TZ_NO_AT;
}
if (trig.typ != NO_TYPE) {
DestroyParser(&p);
FreeTrig(&trig);
return E_PARSE_ERR;
}
if (scanfrom == NO_DATE) {
EnterTimezone(trig.tz);
dse = ComputeTrigger(get_scanfrom(&trig), &trig, &tim, &r, 0);
ExitTimezone(trig.tz);
} else {
/* Hokey... */
if (get_scanfrom(&trig) != DSEToday) {
Wprint(tr("Warning: SCANFROM is ignored in two-argument form of evaltrig()"));
}
EnterTimezone(trig.tz);
dse = ComputeTrigger(scanfrom, &trig, &tim, &r, 0);
ExitTimezone(trig.tz);
}
if (r == E_CANT_TRIG && trig.maybe_uncomputable) {
r = 0;
dse = -1;
}
FreeTrig(&trig);
DestroyParser(&p);
if (r) return r;
if (dse < 0) {
@@ -4237,9 +4300,11 @@ FEvalTrig(func_info *info)
RetVal.type = DATE_TYPE;
RETVAL = dse;
} else {
dse = AdjustTriggerForTimeZone(&trig, dse, &tim);
RetVal.type = DATETIME_TYPE;
RETVAL = (MINUTES_PER_DAY * dse) + tim.ttime;
}
FreeTrig(&trig);
return OK;
}
@@ -4268,6 +4333,10 @@ FMultiTrig(func_info *info)
DestroyParser(&p);
return r;
}
if (trig.tz != NULL && tim.ttime == NO_TIME) {
FreeTrig(&trig);
return E_TZ_NO_AT;
}
if (trig.typ != NO_TYPE) {
DestroyParser(&p);
FreeTrig(&trig);
@@ -4278,6 +4347,9 @@ FMultiTrig(func_info *info)
return E_PARSE_ERR;
}
dse = ComputeTrigger(get_scanfrom(&trig), &trig, &tim, &r, 0);
/* multitrig only does untimed reminders, so no need to worry
about time zones */
DestroyParser(&p);
if (r != E_CANT_TRIG) {
@@ -4325,18 +4397,25 @@ FTrig(func_info *info)
DestroyParser(&p);
return r;
}
if (trig.tz != NULL && tim.ttime == NO_TIME) {
FreeTrig(&trig);
return E_TZ_NO_AT;
}
if (trig.typ != NO_TYPE) {
DestroyParser(&p);
FreeTrig(&trig);
return E_PARSE_ERR;
}
EnterTimezone(trig.tz);
dse = ComputeTrigger(get_scanfrom(&trig), &trig, &tim, &r, 0);
ExitTimezone(trig.tz);
DestroyParser(&p);
if (r == E_CANT_TRIG) {
FreeTrig(&trig);
continue;
}
dse = AdjustTriggerForTimeZone(&trig, dse, &tim);
if (ShouldTriggerReminder(&trig, &tim, dse, &r)) {
LastTrig = dse;
RETVAL = dse;

View File

@@ -47,6 +47,7 @@ EXTERN FILE *ErrFp;
EXTERN int DSEToday;
EXTERN int RealToday;
EXTERN int LocalDSEToday;
EXTERN int CurDay;
EXTERN int CurMon;
EXTERN int CurYear;
@@ -98,9 +99,11 @@ EXTERN INIT( int SortByPrio, SORT_NONE);
EXTERN INIT( int UntimedBeforeTimed, 0);
EXTERN INIT( int DefaultPrio, NO_PRIORITY);
EXTERN INIT( int SysTime, -1);
EXTERN INIT( int ParseUntriggered, 1);
EXTERN INIT( int LocalSysTime, -1);
EXTERN INIT( int ParseUntriggered, 0);
EXTERN char const *InitialFile;
EXTERN char const *LocalTimeZone;
EXTERN int FileAccessDate;
EXTERN INIT( int WeekdayOmits, 0);

View File

@@ -177,8 +177,25 @@ void InitRemind(int argc, char const *argv[])
int dse;
int ttyfd;
/* Make sure remind is not installed set-uid or set-gid */
if (getgid() != getegid() ||
getuid() != geteuid()) {
fprintf(ErrFp, "\nRemind should not be installed set-uid or set-gid.\nCHECK YOUR SYSTEM SECURITY.\n");
exit(EXIT_FAILURE);
}
dse = NO_DATE;
/* Initialize local time zone */
LocalTimeZone = getenv("TZ");
if (LocalTimeZone) {
LocalTimeZone = StrDup(LocalTimeZone);
if (!LocalTimeZone) {
fprintf(stderr, "Out of memory!\n");
exit(1);
}
}
/* Initialize variable hash table */
InitVars();
@@ -203,13 +220,6 @@ void InitRemind(int argc, char const *argv[])
InitDedupeTable();
/* Make sure remind is not installed set-uid or set-gid */
if (getgid() != getegid() ||
getuid() != geteuid()) {
fprintf(ErrFp, "\nRemind should not be installed set-uid or set-gid.\nCHECK YOUR SYSTEM SECURITY.\n");
exit(EXIT_FAILURE);
}
y = NO_YR;
m = NO_MON;
d = NO_DAY;
@@ -221,7 +231,9 @@ void InitRemind(int argc, char const *argv[])
fprintf(ErrFp, "\n");
exit(EXIT_FAILURE);
}
LocalSysTime = SystemTime(0);
DSEToday = RealToday;
LocalDSEToday = DSEToday;
FromDSE(DSEToday, &CurYear, &CurMon, &CurDay);
/* Initialize Latitude and Longitude */
@@ -646,6 +658,7 @@ void InitRemind(int argc, char const *argv[])
case 'q': case 'Q': DebugFlag |= DB_TRANSLATE; break;
case 'n': case 'N': DebugFlag |= DB_NONCONST; break;
case 'u': case 'U': DebugFlag |= DB_UNUSED_VARS; break;
case 'z': case 'Z': DebugFlag |= DB_SWITCH_ZONE; break;
default:
fprintf(ErrFp, GetErr(M_BAD_DB_FLAG), *(arg-1));
fprintf(ErrFp, "\n");
@@ -711,6 +724,7 @@ void InitRemind(int argc, char const *argv[])
if (SysTime != -1L) Usage();
else {
SysTime = (long) tok.val * 60L;
LocalSysTime = SysTime;
DontQueue = 1;
Daemon = 0;
}
@@ -720,6 +734,7 @@ void InitRemind(int argc, char const *argv[])
if (SysTime != -1L) Usage();
if (m != NO_MON || d != NO_DAY || y != NO_YR || dse != NO_DATE) Usage();
SysTime = (tok.val % MINUTES_PER_DAY) * 60;
LocalSysTime = SysTime;
DontQueue = 1;
Daemon = 0;
dse = tok.val / MINUTES_PER_DAY;
@@ -790,6 +805,7 @@ void InitRemind(int argc, char const *argv[])
fprintf(ErrFp, "%s", BadDate);
Usage();
}
LocalDSEToday = DSEToday;
CurYear = y;
CurMon = m;
CurDay = d;
@@ -1017,17 +1033,15 @@ static void InitializeVar(char const *str)
return;
}
if (!*str) {
/* Setting a system var does require =expr on the commandline */
if (*varname == '$') {
fprintf(ErrFp, GetErr(M_I_OPTION), GetErr(E_MISS_EQ));
fprintf(ErrFp, "\n");
return;
}
val.type = INT_TYPE;
val.v.val = 0;
r = SetVar(varname, &val, 1);
if (!r) {
r = PreserveVar(varname);
if (*varname == '$') {
r = SetSysVar(varname+1, &val);
} else {
r = SetVar(varname, &val, 1);
if (!r) {
r = PreserveVar(varname);
}
}
if (r) {
fprintf(ErrFp, GetErr(M_I_OPTION), GetErr(r));
@@ -1146,6 +1160,7 @@ ProcessLongOption(char const *arg)
/* Update RealToday because of TestMode */
RealToday = SystemDate(&CurYear, &CurMon, &CurDay);
DSEToday = RealToday;
LocalDSEToday = DSEToday;
FromDSE(DSEToday, &CurYear, &CurMon, &CurDay);
return;

View File

@@ -153,6 +153,7 @@ int main(int argc, char *argv[])
DBufInit(&(LastTrigger.tags));
LastTrigger.infos = NULL;
LastTrigger.tz = NULL;
ClearLastTriggers();
atexit(exitfunc);
@@ -1290,7 +1291,9 @@ int DoIfTrig(ParsePtr p)
} else {
if ( (r=ParseRem(p, &trig, &tim)) ) return r;
if (trig.typ != NO_TYPE) return E_PARSE_ERR;
EnterTimezone(trig.tz);
dse = ComputeTrigger(get_scanfrom(&trig), &trig, &tim, &r, 1);
ExitTimezone(trig.tz);
if (r) {
if (r != E_CANT_TRIG || !trig.maybe_uncomputable) {
if (!Hush || r != E_RUN_DISABLED) {
@@ -1299,6 +1302,9 @@ int DoIfTrig(ParsePtr p)
}
push_if(0, 0);
} else {
if (dse >= 0) {
dse = AdjustTriggerForTimeZone(&trig, dse, &tim);
}
if (ShouldTriggerReminder(&trig, &tim, dse, &err)) {
push_if(1, 0);
} else {
@@ -1439,6 +1445,11 @@ static int DoDebug(ParsePtr p)
if (val) DebugFlag |= DB_UNUSED_VARS;
else DebugFlag &= ~DB_UNUSED_VARS;
break;
case 'z':
case 'Z':
if (val) DebugFlag |= DB_SWITCH_ZONE;
else DebugFlag &= ~DB_SWITCH_ZONE;
break;
default:
Wprint(GetErr(M_BAD_DB_FLAG), ch);
break;
@@ -2039,6 +2050,10 @@ FreeTrig(Trigger *t)
if (t->infos) {
FreeTrigInfoChain(t->infos);
}
if (t->tz) {
free( (void *) t->tz);
}
t->tz = NULL;
t->infos = NULL;
}
@@ -2069,7 +2084,9 @@ ClearLastTriggers(void)
LastTrigger.complete_through = NO_DATE;
LastTrigger.max_overdue = -1;
FreeTrig(&LastTrigger);
LastTimeTrig.ttime = NO_TIME;
LastTimeTrig.ttime_orig = NO_TIME;
LastTimeTrig.delta = NO_DELTA;
LastTimeTrig.rep = NO_REP;
LastTimeTrig.duration = NO_TIME;
@@ -2098,6 +2115,9 @@ SaveLastTrigger(Trigger const *t)
LastTrigger.infos = NULL;
DBufInit(&(LastTrigger.tags));
if (LastTrigger.tz) {
LastTrigger.tz = StrDup(LastTrigger.tz);
}
DBufPuts(&(LastTrigger.tags), DBufValue(&(t->tags)));
TrigInfo *cur = t->infos;
while(cur) {
@@ -2122,6 +2142,7 @@ System(char const *cmd, int is_queued)
int fd;
int status;
int do_exit = 0;
if (is_queued && IsServerMode()) {
do_exit = 1;
/* Server mode... redirect stdin and stdout to /dev/null */
@@ -2151,10 +2172,9 @@ System(char const *cmd, int is_queued)
}
/* This is the child process or original if we never forked */
if (JSONMode) {
(void) system_to_stderr(cmd);
} else {
(void) system(cmd);
(void) system1(cmd);
}
if (do_exit) {
/* In the child process, so exit! */

View File

@@ -295,3 +295,9 @@ int get_scanfrom(Trigger const *t);
void remove_trailing_newlines(DynamicBuffer *buf);
void set_cloexec(int fd);
int system_to_stderr(char const *cmd);
int system1(char const *cmd);
int tz_set_tz (char const *tz);
int tz_convert(int year, int month, int day, int hour, int minute, char const *src_tz, char const *tgt_tz, struct tm *tm);
int AdjustTriggerForTimeZone(Trigger *trig, int dse, TimeTrig *tim);
void EnterTimezone(char const *tz);
void ExitTimezone(char const *tz);

View File

@@ -123,6 +123,7 @@ Token TokArray[] = {
{ "todo", 4, T_Todo, 0 },
{ "translate", 5, T_Translate, 0 },
{ "tuesday", 3, T_WkDay, 1 },
{ "tz", 2, T_Tz, 0 },
{ "unset", 5, T_UnSet, 0 },
{ "until", 5, T_Until, 0 },
{ "warn", 4, T_Warn, 0 },

View File

@@ -453,6 +453,7 @@ AdjustTriggerForDuration(int today, int r, Trigger *trig, TimeTrig *tim, int sav
/* If we have an AT, save the original event start */
if (tim->ttime != NO_TIME) {
trig->eventstart = MINUTES_PER_DAY * r + tim->ttime;
trig->eventstart_orig = trig->eventstart;
if (tim->duration != NO_TIME) {
trig->eventduration = tim->duration;
}
@@ -515,6 +516,7 @@ int ComputeTrigger(int today, Trigger *trig, TimeTrig *tim,
if (r == today) {
if (tim->ttime != NO_TIME) {
trig->eventstart = MINUTES_PER_DAY * r + tim->ttime;
trig->eventstart_orig = trig->eventstart;
if (tim->duration != NO_TIME) {
trig->eventduration = tim->duration;
}
@@ -613,6 +615,21 @@ int ComputeTriggerNoAdjustDuration(int today, Trigger *trig, TimeTrig const *tim
omit = 0;
}
/** FIXME: If a timed reminder moves to yesterday because of a time
zone adjustment, try again! */
if (trig->tz) {
TimeTrig copy = *tim;
int new_result;
ExitTimezone(trig->tz);
new_result = AdjustTriggerForTimeZone(trig, result, &copy);
EnterTimezone(trig->tz);
if (new_result + duration_days < today) {
nextstart = start+1;
start = nextstart;
continue;
}
}
/** FIXME: Fix bad interaction with SATISFY... need to rethink!!! */
if (result+duration_days >= today &&
(trig->skip != SKIP_SKIP || !omit)) {

View File

@@ -141,6 +141,7 @@ typedef struct {
int priority;
int duration_days; /* Duration converted to days to search */
int eventstart; /* Original event start (datetime) */
int eventstart_orig; /* Original event start in TZ (datetime) */
int eventduration; /* Original event duration (minutes) */
int maybe_uncomputable; /* Suppress "can't compute trigger" warnings */
int addomit; /* Add trigger date to global OMITs */
@@ -152,10 +153,12 @@ typedef struct {
DynamicBuffer tags;
char passthru[PASSTHRU_LEN+1];
TrigInfo *infos;
char const *tz; /* Time Zone */
} Trigger;
/* A time trigger */
typedef struct {
int ttime_orig;
int ttime;
int nexttime;
int delta;
@@ -226,6 +229,7 @@ typedef Parser *ParsePtr; /* Pointer to parser structure */
#define DB_TRANSLATE 0x100
#define DB_NONCONST 0x200
#define DB_UNUSED_VARS 0x400
#define DB_SWITCH_ZONE 0x800
/* Enumeration of the tokens */
enum TokTypes
@@ -239,8 +243,8 @@ enum TokTypes
T_Omit, T_OmitFunc, T_Once, T_Ordinal, T_Pop, T_PopFuncs, T_PopVars,
T_Preserve, T_Priority, T_Push, T_PushFuncs, T_PushVars, T_Rem,
T_RemType, T_Rep, T_Return, T_Scanfrom, T_Sched, T_Set, T_Skip, T_Tag,
T_Through, T_Time, T_Todo, T_Translate, T_UnSet, T_Until, T_Warn, T_WkDay,
T_Year
T_Through, T_Time, T_Todo, T_Translate, T_Tz, T_UnSet, T_Until, T_Warn,
T_WkDay, T_Year,
};
/* The structure of a token */

View File

@@ -31,6 +31,13 @@ static char const DontEscapeMe[] =
#include "globals.h"
#include "protos.h"
/* Call this instead of system() so if called ignores return code,
we don't get a compiler warning */
int system1(char const *cmd)
{
return system(cmd);
}
/***************************************************************/
/* */
/* system_to_stderr */

View File

@@ -786,9 +786,14 @@ int DoDump(ParsePtr p)
DumpSysVarByName(DBufValue(&buf)+1);
} else {
v = FindVar(DBufValue(&buf), 0);
DBufValue(&buf)[VAR_NAME_LEN] = 0;
if (!v) fprintf(ErrFp, "%s %s\n",
if (!v) {
if (DBufLen(&buf) > VAR_NAME_LEN) {
/* Truncate over-long variable name */
DBufValue(&buf)[VAR_NAME_LEN] = 0;
}
fprintf(ErrFp, "%s %s\n",
DBufValue(&buf), UNDEF);
}
else {
fprintf(ErrFp, "%s ", v->name);
PrintValue(&(v->v), ErrFp);

2
tests/Makefile Normal file
View File

@@ -0,0 +1,2 @@
%:
@make -s -C.. $@

View File

@@ -1,4 +1,6 @@
set $AddBlankLines 0
SET $Latitude "45.42055556"
SET $Longitude "-75.68944444"
banner %
set d '2011-01-01'

View File

@@ -33,7 +33,7 @@ fi
alias remind="echo You should be using ../src/remind explicitly in test-rem >&2; exit 1"
# Set a known timezone so moon phases show up in predictable places
TZ=UTC
TZ=Universal
export TZ
# If we're already in a utf-8 locale, do
@@ -573,7 +573,7 @@ EOF
../src/remind --flush '-i$AddBlankLines' - <<'EOF' >> ../tests/test.out 2>&1
BANNER %
DUMP
DUMP $AddBlankLines
EOF
../src/remind --flush ../tests/expr.rem >> ../tests/test.out 2>&1
@@ -622,7 +622,7 @@ grep -F -v '$SysInclude' < ../tests/test.out > ../tests/test.out.1 && mv -f ../t
# If "man" accepts the --warnings flag, test all the man pages.
RUNMAN=0
man man | grep -e --warnings > /dev/null 2>&1
man man 2>&1 | grep -e --warnings > /dev/null 2>&1
if test "$?" = 0 ; then
RUNMAN=1
fi

160
tests/test-timezone-support Executable file
View File

@@ -0,0 +1,160 @@
#!/bin/sh
DIR=`dirname $0`
cd $DIR
if test $? != 0 ; then
echo ""
echo "Unable to cd $DIR" >&2
echo ""
exit 1
fi
if test `id -u` = 0 ; then
echo ""
echo "*** Please do not run the test suite as root; it will fail."
echo ""
exit 1
fi
# We alias "remind" here so that we don't inadvertently add code that
# runs the system-installed verion of Remind rather than
# ../src/remind. This trick was suggested by Jochen Sprickerhof
alias remind="echo You should be using ../src/remind explicitly in test-rem >&2; exit 1"
REMIND="../src/remind -h -q --flush"
OUT=../tests/tz.out
CMP=../tests/tz.cmp
echo -n "" > $OUT 2>&1
TZ=Europe/Amsterdam $REMIND - 2025-09-03@14:00 <<'EOF' >> $OUT 2>&1
SET $AddBlankLines=0
BANNER %
REM Fri AT 23:30 TZ America/Toronto +1000 SATISFY [$Td == 13] MSG Fri 13th @23:30 Eastern is %a %2 here
DEBUG +x
SET a $T
SET b $Tt
SET c trigtz()
SET d trigtime()
SET e trigtimetz()
SET f trigeventstart()
SET g trigeventstarttz()
DEBUG -x
EOF
TZ=Europe/Amsterdam $REMIND - 2026-02-14@14:00 <<'EOF' >> $OUT 2>&1
SET $AddBlankLines=0
BANNER %
REM Fri AT 23:30 TZ America/Toronto +1000 SATISFY [$Td == 13] MSG Fri 13th @23:30 Eastern is %a %2 here
DEBUG +x
SET a $T
SET b $Tt
SET c trigtz()
SET d trigtime()
SET e trigtimetz()
SET f trigeventstart()
SET g trigeventstarttz()
DEBUG -x
EOF
TZ=Europe/Amsterdam $REMIND - 2026-02-15@14:00 <<'EOF' >> $OUT 2>&1
SET $AddBlankLines=0
BANNER %
REM Fri AT 23:30 TZ America/Toronto +1000 SATISFY [$Td == 13] MSG Fri 13th @23:30 Eastern is %a %2 here
SET a $T
SET b $Tt
SET c trigtz()
SET d trigtime()
SET e trigtimetz()
SET f trigeventstart()
SET g trigeventstarttz()
EOF
TZ=America/Toronto $REMIND -p12 - 2025-01-01 <<'EOF' 2>&1 | grep Locally >> $OUT 2>&1
REM Second Thursday AT 15:00 TZ Europe/Amsterdam MSG Locally: %3
EOF
TZ=America/Toronto $REMIND -g - 2026-01-01 <<'EOF' >> $OUT 2>&1
BANNER %
SET $AddBlankLines 0
SET new '2026-01-18@14:53'
SET first '2026-01-25@23:48'
SET full '2026-01-03@05:04'
SET last '2026-01-10@10:49'
REM [new] +100 MSG New Moon %*l %3.
SET a trigtz()
SET b trigtime()
SET c trigtimetz()
SET d trigeventstart()
SET e trigeventstarttz()
REM MSG trigtz = [a], trigtime = [b], trigtimetz = [c], trigeventstart = [d], trigeventstarttz = [e]
REM [first] +100 MSG First Quarter %*l %3.
REM [full] +100 MSG Full Moon %*l %3.
REM [last] +100 MSG Last Quarter %*l %3.
SET new tzconvert(new, "America/Toronto", "Australia/Sydney")
SET first tzconvert(first, "America/Toronto", "Australia/Sydney")
SET full tzconvert(full, "America/Toronto", "Australia/Sydney")
SET last tzconvert(last, "America/Toronto", "Australia/Sydney")
REM [new] +100 TZ Australia/Sydney MSG New Moon %*l %3. (*)
SET a trigtz()
SET b trigtime()
SET c trigtimetz()
SET d trigeventstart()
SET e trigeventstarttz()
REM MSG trigtz = [a], trigtime = [b], trigtimetz = [c], trigeventstart = [d], trigeventstarttz = [e]
REM [first] +100 TZ Australia/Sydney MSG First Quarter %*l %3. (*)
REM [full] +100 TZ Australia/Sydney MSG Full Moon %*l %3. (*)
REM [last] +100 TZ Australia/Sydney MSG Last Quarter %*l %3. (*)
REM MSG new=[new] first=[first] full=[full] last=[last]
EOF
TZ=America/Los_Angeles $REMIND - 2026-01-01 <<'EOF' >> $OUT 2>&1
BANNER %
SET $AddBlankLines 0
REM AT 13:33 MSG Whatsup? %*l %3.
REM AT 13:33 TZ "" MSG Whatsup? %*l %3.
EOF
TZ=America/Los_Angeles $REMIND - 2026-01-01 <<'EOF' >> $OUT 2>&1
BANNER %
SET $AddBlankLines 0
REM AT 13:33 TZ America/Los_Angeles TZ Universal MSG Whatsup? %*l %3.
REM AT 13:33 TZ "" TZ America/Los_Angeles MSG Whatsup? %*l %3.
REM TZ Universal MSG Borked
EOF
TZ=America/Toronto $REMIND -dx - <<'EOF' >> $OUT 2>&1
SET a tzconvert('2025-09-01@14:44', "", "Europe/Berlin")
SET a tzconvert('2025-09-01@14:44', "America/Toronto", "Europe/Berlin")
SET a tzconvert('2025-09-01@14:44', "Europe/Berlin", "")
SET a tzconvert('2025-09-01@14:44', "Europe/Berlin")
SET a tzconvert('2025-09-01@14:44', "", "")
SET a tzconvert('2025-09-01@14:44', "")
EOF
cmp -s $OUT $CMP
if [ "$?" = "0" ] ; then
echo "Remind: Time zone test PASSED"
exit 0
else
echo "Remind: Time zone test FAILED"
echo "Examine the file tz.out to see where it differs from the"
echo "reference file tz.cmp. Here are the first 200 lines of"
echo "diff -u tz.out tz.cmp"
echo ""
diff -u $OUT $CMP | head -n 200
echo ""
exit 1
fi

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,9 @@
#
# ./test-rem # From WITHIN Remind source directory!
SET $Latitude "45.42055556"
SET $Longitude "-75.68944444"
# Should issue a warning
fset year(x) 1
@@ -977,10 +980,17 @@ set a htmlstriptags("this is > whut <b>foo</b>")
set a htmlstriptags("<img src=\"foo\">")
# $ParseUntriggered
# Default is 0
REM 2 Jan 1990 MSG ["bad_expr" / 2]
# Set to 1 explicitly
SET $ParseUntriggered 1
REM 2 Jan 1990 MSG ["bad_expr" / 2]
# Set to 0 explicitly
SET $ParseUntriggered 0
REM 2 Jan 1990 MSG ["bad_expr" / 2]
SET $ParseUntriggered 1
# String multiplication

51
tests/tz.cmp Normal file
View File

@@ -0,0 +1,51 @@
Fri 13th @23:30 Eastern is on Saturday, 14 February, 2026 at 5:30am here
$T => 2026-02-14
$Tt => 05:30
trigtz() => "America/Toronto"
trigtime() => 05:30
trigtimetz() => 23:30
trigeventstart() => 2026-02-14@05:30
trigeventstarttz() => 2026-02-13@23:30
Fri 13th @23:30 Eastern is today at 5:30am here
$T => 2026-02-14
$Tt => 05:30
trigtz() => "America/Toronto"
trigtime() => 05:30
trigtimetz() => 23:30
trigeventstart() => 2026-02-14@05:30
trigeventstarttz() => 2026-02-13@23:30
Fri 13th @23:30 Eastern is on Saturday, 14 March, 2026 at 4:30am here
2025/01/09 * * * 540 9:00am Locally: at 09:00
2025/02/13 * * * 540 9:00am Locally: at 09:00
2025/03/13 * * * 600 10:00am Locally: at 10:00
2025/04/10 * * * 540 9:00am Locally: at 09:00
2025/05/08 * * * 540 9:00am Locally: at 09:00
2025/06/12 * * * 540 9:00am Locally: at 09:00
2025/07/10 * * * 540 9:00am Locally: at 09:00
2025/08/14 * * * 540 9:00am Locally: at 09:00
2025/09/11 * * * 540 9:00am Locally: at 09:00
2025/10/09 * * * 540 9:00am Locally: at 09:00
2025/11/13 * * * 540 9:00am Locally: at 09:00
2025/12/11 * * * 540 9:00am Locally: at 09:00
trigtz = , trigtime = 14:53, trigtimetz = 14:53, trigeventstart = 2026-01-18@14:53, trigeventstarttz = 2026-01-18@14:53
trigtz = Australia/Sydney, trigtime = 14:53, trigtimetz = 06:53, trigeventstart = 2026-01-18@14:53, trigeventstarttz = 2026-01-19@06:53
new=2026-01-19@06:53 first=2026-01-26@15:48 full=2026-01-03@21:04 last=2026-01-11@02:49
Full Moon 2026-01-03 at 05:04.
Full Moon 2026-01-03 at 05:04. (*)
Last Quarter 2026-01-10 at 10:49.
Last Quarter 2026-01-10 at 10:49. (*)
New Moon 2026-01-18 at 14:53.
New Moon 2026-01-18 at 14:53. (*)
First Quarter 2026-01-25 at 23:48.
First Quarter 2026-01-25 at 23:48. (*)
Whatsup? today at 13:33.
Whatsup? today at 13:33.
-stdin-(4): TZ specified twice
-stdin-(5): TZ specified twice
-stdin-(6): TZ specified for non-timed reminder
tzconvert(2025-09-01@14:44, "", "Europe/Berlin") => 2025-09-01@20:44
tzconvert(2025-09-01@14:44, "America/Toronto", "Europe/Berlin") => 2025-09-01@20:44
tzconvert(2025-09-01@14:44, "Europe/Berlin", "") => 2025-09-01@08:44
tzconvert(2025-09-01@14:44, "Europe/Berlin") => 2025-09-01@08:44
tzconvert(2025-09-01@14:44, "", "") => 2025-09-01@14:44
tzconvert(2025-09-01@14:44, "") => 2025-09-01@14:44

View File

@@ -34,3 +34,5 @@ set a utctolocal('2051-01-01@04:44')
set a utctolocal('2050-03-13@05:00')
set a utctolocal('2050-11-06@04:00')
set a utctolocal('2050-11-06@07:00')
set a trigger('1990-01-01@1:22', 1)