Commit 025c888a 025c888ab07458c7b2a1e73d6b607cc9ce26cca6 by Sergey Poznyakoff

Implement locale-independent strftime function.

* include/mailutils/util.h (mu_c_streamftime): New prototype.
* libmailutils/base/date.c (mu_c_streamftime): New function.
* libmailutils/tests/.gitignore: Update.
* libmailutils/tests/strftime.at: New test script.
* libmailutils/tests/strftime.c: New test program.
* libmailutils/tests/Makefile.am (noinst_PROGRAMS): Add strftime.
(TESTSUITE_AT): Add strftime.at.
* libmailutils/tests/testsuite.at: Include strftime.at.
1 parent 217ae7a2
......@@ -71,6 +71,9 @@ time_t mu_tm2time (struct tm *timeptr, mu_timezone *tz);
size_t mu_strftime (char *s, size_t max, const char *format,
const struct tm *tm);
int mu_c_streamftime (mu_stream_t str, const char *fmt, struct tm *tm,
struct mu_timezone *tz);
/* ----------------------- */
/* File & path names. */
/* ----------------------- */
......
......@@ -23,16 +23,24 @@
#include <stdlib.h>
#include <string.h>
#include <mailutils/util.h>
#include <mailutils/stream.h>
#include <mailutils/errno.h>
#include <mailutils/cstr.h>
#define SECS_PER_DAY 86400
#define ADJUSTMENT -719162L
static time_t
/* Julian day is the number of days since Jan 1, 4713 BC (ouch!).
Eg. Jan 1, 1900 is: */
#define JULIAN_1900 1721425L
/* Computes the number of days elapsed since January, 1 1900 to the
January, 1 of the given year */
static unsigned
jan1st (int year)
{
year--; /* Do not consider the current year */
return year*365L
return year * 365L
+ year/4L /* Years divisible by 4 are leap years */
+ year/400L /* Years divisible by 400 are always leap years */
- year/100L; /* Years divisible by 100 but not 400 aren't */
......@@ -48,7 +56,7 @@ static int month_start[]=
#define leap_year(y) ((y) % 4 == 0 && (y) % 100 != 0 || (y) % 400 == 0)
static int
dayofyear (time_t *pday, int year, int month, int day)
dayofyear (int year, int month, int day)
{
int leap, month_days;
......@@ -66,10 +74,50 @@ dayofyear (time_t *pday, int year, int month, int day)
if (month <= 2)
leap = 0;
*pday = month_start[month] + day + leap;
return month_start[month] + day + leap;
}
/* Returns number of days in a given year */
static int
year_days (int year)
{
return dayofyear (year, 11, 31);
}
static int
julianday (unsigned *pd, int year, int month, int day)
{
int total = dayofyear (year, month, day);
if (total == -1)
return -1;
*pd = JULIAN_1900 + total + jan1st (year);
return 0;
}
static int
dayofweek (int year, int month, int day)
{
unsigned jd;
if (julianday (&jd, year, month, day))
return -1;
/* January 1, 1900 was Monday, hence +1 */
return (jd + 1) % 7;
}
#define ISO_8601_START_WDAY 1 /* Monday */
#define ISO_8601_MAX_WDAY 4 /* Thursday */
#define MAXDAYS 366 /* Max. number of days in a year */
#define ISO_8601_OFF ((MAXDAYS / 7 + 2) * 7)
int
ISO_8601_weekdays (int yday, int wday)
{
return (yday
- (yday - wday + ISO_8601_MAX_WDAY + ISO_8601_OFF) % 7
+ ISO_8601_MAX_WDAY - ISO_8601_START_WDAY);
}
/* Convert struct tm into time_t, taking into account timezone offset. */
/* FIXME: It does not take DST into account */
......@@ -77,12 +125,14 @@ time_t
mu_tm2time (struct tm *tm, mu_timezone *tz)
{
time_t t;
int day;
if (dayofyear (&t, tm->tm_year, tm->tm_mon, tm->tm_mday - 1))
day = dayofyear (tm->tm_year, tm->tm_mon, tm->tm_mday - 1);
if (day == -1)
return -1;
t = (t + ADJUSTMENT + jan1st (1900 + tm->tm_year)) * SECS_PER_DAY
t = (day + ADJUSTMENT + jan1st (1900 + tm->tm_year)) * SECS_PER_DAY
+ (tm->tm_hour * 60 + tm->tm_min) * 60 + tm->tm_sec
- tz->utc_offset;
- (tz ? tz->utc_offset : 0);
return t;
}
......@@ -97,17 +147,387 @@ mu_utc_offset (void)
return - mktime (tm);
}
static const char *months[] =
static const char *short_month[] =
{
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static const char *full_month[] =
{
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"
};
static const char *wdays[] =
static const char *short_wday[] =
{
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
static const char *full_wday[] =
{
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday"
};
int
mu_c_streamftime (mu_stream_t str, const char *fmt, struct tm *input_tm,
struct mu_timezone *tz)
{
int rc = 0;
struct tm tm;
/* Copy input TM because it might have been received from gmtime and
the fmt might result in further calls to gmtime which will clobber
it. */
tm = *input_tm;
while (*fmt && rc == 0)
{
size_t len = strcspn (fmt, "%");
if (len)
{
rc = mu_stream_write (str, fmt, len, NULL);
if (rc)
break;
}
fmt += len;
restart:
if (!*fmt || !*++fmt)
break;
switch (*fmt)
{
case 'a':
/* The abbreviated weekday name. */
if (tm.tm_wday < 0 || tm.tm_wday > 6)
rc = ERANGE;
else
rc = mu_stream_write (str, short_wday[tm.tm_wday],
strlen (short_wday[tm.tm_wday]), NULL);
break;
case 'A':
/* The full weekday name. */
if (tm.tm_wday < 0 || tm.tm_wday > 6)
rc = ERANGE;
else
rc = mu_stream_write (str, full_wday[tm.tm_wday],
strlen (full_wday[tm.tm_wday]), NULL);
break;
case 'b':
case 'h':
/* The abbreviated month name. */
if (tm.tm_mon < 0 || tm.tm_mon > 11)
rc = ERANGE;
else
rc = mu_stream_write (str, short_month[tm.tm_mon],
strlen (short_month[tm.tm_mon]), NULL);
break;
case 'B':
/* The full month name. */
if (tm.tm_mon < 0 || tm.tm_mon > 11)
rc = ERANGE;
else
rc = mu_stream_write (str, full_month[tm.tm_mon],
strlen (full_month[tm.tm_mon]), NULL);
break;
case 'c':
/* The preferred date and time representation. */
rc = mu_c_streamftime (str, "%a %b %e %H:%M:%S %Y", &tm, tz);
break;
case 'C':
/* The century number (year/100) as a 2-digit integer. */
rc = mu_stream_printf (str, "%02d", (tm.tm_year + 1900) / 100);
break;
case 'd':
/* The day of the month as a decimal number (range 01 to 31). */
if (tm.tm_mday < 1 || tm.tm_mday > 31)
rc = ERANGE;
else
rc = mu_stream_printf (str, "%02d", tm.tm_mday);
break;
case 'D':
/* Equivalent to %m/%d/%y. */
rc = mu_c_streamftime (str, "%m/%d/%y", &tm, tz);
break;
case 'e':
/* Like %d, the day of the month as a decimal number, but a leading
zero is replaced by a space. */
if (tm.tm_mday < 1 || tm.tm_mday > 31)
rc = ERANGE;
else
rc = mu_stream_printf (str, "%2d", tm.tm_mday);
break;
case 'E':
/* Modifier. The Single Unix Specification mentions %Ec, %EC, %Ex,
%EX, %Ey, and %EY, which are supposed to use a corresponding
locale-dependent alternative representation.
A no-op in POSIX locale */
goto restart;
case 'F':
/* Equivalent to %Y-%m-%d (the ISO 8601 date format). */
rc = mu_c_streamftime (str, "%Y-%m-%d", &tm, tz);
break;
case 'V':
/* The ISO 8601:1988 week number of the current year as a decimal
number range 01 to 53, where week 1 is the first week that has
at least 4 days in the current year, and with Monday as the
first day of the week.
*/
case 'G':
/* The ISO 8601 year with century as a decimal number. The 4-digit
year corresponding to the ISO week number (see %V). This has
the same format and value as %y, except that if the ISO week
number belongs to the previous or next year, that year is used
instead.
*/
case 'g':
/* Like %G, but without century, that is, with a 2-digit year
(00-99). */
{
int year = tm.tm_year + 1900;
int days = ISO_8601_weekdays (tm.tm_yday, tm.tm_wday);
if (days < 0)
{
days = ISO_8601_weekdays (tm.tm_yday + year_days (year - 1),
tm.tm_wday);
year--;
}
else
{
int d = ISO_8601_weekdays (tm.tm_yday - year_days (year),
tm.tm_wday);
if (d >= 0)
{
year++;
days = d;
}
}
switch (*fmt)
{
case 'V':
rc = mu_stream_printf (str, "%02d", days / 7 + 1);
break;
case 'G':
rc = mu_stream_printf (str, "%4d", year);
break;
case 'g':
rc = mu_stream_printf (str, "%02d", year % 100);
}
}
break;
case 'H':
/* The hour as a decimal number using a 24-hour clock (range 00 to
23). */
rc = mu_stream_printf (str, "%02d", tm.tm_hour);
break;
case 'I':
/* The hour as a decimal number using a 12-hour clock (range 01 to
12). */
{
unsigned n = tm.tm_hour % 12;
rc = mu_stream_printf (str, "%02d", n == 0 ? 12 : n);
}
break;
case 'j':
/* The day of the year as a decimal number (range 001 to 366). */
rc = mu_stream_printf (str, "%03d", tm.tm_yday + 1);
break;
case 'k':
/* The hour (24-hour clock) as a decimal number (range 0 to 23);
single digits are preceded by a blank. */
rc = mu_stream_printf (str, "%2d", tm.tm_hour);
break;
case 'l':
/* The hour (12-hour clock) as a decimal number (range 1 to 12);
single digits are preceded by a blank. */
{
unsigned n = tm.tm_hour % 12;
rc = mu_stream_printf (str, "%2d", n == 0 ? 12 : n);
}
break;
case 'm':
/* The month as a decimal number (range 01 to 12). */
rc = mu_stream_printf (str, "%02d", tm.tm_mon + 1);
break;
case 'M':
/* The minute as a decimal number (range 00 to 59). */
rc = mu_stream_printf (str, "%02d", tm.tm_min);
break;
case 'n':
/* A newline character. */
rc = mu_stream_write (str, "\n", 1, NULL);
break;
case 'O':
/* Modifier. The Single Unix Specification mentions %Od, %Oe, %OH,
%OI, %Om, %OM, %OS, %Ou, %OU, %OV, %Ow, %OW, and %Oy, which are
supposed to use alternative numeric symbols.
Hardly of any use for our purposes, hence a no-op. */
goto restart;
case 'p':
/* Either "AM" or "PM" according to the given time value.
Noon is treated as "PM" and midnight as "AM". */
rc = mu_stream_write (str,
tm.tm_hour < 12 ? "AM" : "PM",
2, NULL);
break;
case 'P':
/* Like %p but in lowercase: "am" or "pm". */
rc = mu_stream_write (str,
tm.tm_hour < 12 ? "am" : "pm",
2, NULL);
break;
case 'r':
/* The time in a.m. or p.m. notation, i.e. %I:%M:%S %p. */
rc = mu_c_streamftime (str, "%I:%M:%S %p", &tm, tz);
break;
case 'R':
/* The time in 24-hour notation (%H:%M) */
rc = mu_c_streamftime (str, "%H:%M", &tm, tz);
break;
case 's':
/* The number of seconds since the Epoch */
rc = mu_stream_printf (str, "%lu",
(unsigned long) mu_tm2time (&tm, tz));
break;
case 'S':
/* The second as a decimal number (range 00 to 60) */
rc = mu_stream_printf (str, "%02d", tm.tm_sec);
break;
case 't':
/* A tab character. */
rc = mu_stream_write (str, "\t", 1, NULL);
break;
case 'T':
/* The time in 24-hour notation (%H:%M:%S) */
rc = mu_c_streamftime (str, "%H:%M:%S", &tm, tz);
break;
case 'u':
/* The day of the week as a decimal, range 1 to 7, Monday being 1.
*/
rc = mu_stream_printf (str, "%1d",
tm.tm_wday == 0 ? 7 : tm.tm_wday);
break;
case 'U':
/* The week number of the current year as a decimal number, range
00 to 53, starting with the first Sunday as the first day of
week 01.
*/
rc = mu_stream_printf (str, "%02d",
(tm.tm_yday - tm.tm_wday + 7) / 7);
break;
case 'w':
/* The day of the week as a decimal, range 0 to 6, Sunday being 0.
*/
rc = mu_stream_printf (str, "%01d", tm.tm_wday);
break;
case 'W':
/* The week number of the current year as a decimal number, range
00 to 53, starting with the first Monday as the first day of
week 01. */
rc = mu_stream_printf (str, "%02d",
(tm.tm_yday - (tm.tm_wday - 1 + 7) % 7 + 7) / 7);
break;
/* The preferred date representation without the time:
equivalent to %D */
case 'x':
rc = mu_c_streamftime (str, "%m/%d/%y", &tm, tz);
break;
case 'X':
/* The preferred date representation without the date */
rc = mu_c_streamftime (str, "%H:%M:%S", &tm, tz);
break;
case 'y':
/* The year as a decimal number without a century (range 00 to 99).
*/
rc = mu_stream_printf (str, "%02d", (tm.tm_year + 1900) % 100);
break;
case 'Y':
/* The year as a decimal number including the century. */
rc = mu_stream_printf (str, "%d", tm.tm_year + 1900);
break;
case 'z':
case 'Z':
/* The time-zone as hour offset from GMT, for formatting RFC-822
dates (e.g. "%a, %d %b %Y %H:%M:%S %z") */
{
int utc_off = tz ? tz->utc_offset : mu_utc_offset ();
int sign;
if (utc_off < 0)
{
sign = '-';
utc_off = - utc_off;
}
else
sign = '+';
utc_off /= 60;
rc = mu_stream_printf (str, "%c%02u%02u", sign,
utc_off / 60, utc_off % 60);
}
break;
case '%':
/* A literal '%' character. */
rc = mu_stream_write (str, "%", 1, NULL);
break;
case '+':
/* Not supported (date and time in date(1) format. */
default:
rc = mu_stream_write (str, fmt-1, 2, NULL);
break;
}
fmt++;
}
/* Restore input tm */
*input_tm = tm;
return rc;
}
int
mu_parse_imap_date_time (const char **p, struct tm *tm, mu_timezone *tz)
{
......@@ -146,7 +566,7 @@ mu_parse_imap_date_time (const char **p, struct tm *tm, mu_timezone *tz)
for (i = 0; i < 12; i++)
{
if (mu_c_strncasecmp (month, months[i], 3) == 0)
if (mu_c_strncasecmp (month, short_month[i], 3) == 0)
{
mon = i;
break;
......@@ -204,7 +624,7 @@ mu_parse_ctime_date_time (const char **p, struct tm *tm, mu_timezone *tz)
for (i = 0; i < 7; i++)
{
if (mu_c_strncasecmp (weekday, wdays[i], 3) == 0)
if (mu_c_strncasecmp (weekday, short_wday[i], 3) == 0)
{
wday = i;
break;
......@@ -213,7 +633,7 @@ mu_parse_ctime_date_time (const char **p, struct tm *tm, mu_timezone *tz)
for (i = 0; i < 12; i++)
{
if (mu_c_strncasecmp (month, months[i], 3) == 0)
if (mu_c_strncasecmp (month, short_month[i], 3) == 0)
{
mon = i;
break;
......
......@@ -16,6 +16,7 @@ imapio
listop
mailcap
prop
strftime
url-comp
url-parse
wicket
......
......@@ -51,6 +51,7 @@ noinst_PROGRAMS = \
listop\
mailcap\
prop\
strftime\
tempfile\
url-comp\
url-parse\
......@@ -81,6 +82,7 @@ TESTSUITE_AT = \
list.at\
mailcap.at\
prop.at\
strftime.at\
testsuite.at\
url.at\
url-comp.at\
......
# This file is part of GNU Mailutils. -*- Autotest -*-
# Copyright (C) 2011 Free Software Foundation, Inc.
#
# GNU Mailutils is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 3, or (at
# your option) any later version.
#
# GNU Mailutils is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
AT_BANNER(mu_c_streamftime)
m4_pushdef([STRFTIME_TZ],[0])
dnl ------------------------------------------------------------------
dnl STRFTIME([FMT], [INPUT], [STDOUT = `'], [STDERR = `'])
dnl
m4_pushdef([STRFTIME],[
m4_pushdef([MU_TEST_GROUP],[streamftime])
m4_pushdef([MU_TEST_KEYWORDS],[strftime strftime-$1])
m4_pushdef([MU_TEST_COMMAND],[strftime -tz=STRFTIME_TZ -format='$1'])
MU_GENERIC_TEST([$1],[],[$2],[],[$3],[$4])
m4_popdef([MU_TEST_COMMAND])
m4_popdef([MU_TEST_KEYWORDS])
m4_popdef([MU_TEST_GROUP])
])
dnl ------------------------------------------------------------
# Test data
# January 1970
# Su Mo Tu We Th Fr Sa
# 1 2 3
# 4 5 6 7 8 9 10
# 11 12 13 14 15 16 17
# 18 19 20 21 22 23 24
# 25 26 27 28 29 30 31
#
# 1 0
# 2 86400
# 3 172800
# 4 259200
# 5 345600
# 6 432000
# 7 518400
# 8 604800
# 9 691200
# 10 777600
# 11 864000
# 12 950400
# 13 1036800
# 14 1123200
# 15 1209600
# 16 1296000
# 17 1382400
# 18 1468800
# 19 1555200
# 20 1641600
# 21 1728000
# 22 1814400
# 23 1900800
# 24 1987200
# 25 2073600
# 26 2160000
# 27 2246400
# 28 2332800
# 29 2419200
# 30 2505600
# 31 2592000
# November 1970
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5 6 7
# 8 9 10 11 12 13 14
# 15 16 17 18 19 20 21
# 22 23 24 25 26 27 28
# 29 30
# 1 26265600
# 2 26352000
# 3 26438400
# 4 26524800
# 5 26611200
# 6 26697600
# 7 26784000
# Months
# Jan 0
# Feb 2678400
# Mar 5097600
# Apr 7776000
# May 10368000
# Jun 13046400
# Jul 15638400
# Aug 18316800
# Sep 20995200
# Oct 23587200
# Nov 26265600
# Dec 28857600
#
dnl ------------------------------------------------------------
# The abbreviated weekday name.
STRFTIME([%a],
[26265600
26352000
26438400
26524800
26611200
26697600
26784000],
[Sun
Mon
Tue
Wed
Thu
Fri
Sat
])
# The full weekday name.
STRFTIME([%A],
[26265600
26352000
26438400
26524800
26611200
26697600
26784000],
[Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
])
The abbreviated month name.
STRFTIME([%b],
[0
2678400
5097600
7776000
10368000
13046400
15638400
18316800
20995200
23587200
26265600
28857600],
[Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
])
# The abbreviated month name.
STRFTIME([%h],
[0
2678400
5097600
7776000
10368000
13046400
15638400
18316800
20995200
23587200
26265600
28857600],
[Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
])
STRFTIME([%B],
[0
2678400
5097600
7776000
10368000
13046400
15638400
18316800
20995200
23587200
26265600
28857600],
[January
February
March
April
May
June
July
August
September
October
November
December
])
# The century number (year/100) as a 2-digit integer.
STRFTIME([%C],
[26697600
1322872282],
[19
20
])
# The day of the month as a decimal number (range 01 to 31).
STRFTIME([%d],
[26265600
28771200],
[01
30
])
# Like %d, the day of the month as a decimal number, but a leading
# zero is replaced by a space.
STRFTIME([%e],
[26265600
28771200],
[ 1
30
])
# A no-op modifier.
STRFTIME([%EC],
[26697600
1322872282],
[19
20
])
# The ISO 8601 year with century as a decimal number. The 4-digit
# year corresponding to the ISO week number (see %V). This has
# the same format and value as %y, except that if the ISO week
# number belongs to the previous or next year, that year is used
# instead.
# Test data:
# Date TS %V %G
# 1972-12-31 94608000 52 1972
#
# 1973-01-01 94694400 01 1973
# 1973-12-01 123552000 48 1973
# 1973-12-30 126057600 52 1973
# 1973-12-31 126144000 01 1974
#
# 1975-01-01 157766400 01 1975
# 1975-12-28 188956800 52 1975
# 1975-12-29 189043200 01 1976
# 1975-12-31 189216000 01 1976
#
# 1977-01-01 220924800 53 1976
# 1977-01-02 221011200 53 1976
# 1977-01-03 221097600 01 1977
# 1977-12-25 251856000 51 1977
# 1977-12-26 251942400 52 1977
# 1977-12-31 252374400 52 1977
STRFTIME([%G],
[;1972
94608000
;1973
94694400
123552000
126057600
126144000
;1975
157766400
188956800
189043200
189216000
;1977
220924800
221011200
221097600
251856000
251942400
252374400],
[;1972
1972
;1973
1973
1973
1973
1974
;1975
1975
1975
1976
1976
;1977
1976
1976
1977
1977
1977
1977
])
# Like %G, but without century, that is, with a 2-digit year (00-99).
STRFTIME([%g],
[;1972
94608000
;1973
94694400
123552000
126057600
126144000
;1975
157766400
188956800
189043200
189216000
;1977
220924800
221011200
221097600
251856000
251942400
252374400],
[;1972
72
;1973
73
73
73
74
;1975
75
75
76
76
;1977
76
76
77
77
77
77
])
# The hour as a decimal number using a 24-hour clock (range 00 to 23).
STRFTIME([%H],
[1322870400
1322874000
1322877600
1322881200
1322884800
1322888400
1322892000
1322895600
1322899200
1322902800
1322906400
1322910000
1322913600
1322917200
1322920800
1322924400
1322928000
1322931600
1322935200
1322938800
1322942400
1322946000
1322949600
1322953200],
[00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
])
# The hour as a decimal number using a 12-hour clock (range 01 to 12).
STRFTIME([%I],
[1322870400
1322874000
1322877600
1322881200
1322884800
1322888400
1322892000
1322895600
1322899200
1322902800
1322906400
1322910000
1322913600
1322917200
1322920800
1322924400
1322928000
1322931600
1322935200
1322938800
1322942400
1322946000
1322949600
1322953200],
[12
01
02
03
04
05
06
07
08
09
10
11
12
01
02
03
04
05
06
07
08
09
10
11
])
# The day of the year as a decimal number (range 001 to 366).
STRFTIME([%j],
[1322872282
1204329600],
[337
061
])
# The hour (24-hour clock) as a decimal number (range 0 to 23);
# single digits are preceded by a blank.
STRFTIME([%k],
[1322870400
1322874000
1322877600
1322881200
1322884800
1322888400
1322892000
1322895600
1322899200
1322902800
1322906400
1322910000
1322913600
1322917200
1322920800
1322924400
1322928000
1322931600
1322935200
1322938800
1322942400
1322946000
1322949600
1322953200],
[ 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
])
# The hour (12-hour clock) as a decimal number (range 1 to 12);
# single digits are preceded by a blank.
STRFTIME([%l],
[1322870400
1322874000
1322877600
1322881200
1322884800
1322888400
1322892000
1322895600
1322899200
1322902800
1322906400
1322910000
1322913600
1322917200
1322920800
1322924400
1322928000
1322931600
1322935200
1322938800
1322942400
1322946000
1322949600
1322953200],
[12
1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
])
# The month as a decimal number (range 01 to 12)
STRFTIME([%m],
[0
2678400
5097600
7776000
10368000
13046400
15638400
18316800
20995200
23587200
26265600
28857600],
[01
02
03
04
05
06
07
08
09
10
11
12
])
# The minute as a decimal number (range 00 to 59).
STRFTIME([%M],
[1322870400
1322870460
1322870520
1322870580
1322870640
1322870700
1322870760
1322870820
1322870880
1322870940
1322871000
1322871060
1322871120
1322871180
1322871240
1322871300
1322871360
1322871420
1322871480
1322871540
1322871600
1322871660
1322871720
1322871780
1322871840
1322871900
1322871960
1322872020
1322872080
1322872140
1322872200
1322872260
1322872320
1322872380
1322872440
1322872500
1322872560
1322872620
1322872680
1322872740
1322872800
1322872860
1322872920
1322872980
1322873040
1322873100
1322873160
1322873220
1322873280
1322873340
1322873400
1322873460
1322873520
1322873580
1322873640
1322873700
1322873760
1322873820
1322873880
1322873940],
[00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
])
# A newline character
STRFTIME([<%n>],
[0],
[<
>
])
# %O - a no-op modifier
STRFTIME([%OC],
[26697600
1322872282],
[19
20
])
# Either "AM" or "PM" according to the given time value.
# Noon is treated as "PM" and midnight as "AM".
STRFTIME([%p],
[1322870400
1322910000
1322913600
1322917200],
[AM
AM
PM
PM
])
# Like %p but in lowercase: "am" or "pm".
STRFTIME([%P],
[1322870400
1322910000
1322913600
1322917200],
[am
am
pm
pm
])
# The number of seconds since the Epoch
STRFTIME([%s],
[0
1322913600],
[0
1322913600
])
# The second as a decimal number (range 00 to 60)
STRFTIME([%S],
[1322870400
1322870401
1322870402
1322870403
1322870404
1322870405
1322870406
1322870407
1322870408
1322870409
1322870410
1322870411
1322870412
1322870413
1322870414
1322870415
1322870416
1322870417
1322870418
1322870419
1322870420
1322870421
1322870422
1322870423
1322870424
1322870425
1322870426
1322870427
1322870428
1322870429
1322870430
1322870431
1322870432
1322870433
1322870434
1322870435
1322870436
1322870437
1322870438
1322870439
1322870440
1322870441
1322870442
1322870443
1322870444
1322870445
1322870446
1322870447
1322870448
1322870449
1322870450
1322870451
1322870452
1322870453
1322870454
1322870455
1322870456
1322870457
1322870458
1322870459],
[00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
])
# A tab character
STRFTIME([<%t>],
[0],
[< >
])
# The day of the week as a decimal, range 1 to 7, Monday being 1.
STRFTIME([%u],
[26265600
26352000
26438400
26524800
26611200
26697600
26784000],
[7
1
2
3
4
5
6
])
# The week number of the current year as a decimal number, range
# 00 to 53, starting with the first Sunday as the first day of
# week 01.
#
# Test data are days of January of years from 1973 to 1978:
# Year Day of Jan 1st
# 1973 Mon
# 1974 Tue
# 1975 Wed
# 1976 Thu
# 1977 Sat
# 1978 Sun
STRFTIME([%U],
[; 1973 - Monday
94694400
94780800
94867200
94953600
95040000
95126400
95212800
95299200
95385600
95472000
95558400
95644800
95731200
95817600
95904000
95990400
96076800
96163200
96249600
96336000
96422400
96508800
96595200
96681600
96768000
96854400
96940800
97027200
97113600
97200000
97286400
; 1974 - Tuesday
126230400
126316800
126403200
126489600
126576000
126662400
126748800
126835200
126921600
127008000
127094400
127180800
127267200
127353600
127440000
127526400
127612800
127699200
127785600
127872000
127958400
128044800
128131200
128217600
128304000
128390400
128476800
128563200
128649600
128736000
128822400
; 1975 - Wednesday
157766400
157852800
157939200
158025600
158112000
158198400
158284800
158371200
158457600
158544000
158630400
158716800
158803200
158889600
158976000
159062400
159148800
159235200
159321600
159408000
159494400
159580800
159667200
159753600
159840000
159926400
160012800
160099200
160185600
160272000
160358400
; 1976 - Thursday
189302400
189388800
189475200
189561600
189648000
189734400
189820800
189907200
189993600
190080000
190166400
190252800
190339200
190425600
190512000
190598400
190684800
190771200
190857600
190944000
191030400
191116800
191203200
191289600
191376000
191462400
191548800
191635200
191721600
191808000
191894400
; 1977 - Saturday
220924800
221011200
221097600
221184000
221270400
221356800
221443200
221529600
221616000
221702400
221788800
221875200
221961600
222048000
222134400
222220800
222307200
222393600
222480000
222566400
222652800
222739200
222825600
222912000
222998400
223084800
223171200
223257600
223344000
223430400
223516800
; 1978 - Sunday
252460800
252547200
252633600
252720000
252806400
252892800
252979200
253065600
253152000
253238400
253324800
253411200
253497600
253584000
253670400
253756800
253843200
253929600
254016000
254102400
254188800
254275200
254361600
254448000
254534400
254620800
254707200
254793600
254880000
254966400
255052800],
[; 1973 - Monday
00
00
00
00
00
00
01
01
01
01
01
01
01
02
02
02
02
02
02
02
03
03
03
03
03
03
03
04
04
04
04
; 1974 - Tuesday
00
00
00
00
00
01
01
01
01
01
01
01
02
02
02
02
02
02
02
03
03
03
03
03
03
03
04
04
04
04
04
; 1975 - Wednesday
00
00
00
00
01
01
01
01
01
01
01
02
02
02
02
02
02
02
03
03
03
03
03
03
03
04
04
04
04
04
04
; 1976 - Thursday
00
00
00
01
01
01
01
01
01
01
02
02
02
02
02
02
02
03
03
03
03
03
03
03
04
04
04
04
04
04
04
; 1977 - Saturday
00
01
01
01
01
01
01
01
02
02
02
02
02
02
02
03
03
03
03
03
03
03
04
04
04
04
04
04
04
05
05
; 1978 - Sunday
01
01
01
01
01
01
01
02
02
02
02
02
02
02
03
03
03
03
03
03
03
04
04
04
04
04
04
04
05
05
05
])
# The ISO 8601:1988 week number of the current year as a decimal
# number range 01 to 53, where week 1 is the first week that has
# at least 4 days in the current year, and with Monday as the
# first day of the week.
#
# Test data:
# Date TS %V %G
# 1972-12-31 94608000 52 1972
#
# 1973-01-01 94694400 01 1973
# 1973-12-01 123552000 48 1973
# 1973-12-30 126057600 52 1973
# 1973-12-31 126144000 01 1974
#
# 1975-01-01 157766400 01 1975
# 1975-12-28 188956800 52 1975
# 1975-12-29 189043200 01 1976
# 1975-12-31 189216000 01 1976
#
# 1977-01-01 220924800 53 1976
# 1977-01-02 221011200 53 1976
# 1977-01-03 221097600 01 1977
# 1977-12-25 251856000 51 1977
# 1977-12-26 251942400 52 1977
# 1977-12-31 252374400 52 1977
STRFTIME([%V],
[;1972
94608000
;1973
94694400
123552000
126057600
126144000
;1975
157766400
188956800
189043200
189216000
;1977
220924800
221011200
221097600
251856000
251942400
252374400],
[;1972
52
;1973
01
48
52
01
;1975
01
52
01
01
;1977
53
53
01
51
52
52
])
# The day of the week as a decimal, range 0 to 6, Sunday being 0.
STRFTIME([%w],
[26265600
26352000
26438400
26524800
26611200
26697600
26784000],
[0
1
2
3
4
5
6
])
# The week number of the current year as a decimal number, range
# 00 to 53, starting with the first Monday as the first day of
# week 01.
STRFTIME([%W],
[; 1973 - Monday
94694400
94780800
94867200
94953600
95040000
95126400
95212800
95299200
95385600
95472000
95558400
95644800
95731200
95817600
95904000
95990400
96076800
96163200
96249600
96336000
96422400
96508800
96595200
96681600
96768000
96854400
96940800
97027200
97113600
97200000
97286400
; 1974 - Tuesday
126230400
126316800
126403200
126489600
126576000
126662400
126748800
126835200
126921600
127008000
127094400
127180800
127267200
127353600
127440000
127526400
127612800
127699200
127785600
127872000
127958400
128044800
128131200
128217600
128304000
128390400
128476800
128563200
128649600
128736000
128822400
; 1975 - Wednesday
157766400
157852800
157939200
158025600
158112000
158198400
158284800
158371200
158457600
158544000
158630400
158716800
158803200
158889600
158976000
159062400
159148800
159235200
159321600
159408000
159494400
159580800
159667200
159753600
159840000
159926400
160012800
160099200
160185600
160272000
160358400
; 1976 - Thursday
189302400
189388800
189475200
189561600
189648000
189734400
189820800
189907200
189993600
190080000
190166400
190252800
190339200
190425600
190512000
190598400
190684800
190771200
190857600
190944000
191030400
191116800
191203200
191289600
191376000
191462400
191548800
191635200
191721600
191808000
191894400
; 1977 - Saturday
220924800
221011200
221097600
221184000
221270400
221356800
221443200
221529600
221616000
221702400
221788800
221875200
221961600
222048000
222134400
222220800
222307200
222393600
222480000
222566400
222652800
222739200
222825600
222912000
222998400
223084800
223171200
223257600
223344000
223430400
223516800
; 1978 - Sunday
252460800
252547200
252633600
252720000
252806400
252892800
252979200
253065600
253152000
253238400
253324800
253411200
253497600
253584000
253670400
253756800
253843200
253929600
254016000
254102400
254188800
254275200
254361600
254448000
254534400
254620800
254707200
254793600
254880000
254966400
255052800],
[; 1973 - Monday
01
01
01
01
01
01
01
02
02
02
02
02
02
02
03
03
03
03
03
03
03
04
04
04
04
04
04
04
05
05
05
; 1974 - Tuesday
00
00
00
00
00
00
01
01
01
01
01
01
01
02
02
02
02
02
02
02
03
03
03
03
03
03
03
04
04
04
04
; 1975 - Wednesday
00
00
00
00
00
01
01
01
01
01
01
01
02
02
02
02
02
02
02
03
03
03
03
03
03
03
04
04
04
04
04
; 1976 - Thursday
00
00
00
00
01
01
01
01
01
01
01
02
02
02
02
02
02
02
03
03
03
03
03
03
03
04
04
04
04
04
04
; 1977 - Saturday
00
00
01
01
01
01
01
01
01
02
02
02
02
02
02
02
03
03
03
03
03
03
03
04
04
04
04
04
04
04
05
; 1978 - Sunday
00
01
01
01
01
01
01
01
02
02
02
02
02
02
02
03
03
03
03
03
03
03
04
04
04
04
04
04
04
05
05
])
# The year as a decimal number without a century (range 00 to 99).
STRFTIME([%y],
[10
80870400
1322870453],
[70
72
11
])
# The year as a decimal number including the century.
STRFTIME([%Y],
[10
80870400
1322870453],
[1970
1972
2011
])
# The time-zone as hour offset from GMT, for formatting RFC-822
# dates (e.g. "%a, %d %b %Y %H:%M:%S %z") */
m4_define([STRFTIME_TZ],[0200])
STRFTIME([%z],
[1322870453],
[+0200
])
m4_define([STRFTIME_TZ],[-0200])
STRFTIME([%z],
[1322870453],
[-0200
])
m4_define([STRFTIME_TZ],[0])
# A literal '%' character.
STRFTIME([<%%>],
[1],
[<%>
])
# Any other character after % is reproduced verbatim
STRFTIME([<%5d>],
[1],
[<%5d>
])
dnl ------------------------------------------------------------------
STRFTIME([Today is %A, %B %e %Y%n%H:%M:%S (%z).],
[1322879591],
[Today is Saturday, December 3 2011
02:33:11 (+0000).
])
dnl ------------------------------------------------------------------
# Equivalent to %a %b %e %H:%M:%S %Y
STRFTIME([%c],
[26697600],
[Fri Nov 6 00:00:00 1970
])
# Equivalent to %m/%d/%y
STRFTIME([%D],
[80870400
1322872282],
[07/25/72
12/03/11
])
# Equivalent to %Y-%m-%d (the ISO 8601 date format)
STRFTIME([%F],
[80870400
1322872282],
[1972-07-25
2011-12-03
])
# The time in a.m. or p.m. notation, i.e. %I:%M:%S %p. */
STRFTIME([%r],
[1322872282],
[12:31:22 AM
])
# The time in 24-hour notation (%H:%M)
STRFTIME([%R],
[1322872282],
[00:31
])
# The time in 24-hour notation (%H:%M:%S)
STRFTIME([%T],
[1322872282],
[00:31:22
])
# The preferred date representation without the time:
# equivalent to %D
STRFTIME([%x],
[1322872282],
[12/03/11
])
# The preferred date representation without the date:
# equivalent to %H:%M:%S
STRFTIME([%X],
[1322872282],
[00:31:22
])
dnl ------------------------------------------------------------------
m4_popdef([STRFTIME])
m4_popdef([STRFTIME_TZ])
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2007, 2009, 2010, 2011 Free Software
Foundation, Inc.
GNU Mailutils is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GNU Mailutils is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <mailutils/error.h>
#include <mailutils/errno.h>
#include <mailutils/util.h>
#include <mailutils/stream.h>
#include <mailutils/cctype.h>
#include <mailutils/cstr.h>
#include <mailutils/stdstream.h>
void
usage ()
{
mu_stream_printf (mu_strout, "usage: %s [-format=FMT] [-tz=TZ]\n",
mu_program_name);
exit (0);
}
int
main (int argc, char **argv)
{
int rc, i;
char *format = "%c";
char *buf = NULL;
size_t size = 0;
size_t n;
struct mu_timezone tz, *tzp = NULL;
mu_set_program_name (argv[0]);
mu_stdstream_setup (MU_STDSTREAM_RESET_NONE);
memset (&tz, 0, sizeof tz);
for (i = 1; i < argc; i++)
{
char *opt = argv[i];
if (strncmp (opt, "-format=", 8) == 0)
format = opt + 8;
else if (strncmp (opt, "-tz=", 4) == 0)
{
int sign;
int n = atoi (opt + 4);
if (n < 0)
{
sign = -1;
n = - n;
}
else
sign = 1;
tz.utc_offset = sign * ((n / 100 * 60) + n % 100) * 60;
tzp = &tz;
}
else if (strcmp (opt, "-h") == 0)
usage ();
else
{
mu_error ("%s: unrecognized argument", opt);
exit (1);
}
}
while ((rc = mu_stream_getline (mu_strin, &buf, &size, &n)) == 0 && n > 0)
{
char *p;
struct tm *tm;
time_t t;
mu_rtrim_class (buf, MU_CTYPE_ENDLN);
if (*buf == ';')
{
mu_printf ("%s\n", buf);
continue;
}
t = strtoul (buf, &p, 10);
if (*p)
{
mu_error ("bad input line near %s", p);
continue;
}
tm = gmtime (&t);
mu_c_streamftime (mu_strout, format, tm, tzp);
mu_printf ("\n");
}
if (rc)
{
mu_error ("%s", mu_strerror (rc));
return 1;
}
return 0;
}
......@@ -83,4 +83,6 @@ m4_include([debugspec.at])
AT_BANNER([IMAP IO])
m4_include([imapio.at])
m4_include([strftime.at])
m4_include([fsaf.at])
\ No newline at end of file
......