Compare commits

...

8 Commits

Author SHA1 Message Date
Dianne Skoll
c3c1781021 Update COPYRIGHT date. 2020-02-28 08:56:49 -05:00
Dianne Skoll
cd39480a98 Remove extraneous spaces. 2020-02-27 16:50:44 -05:00
Dianne Skoll
281a1a206e Implement JSONQUEUE daemon command. 2020-02-27 15:18:23 -05:00
Dianne Skoll
cbff2a7bf2 Document ampm() 2020-02-27 08:39:21 -05:00
Dianne Skoll
2a08be8fd0 Fix up unit tests. 2020-02-26 17:43:47 -05:00
Dianne Skoll
0826678209 Make coerce from string to time and datetime accept ampm 2020-02-26 17:41:51 -05:00
Dianne Skoll
f2e421bfa5 Add acceptance tests for ampm() function. 2020-02-26 17:25:09 -05:00
Dianne Skoll
ce53a9b91a Add "ampm" built-in function. 2020-02-26 17:21:34 -05:00
11 changed files with 346 additions and 68 deletions

View File

@@ -3,7 +3,7 @@ THE REMIND COPYRIGHT
1. REMIND refers to the entire set of files and documentation in the
REMIND package.
2. REMIND is Copyright 1992-2018 Dianne Skoll, except where noted in
2. REMIND is Copyright 1992-2020 Dianne Skoll, except where noted in
individual files.
3. DISTRIBUTION AND USE

View File

@@ -27,6 +27,8 @@ CHANGES TO REMIND
- IMPROVEMENT: If terminal size can be determined, set $FormWidth to
terminal width - 8; if not, set $FormWidth to 72.
- MINOR IMPROVEMENT: Add the "ampm()" built-in function.
* Version 3.3 Patch 0 - 2020-01-31
- FIX: rem2ps: Add a %%PageBoundingBox: document structuring convention

View File

@@ -2223,6 +2223,20 @@ is supplied, only the date component is used.
Returns the time of "astronomical twilight" on the specified \fIdate\fR. If
\fIdate\fR is omitted, defaults to \fBtoday()\fR.
.TP
.B ampm(t_time [,s_am [,s_pm]])
Returns a \fBSTRING\fR that is the result of converting \fItime\fR
to "AM/PM" format. The optional arguments \fIam\fR and \fIpm\fR are
the strings to append in the AM and PM case, respectively; they default
to "AM" and "PM". For example:
.RS
.PP
.nf
ampm(0:22) returns "12:22AM"
ampm(17:45, "am", "pm") returns "5:45pm"
.fi
.PP
.RE
.TP
.B args(s_fname)
Returns the number of arguments expected by the user-defined function
\fIfname\fR, or \-1 if no such user-defined function exists. Note that

View File

