diff --git a/src/lib-sieve/plugins/date/Makefile.am b/src/lib-sieve/plugins/date/Makefile.am
index 8d1f731225a554fffcd18709b9eaf8fcfa778c55..3d761c3f4ba2da2a455d04bf52cb3b7872b9890a 100644
--- a/src/lib-sieve/plugins/date/Makefile.am
+++ b/src/lib-sieve/plugins/date/Makefile.am
@@ -12,6 +12,7 @@ tests = \
 
 libsieve_ext_date_la_SOURCES = \
 	$(tests) \
+	ext-date-common.c \
 	ext-date.c
 
 noinst_HEADERS = \
diff --git a/src/lib-sieve/plugins/date/ext-date-common.c b/src/lib-sieve/plugins/date/ext-date-common.c
new file mode 100644
index 0000000000000000000000000000000000000000..efea31680dfa3aca495532523885e981d3509b7c
--- /dev/null
+++ b/src/lib-sieve/plugins/date/ext-date-common.c
@@ -0,0 +1,355 @@
+/* Copyright (c) 2002-2009 Dovecot Sieve authors, see the included COPYING file
+ */
+
+#include "lib.h"
+
+#include "sieve-common.h"
+#include "sieve-interpreter.h"
+#include "sieve-message.h"
+
+#include "ext-date-common.h"
+
+#include <time.h>
+#include <ctype.h>
+
+struct ext_date_context {
+	time_t current_date;
+};
+
+/*
+ * Runtime initialization
+ */
+
+static void ext_date_runtime_init
+(const struct sieve_runtime_env *renv, void *context ATTR_UNUSED)
+{
+	struct ext_date_context *dctx;
+	pool_t pool;
+	time_t current_date;
+
+	/* Get current time at instance main script is started */
+	time(&current_date);	
+
+	/* Create context */
+	pool = sieve_message_context_pool(renv->msgctx);
+	dctx = p_new(pool, struct ext_date_context, 1);
+	dctx->current_date = current_date;
+
+	sieve_message_context_extension_set
+		(renv->msgctx, &date_extension, (void *) dctx);
+}
+
+static struct sieve_interpreter_extension date_interpreter_extension = {
+	&date_extension,
+	ext_date_runtime_init,
+	NULL,
+};
+
+bool ext_date_interpreter_load
+(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED)
+{	
+	if ( renv->msgctx == NULL ||
+		sieve_message_context_extension_get(renv->msgctx, &date_extension)
+		== NULL ) {
+		sieve_interpreter_extension_register
+			(renv->interp, &date_interpreter_extension, NULL);
+	}
+
+	return TRUE;
+}
+
+/*
+ * Zone string
+ */
+
+bool ext_date_parse_timezone
+(const char *zone, int *zone_offset_r)
+{
+	const unsigned char *str = (const unsigned char *) zone;
+	size_t len = strlen(zone);
+
+	if (len == 5 && (*str == '+' || *str == '-')) {
+		int offset;
+
+		if (!i_isdigit(str[1]) || !i_isdigit(str[2]) ||
+		    !i_isdigit(str[3]) || !i_isdigit(str[4]))
+			return FALSE;
+
+		offset = ((str[1]-'0') * 10 + (str[2]-'0')) * 60  +
+			(str[3]-'0') * 10 + (str[4]-'0');
+
+		if ( zone_offset_r != NULL )		
+			*zone_offset_r = *str == '+' ? offset : -offset;
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/*
+ * Current date
+ */
+
+time_t ext_date_get_current_date(const struct sieve_runtime_env *renv)
+{	
+	struct ext_date_context *dctx = (struct ext_date_context *) 
+		sieve_message_context_extension_get(renv->msgctx, &date_extension);
+
+	i_assert( dctx != NULL );
+
+	return dctx->current_date;
+}
+
+/* 
+ * Date parts 
+ */
+
+/* "year"      => the year, "0000" .. "9999". 
+ */
+
+static const char *ext_date_year_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part year_date_part = {
+	"year",
+	ext_date_year_part_get
+};
+
+/* "month"     => the month, "01" .. "12".
+ */
+
+static const char *ext_date_month_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part month_date_part = {
+	"month",
+	ext_date_month_part_get
+};
+
+/* "day"       => the day, "01" .. "31".
+ */
+
+static const char *ext_date_day_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part day_date_part = {
+	"day",
+	ext_date_day_part_get
+};
+
+/* "date"      => the date in "yyyy-mm-dd" format.
+ */
+
+static const char *ext_date_date_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part date_date_part = {
+	"date",
+	ext_date_date_part_get
+};
+
+/* "julian"    => the Modified Julian Day, that is, the date
+ *              expressed as an integer number of days since
+ *              00:00 UTC on November 17, 1858 (using the Gregorian
+ *              calendar).  This corresponds to the regular
+ *              Julian Day minus 2400000.5.  Sample routines to
+ *              convert to and from modified Julian dates are
+ *              given in Appendix A.
+ */ 
+
+static const char *ext_date_julian_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part julian_date_part = {
+	"julian",
+	ext_date_julian_part_get
+};
+
+/* "hour"      => the hour, "00" .. "23". 
+ */
+static const char *ext_date_hour_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part hour_date_part = {
+	"hour",
+	ext_date_hour_part_get
+};
+
+/* "minute"    => the minute, "00" .. "59".
+ */
+static const char *ext_date_minute_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part minute_date_part = {
+	"minute",
+	ext_date_minute_part_get
+};
+
+/* "second"    => the second, "00" .. "60".
+ */
+static const char *ext_date_second_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part second_date_part = {
+	"second",
+	ext_date_second_part_get
+};
+
+/* "time"      => the time in "hh:mm:ss" format.
+ */
+static const char *ext_date_time_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part time_date_part = {
+	"time",
+	ext_date_time_part_get
+};
+
+/* "iso8601"   => the date and time in restricted ISO 8601 format.
+ */
+static const char *ext_date_iso8601_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part iso8601_date_part = {
+	"iso8601",
+	ext_date_iso8601_part_get
+};
+
+/* "std11"     => the date and time in a format appropriate
+ *                for use in a Date: header field [RFC2822].
+ */
+static const char *ext_date_std11_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part std11_date_part = {
+	"std11",
+	ext_date_std11_part_get
+};
+
+/* "zone"      => the time zone in use.  If the user specified a
+ *                time zone with ":zone", "zone" will
+ *                contain that value.  If :originalzone is specified
+ *                this value will be the original zone specified
+ *                in the date-time value.  If neither argument is
+ *                specified the value will be the server's default
+ *                time zone in offset format "+hhmm" or "-hhmm".  An
+ *                 offset of 0 (Zulu) always has a positive sign.
+ */
+static const char *ext_date_zone_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part zone_date_part = {
+	"zone",
+	ext_date_zone_part_get
+};
+ 
+/* "weekday"   => the day of the week expressed as an integer between
+ *                "0" and "6". "0" is Sunday, "1" is Monday, etc.
+ */
+static const char *ext_date_weekday_part_get(struct tm *tm, int zone_offset);
+
+static const struct ext_date_part weekday_date_part = {
+	"weekday",
+	ext_date_weekday_part_get
+};
+
+/*
+ * Date part extraction
+ */
+
+static const struct ext_date_part *date_parts[] = {
+	&year_date_part, &month_date_part, &day_date_part, &date_date_part,
+	&julian_date_part, &hour_date_part, &minute_date_part, &second_date_part,
+	&iso8601_date_part, &std11_date_part, &zone_date_part, &weekday_date_part 
+};
+
+unsigned int date_parts_count = N_ELEMENTS(date_parts);
+
+const char *ext_date_part_extract
+(const char *part, struct tm *tm, int zone_offset)
+{
+	unsigned int i;
+
+	for ( i = 0; i < date_parts_count; i++ ) {
+		if ( strcasecmp(date_parts[i]->identifier, part) == 0 ) {
+			if ( date_parts[i]->get_string != NULL )
+				return date_parts[i]->get_string(tm, zone_offset);
+
+			return NULL;
+		}
+	}
+	
+	return NULL;
+}
+
+/*
+ * Date part implementations
+ */
+
+static const char *ext_date_year_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%04d", tm->tm_year + 1900);
+}
+
+static const char *ext_date_month_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%02d", tm->tm_mon);
+}
+
+static const char *ext_date_day_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%02d", tm->tm_mday);
+}
+
+static const char *ext_date_date_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%04d-%02d-%02d", 
+		tm->tm_year + 1900, tm->tm_mon, tm->tm_mday);
+}
+
+static const char *ext_date_julian_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return "";
+}
+
+static const char *ext_date_hour_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%02d", tm->tm_hour);
+}
+
+static const char *ext_date_minute_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%02d", tm->tm_min);
+}
+
+static const char *ext_date_second_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%02d", tm->tm_sec);
+}
+
+static const char *ext_date_time_part_get
+(struct tm *tm, int zone_offset ATTR_UNUSED)
+{
+	return t_strdup_printf("%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
+}
+
+static const char *ext_date_iso8601_part_get
+(struct tm *tm, int zone_offset)
+{
+	return "";
+}
+
+static const char *ext_date_std11_part_get
+(struct tm *tm, int zone_offset)
+{
+	return "";
+}
+
+static const char *ext_date_zone_part_get
+(struct tm *tm ATTR_UNUSED, int zone_offset)
+{
+	return "";
+}
+
+static const char *ext_date_weekday_part_get(struct tm *tm, int zone_offset)
+{
+	return "";
+}
+
diff --git a/src/lib-sieve/plugins/date/ext-date-common.h b/src/lib-sieve/plugins/date/ext-date-common.h
index 81001c44ac8a698ba6d575f28360e9c90a8ca73a..9d8d54e94f5d554bbe96f4477b31009e7956d67b 100644
--- a/src/lib-sieve/plugins/date/ext-date-common.h
+++ b/src/lib-sieve/plugins/date/ext-date-common.h
@@ -4,12 +4,19 @@
 #ifndef __EXT_DATE_COMMON_H
 #define __EXT_DATE_COMMON_H
 
+#include "sieve-common.h"
+
+#include <time.h>
+
 /*
  * Extension
  */
  
 extern const struct sieve_extension date_extension;
 
+bool ext_date_interpreter_load
+	(const struct sieve_runtime_env *renv, sieve_size_t *address ATTR_UNUSED);
+
 /* 
  * Tests
  */
@@ -29,4 +36,29 @@ enum ext_date_opcode {
 extern const struct sieve_operation date_operation;
 extern const struct sieve_operation currentdate_operation;
 
+/*
+ * Zone string
+ */
+
+bool ext_date_parse_timezone(const char *zone, int *zone_offset_r);
+
+/*
+ * Current date
+ */
+
+time_t ext_date_get_current_date(const struct sieve_runtime_env *renv);
+
+/*
+ * Date part
+ */
+
+struct ext_date_part {
+	const char *identifier;
+
+	const char *(*get_string)(struct tm *tm, int zone_offset);
+};
+
+const char *ext_date_part_extract
+	(const char *part, struct tm *tm, int zone_offset);
+
 #endif /* __EXT_DATE_COMMON_H */
diff --git a/src/lib-sieve/plugins/date/ext-date.c b/src/lib-sieve/plugins/date/ext-date.c
index a899fb0cd199b979c4aa4c3da2aa32e410f16af4..5d809688f4f1ddf0439ba821d506af210b0b7096 100644
--- a/src/lib-sieve/plugins/date/ext-date.c
+++ b/src/lib-sieve/plugins/date/ext-date.c
@@ -48,7 +48,9 @@ const struct sieve_extension date_extension = {
 	&ext_date_my_id,
 	NULL, NULL,
 	ext_date_validator_load, 
-	NULL, NULL, NULL, NULL, NULL,
+	NULL, 
+	ext_date_interpreter_load, 
+	NULL, NULL, NULL,
 	SIEVE_EXT_DEFINE_OPERATIONS(ext_date_operations), 
 	SIEVE_EXT_DEFINE_NO_OPERANDS
 };
diff --git a/src/lib-sieve/plugins/date/tst-date.c b/src/lib-sieve/plugins/date/tst-date.c
index ecc78d18d603fdebc3f0f01fb13c25d7de628393..7f7b5ab09fd2f525f8c27d4ed1c02a3eb54fae02 100644
--- a/src/lib-sieve/plugins/date/tst-date.c
+++ b/src/lib-sieve/plugins/date/tst-date.c
@@ -3,6 +3,7 @@
 
 #include "lib.h"
 #include "str-sanitize.h"
+#include "message-date.h"
 
 #include "sieve-common.h"
 #include "sieve-commands.h"
@@ -18,6 +19,8 @@
 
 #include "ext-date-common.h"
 
+#include <time.h>
+
 /*
  * Tests
  */
@@ -146,25 +149,26 @@ static bool tag_zone_validate
 (struct sieve_validator *validator, struct sieve_ast_argument **arg,
     struct sieve_command_context *cmd)
 {
-    struct sieve_ast_argument *tag = *arg;
+	struct sieve_ast_argument *tag = *arg;
 
-    if ( (bool) cmd->data ) {
+	if ( (bool) cmd->data ) {
 		if ( cmd->command == &date_test ) {
 			sieve_argument_validate_error(validator, *arg,
 				"multiple :zone or :originalzone arguments specified for "
 				"the currentdate test");
 		} else {
-	        sieve_argument_validate_error(validator, *arg,
-    	        "multiple :zone arguments specified for the currentdate test");
+			sieve_argument_validate_error(validator, *arg,
+				"multiple :zone arguments specified for the currentdate test");
 		}
 		return FALSE;
-    }
+	}
 
 	/* Skip tag */
  	*arg = sieve_ast_argument_next(*arg);
 
 	/* :content tag has a string-list argument */
 	if ( tag->argument == &date_zone_tag ) {
+
 		/* Check syntax:
 		 *   :zone <time-zone: string>
 		 */
@@ -173,7 +177,18 @@ static bool tag_zone_validate
 			return FALSE;
 		}
 
-        /* Assign tag parameters */
+		/* Check it */
+		if ( sieve_argument_is_string_literal(*arg) ) {
+			const char *zone = sieve_ast_argument_strc(*arg);
+	
+			if ( !ext_date_parse_timezone(zone, NULL) ) {
+				sieve_argument_validate_warning(validator, *arg,
+					"specified :zone argument '%s' is not a valid timezone",
+					str_sanitize(zone, 40));
+			}		
+		}
+	
+		/* Assign tag parameters */
 		tag->parameters = *arg;
 		*arg = sieve_ast_arguments_detach(*arg,1);
 	} 
@@ -319,15 +334,14 @@ static bool tst_date_operation_dump
 	sieve_code_descend(denv);
 	
 	/* Handle any optional arguments */
-    do {
-
-        if ( !sieve_match_dump_optional_operands(denv, address, &opt_code) )
-            return FALSE;
+  do {
+		if ( !sieve_match_dump_optional_operands(denv, address, &opt_code) )
+			return FALSE;
 
-        switch ( opt_code ) {
-        case SIEVE_MATCH_OPT_END:
-            break;
-        case OPT_DATE_ZONE:
+		switch ( opt_code ) {
+		case SIEVE_MATCH_OPT_END:
+			break;
+		case OPT_DATE_ZONE:
 			operand = sieve_operand_read(denv->sbin, address);
 			if ( operand == NULL ) {
 				sieve_code_dumpf(denv, "ERROR: INVALID OPERAND");
@@ -337,16 +351,15 @@ static bool tst_date_operation_dump
 			if ( sieve_operand_is_omitted(operand) ) {
 				sieve_code_dumpf(denv, "zone: ORIGINAL");
 			} else {
-   				if ( !sieve_opr_string_dump_data
+				if ( !sieve_opr_string_dump_data
 					(denv, operand, address, "zone") )
 					return FALSE;
 			}
 			break;
-
-        default:
-            return FALSE;
-        }
-    } while ( opt_code != SIEVE_MATCH_OPT_END );
+    default:
+			return FALSE;
+		}
+	} while ( opt_code != SIEVE_MATCH_OPT_END );
 
 	if ( op == &date_operation &&
 		!sieve_opr_string_dump(denv, address, "header name") )
@@ -365,15 +378,19 @@ static int tst_date_operation_execute
 (const struct sieve_operation *op, 
 	const struct sieve_runtime_env *renv, sieve_size_t *address)
 {	
-	bool result = TRUE;
+	bool result = TRUE, matched = FALSE;
 	int opt_code = 0;
+	const struct sieve_message_data *msgdata = renv->msgdata;
 	const struct sieve_comparator *cmp = &i_ascii_casemap_comparator;
 	const struct sieve_match_type *mtch = &is_match_type;
 	const struct sieve_operand *operand;
 	struct sieve_match_context *mctx;
 	string_t *header_name = NULL, *date_part = NULL, *zone = NULL;
 	struct sieve_coded_stringlist *key_list;
-	bool matched = FALSE;
+	time_t date_value;
+	struct tm *date_tm;
+	const char *part_value;
+	int zone_offset = 0;
 	int ret;
 	
 	/* Read optional operands */
@@ -398,7 +415,7 @@ static int tst_date_operation_execute
 					sieve_runtime_trace_error(renv, "invalid zone operand");
 					return SIEVE_EXEC_BIN_CORRUPT;
 				} 
-    		}
+			}
 			break;
 		default:
 			sieve_runtime_trace_error(renv, "unknown optional operand");
@@ -431,8 +448,80 @@ static int tst_date_operation_execute
 
 	sieve_runtime_trace(renv, "%s test", op->mnemonic);
 
-	/* FIXME: implement */
+	/* Get the date value */
+
+	if ( op == 	&date_operation ) {
+		const char *header_value;
+		const char *date_string;
+
+		/* Get date from the message */
 
-	sieve_interpreter_set_test_result(renv->interp, matched);
-	return SIEVE_EXEC_OK;
+		/* Read first header
+		 *   NOTE: need something for index extension to hook into some time. 
+		 */
+		if ( (ret = mail_get_first_header
+			(msgdata->mail, str_c(header_name), &header_value)) < 0 ) {
+			/* No such header, test failed */
+			sieve_interpreter_set_test_result(renv->interp, FALSE);
+			return SIEVE_EXEC_OK;
+		}
+
+		/* Extract the date string value */
+		date_string = strrchr(header_value, ';');
+		if ( date_string == NULL )
+			/* Direct header value */
+			date_string = header_value;
+		else {
+			/* Delimited by ';', e.g. a Received: header */
+			date_string++; 
+		}
+
+		/* Parse the date value */
+		if ( !message_date_parse((const unsigned char *) date_string,
+			strlen(date_string), &date_value, &zone_offset) ) {
+			/* Uparseable addres, test failed */
+			sieve_interpreter_set_test_result(renv->interp, FALSE);
+			return SIEVE_EXEC_OK;
+		}
+
+	} else if ( op == &currentdate_operation ) {
+		/* Use time stamp recorded at the time the script first started */
+
+		date_value = ext_date_get_current_date(renv);
+
+	} else {
+		i_unreached();
+	}
+
+	if ( (date_tm=gmtime(&date_value)) == NULL ) {
+		sieve_interpreter_set_test_result(renv->interp, FALSE);
+		return SIEVE_EXEC_OK;
+	}
+
+	/* Extract the date part */
+	part_value = ext_date_part_extract(str_c(date_part), date_tm, zone_offset);
+
+	/* Initialize match */
+	mctx = sieve_match_begin(renv->interp, mtch, cmp, NULL, key_list); 	
+			
+	/* Match value */
+	if ( (ret=sieve_match_value(mctx, part_value, strlen(part_value))) < 0 )
+		result = FALSE;
+	else
+		matched = ret > 0;				
+
+	/* Finish match */
+	if ( (ret=sieve_match_end(&mctx)) < 0 ) 
+		result = FALSE;
+	else
+		matched = ( ret > 0 || matched );
+	
+	/* Set test result for subsequent conditional jump */
+	if ( result ) {
+		sieve_interpreter_set_test_result(renv->interp, matched);
+		return SIEVE_EXEC_OK;
+	}	
+
+	sieve_runtime_trace_error(renv, "invalid string-list item");
+	return SIEVE_EXEC_BIN_CORRUPT;
 }