From acec689ef648adaa5c420049bfb775953e184bda Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Thu, 8 Dec 2011 17:04:29 +0200 Subject: Revamp date/time calculations. * configure.ac: Build libmailutils/datetime/Makefile.am * include/mailutils/datetime.h (mu_datetime_julianday) (mu_datetime_dayofweek,mu_datetime_dayofyear) (mu_datetime_year_days): New protos. * libmailutils/Makefile.am (SUBDIRS): Add datetime (libmailutils_la_LIBADD): Add libdatetime.la * libmailutils/base/Makefile.am (libbase_la_SOURCES): Remove date.c and strftime.c. * libmailutils/base/date.c: Remove. * libmailutils/base/strftime.c: Move to libmailutils/datetime * libmailutils/datetime/Makefile.am: New file. * libmailutils/datetime/dow.c: New file. * libmailutils/datetime/doy.c: New file. * libmailutils/datetime/jd.c: New file. * libmailutils/datetime/scantime.c: New file. * libmailutils/datetime/streamftime.c: New file. * libmailutils/datetime/tab.c: New file. * libmailutils/datetime/unixtime.c: New file. * libmailutils/datetime/utcoff.c: New file. * libmailutils/datetime/yd.c: New file. * libmailutils/tests/scantime.at: Fix yday numbers. --- libmailutils/datetime/streamftime.c | 411 ++++++++++++++++++++++++++++++++++++ 1 file changed, 411 insertions(+) create mode 100644 libmailutils/datetime/streamftime.c (limited to 'libmailutils/datetime/streamftime.c') diff --git a/libmailutils/datetime/streamftime.c b/libmailutils/datetime/streamftime.c new file mode 100644 index 000000000..e67d7d4d0 --- /dev/null +++ b/libmailutils/datetime/streamftime.c @@ -0,0 +1,411 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 1999, 2000, 2001, 2002, 2007, 2009, 2010, 2011 Free + Software Foundation, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library. If not, see + . */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} + +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, _mu_datetime_short_wday[tm.tm_wday], + strlen (_mu_datetime_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, _mu_datetime_full_wday[tm.tm_wday], + strlen (_mu_datetime_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, _mu_datetime_short_month[tm.tm_mon], + strlen (_mu_datetime_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, _mu_datetime_full_month[tm.tm_mon], + strlen (_mu_datetime_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 + + mu_datetime_year_days (year - 1), + tm.tm_wday); + year--; + } + else + { + int d = ISO_8601_weekdays (tm.tm_yday - + mu_datetime_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 '$': + /* Ignored for compatibilty with mu_scan_datetime */ + 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; +} -- cgit v1.2.1