@@ -582,7 +582,7 @@ proc ConfigureCalFrame { w firstDay numDays } {
proc DoQueue {} {
global DaemonFile
puts $DaemonFile "QUEUE"
puts $DaemonFile "JSONQUEUE"
flush $DaemonFile
}
@@ -2418,18 +2418,19 @@ proc ShowQueue { file } {
grid columnconfigure $w 1 -weight 0
grid rowconfigure $w 0 -weight 1
grid rowconfigure $w 1 -weight 0
set did 0
CenterWindow $w .
while (1) {
# We should only get one line
gets $file line
if {$line == "NOTE endqueue"} {
if {$line == "NOTE ENDJSONQUEUE"} {
break
}
set did 1
$w.t insert end "$line\n"
}
if {!$did} {
$w.t insert end "*** Queue is empty ***\n"
if {[catch {set obj [::json::many-json2dict $line]}]} {
continue;
}
foreach q $obj {
$w.t insert end "$q\n"
}
}
$w.t configure -state disabled
}
@@ -2456,7 +2457,7 @@ proc DaemonReadable { file } {
scan $line "NOTE reminder %s %s %s" time now tag
IssueBackgroundReminder $file $time $now $tag
}
"NOTE queue" {
"NOTE JSONQUEUE" {
ShowQueue $file
}
"NOTE newdate" {

View File

@@ -245,7 +245,7 @@ static void WriteBottomCalLine (void);
static void WriteIntermediateCalLine (void);
static void WriteCalDays (void);
static void PrintJSONString(char const *s)
void PrintJSONString(char const *s)
{
while (*s) {
switch(*s) {
@@ -262,14 +262,14 @@ static void PrintJSONString(char const *s)
}
}
static void PrintJSONKeyPairInt(char const *name, int val)
void PrintJSONKeyPairInt(char const *name, int val)
{
printf("\"");
PrintJSONString(name);
printf("\":%d, ", val);
}
static void PrintJSONKeyPairString(char const *name, char const *val)
void PrintJSONKeyPairString(char const *name, char const *val)
{
/* If value is blank, skip it! */
if (!val || !*val) {
@@ -283,7 +283,7 @@ static void PrintJSONKeyPairString(char const *name, char const *val)
printf("\", ");
}
static void PrintJSONKeyPairDate(char const *name, int jul)
void PrintJSONKeyPairDate(char const *name, int jul)
{
int y, m, d;
if (jul == NO_DATE) {
@@ -297,7 +297,7 @@ static void PrintJSONKeyPairDate(char const *name, int jul)
}
static void PrintJSONKeyPairDateTime(char const *name, int dt)
void PrintJSONKeyPairDateTime(char const *name, int dt)
{
int y, m, d, h, i, k;
if (dt == NO_TIME) {
@@ -315,6 +315,21 @@ static void PrintJSONKeyPairDateTime(char const *name, int dt)
}
void PrintJSONKeyPairTime(char const *name, int t)
{
int h, i;
if (t == NO_TIME) {
/* Skip it! */
return;
}
h = t / 60;
i = t % 60;
printf("\"");
PrintJSONString(name);
printf("\":\"%02d:%02d\", ", h, i);
}
#ifdef REM_USE_WCHAR
static void PutWideChar(wchar_t const wc)
{

View File

@@ -722,26 +722,12 @@ int DoCoerce(char type, Value *v)
return OK;
case STR_TYPE:
h = 0;
m = 0;
s = v->v.str;
if (!isdigit(*s)) return E_CANT_COERCE;
while (isdigit(*s)) {
h *= 10;
h += *s++ - '0';
}
if (*s != ':' && *s != '.' && *s != TimeSep)
return E_CANT_COERCE;
s++;
if (!isdigit(*s)) return E_CANT_COERCE;
while (isdigit(*s)) {
m *= 10;
m += *s++ - '0';
}
if (*s || h>23 || m>59) return E_CANT_COERCE;
if (ParseLiteralTime(&s, &i)) return E_CANT_COERCE;
if (*s) return E_CANT_COERCE;
v->type = TIME_TYPE;
free(v->v.str);
v->v.val = h*60+m;
v->v.val = i;
return OK;
default: return E_CANT_COERCE;
@@ -1243,6 +1229,48 @@ int CopyValue(Value *dest, const Value *src)
return OK;
}
int ParseLiteralTime(char const **s, int *tim)
{
int h=0;
int m=0;
int ampm=0;
if (!isdigit(**s)) return E_BAD_TIME;
while(isdigit(**s)) {
h *= 10;
h += *(*s)++ - '0';
}
if (**s != ':' && **s != '.' && **s != TimeSep) return E_BAD_TIME;
(*s)++;
if (!isdigit(**s)) return E_BAD_TIME;
while(isdigit(**s)) {
m *= 10;
m += *(*s)++ - '0';
}
/* Check for p[m] or a[m] */
if (**s == 'A' || **s == 'a' || **s == 'P' || **s == 'p') {
ampm = tolower(**s);
(*s)++;
if (**s == 'm' || **s == 'M') {
(*s)++;
}
}
if (h>23 || m>59) return E_BAD_TIME;
if (ampm) {
if (h < 1 || h > 12) return E_BAD_TIME;
if (ampm == 'a') {
if (h == 12) {
h = 0;
}
} else if (ampm == 'p') {
if (h < 12) {
h += 12;
}
}
}
*tim = h * 60 + m;
return OK;
}
/***************************************************************/
/* */
/* ParseLiteralDate */
@@ -1254,11 +1282,9 @@ int CopyValue(Value *dest, const Value *src)
int ParseLiteralDate(char const **s, int *jul, int *tim)
{
int y, m, d;
int hour, min;
int ampm = 0;
int r;
y=0; m=0; d=0;
hour=0; min=0;
*tim = NO_TIME;
if (!isdigit(**s)) return E_BAD_DATE;
@@ -1288,40 +1314,9 @@ int ParseLiteralDate(char const **s, int *jul, int *tim)
/* Do we have a time part as well? */
if (**s == ' ' || **s == '@' || **s == 'T' || **s == 't') {
(*s)++;
while(isdigit(**s)) {
hour *= 10;
hour += *(*s)++ - '0';
}
if (**s != ':' && **s != '.' && **s != TimeSep) return E_BAD_TIME;
(*s)++;
while(isdigit(**s)) {
min *= 10;
min += *(*s)++ - '0';
}
/* Check for p[m] or a[m] */
if (**s == 'A' || **s == 'a' || **s == 'P' || **s == 'p') {
ampm = tolower(**s);
(*s)++;
if (**s == 'm' || **s == 'M') {
(*s)++;
}
}
if (hour>23 || min>59) return E_BAD_TIME;
if (ampm) {
if (hour < 1 || hour > 12) return E_BAD_TIME;
if (ampm == 'a') {
if (hour == 12) {
hour = 0;
}
} else if (ampm == 'p') {
if (hour < 12) {
hour += 12;
}
}
}
*tim = hour * 60 + min;
r = ParseLiteralTime(s, tim);
if (r != OK) return r;
}
return OK;
}

View File

@@ -57,6 +57,7 @@ static int FADawn (func_info *);
static int FADusk (func_info *);
static int FAbs (func_info *);
static int FAccess (func_info *);
static int FAmpm (func_info *);
static int FArgs (func_info *);
static int FAsc (func_info *);
static int FBaseyr (func_info *);
@@ -206,6 +207,7 @@ BuiltinFunc Func[] = {
{ "access", 2, 2, 0, FAccess },
{ "adawn", 0, 1, 0, FADawn},
{ "adusk", 0, 1, 0, FADusk},
{ "ampm", 1, 3, 1, FAmpm },
{ "args", 1, 1, 0, FArgs },
{ "asc", 1, 1, 1, FAsc },
{ "baseyr", 0, 0, 1, FBaseyr },
@@ -886,6 +888,57 @@ static int FSgn(func_info *info)
return OK;
}
/***************************************************************/
/* */
/* FAmpm - return a time as a string with "AM" or "PM" suffix */
/* */
/***************************************************************/
static int FAmpm(func_info *info)
{
int h, m;
char const *am = "AM";
char const *pm = "PM";
char const *ampm = NULL;
char outbuf[64];
ASSERT_TYPE(0, TIME_TYPE);
if (Nargs >= 2) {
ASSERT_TYPE(1, STR_TYPE);
am = ARGSTR(1);
if (Nargs >= 3) {
ASSERT_TYPE(2, STR_TYPE);
pm = ARGSTR(2);
}
}
h = ARGV(0) / 60;
m = ARGV(0) % 60;
if (h <= 11) {
/* AM */
if (h == 0) {
snprintf(outbuf, sizeof(outbuf), "12:%02d", m);
} else {
snprintf(outbuf, sizeof(outbuf), "%d:%02d", h, m);
}
ampm = am;
} else {
if (h > 12) {
h -= 12;
}
snprintf(outbuf, sizeof(outbuf), "%d:%02d", h, m);
ampm = pm;
}
RetVal.type = STR_TYPE;
RetVal.v.str = malloc(strlen(outbuf) + strlen(ampm) + 1);
if (!RetVal.v.str) {
RetVal.type = ERR_TYPE;
return E_NO_MEM;
}
strcpy(RetVal.v.str, outbuf);
strcat(RetVal.v.str, ampm);
return OK;
}
/***************************************************************/
/* */
/* FOrd - returns a string containing ordinal number. */

View File

@@ -37,6 +37,7 @@ int ShouldTriggerReminder (Trigger *t, TimeTrig *tim, int jul, int *err);
int DoSubst (ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig *tt, int jul, int mode);
int DoSubstFromString (char const *source, DynamicBuffer *dbuf, int jul, int tim);
int ParseLiteralDate (char const **s, int *jul, int *tim);
int ParseLiteralTime (char const **s, int *tim);
int EvalExpr (char const **e, Value *v, ParsePtr p);
int DoCoerce (char type, Value *v);
void PrintValue (Value *v, FILE *fp);
@@ -153,3 +154,9 @@ void SaveAllTriggerInfo(Trigger const *t, TimeTrig const *tt, int trigdate, int
void PerIterationInit(void);
char const *Decolorize(int r, int g, int b);
char const *Colorize(int r, int g, int b);
void PrintJSONString(char const *s);
void PrintJSONKeyPairInt(char const *name, int val);
void PrintJSONKeyPairString(char const *name, char const *val);
void PrintJSONKeyPairDate(char const *name, int jul);
void PrintJSONKeyPairDateTime(char const *name, int dt);
void PrintJSONKeyPairTime(char const *name, int t);

View File

@@ -448,6 +448,66 @@ static int CalculateNextTimeUsingSched(QueuedRem *q)
}
}
/* Dump the queue in JSON format */
static void
json_queue(QueuedRem const *q)
{
printf("[");
while(q) {
if (q->tt.nexttime == NO_TIME) {
q = q->next;
continue;
}
printf("{");
switch(q->typ) {
case NO_TYPE: PrintJSONKeyPairString("type", "NO_TYPE"); break;
case MSG_TYPE: PrintJSONKeyPairString("type", "MSG_TYPE"); break;
case RUN_TYPE: PrintJSONKeyPairString("type", "RUN_TYPE"); break;
case CAL_TYPE: PrintJSONKeyPairString("type", "CAL_TYPE"); break;
case SAT_TYPE: PrintJSONKeyPairString("type", "SAT_TYPE"); break;
case PS_TYPE: PrintJSONKeyPairString("type", "PS_TYPE"); break;
case PSF_TYPE: PrintJSONKeyPairString("type", "PSF_TYPE"); break;
case MSF_TYPE: PrintJSONKeyPairString("type", "MSF_TYPE"); break;
case PASSTHRU_TYPE: PrintJSONKeyPairString("type", "PASSTHRU_TYPE"); break;
default: PrintJSONKeyPairString("type", "?"); break;
}
PrintJSONKeyPairInt("rundisabled", q->RunDisabled);
PrintJSONKeyPairInt("ntrig", q->ntrig);
PrintJSONKeyPairTime("ttime", q->tt.ttime);
PrintJSONKeyPairTime("nextttime", q->tt.nexttime);
PrintJSONKeyPairInt("delta", q->tt.delta);
if (q->tt.rep != NO_TIME) {
PrintJSONKeyPairInt("rep", q->tt.rep);
}
if (q->tt.duration != NO_TIME) {
PrintJSONKeyPairInt("duration", q->tt.duration);
}
if (q->passthru[0]) {
PrintJSONKeyPairString("passthru", q->passthru);
}
if (q->sched[0]) {
PrintJSONKeyPairString("sched", q->sched);
}
if (DBufLen(&(q->tags))) {
PrintJSONKeyPairString("tags", DBufValue(&(q->tags)));
}
/* Last one is a special case - no trailing comma */
printf("\"");
PrintJSONString("body");
printf("\":\"");
if (q->text) {
PrintJSONString(q->text);
} else {
PrintJSONString("");
}
printf("\"}");
q = q->next;
}
printf("]\n");
}
/***************************************************************/
/* */
/* DaemonWait */
@@ -532,6 +592,11 @@ static void DaemonWait(unsigned int sleeptime)
}
printf("NOTE endqueue\n");
fflush(stdout);
} else if (!strcmp(cmdLine, "JSONQUEUE\n")) {
printf("NOTE JSONQUEUE\n");
json_queue(QueueHead);
printf("NOTE ENDJSONQUEUE\n");
fflush(stdout);
} else if (!strcmp(cmdLine, "REREAD\n")) {
printf("NOTE reread\n");
fflush(stdout);

View File

@@ -1745,7 +1745,96 @@ SET x '2015-02-03@12:00pm' + 0
SET x '2015-02-03@13:00PM' + 0
../tests/test.rem(492): Ill-formed time
# Test the ampm function
set x ampm(0:12) + ""
ampm(00:12) => "12:12AM"
"12:12AM" + "" => "12:12AM"
set x ampm(1:12) + ""
ampm(01:12) => "1:12AM"
"1:12AM" + "" => "1:12AM"
set x ampm(2:12) + ""
ampm(02:12) => "2:12AM"
"2:12AM" + "" => "2:12AM"
set x ampm(3:12) + ""
ampm(03:12) => "3:12AM"
"3:12AM" + "" => "3:12AM"
set x ampm(4:12) + ""
ampm(04:12) => "4:12AM"
"4:12AM" + "" => "4:12AM"
set x ampm(5:12) + ""
ampm(05:12) => "5:12AM"
"5:12AM" + "" => "5:12AM"
set x ampm(6:12) + ""
ampm(06:12) => "6:12AM"
"6:12AM" + "" => "6:12AM"
set x ampm(7:12) + ""
ampm(07:12) => "7:12AM"
"7:12AM" + "" => "7:12AM"
set x ampm(8:12) + ""
ampm(08:12) => "8:12AM"
"8:12AM" + "" => "8:12AM"
set x ampm(9:12) + ""
ampm(09:12) => "9:12AM"
"9:12AM" + "" => "9:12AM"
set x ampm(10:12) + ""
ampm(10:12) => "10:12AM"
"10:12AM" + "" => "10:12AM"
set x ampm(11:12) + ""
ampm(11:12) => "11:12AM"
"11:12AM" + "" => "11:12AM"
set x ampm(12:12) + ""
ampm(12:12) => "12:12PM"
"12:12PM" + "" => "12:12PM"
set x ampm(13:12) + ""
ampm(13:12) => "1:12PM"
"1:12PM" + "" => "1:12PM"
set x ampm(14:12) + ""
ampm(14:12) => "2:12PM"
"2:12PM" + "" => "2:12PM"
set x ampm(15:12) + ""
ampm(15:12) => "3:12PM"
"3:12PM" + "" => "3:12PM"
set x ampm(16:12) + ""
ampm(16:12) => "4:12PM"
"4:12PM" + "" => "4:12PM"
set x ampm(17:12) + ""
ampm(17:12) => "5:12PM"
"5:12PM" + "" => "5:12PM"
set x ampm(18:12) + ""
ampm(18:12) => "6:12PM"
"6:12PM" + "" => "6:12PM"
set x ampm(19:12) + ""
ampm(19:12) => "7:12PM"
"7:12PM" + "" => "7:12PM"
set x ampm(20:12) + ""
ampm(20:12) => "8:12PM"
"8:12PM" + "" => "8:12PM"
set x ampm(21:12) + ""
ampm(21:12) => "9:12PM"
"9:12PM" + "" => "9:12PM"
set x ampm(22:12) + ""
ampm(22:12) => "10:12PM"
"10:12PM" + "" => "10:12PM"
set x ampm(23:12) + ""
ampm(23:12) => "11:12PM"
"11:12PM" + "" => "11:12PM"
# Coerce with am/pm
set x coerce("TIME", "12:45am")
coerce("TIME", "12:45am") => 00:45
set x coerce("TIME", "12:45")
coerce("TIME", "12:45") => 12:45
set x coerce("TIME", "1:45pm")
coerce("TIME", "1:45pm") => 13:45
set x coerce("DATETIME", "2020-05-05@12:45am")
coerce("DATETIME", "2020-05-05@12:45am") => 2020-05-05@00:45
set x coerce("DATETIME", "2020-05-05@12:45")
coerce("DATETIME", "2020-05-05@12:45") => 2020-05-05@12:45
set x coerce("DATETIME", "2020-05-05@1:45pm")
coerce("DATETIME", "2020-05-05@1:45pm") => 2020-05-05@13:45
# Don't want Remind to queue reminders
EXIT
Test 2

View File

@@ -491,6 +491,43 @@ SET x '2015-02-03@11:00PM' + 0
SET x '2015-02-03@12:00pm' + 0
SET x '2015-02-03@13:00PM' + 0
# Test the ampm function
set x ampm(0:12) + ""
set x ampm(1:12) + ""
set x ampm(2:12) + ""
set x ampm(3:12) + ""
set x ampm(4:12) + ""
set x ampm(5:12) + ""
set x ampm(6:12) + ""
set x ampm(7:12) + ""
set x ampm(8:12) + ""
set x ampm(9:12) + ""
set x ampm(10:12) + ""
set x ampm(11:12) + ""
set x ampm(12:12) + ""
set x ampm(13:12) + ""
set x ampm(14:12) + ""
set x ampm(15:12) + ""
set x ampm(16:12) + ""
set x ampm(17:12) + ""
set x ampm(18:12) + ""
set x ampm(19:12) + ""
set x ampm(20:12) + ""
set x ampm(21:12) + ""
set x ampm(22:12) + ""
set x ampm(23:12) + ""
# Coerce with am/pm
set x coerce("TIME", "12:45am")
set x coerce("TIME", "12:45")
set x coerce("TIME", "1:45pm")
set x coerce("DATETIME", "2020-05-05@12:45am")
set x coerce("DATETIME", "2020-05-05@12:45")
set x coerce("DATETIME", "2020-05-05@1:45pm")
# Don't want Remind to queue reminders
EXIT
__EOF__
REM This line should not even be seen
And you can put whatever you like here.