view src/lib-mail/rfc822-date.c @ 0:3b1985cbc908 HEAD

Initial revision
author Timo Sirainen <tss@iki.fi>
date Fri, 09 Aug 2002 12:15:38 +0300
parents
children 6be018ca51ef
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "lib.h"
#include "gmtoff.h"
#include "rfc822-date.h"
#include "rfc822-tokenize.h"

#include <ctype.h>

static const char *month_names[] = {
	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

static const char *weekday_names[] = {
	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};

static int parse_timezone(const char *str, unsigned int len)
{
	int offset;
	char chr;

	if (len == 5 && (*str == '+' || *str == '-')) {
		/* numeric offset */
		offset = (str[1]-'0') * 1000 + (str[2]-'0') * 100 +
			(str[3]-'0') * 10 + (str[4]-'0');
		return *str == '+' ? offset : -offset;
	}

	if (len == 1) {
		/* military zone - handle them the correct way, not as
		   RFC822 says. RFC2822 though suggests that they'd be
		   considered as unspecified.. */
		chr = i_toupper(*str);
		if (chr < 'J')
			return (*str-'A'+1) * 60;
		if (chr == 'J')
			return 0;
		if (chr <= 'M')
			return (*str-'A') * 60;
		if (chr < 'Z')
			return ('M'-*str) * 60;
		return 0;
	}

	if (len == 2 && i_toupper(str[0]) == 'U' && i_toupper(str[1]) == 'T') {
		/* UT - Universal Time */
		return 0;
	}

	if (len == 3) {
		/* GMT | [ECMP][DS]T */
		if (str[2] != 'T')
			return 0;

		switch (i_toupper(*str)) {
		case 'E':
			offset = -5 * 60;
			break;
		case 'C':
			offset = -6 * 60;
			break;
		case 'M':
			offset = -7 * 60;
			break;
		case 'P':
			offset = -8 * 60;
			break;
		default:
			/* GMT and others */
			return 0;
		}

		if (i_toupper(str[1]) == 'D')
			return offset + 60;
		if (i_toupper(str[1] == 'S'))
			return offset;
	}

	return 0;
}

static const Rfc822Token *next_token(const Rfc822Token **tokens)
{
	const Rfc822Token *ret;

	if ((*tokens)->token == 0)
		return NULL;

	ret = *tokens;
	(*tokens)++;
	return ret;
}

int rfc822_parse_date(const char *str, time_t *time)
{
	struct tm tm;
	const Rfc822Token *tokens, *tok;
	unsigned int i;
	int zone_offset;

	if (str == NULL || *str == '\0')
		return FALSE;

	/* [weekday_name "," ] dd month_name [yy]yy hh:mi[:ss] timezone

	   don't waste time checking it too properly, if there's errors we
	   either pick them up at mktime() or have an invalid timestamp,
	   which would happen anyway.

	   we support comments here even while no-one ever uses them */

	tokens = rfc822_tokenize(str, NULL, NULL, NULL);

	memset(&tm, 0, sizeof(tm));

	/* skip the optional weekday */
	tok = next_token(&tokens);
	if (tok != NULL && tok->token == 'A' && tok->len == 3) {
		tok = next_token(&tokens);
		if (tok == NULL || tok->token != ',')
			return FALSE;

		tok = next_token(&tokens);
	}

	/* dd */
	if (tok == NULL || tok->token != 'A' || tok->len != 2)
		return FALSE;
	tm.tm_mday = (tok->ptr[0]-'0') * 10 + tok->ptr[1]-'0';

	/* month name */
	tok = next_token(&tokens);
	if (tok == NULL || tok->token != 'A' || tok->len != 3)
		return FALSE;

	for (i = 0; i < 12; i++) {
		if (strncasecmp(month_names[i], tok->ptr, 3) == 0) {
			tm.tm_mon = i;
			break;
		}
	}
	if (i == 12)
		return FALSE;

	/* [yy]yy */
	tok = next_token(&tokens);
	if (tok == NULL || tok->token != 'A')
		return FALSE;

	for (i = 0; i < tok->len; i++)
		tm.tm_year = tm.tm_year * 10 + tok->ptr[i]-'0';

	if (tok->len == 2) {
		/* two digit year, assume 1970+ */
		if (tm.tm_year < 70)
			tm.tm_year += 100;
	} else if (tok->len != 4) {
		/* y10k bug here */
		tm.tm_year -= 1900;
		return FALSE;
	}

	/* hh */
	tok = next_token(&tokens);
	if (tok == NULL || tok->token != 'A' || tok->len != 2)
		return FALSE;
	tm.tm_hour = (tok->ptr[0]-'0') * 10 + tok->ptr[1]-'0';

	/* :mm */
	tok = next_token(&tokens);
	if (tok == NULL || tok->token != ':')
		return FALSE;
	tok = next_token(&tokens);
	if (tok == NULL || (tok->token != 'A' && tok->len != 2))
		return FALSE;
	tm.tm_min = (tok->ptr[0]-'0') * 10 + tok->ptr[1]-'0';

	/* [:ss] */
	tok = next_token(&tokens);
	if (tok != NULL && tok->token == ':') {
		tok = next_token(&tokens);
		if (tok == NULL || (tok->token != 'A' && tok->len != 2))
			return FALSE;
		tm.tm_sec = (tok->ptr[0]-'0') * 10 + tok->ptr[1]-'0';
	}

	/* timezone */
	if (tok == NULL || tok->token != 'A')
		return FALSE;

	zone_offset = parse_timezone(tok->ptr, tok->len);

	tm.tm_isdst = -1;
	*time = mktime(&tm);
	if (*time < 0)
		return FALSE;

	*time -= zone_offset * 60;
	return TRUE;
}

const char *rfc822_to_date(time_t time)
{
	struct tm *tm;
	int offset, negative;

	tm = localtime(&time);
	offset = gmtoff(tm, time);
	if (offset >= 0)
		negative = 0;
	else {
		negative = 1;
		offset = -offset;
	}
	offset /= 60;

	return t_strdup_printf("%s, %02d %s %04d %02d:%02d:%02d %c%02d%02d",
			       weekday_names[tm->tm_wday],
			       tm->tm_mday,
			       month_names[tm->tm_mon],
			       tm->tm_year+1900,
			       tm->tm_hour, tm->tm_min, tm->tm_sec,
			       negative ? '-' : '+', offset / 60, offset % 60);
}