+ return gly;
+}
+
+static int
+a2time(time_t *t, const char *fmt, const char *p)
+{
+ struct tm tm;
+ char *pp;
+
+ memset(&tm, 0, sizeof(struct tm));
+
+ pp = NULL;
+#if HAVE_STRPTIME
+ pp = strptime(p, fmt, &tm);
+#endif
+ if (NULL != pp && '\0' == *pp) {
+ *t = mktime(&tm);
+ return 1;
+ }
+
+ return 0;
+}
+
+static char *
+time2a(time_t t)
+{
+ struct tm *tm;
+ char *buf, *p;
+ size_t ssz;
+ int isz;
+
+ buf = NULL;
+ tm = localtime(&t);
+ if (tm == NULL)
+ goto fail;
+
+ /*
+ * Reserve space:
+ * up to 9 characters for the month (September) + blank
+ * up to 2 characters for the day + comma + blank
+ * 4 characters for the year and a terminating '\0'
+ */
+
+ p = buf = mandoc_malloc(10 + 4 + 4 + 1);
+
+ if ((ssz = strftime(p, 10 + 1, "%B ", tm)) == 0)
+ goto fail;
+ p += (int)ssz;
+
+ /*
+ * The output format is just "%d" here, not "%2d" or "%02d".
+ * That's also the reason why we can't just format the
+ * date as a whole with "%B %e, %Y" or "%B %d, %Y".
+ * Besides, the present approach is less prone to buffer
+ * overflows, in case anybody should ever introduce the bug
+ * of looking at LC_TIME.
+ */
+
+ isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday);
+ if (isz < 0 || isz > 4)
+ goto fail;
+ p += isz;
+
+ if (strftime(p, 4 + 1, "%Y", tm) == 0)
+ goto fail;
+ return buf;
+
+fail:
+ free(buf);
+ return mandoc_strdup("");
+}
+
+char *
+mandoc_normdate(struct roff_man *man, char *in, int ln, int pos)
+{
+ char *cp;
+ time_t t;
+
+ if (man->quick)
+ return mandoc_strdup(in == NULL ? "" : in);
+
+ /* No date specified: use today's date. */
+
+ if (in == NULL || *in == '\0')
+ mandoc_msg(MANDOCERR_DATE_MISSING, ln, pos, NULL);
+ if (in == NULL || *in == '\0' || strcmp(in, "$" "Mdocdate$") == 0)
+ return time2a(time(NULL));
+
+ /* Valid mdoc(7) date format. */
+
+ if (a2time(&t, "$" "Mdocdate: %b %d %Y $", in) ||
+ a2time(&t, "%b %d, %Y", in)) {
+ cp = time2a(t);
+ if (t > time(NULL) + 86400)
+ mandoc_msg(MANDOCERR_DATE_FUTURE, ln, pos, "%s", cp);
+ else if (*in != '$' && strcmp(in, cp) != 0)
+ mandoc_msg(MANDOCERR_DATE_NORM, ln, pos, "%s", cp);
+ return cp;
+ }
+
+ /* In man(7), do not warn about the legacy format. */
+
+ if (a2time(&t, "%Y-%m-%d", in) == 0)
+ mandoc_msg(MANDOCERR_DATE_BAD, ln, pos, "%s", in);
+ else if (t > time(NULL) + 86400)
+ mandoc_msg(MANDOCERR_DATE_FUTURE, ln, pos, "%s", in);
+ else if (man->meta.macroset == MACROSET_MDOC)
+ mandoc_msg(MANDOCERR_DATE_LEGACY, ln, pos, "Dd %s", in);
+
+ /* Use any non-mdoc(7) date verbatim. */
+
+ return mandoc_strdup(in);
+}
+
+int
+mandoc_eos(const char *p, size_t sz)
+{
+ const char *q;
+ int enclosed, found;
+
+ if (0 == sz)
+ return 0;
+
+ /*
+ * End-of-sentence recognition must include situations where
+ * some symbols, such as `)', allow prior EOS punctuation to
+ * propagate outward.
+ */
+
+ enclosed = found = 0;
+ for (q = p + (int)sz - 1; q >= p; q--) {
+ switch (*q) {
+ case '\"':
+ case '\'':
+ case ']':
+ case ')':
+ if (0 == found)
+ enclosed = 1;