Compare commits

...

74 Commits

Author SHA1 Message Date
Dianne Skoll
d75587fa7b Update change log. 2025-09-12 11:43:15 -04:00
Dianne Skoll
d9a2b15814 Bump version number to 06.01.01. 2025-09-12 11:33:15 -04:00
Dianne Skoll
aa090bf319 Add tests for PrintValue string truncation. 2025-09-12 11:22:51 -04:00
Dianne Skoll
dc6a229f5f Add some const qualifiers, as recommended by cppcheck. 2025-09-12 11:15:10 -04:00
Dianne Skoll
5a3840759a Make PrintValue truncate long strings on a multibyte-character boundary instead of potentially splitting a UTF-8 sequence. 2025-09-12 11:12:19 -04:00
Dianne Skoll
779174ae32 Proper font formatting. 2025-09-11 17:26:43 -04:00
Dianne Skoll
2f70b37d4c Be stricter about parsing hex constants. 2025-09-11 17:22:24 -04:00
Dianne Skoll
d15c8f106b Support hexadecimal integer constants in expressions; add hex() function. 2025-09-11 17:02:22 -04:00
Dianne Skoll
9d999a0074 Support hexacdecimal integer constants in expressions. 2025-09-11 16:50:13 -04:00
Dianne Skoll
097dda1750 Make asc() always return a number between 0 and 255. 2025-09-11 16:42:32 -04:00
Dianne Skoll
565bca4380 Add statistic for max expr node evaluations per line. 2025-09-11 12:04:32 -04:00
Dianne Skoll
adb38fe82e Fix off-by-one error. 2025-09-11 11:25:20 -04:00
Dianne Skoll
57545ddc3f Add test for --max-expr-complexity 2025-09-11 11:20:29 -04:00
Dianne Skoll
804cf14a78 Implement --max-expr-complexity 2025-09-11 11:11:11 -04:00
Dianne Skoll
641b5cec57 Mode docs on columns() vs mbstrlen() 2025-09-10 23:07:23 -04:00
Dianne Skoll
df53db19c4 Fix typo 2025-09-10 23:02:15 -04:00
Dianne Skoll
d9bf902153 Put #ifdef guards around wide char code. 2025-09-10 17:56:32 -04:00
Dianne Skoll
53a12de2f9 Fix typo 2025-09-10 17:54:26 -04:00
Dianne Skoll
fe2b34da68 Avoid memory leak. 2025-09-10 17:54:06 -04:00
Dianne Skoll
7e70ffe7f5 Fix typo 2025-09-10 17:43:59 -04:00
Dianne Skoll
55975154b1 Add codepoint() and mbchar() functions. 2025-09-10 17:33:57 -04:00
Dianne Skoll
3c6191ba61 Add tests where index() does not find the needle. 2025-09-10 13:48:28 -04:00
Dianne Skoll
b00cf9c5b7 Remove some unnecessary assignments. 2025-09-10 13:48:18 -04:00
Dianne Skoll
a6838802ad Use mbsubstr and mbstrlen. 2025-09-10 13:27:57 -04:00
Dianne Skoll
375576fcc5 Add better support for multi-byte character set.
The functions mbindex, mbstrlen and mbsubstr are just like
index, strlen and substr, but they use and return 1-based
character indexes rather than 1-based byte indexes.
2025-09-10 13:25:27 -04:00
Dianne Skoll
2c9087aa79 Update WHATSNEW and prep for 06.01.00 release. 2025-09-08 12:27:27 -04:00
Dianne Skoll
928f65ae47 Add a few more time zone tests. 2025-09-08 12:26:34 -04:00
Dianne Skoll
ed7b4f5ecc Nicer output. 2025-09-06 21:17:36 -04:00
Dianne Skoll
9aad9401ea Fix man page wording. 2025-09-06 18:15:47 -04:00
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
31 changed files with 1593 additions and 107 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

18
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.02.
# Generated by GNU Autoconf 2.72 for remind 06.01.01.
#
#
# 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.02'
PACKAGE_STRING='remind 06.00.02'
PACKAGE_VERSION='06.01.01'
PACKAGE_STRING='remind 06.01.01'
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.02 to adapt to many kinds of systems.
'configure' configures remind 06.01.01 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.02:";;
short | recursive ) echo "Configuration of remind 06.01.01:";;
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.02
remind configure 06.01.01
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.02, which was
It was created by remind $as_me 06.01.01, which was
generated by GNU Autoconf 2.72. Invocation command line was
$ $0$ac_configure_args_raw
@@ -4848,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.02, which was
This file was extended by remind $as_me 06.01.01, which was
generated by GNU Autoconf 2.72. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -4913,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.02
remind config.status 06.01.01
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.02, , , https://dianne.skoll.ca/projects/remind/)
AC_INIT(remind, 06.01.01, , , https://dianne.skoll.ca/projects/remind/)
AC_CONFIG_SRCDIR([src/queue.c])
cat <<'EOF'

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)))))
@@ -166,12 +166,13 @@
(defconst remind-builtin-functions
(sort
(list "_" "abs" "access" "adawn" "adusk" "ampm" "ansicolor" "args" "asc"
"baseyr" "catch" "catcherr" "char" "choose" "coerce" "columns" "const" "current" "date"
"baseyr" "catch" "catcherr" "char" "choose" "codepoint" "coerce" "columns" "const" "current" "date"
"datepart" "datetime" "dawn" "day" "daysinmon" "defined" "dosubst"
"dusk" "easterdate" "escape" "eval" "evaltrig" "filedate" "filedatetime"
"filedir" "filename" "getenv" "hebdate" "hebday" "hebmon" "hebyear"
"filedir" "filename" "getenv" "hebdate" "hebday" "hebmon" "hebyear" "hex"
"hour" "htmlescape" "htmlstriptags" "iif" "index" "isany" "isconst" "isdst"
"isleap" "isomitted" "language" "localtoutc" "lower" "max" "min"
"isleap" "isomitted" "language" "localtoutc" "lower" "max"
"mbasc" "mbindex" "mbstrlen" "mbsubstr" "min"
"minsfromutc" "minute" "mon" "monnum" "moondate" "moondatetime"
"moonphase" "moonrise" "moonrisedir" "moonset" "moonsetdir" "moontime"
"multitrig" "ndawn" "ndusk" "nonconst" "nonomitted" "now" "ord" "orthodoxeaster"
@@ -179,9 +180,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,62 @@
CHANGES TO REMIND
* VERSION 6.1 Patch 1 - 2025-09-12
- NEW FEATURE: remind: Add the new --max-expr-complexity=n
command-line argument. It is possible to write expressions that use
enormous amounts of CPU time, such as the following:
FSET fib(n) iif(n <= 2, 1, fib(n-1)+fib(n-2))
SET a fib(100)
That will take essentially forever to execute, but will not hit the
built-in recursion limit. Using a command-line argument of
--max-expr-complexity=1000000 will terminate evaluation in a few
dozen milliseconds on modern hardware, and should not affect
realistic reminder scripts. See the man page for details.
- IMPROVEMENT: remind: Add UTF-8-aware functions to complement the
byte-aware functions that could give incorrect results by splitting
a UTF-8 character sequence. The correspondence between old and new
functions is:
NON-UTF-8-AWARE UTF-8-AWARE
=============== ===========
strlen mbstrlen
substr mbsubstr
index mbindex
char mbchar
asc codepoint
See the remind(1) man page for details.
- MINOR NEW FEATURE: remind: You can use hexadecimal integer constants
like 0xFE12 in expressions. This is mostly useful for using
codepoint() since Unicode code points are often expressed in
hexadecimal.
- BUG FIX: remind: When truncating a string when executing DUMPVARS or
during debugging of expression evaluation, Remind could sometimes
cut the string in the middle of a UTF-8 sequence. This has been
fixed.
* VERSION 6.1 Patch 0 - 2025-09-08
- 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.
- DOCUMENTATION: Update README.md to document prerequisites for readline
support.
- DOCUMENTATION: remind.1: Document system variables such as $T that can
have multiple types.
- 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
@@ -11,8 +68,8 @@ CHANGES TO REMIND
- 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: 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

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

24
examples/tflag.rem Executable file
View File

@@ -0,0 +1,24 @@
#!/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.
SET $AddBlankLines 0
BANNER %
set c max(columns()-1, 10)
set r max(rows(), 5)
set bar "█" * c
set dups r/5
set bars (bar + "%_") * dups
set bars mbsubstr(bars, 0, mbstrlen(bars)-2)
REM SPECIAL COLOR 91 206 250 [bars]
REM SPECIAL COLOR 245 169 184 [bars]
REM SPECIAL COLOR 255 255 255 [bars]
REM SPECIAL COLOR 245 169 184 [bars]
REM SPECIAL COLOR 91 206 250 [bars]

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

@@ -616,6 +616,15 @@ If \fBRemind\fR finishes processing the script and then starts handling
queued reminders, the time limit is reset to no limit.
.RE
.TP
.B \-\-max-expr-complexity\fR=\fIn\fR
Limit the total complexity of expression valuation for a given line in a script
to \fIn\fR nodes. Roughly speaking, each function call, operator, constant,
variable reference, etc corresponds to one expression node. By default,
there's no limit to how many nodes can be evaluated per line, but if you
set this to a high number like 1000000 (one million) or so, you are unlikely
to cause problems with any real-world scripts while preventing pathological
behavior from badly-written or malicious expressions.
.TP
.B \-\-test
The \fB\-\-test\fR long option is only for use by the acceptance tests
run by "make test". Do not use this option in production.
@@ -693,6 +702,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
@@ -2206,6 +2216,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,
@@ -2264,7 +2349,7 @@ symbolic links to files.
.PP
The \fBSYSINCLUDE\fR command is similar to \fBDO\fR, but it looks for
relative pathnames under the system directory containing standard reminder
scripts. For this version of \fBRemind\fR, the system directory is
scripts. For this installation of \fBRemind\fR, the system directory is
"@prefix@/share/remind".
.PP
.SH THE RETURN COMMAND
@@ -2464,6 +2549,26 @@ word. The \fBINT\fR data type corresponds to the C "int" type.
The \fBSTRING\fR data type consists of strings of characters. It is
somewhat comparable to a C character array, but more closely resembles
the string type in BASIC.
.RS
.PP
\fBRemind\fR normally expects to be running in a UTF-8 environment.
In this environment, there is a difference between \fIbytes\fR and
\fIcharacters\fR since in UTF-8, a character may be represented by
a sequence of more than one byte. For example, in a UTF-8 environment,
the string "🙂" contains one character but four bytes. And the string
"één" contains three characters but five bytes.
.PP
\fBRemind\fR has a set of functions
that work on \fIbytes\fR, namely \fBindex\fR, \fBstrlen\fR and \fBsubstr\fR.
These are not safe to use on multi-byte strings; instead use
\fBmbindex\fR, \fBmbstrlen\fR and \fBmbsubstr\fR. If you know \fIfor sure\fR
that a string contains only single-byte characters, then the byte-oriented
versions may be used and are faster than the multi-byte versions.
.PP
Some ancient or embedded systems may lack the C library functions needed
to deal with multi-byte strings. In that case, the \fBmb\fIxxx\fR functions
all return an error.
.RE
.TP
.B TIME
The \fBTIME\fR data type is used for two different purposes: To represent
@@ -2500,7 +2605,7 @@ as being the combination of \fBDATE\fR and \fBTIME\fR parts.
The following examples illustrate constants in \fBRemind\fR expressions:
.TP
.B INT constants
12, 36, \-10, 0, 1209
12, 36, \-10, 0, 1209, 0x1F, 0xfe00 (the last two demonstrate the use of hexadecimal constants)
.TP
.B STRING constants
"Hello there", "This is a test", "\\nHello\\tThere", ""
@@ -3273,10 +3378,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)
@@ -3285,7 +3390,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)
@@ -3294,7 +3399,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)
@@ -3555,11 +3660,15 @@ function has been defined previously. The \fBargs()\fR function is
available only in versions of \fBRemind\fR from 03.00.04 and up.
.TP
.B asc(s_string)
Returns an \fBINT\fR that is the ASCII code of the first character
in \fIstring\fR. As a special case, \fBasc("")\fR returns 0. For UTF-8
strings, this will return the UTF-8 byte with which the string
begins, which is not likely to be very useful (and may indeed be negative
on machines where \fBchar\fR is a signed type.)
Returns an \fBINT\fR that is the ASCII code of the first byte in
\fIstring\fR. As a special case, \fBasc("")\fR returns 0. For UTF-8
strings, this will return the UTF-8 byte with which the string begins,
which is not likely to be very useful.
.TP
.B codepoint(s_string)
Returns an \fBINT\fR that is the code point of the first character
in \fIstring\fR, treating multi-byte characters correctly. As a special case,
\fBcodepoint("")\fR returns 0.
.TP
.B baseyr()
Returns the "base year" that was compiled into \fBRemind\fR (normally
@@ -3616,6 +3725,15 @@ It is easy to create invalid UTF-8 sequences; \fBchar\fR does not check
for this. Note that none of the arguments can be 0, unless there is only one
argument. As a special case, \fBchar(0)\fR returns "".
.TP
.B mbchar(i_i1 [,i_i2...])
This function can take any number of \fBINT\fR arguments. It returns
a \fBSTRING\fR consisting of the characters specified by the
arguments. Any codepoint may be supplied and a correct multi-byte
character string will be returned. Note that none of the arguments
can be 0, unless there is only one argument. As a special case,
\fBmbchar(0)\fR returns "". Additionally, no argument may be a
negative number.
.TP
.B choose(i_index, x_arg1 [,x_arg2...])
\fBChoose\fR must take at least two arguments, the first of which is
an \fBINT\fR. If \fIindex\fR is \fIn\fR, then the \fIn\fRth subsequent
@@ -3687,6 +3805,13 @@ ANSI color-changing sequences occupy zero columns whereas some Unicode
characters occupy two columns. \fBcolumns(str)\fR takes all of that
into account. Note that if \fBRemind\fR was compiled without Unicode support,
\fBcolumns(str)\fR returns a type mismatch error.
.PP
The result of \fBcolumns(str)\fR may be less than, equal to, or
greater than the result of \fBmbstrlen(str)\fR. This is because some
Unicode characters are so-called combining characters that add one to the
character length, but don't occupy any columns on their own. And other
Unicode characters are double-width characters that add one to the
character length, but two to the number of display columns.
.RE
.TP
.B const(x_arg)
@@ -3930,6 +4055,11 @@ Support for Hebrew dates - see the section "THE HEBREW CALENDAR"
.B hebyear(dq_date)
Support for Hebrew dates - see the section "THE HEBREW CALENDAR"
.TP
.B hex(i_n)
Returns a \fBSTRING\fR that is the hexadecimal representation of \fIn\fR.
There is no "0x" prefix and any letters in the returned value
are uppper-case.
.TP
.B hour(tq_time)
Returns the hour component of \fItime\fR.
.TP
@@ -3955,14 +4085,23 @@ compatible with previous versions of \fBRemind\fR.
.TP
.B index(s_search, s_target [,i_start)
Returns an \fBINT\fR that is the location of \fItarget\fR in the
string \fIsearch\fR. The first character of a string is numbered 1.
If \fItarget\fR does not exist in \fIsearch\fR, then 0 is returned.
string \fIsearch\fR. Note that \fBindex\fR uses \fIbyte\fR positions,
not character positions, so should not be used on non-ASCII strings. Use
\fBmbindex\fR for non-ASCII strings.
.PP
The first byte of a string is numbered 1. If \fItarget\fR does not
exist in \fIsearch\fR, then 0 is returned.
.RS
.PP
The optional parameter \fIstart\fR specifies the position in
\fIsearch\fR at which to start looking for \fItarget\fR.
.RE
.TP
.B mbindex(s_search, s_target [,i_start])
Similar to \fBindex()\fR but returns the \fIcharacter\fR position rather
than the \fIbyte\fR position. Also, \fIstart\fR is interpreted as a
1-based character index rather than a byte index.
.TP
.B isany(arg1 [,arg2, ..., argN]);
Returns 1 if the first argument \fIarg1\fR is equal to any of the
subsequent arguments \fIarg2\fR through \fIargN\fR; returns 0 otherwise.
@@ -4566,17 +4705,27 @@ output is not going to a TTY.
.RE
.TP
.B strlen(s_str)
Returns the length of \fIstr\fR. If the length of \fIstr\fR is too large
to represent as an integer, emits a "Number too high" error. Note that
\fBstrlen\fR returns the number of \fIbytes\fR in the string, not the
number of \fIcharacters\fR. These numbers are the same for ASCII strings,
but may be different for UTF-8 strings.
Returns the length of \fIstr\fR in bytes. If the length of \fIstr\fR
is too large to represent as an integer, emits a "Number too high"
error. Note that \fBstrlen\fR returns the number of \fIbytes\fR in
the string, not the number of \fIcharacters\fR. These numbers are the
same for ASCII strings, but may be different for UTF-8 strings.
.TP
.B mbstrlen(str)
Similar to \fBstrlen\fR, but returns the length of the string in
\fIcharacters\fR rather than \fIbytes\fR and is thus safe for use
on multi-byte strings.
.TP
.B substr(s_str, i_start [,i_end])
Returns a \fBSTRING\fR consisting of all characters in \fIstr\fR from
\fIstart\fR up to and including \fIend\fR. Characters are numbered
Returns a \fBSTRING\fR consisting of all bytes in \fIstr\fR from
\fIstart\fR up to and including \fIend\fR. Bytes are numbered
from 1. If \fIend\fR is not supplied, then it defaults to the length
of \fIstr\fR.
of \fIstr\fR. Because \fBsubstr\fR uses \fIbyte\fR indexes rather than
\fIcharacter\fR indexes, it should not be used on multi-byte strings.
.TP
.B mbsubstr(s_str, i_start [,i_end])
Similar to \fBsubstr\fR but uses \fIcharacter\fR indexes rather than
\fIbyte\fR indexes, and is thus safe for use on multi-byte strings.
.TP
.B sunrise([dq_date])
Returns a \fBTIME\fR indicating the time of sunrise on the specified
@@ -4703,6 +4852,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
@@ -4753,6 +4907,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
@@ -4814,12 +4973,10 @@ Returns a string suitable for use in a \fBREM\fR command or a
dates in advance. Note that in earlier versions of \fBRemind\fR,
\fBtrigger\fR was required to convert a date into something the
\fBREM\fR command could consume. However, in this version of
\fBRemind\fR, you can omit it. Note that \fBtrigger()\fR \fIalways\fR
returns its result in English, even for non-English versions of
\fBRemind\fR. Normally, the \fIdate\fR and \fItime\fR are the local
date and time; however, if \fIutcflag\fR is non-zero, the \fIdate\fR
and \fItime\fR are interpreted as UTC times, and are converted to
local time. Examples:
\fBRemind\fR, you can omit it. Normally, the \fIdate\fR and
\fItime\fR are the local date and time; however, if \fIutcflag\fR is
non-zero, the \fIdate\fR and \fItime\fR are interpreted as UTC times,
and are converted to local time. Examples:
.RS
.PP
trigger('1993/04/01')
@@ -4852,11 +5009,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
@@ -4873,11 +5035,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

@@ -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,7 +132,11 @@
#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
#define E_NO_MB 113
#define E_BAD_MB_SEQ 114
#define E_EXPR_NODES_EXCEEDED 115
#ifdef MK_GLOBALS
#undef EXTERN
#define EXTERN
@@ -261,6 +265,11 @@ 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",
/* E_NO_MB */ "C library does not support multibyte characters",
/* E_BAD_MB_SEQ */ "Invalid multibyte sequence",
/* E_EXPR_NODES_EXCEEDED */ "Maximum expression complexity exceeded",
}
#endif /* MK_GLOBALS */
;

View File

@@ -316,7 +316,7 @@ add_child(expr_node *parent, expr_node *child)
/* */
/***************************************************************/
static void
debug_evaluation(Value *ans, int r, char const *fmt, ...)
debug_evaluation(Value const *ans, int r, char const *fmt, ...)
{
va_list argptr;
va_start(argptr, fmt);
@@ -343,7 +343,7 @@ debug_evaluation(Value *ans, int r, char const *fmt, ...)
/* */
/***************************************************************/
static void
debug_evaluation_binop(Value *ans, int r, Value *v1, Value *v2, char const *fmt, ...)
debug_evaluation_binop(Value const *ans, int r, Value const *v1, Value const *v2, char const *fmt, ...)
{
va_list argptr;
va_start(argptr, fmt);
@@ -381,7 +381,7 @@ debug_evaluation_binop(Value *ans, int r, Value *v1, Value *v2, char const *fmt,
/* */
/***************************************************************/
static void
debug_evaluation_unop(Value *ans, int r, Value *v1, char const *fmt, ...)
debug_evaluation_unop(Value const *ans, int r, Value const *v1, char const *fmt, ...)
{
va_list argptr;
va_start(argptr, fmt);
@@ -641,7 +641,7 @@ debug_enter_userfunc(expr_node *node, Value *locals, int nargs)
/* */
/***************************************************************/
static void
debug_exit_userfunc(expr_node *node, Value *ans, int r, Value *locals, int nargs)
debug_exit_userfunc(expr_node *node, Value const *ans, int r, Value *locals, int nargs)
{
char const *fname;
int i;
@@ -888,6 +888,15 @@ evaluate_expr_node(expr_node *node, Value *locals, Value *ans, int *nonconst)
if (!node) {
return E_SWERR;
}
if (ExpressionNodeLimitPerLine > 0 &&
ExpressionNodesEvaluatedThisLine >= ExpressionNodeLimitPerLine) {
return E_EXPR_NODES_EXCEEDED;
}
ExpressionNodesEvaluated++;
ExpressionNodesEvaluatedThisLine++;
if (ExpressionNodesEvaluatedThisLine > MaxExprNodesPerLine) {
MaxExprNodesPerLine = ExpressionNodesEvaluatedThisLine;
}
switch(node->type) {
case N_FREE:
case N_ERROR:
@@ -2132,6 +2141,31 @@ static int set_constant_value(expr_node *atom)
atom->u.value.type = INT_TYPE;
val = 0;
prev_val = 0;
if (*s == '0' && (*(s+1) == 'x' || *(s+1) == 'X')) {
/* Hex constant */
s += 2;
if (!*s || !isxdigit(*s)) {
return E_BAD_NUMBER;
}
while (*s && isxdigit(*s)) {
val *= 16;
if (*s >= '0' && *s <= '9') {
val += (*s - '0');
} else {
val += (toupper(*s) - 'A') + 10;
}
s++;
if (val < prev_val) {
return E_2HIGH;
}
prev_val = val;
}
if (*s) {
return E_BAD_NUMBER;
}
atom->u.value.v.val = val;
return OK;
}
while (*s && isdigit(*s)) {
val *= 10;
val += (*s++ - '0');
@@ -2930,6 +2964,28 @@ int EvalExpr(char const **e, Value *v, ParsePtr p)
return r;
}
#ifdef REM_USE_WCHAR
/* Truncate a wide-char string to MAX_PRT_LEN characters */
static char const *truncate_string(char const *src)
{
static wchar_t wbuf[MAX_PRT_LEN+1];
static char cbuf[(MAX_PRT_LEN * 8) + 1];
size_t l;
l = mbstowcs(wbuf, src, MAX_PRT_LEN);
if (l == (size_t) -1) {
return src;
}
wbuf[MAX_PRT_LEN] = 0;
l = wcstombs(cbuf, wbuf, MAX_PRT_LEN*8);
if (l == (size_t) -1) {
return src;
}
cbuf[MAX_PRT_LEN*8] = 0;
return cbuf;
}
#endif
/***************************************************************/
/* */
/* PrintValue */
@@ -2940,10 +2996,13 @@ int EvalExpr(char const **e, Value *v, ParsePtr p)
static DynamicBuffer printbuf = {NULL, 0, 0, ""};
#define PV_PUTC(fp, c) do { if (fp) { putc((c), fp); } else { DBufPutc(&printbuf, (c)); } } while(0);
char const *PrintValue (Value *v, FILE *fp)
char const *PrintValue (Value const *v, FILE *fp)
{
int y, m, d;
unsigned char const *s;
int max_str_put = MAX_PRT_LEN;
int truncated = 0;
char pvbuf[512];
if (!fp) {
/* It's OK to DBufFree an uninitialized *BUT STATIC* dynamic buffer */
@@ -2951,9 +3010,19 @@ char const *PrintValue (Value *v, FILE *fp)
}
if (v->type == STR_TYPE) {
#ifdef REM_USE_WCHAR
s = (unsigned char const *) truncate_string(v->v.str);
if (s != (unsigned char const *) v->v.str) {
max_str_put = INT_MAX;
if (strlen((char const *) s) != strlen(v->v.str)) {
truncated = 1;
}
}
#else
s = (unsigned char const *) v->v.str;
#endif
PV_PUTC(fp, '"');
for (y=0; y<MAX_PRT_LEN && *s; y++) {
for (y=0; y<max_str_put && *s; y++) {
switch(*s) {
case '\a': PV_PUTC(fp, '\\'); PV_PUTC(fp, 'a'); break;
case '\b': PV_PUTC(fp, '\\'); PV_PUTC(fp, 'b'); break;
@@ -2979,7 +3048,7 @@ char const *PrintValue (Value *v, FILE *fp)
s++;
}
PV_PUTC(fp, '"');
if (*s) {
if (*s || truncated) {
if (fp) {
fprintf(fp, "...");
} else {

View File

@@ -187,6 +187,7 @@ got_a_fresh_line(void)
{
FreshLine = 1;
WarnedAboutImplicit = 0;
ExpressionNodesEvaluatedThisLine = 0;
}
void set_cloexec(int fd)
@@ -304,6 +305,10 @@ static int ReadLineFromFile(int use_pclose)
DBufFree(&LineBuffer);
LineNoStart = LineNo+1;
if (!fp) {
return E_EOF;
}
while(fp) {
#ifdef USE_READLINE
if (IS_INTERACTIVE()) {
@@ -454,6 +459,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;
@@ -671,6 +677,7 @@ static int PopFile(void)
IncludeStruct *i;
pop_excess_ifs(FileName);
fp = NULL;
if (!IStackPtr) return E_EOF;
i = &IStack[IStackPtr-1];
@@ -683,17 +690,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

@@ -87,6 +87,7 @@ static int FCatch (expr_node *, Value *, Value *, int *);
static int FCatchErr (func_info *);
static int FChar (func_info *);
static int FChoose (expr_node *, Value *, Value *, int *);
static int FCodepoint (func_info *);
static int FCoerce (func_info *);
static int FColumns (func_info *);
static int FCurrent (func_info *);
@@ -112,6 +113,7 @@ static int FHebdate (func_info *);
static int FHebday (func_info *);
static int FHebmon (func_info *);
static int FHebyear (func_info *);
static int FHex (func_info *);
static int FHour (func_info *);
static int FHtmlEscape (func_info *);
static int FHtmlStriptags (func_info *);
@@ -126,6 +128,10 @@ static int FLanguage (func_info *);
static int FLocalToUTC (func_info *);
static int FLower (func_info *);
static int FMax (func_info *);
static int FMbchar (func_info *);
static int FMbindex (func_info *);
static int FMbstrlen (func_info *);
static int FMbsubstr (func_info *);
static int FMin (func_info *);
static int FMinsfromutc (func_info *);
static int FMinute (func_info *);
@@ -181,6 +187,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 +197,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 +215,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. */
@@ -263,6 +271,7 @@ BuiltinFunc Func[] = {
{ "catcherr", 0, 0, 0, FCatchErr, NULL },
{ "char", 1, NO_MAX, 1, FChar, NULL },
{ "choose", 2, NO_MAX, 1, NULL, FChoose }, /*NEW-STYLE*/
{ "codepoint", 1, 1, 1, FCodepoint, NULL },
{ "coerce", 2, 2, 1, FCoerce, NULL },
{ "columns", 0, 1, 0, FColumns, NULL },
{ "const", 1, 1, 1, FNonconst, NULL },
@@ -289,6 +298,7 @@ BuiltinFunc Func[] = {
{ "hebday", 1, 1, 0, FHebday, NULL },
{ "hebmon", 1, 1, 0, FHebmon, NULL },
{ "hebyear", 1, 1, 0, FHebyear, NULL },
{ "hex", 1, 1, 1, FHex, NULL },
{ "hour", 1, 1, 1, FHour, NULL },
{ "htmlescape", 1, 1, 1, FHtmlEscape, NULL },
{ "htmlstriptags",1, 1, 1, FHtmlStriptags, NULL },
@@ -303,6 +313,10 @@ BuiltinFunc Func[] = {
{ "localtoutc", 1, 1, 1, FLocalToUTC, NULL },
{ "lower", 1, 1, 1, FLower, NULL },
{ "max", 1, NO_MAX, 1, FMax, NULL },
{ "mbchar", 1, NO_MAX, 1, FMbchar, NULL },
{ "mbindex", 2, 3, 1, FMbindex, NULL },
{ "mbstrlen", 1, 1, 1, FMbstrlen, NULL },
{ "mbsubstr", 2, 3, 1, FMbsubstr, NULL },
{ "min", 1, NO_MAX, 1, FMin, NULL },
{ "minsfromutc", 0, 2, 0, FMinsfromutc, NULL },
{ "minute", 1, 1, 1, FMinute, NULL },
@@ -357,6 +371,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 +384,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 },
@@ -471,6 +488,28 @@ static int FStrlen(func_info *info)
return OK;
}
/***************************************************************/
/* */
/* FMBstrlen - string length in wide characters */
/* */
/***************************************************************/
static int FMbstrlen(func_info *info)
{
#ifdef REM_USE_WCHAR
ASSERT_TYPE(0, STR_TYPE);
size_t l = mbstowcs(NULL, ARGSTR(0), 0);
if (l == (size_t) -1) {
return E_BAD_MB_SEQ;
}
if (l > INT_MAX) return E_2HIGH;
RetVal.type = INT_TYPE;
RETVAL = (int) l;
return OK;
#else
return E_NO_MB;
#endif
}
/***************************************************************/
/* */
/* FBaseyr - system base year */
@@ -693,10 +732,50 @@ static int FAsc(func_info *info)
{
ASSERT_TYPE(0, STR_TYPE);
RetVal.type = INT_TYPE;
RETVAL = *(ARGSTR(0));
RETVAL = (int) *( (unsigned char const *) ARGSTR(0));
return OK;
}
/***************************************************************/
/* */
/* FHex - return hexadecimal representation of an integer */
/* */
/***************************************************************/
static int FHex(func_info *info)
{
char buf[64];
ASSERT_TYPE(0, INT_TYPE);
snprintf(buf, sizeof(buf), "%X", (unsigned int) ARGV(0));
return RetStrVal(buf, info);
}
/***************************************************************/
/* */
/* FCodepoint - wide-character codepoint of start of str */
/* */
/***************************************************************/
static int FCodepoint(func_info *info)
{
#ifdef REM_USE_WCHAR
wchar_t arr[2];
size_t len;
ASSERT_TYPE(0, STR_TYPE);
len = mbstowcs(arr, ARGSTR(0), sizeof(arr) / sizeof(arr[0]));
if (len == (size_t) -1) {
return E_BAD_MB_SEQ;
}
RetVal.type = INT_TYPE;
RETVAL = (int) arr[0];
return OK;
#else
return E_NO_MB;
#endif
}
/***************************************************************/
/* */
/* FChar - build a string from ASCII values */
@@ -745,6 +824,65 @@ static int FChar(func_info *info)
*(RetVal.v.str + Nargs) = 0;
return OK;
}
/***************************************************************/
/* */
/* FMbchar - build a string from wide character code points */
/* */
/***************************************************************/
static int FMbchar(func_info *info)
{
#ifdef REM_USE_WCHAR
int i;
size_t len;
wchar_t *arr;
char *s;
for (i=0; i<Nargs; i++) {
ASSERT_TYPE(i, INT_TYPE);
}
/* Special case of one arg - if given value 0, create empty string */
if (Nargs == 1) {
if (ARGV(0) == 0) {
return RetStrVal("", info);
}
}
arr = calloc(Nargs+1, sizeof(wchar_t));
if (!arr) {
return E_NO_MEM;
}
for (i=0; i<Nargs; i++) {
if (ARGV(i) <= 0) {
return E_2LOW;
}
arr[i] = (wchar_t) ARGV(i);
}
arr[Nargs] = (wchar_t) 0;
len = wcstombs(NULL, arr, 0);
if (len == (size_t) -1) {
free( (void *) arr);
return E_BAD_MB_SEQ;
}
s = malloc(len+1);
if (!s) {
free( (void *) arr);
return E_NO_MEM;
}
(void) wcstombs(s, arr, len+1);
free( (void *) arr);
RetVal.type = STR_TYPE;
RetVal.v.str = s;
return OK;
#else
return E_NO_MB;
#endif
}
/***************************************************************/
/* */
/* Functions for extracting the components of a date. */
@@ -1956,6 +2094,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 +2137,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 +2239,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) {
@@ -2333,6 +2511,75 @@ static int FSubstr(func_info *info)
return RetStrVal(t, info);
}
/***************************************************************/
/* */
/* FMbubstr */
/* */
/* The mbsubstr function. */
/* */
/***************************************************************/
static int FMbsubstr(func_info *info)
{
#ifdef REM_USE_WCHAR
wchar_t *str;
wchar_t *s;
wchar_t const *t;
size_t mblen;
char *converted;
size_t len;
int start;
int end;
if (ARG(0).type != STR_TYPE || ARG(1).type != INT_TYPE) return E_BAD_TYPE;
if (Nargs == 3 && ARG(2).type != INT_TYPE) return E_BAD_TYPE;
mblen = mbstowcs(NULL, ARGSTR(0), 0);
if (mblen == (size_t) -1) {
return E_BAD_MB_SEQ;
}
str = calloc(mblen+1, sizeof(wchar_t));
if (!str) {
return E_NO_MEM;
}
(void) mbstowcs(str, ARGSTR(0), mblen+1);
s = str;
start = 1;
while (start < ARGV(1)) {
if (!*s) break;
s++;
start++;
}
t = s;
if (Nargs >= 3) {
end = start;
while (end <= ARGV(2)) {
if (!*s) break;
s++;
end++;
}
*s = (wchar_t) 0;
}
len = wcstombs(NULL, t, 0);
if (len == (size_t) -1) {
free( (void *) str);
return E_BAD_MB_SEQ;
}
converted = malloc(len+1);
if (!converted) {
free( (void *) str);
return E_NO_MEM;
}
(void) wcstombs(converted, t, len+1);
RetVal.type = STR_TYPE;
RetVal.v.str = converted;
free( (void *) str);
return OK;
#else
return E_NO_MB;
#endif
}
/***************************************************************/
/* */
/* FIndex */
@@ -2371,6 +2618,73 @@ static int FIndex(func_info *info)
return OK;
}
/***************************************************************/
/* */
/* FMbindex */
/* */
/* The wide-char of one string embedded in another. */
/* */
/***************************************************************/
static int FMbindex(func_info *info)
{
#ifdef REM_USE_WCHAR
wchar_t *haystack;
wchar_t *needle;
wchar_t const *s;
size_t haylen, needlelen;
if (ARG(0).type != STR_TYPE || ARG(1).type != STR_TYPE ||
(Nargs == 3 && ARG(2).type != INT_TYPE)) return E_BAD_TYPE;
haylen = mbstowcs(NULL, ARGSTR(0), INT_MAX);
if (haylen == (size_t) -1) {
return E_BAD_MB_SEQ;
}
haystack = calloc(haylen+1, sizeof(wchar_t));
if (!haystack) {
return E_NO_MEM;
}
(void) mbstowcs(haystack, ARGSTR(0), haylen+1);
needlelen = mbstowcs(NULL, ARGSTR(1), INT_MAX);
if (needlelen == (size_t) -1) {
free( (void *) haystack);
return E_BAD_MB_SEQ;
}
needle = calloc(needlelen+1, sizeof(wchar_t));
if (!needle) {
free( (void *) haystack);
return E_NO_MEM;
}
(void) mbstowcs(needle, ARGSTR(1), needlelen+1);
s = haystack;
/* If 3 args, bump up the start */
if (Nargs == 3) {
if (ARGV(2) > (int) haylen) {
s += haylen;
} else {
s += ARGV(2) - 1;
}
}
/* Find the string */
RetVal.type = INT_TYPE;
s = wcsstr(s, needle);
if (!s) {
free( (void *) haystack);
free( (void *) needle);
RETVAL = 0;
return OK;
}
RETVAL = s - haystack + 1;
free( (void *) haystack);
free( (void *) needle);
return OK;
#else
return E_NO_MB;
#endif
}
/***************************************************************/
/* */
/* FIif */
@@ -3879,7 +4193,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) {
@@ -3892,7 +4206,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)
@@ -3919,9 +4233,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);
@@ -4217,25 +4534,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) {
@@ -4245,9 +4569,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;
}
@@ -4276,6 +4602,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);
@@ -4286,6 +4616,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) {
@@ -4333,18 +4666,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;
@@ -87,6 +88,10 @@ EXTERN INIT( int DeltaOverride, 0);
EXTERN INIT( int RunDisabled, 0);
EXTERN INIT( int ExpressionEvaluationDisabled, 0);
EXTERN INIT( int ExpressionEvaluationTimeLimit, 0);
EXTERN INIT( int ExpressionNodesEvaluated, 0);
EXTERN INIT( int MaxExprNodesPerLine, 0);
EXTERN INIT( int ExpressionNodesEvaluatedThisLine, 0);
EXTERN INIT( int ExpressionNodeLimitPerLine, 0);
EXTERN INIT( volatile sig_atomic_t ExpressionTimeLimitExceeded, 0);
EXTERN INIT( int IgnoreOnce, 0);
EXTERN INIT( char const *OnceFile, NULL);
@@ -98,9 +103,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 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;
@@ -864,6 +880,7 @@ void Usage(void)
fprintf(ErrFp, " --only-events Do not issue TODO reminders\n");
fprintf(ErrFp, " --json Use JSON output instead of plain-text\n");
fprintf(ErrFp, " --max-execution-time=n Limit execution time to n seconds\n");
fprintf(ErrFp, " --max-expr-complexity=n Limit expression evaluation to n nodes per line\n");
fprintf(ErrFp, " --print-config-cmd Print ./configure cmd used to build Remind\n");
fprintf(ErrFp, " --print-errs Print all possible error messages\n");
fprintf(ErrFp, " --print-tokens Print all possible Remind tokens\n");
@@ -1144,6 +1161,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;
@@ -1204,6 +1222,14 @@ ProcessLongOption(char const *arg)
print_sysvar_tokens();
exit(0);
}
if (sscanf(arg, "max-expr-complexity=%d", &t) == 1) {
if (t < 0) {
fprintf(ErrFp, "%s: --max-expr-complexity must be non-negative\n", ArgV[0]);
return;
}
ExpressionNodeLimitPerLine = t;
return;
}
if (sscanf(arg, "max-execution-time=%d", &t) == 1) {
if (t < 0) {
fprintf(ErrFp, "%s: --max-execution-time must be non-negative\n", ArgV[0]);

View File

@@ -84,6 +84,8 @@ exitfunc(void)
UnsetAllUserFuncs();
print_expr_nodes_stats();
fprintf(ErrFp, "Max expr node evaluations per line: %d\n", MaxExprNodesPerLine);
fprintf(ErrFp, "Total expression node evaluations: %d\n", ExpressionNodesEvaluated);
}
}
@@ -153,6 +155,7 @@ int main(int argc, char *argv[])
DBufInit(&(LastTrigger.tags));
LastTrigger.infos = NULL;
LastTrigger.tz = NULL;
ClearLastTriggers();
atexit(exitfunc);
@@ -1290,7 +1293,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 +1304,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 +1447,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 +2052,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 +2086,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 +2117,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) {

View File

@@ -68,7 +68,7 @@ expr_node *clone_expr_tree(expr_node const *node, int *r);
int EvalExpr (char const **e, Value *v, ParsePtr p);
int EvalExprRunDisabled(char const **e, Value *v, ParsePtr p);
int DoCoerce (char type, Value *v);
char const *PrintValue (Value *v, FILE *fp);
char const *PrintValue (Value const *v, FILE *fp);
int CopyValue (Value *dest, const Value *src);
int ReadLine (void);
int DoInclude (ParsePtr p, enum TokTypes tok);
@@ -296,3 +296,8 @@ 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 */

2
tests/Makefile Normal file
View File

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

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
@@ -59,7 +59,7 @@ chmod 000 include_dir/04cantread.rem
TEST_GETENV="foo bar baz" ; export TEST_GETENV
echo "Test 1" > ../tests/test.out
echo "" >> ../tests/test.out
../src/remind --flush -e -dxte ../tests/test.rem 16 feb 1991 12:13 2>&1 | grep -v 'TimetIs64bit' >> ../tests/test.out
../src/remind --flush -e -dxte ../tests/test.rem 16 feb 1991 12:13 2>&1 | grep -v -a 'TimetIs64bit' >> ../tests/test.out 2>&1
echo "" >> ../tests/test.out
echo "Test 2" >> ../tests/test.out
echo "" >> ../tests/test.out
@@ -618,7 +618,7 @@ rm -f ../tests/once.timestamp
../src/remind --flush -q ../tests/dedupe.rem 8 November 2023 >> ../tests/test.out 2>&1
# Remove references to SysInclude, which is build-specific
grep -F -v '$SysInclude' < ../tests/test.out > ../tests/test.out.1 && mv -f ../tests/test.out.1 ../tests/test.out
grep -F -v -a '$SysInclude' < ../tests/test.out > ../tests/test.out.1 && mv -f ../tests/test.out.1 ../tests/test.out
# If "man" accepts the --warnings flag, test all the man pages.
RUNMAN=0
@@ -785,7 +785,7 @@ echo "... and here is stderr" >> ../tests/test.out 2>&1
# Test %: substitution sequence in all the languages
for i in ../include/lang/??.rem ; do
../src/remind --flush "-ii=\"$i\"" -p - 2025-08-13 <<'EOF' 2>&1 | grep 2025/ >> ../tests/test.out
../src/remind --flush "-ii=\"$i\"" -p - 2025-08-13 <<'EOF' 2>&1 | grep -a 2025/ >> ../tests/test.out
DO [i]
REM TODO 2025-08-13 MSG %(LANGID) Task1%:
REM TODO 2025-08-13 COMPLETE-THROUGH 2025-08-12 MSG %(LANGID) Task2%:
@@ -795,6 +795,49 @@ done
../src/remind --flush -q ../tests/safety.rem 2025-08-13 >> ../tests/test.out 2>&1
# Test --max-expr-complexity
../src/remind -dh --flush -q --max-expr-complexity=1000000 - 2025-01-01 <<'EOF' >> ../tests/test.out 2>&1
BANNER %
SET $AddBlankLines 0
FSET fib(n) iif(n < 3, 1, fib(n-1) + fib(n-2))
REM MSG fib(10) = [fib(10)]
REM MSG fib(15) = [fib(15)]
REM MSG fib(20) = [fib(20)]
REM MSG fib(21) = [fib(21)]
REM MSG fib(22) = [fib(22)]
REM MSG fib(23) = [fib(23)]
REM MSG fib(24) = [fib(24)]
REM MSG fib(25) = [fib(25)]
REM MSG fib(30) = [fib(30)]
EOF
# Test hex constants
../src/remind -dh --flush -q - 2025-01-01 <<'EOF' >> ../tests/test.out 2>&1
BANNER %
SET $AddBlankLines 0
set a 0xfe + 0xef
rem msg a = [a]; hex(a) = [hex(a)]
rem msg hex(-1) = [hex(-1)]
set a 0x7fff
rem msg a = [a]; hex(a) = [hex(a)]
EOF
# Test proper truncation by "dumpvars"
../src/remind -h --flush -q - 2025-01-01 <<'EOF' >> ../tests/test.out 2>&1
BANNER %
SET $AddBlankLines 0
set a "¢" * 41
set b "D" * 41
set c mbchar(0x1F642) * 41
DUMP
set a "¢" * 40
set b "D" * 40
set c mbchar(0x1F642) * 40
DUMP
EOF
cmp -s ../tests/test.out ../tests/test.cmp
if [ "$?" = "0" ]; then
echo "Remind: Acceptance test PASSED"

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

@@ -0,0 +1,177 @@
#!/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 - 2025-09-01@01:00 <<'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', "")
SET b evaltrig("Wednesday at 23:00 TZ Australia/Sydney")
SET b evaltrig("Wednesday at 23:00 TZ Europe/Amsterdam")
SET b evaltrig("Wednesday at 23:00 TZ America/Los_Angeles")
set b evaltrig("Wednesday TZ Europe/Amsterdam")
debug +t
set c trig("Mon at 00:00 TZ Australia/Sydney",\
"Tue at 00:00 TZ America/Toronto",\
"Wed at 00:00 TZ Europe/Amsterdam",\
"Thu at 00:00 TZ America/Los_Angeles",\
"Fri at 00:00 TZ Australia/Sydney",\
"Sat at 00:00 TZ Australia/Sydney",\
"Sun at 00:00 TZ Australia/Sydney", \
"at 14:42")
debug -t
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

View File

@@ -1045,7 +1045,7 @@ set a057 value("a05"+"6")
"a05" + "6" => "a056"
value("a056") => "SDFJHSDF KSJDFH KJSDFH KSJDFH"
set a058 version()
version() => "06.00.02"
version() => "06.01.01"
set a059 wkday(today())
today() => 1991-02-16
wkday(1991-02-16) => "Saturday"
@@ -2608,7 +2608,7 @@ a056 "SDFJHSDF KSJDFH KJSDFH KSJDFH"
a007 "1991-02-16"
a057 "SDFJHSDF KSJDFH KJSDFH KSJDFH"
a008 "11:44"
a058 "06.00.02"
a058 "06.01.01"
a059 "Saturday"
a010 12
a060 6
@@ -5561,8 +5561,8 @@ REM SATISFY ""
REM SATISFY [version() > "01.00.00"]
../tests/test.rem(1074): SATISFY: expression has no reference to trigdate() or $T...
../tests/test.rem(1074): Trig = Saturday, 16 February, 1991
version() => "06.00.02"
"06.00.02" > "01.00.00" => 1
version() => "06.01.01"
"06.01.01" > "01.00.00" => 1
../tests/test.rem(1074): Trig(satisfied) = Saturday, 16 February, 1991
REM SATISFY [max(x, max(x, 1, 2, 3), 4, 5, 6) * 5]
../tests/test.rem(1075): SATISFY: expression has no reference to trigdate() or $T...
@@ -16592,8 +16592,63 @@ Leaving UserFN c() => 33
DEBUG -xe
Overridden: subst_colon subst_bang subst_question subst_at subst_hash
bad => "ÿ"
mbstrlen("ÿ") => Invalid multibyte sequence
../tests/test.rem(1734): mbstrlen(): Invalid multibyte sequence
bad => "ÿ"
strlen("ÿ") => 1
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbstrlen("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo") => 15
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
strlen("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo") => 34
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbindex("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "ç") => 9
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
index("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "ç") => 24
bad => "ÿ"
bad => "ÿ"
mbindex("ÿ", "ÿ") => Invalid multibyte sequence
../tests/test.rem(1742): mbindex(): Invalid multibyte sequence
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbindex("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "ç", 11) => 11
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
index("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "ç", 25) => 26
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
index("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "whoopér") => 0
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbindex("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "whoopér") => 0
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
index("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "whoopér", 3) => 0
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbindex("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", "whoopér", 3) => 0
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbsubstr("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", 2) => "ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
mbsubstr("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", 2, 9) => "ðŸ™ðŸ™ðŸ™ðŸ™xyzç"
bad => "ÿ"
mbsubstr("ÿ", 1) => Invalid multibyte sequence
../tests/test.rem(1754): mbsubstr(): Invalid multibyte sequence
bad => "ÿ"
mbsubstr("ÿ", 1, 20) => Invalid multibyte sequence
../tests/test.rem(1755): mbsubstr(): Invalid multibyte sequence
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
substr("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", 2) => "Ÿ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
substr("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo", 2, 9) => "Ÿ™‚🙂ð"
faces => "ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo"
codepoint("ðŸ™ðŸ™ðŸ™ðŸ™ðŸ™xyzçççéfoo") => 128578
mbchar(128578, 162, 122) => "ðŸ™Â¢z"
bad => "ÿ"
codepoint("ÿ") => Invalid multibyte sequence
../tests/test.rem(1762): codepoint(): Invalid multibyte sequence
codepoint("") => 0
mbchar(0) => ""
mbchar(0, 120) => Number too low
../tests/test.rem(1765): mbchar(): Number too low
mbchar(120, 0) => Number too low
../tests/test.rem(1766): mbchar(): Number too low
Variable hash table statistics:
Entries: 100144; Buckets: 87719; Non-empty Buckets: 66301
Entries: 100146; Buckets: 87719; Non-empty Buckets: 66303
Maxlen: 5; Minlen: 0; Avglen: 1.142; Stddev: 0.878; Avg nonempty len: 1.510
Growths: 13; Shrinks: 0
Function hash table statistics:
@@ -16612,6 +16667,8 @@ Translation hash table statistics:
Expression nodes high-water: 302076
Expression nodes leaked: 0
Parse level high-water: 34
Max expr node evaluations per line: 2001
Total expression node evaluations: 106467
Test 2
@@ -23658,7 +23715,7 @@ SECURITY: Won't read world-writable file or directory!
Error reading include_dir/ww: Can't open file
SECURITY: Won't read world-writable file or directory!
Error reading include_dir/ww: No files matching *.rem
06.00.02
06.01.01
Enabling test mode: This is meant for the acceptance test.
Do not use --test in production.
In test mode, the system time is fixed at 2025-01-06@19:00
@@ -24234,6 +24291,8 @@ Translation hash table statistics:
Expression nodes high-water: 499
Expression nodes leaked: 0
Parse level high-water: 2001
Max expr node evaluations per line: 499
Total expression node evaluations: 632
-stdin-(14): Unmatched PUSH-OMIT-CONTEXT at -stdin-(7)
-stdin-(14): Warning: PUSH-OMIT-CONTEXT without matching POP-OMIT-CONTEXT
No reminders.
@@ -24485,6 +24544,7 @@ through
todo
trans
translate
tz
unset
until
warn
@@ -24548,6 +24608,7 @@ catch
catcherr
char
choose
codepoint
coerce
columns
const
@@ -24574,6 +24635,7 @@ hebdate
hebday
hebmon
hebyear
hex
hour
htmlescape
htmlstriptags
@@ -24588,6 +24650,10 @@ language
localtoutc
lower
max
mbchar
mbindex
mbstrlen
mbsubstr
min
minsfromutc
minute
@@ -24642,6 +24708,7 @@ trigdelta
trigduration
trigeventduration
trigeventstart
trigeventstarttz
trigfrom
trigger
triginfo
@@ -24654,6 +24721,8 @@ trigtags
trigtime
trigtimedelta
trigtimerep
trigtimetz
trigtz
triguntil
trigvalid
typeof
@@ -24809,6 +24878,8 @@ Translation hash table statistics:
Expression nodes high-water: 1800000
Expression nodes leaked: 0
Parse level high-water: 7
Max expr node evaluations per line: 3
Total expression node evaluations: 300001
set a 8 * "]]]" & 6
-stdin-(1): Parse error `&' (did you mean `&&'?)
8 * "]]]" & 6
@@ -24987,6 +25058,11 @@ TRANSLATE "Time limit for expression evaluation exceeded" ""
TRANSLATE "COMPLETE-THROUGH specified without TODO" ""
TRANSLATE "MAX-OVERDUE specified twice" ""
TRANSLATE "MAX-OVERDUE specified without TODO" ""
TRANSLATE "TZ specified twice" ""
TRANSLATE "TZ specified for non-timed reminder" ""
TRANSLATE "C library does not support multibyte characters" ""
TRANSLATE "Invalid multibyte sequence" ""
TRANSLATE "Maximum expression complexity exceeded" ""
# Other Messages
TRANSLATE "%s function `%s' defined at %s(%s) does not use its argument" ""
@@ -25004,6 +25080,7 @@ TRANSLATE "Cannot stat %s - not running as daemon!" ""
TRANSLATE "Cannot use AT clause in multitrig() function" ""
TRANSLATE "Do not use ["["]] around expression in SET command" ""
TRANSLATE "Duplicate INFO headers are not permitted" ""
TRANSLATE "Error adjusting trigger to local time zone" ""
TRANSLATE "Error: THROUGH date earlier than start date" ""
TRANSLATE "Executing `%s' for INCLUDECMD and caching as `%s'" ""
TRANSLATE "Found cached directory listing for `%s'" ""
@@ -39751,3 +39828,73 @@ nooooo....
../tests/safety.rem(37): [#0] In function `subst_b'
today
nooooo....
fib(10) = 55
fib(15) = 610
fib(20) = 6765
fib(21) = 10946
fib(22) = 17711
fib(23) = 28657
fib(24) = 46368
-stdin-(12): `-': Maximum expression complexity exceeded
-stdin-(3): [#0] In function `fib'
[remaining call frames omitted]
-stdin-(13): `-': Maximum expression complexity exceeded
-stdin-(3): [#0] In function `fib'
[remaining call frames omitted]
Variable hash table statistics:
Entries: 0; Buckets: 7; Non-empty Buckets: 0
Maxlen: 0; Minlen: 0; Avglen: 0.000; Stddev: 0.000; Avg nonempty len: 0.000
Growths: 0; Shrinks: 0
Function hash table statistics:
Entries: 1; Buckets: 7; Non-empty Buckets: 1
Maxlen: 1; Minlen: 0; Avglen: 0.143; Stddev: 0.350; Avg nonempty len: 1.000
Growths: 0; Shrinks: 0
Dedupe hash table statistics:
Entries: 0; Buckets: 7; Non-empty Buckets: 0
Maxlen: 0; Minlen: 0; Avglen: 0.000; Stddev: 0.000; Avg nonempty len: 0.000
Growths: 0; Shrinks: 0
Translation hash table statistics:
Entries: 1; Buckets: 7; Non-empty Buckets: 1
Maxlen: 1; Minlen: 0; Avglen: 0.143; Stddev: 0.350; Avg nonempty len: 1.000
Growths: 0; Shrinks: 0
Expression nodes allocated: 256
Expression nodes high-water: 16
Expression nodes leaked: 0
Parse level high-water: 25
Max expr node evaluations per line: 1000000
Total expression node evaluations: 3999940
a = 493; hex(a) = 1ED
hex(-1) = FFFFFFFF
a = 32767; hex(a) = 7FFF
Variable hash table statistics:
Entries: 1; Buckets: 7; Non-empty Buckets: 1
Maxlen: 1; Minlen: 0; Avglen: 0.143; Stddev: 0.350; Avg nonempty len: 1.000
Growths: 0; Shrinks: 0
Function hash table statistics:
Entries: 0; Buckets: 7; Non-empty Buckets: 0
Maxlen: 0; Minlen: 0; Avglen: 0.000; Stddev: 0.000; Avg nonempty len: 0.000
Growths: 0; Shrinks: 0
Dedupe hash table statistics:
Entries: 0; Buckets: 7; Non-empty Buckets: 0
Maxlen: 0; Minlen: 0; Avglen: 0.000; Stddev: 0.000; Avg nonempty len: 0.000
Growths: 0; Shrinks: 0
Translation hash table statistics:
Entries: 1; Buckets: 7; Non-empty Buckets: 1
Maxlen: 1; Minlen: 0; Avglen: 0.143; Stddev: 0.350; Avg nonempty len: 1.000
Growths: 0; Shrinks: 0
Expression nodes allocated: 256
Expression nodes high-water: 3
Expression nodes leaked: 0
Parse level high-water: 17
Max expr node evaluations per line: 3
Total expression node evaluations: 13
Variable Value
a "¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢"...
b "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"...
c "🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂"...
Variable Value
a "¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢¢"
b "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
c "🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂"

View File

@@ -1725,7 +1725,47 @@ fset subst_hash(a, b, c) "subst_hash"
REM MSG Overridden: %: %! %? %@ %#
# mbstrlen and friends
DEBUG -xe
set bad char(255)
set faces "🙂" * 5 + "xyz" + "çççéfoo"
DEBUG +x
set a mbstrlen(bad)
set a strlen(bad)
set a mbstrlen(faces)
set a strlen(faces)
set a mbindex(faces, "ç")
set a index(faces, "ç")
set a mbindex(bad, bad)
set a mbindex(faces, "ç", 11)
set a index(faces, "ç", 25)
set a index(faces, "whoopér")
set a mbindex(faces, "whoopér")
set a index(faces, "whoopér", 3)
set a mbindex(faces, "whoopér", 3)
set a mbsubstr(faces, 2)
set a mbsubstr(faces, 2, 9)
set a mbsubstr(bad, 1)
set a mbsubstr(bad, 1, 20)
set a substr(faces, 2)
set a substr(faces, 2, 9)
set a codepoint(faces)
set a mbchar(128578, 162, 122)
set a codepoint(bad)
set a codepoint("")
set a mbchar(0)
set a mbchar(0, 120)
set a mbchar(120, 0)
DEBUG -x
# Don't want Remind to queue reminders
EXIT

80
tests/tz.cmp Normal file
View File

@@ -0,0 +1,80 @@
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
evaltrig("Wednesday at 23:00 TZ Australia/Sydney") => 2025-09-03@09:00
evaltrig("Wednesday at 23:00 TZ Europe/Amsterdam") => 2025-09-03@17:00
evaltrig("Wednesday at 23:00 TZ America/Los_Angele"...) => 2025-09-04@02:00
evaltrig("Wednesday TZ Europe/Amsterdam") => TZ specified for non-timed reminder
-stdin-(11): evaltrig(): TZ specified for non-timed reminder
trig("Mon at 00:00 TZ Australia/Sydney", "Tue at 00:00 TZ America/Toronto", "Wed at 00:00 TZ Europe/Amsterdam", "Thu at 00:00 TZ America/Los_Angeles", "Fri at 00:00 TZ Australia/Sydney", "Sat at 00:00 TZ Australia/Sydney", "Sun at 00:00 TZ Australia/Sydney", "at 14:42") => -stdin-(14:21): Trig(tz_adj Australia/Sydney) = Sunday, 31 August, 2025 AT 10:00
-stdin-(14:21): Trig(tz_adj Australia/Sydney) = Sunday, 7 September, 2025 AT 10:00
-stdin-(14:21): Trig = Monday, 8 September, 2025 AT 00:00
-stdin-(14:21): Trig(tz_adj Australia/Sydney) = Sunday, 7 September, 2025 AT 10:00
-stdin-(14:21): Trig(tz_adj America/Toronto) = Tuesday, 2 September, 2025 AT 00:00
-stdin-(14:21): Trig = Tuesday, 2 September, 2025 AT 00:00
-stdin-(14:21): Trig(tz_adj America/Toronto) = Tuesday, 2 September, 2025 AT 00:00
-stdin-(14:21): Trig(tz_adj Europe/Amsterdam) = Tuesday, 2 September, 2025 AT 18:00
-stdin-(14:21): Trig = Wednesday, 3 September, 2025 AT 00:00
-stdin-(14:21): Trig(tz_adj Europe/Amsterdam) = Tuesday, 2 September, 2025 AT 18:00
-stdin-(14:21): Trig(tz_adj America/Los_Angeles) = Thursday, 4 September, 2025 AT 03:00
-stdin-(14:21): Trig = Thursday, 4 September, 2025 AT 00:00
-stdin-(14:21): Trig(tz_adj America/Los_Angeles) = Thursday, 4 September, 2025 AT 03:00
-stdin-(14:21): Trig(tz_adj Australia/Sydney) = Thursday, 4 September, 2025 AT 10:00
-stdin-(14:21): Trig = Friday, 5 September, 2025 AT 00:00
-stdin-(14:21): Trig(tz_adj Australia/Sydney) = Thursday, 4 September, 2025 AT 10:00
-stdin-(14:21): Trig(tz_adj Australia/Sydney) = Friday, 5 September, 2025 AT 10:00
-stdin-(14:21): Trig = Saturday, 6 September, 2025 AT 00:00
-stdin-(14:21): Trig(tz_adj Australia/Sydney) = Friday, 5 September, 2025 AT 10:00
-stdin-(14:21): Trig(tz_adj Australia/Sydney) = Saturday, 6 September, 2025 AT 10:00
-stdin-(14:21): Trig = Sunday, 7 September, 2025 AT 00:00
-stdin-(14:21): Trig(tz_adj Australia/Sydney) = Saturday, 6 September, 2025 AT 10:00
-stdin-(14:21): Trig = Monday, 1 September, 2025 AT 14:42
2025-09-01