/* test_time.c -- regression test for cyrus time routines
 *
 * Copyright (c) 2010 Carnegie Mellon University.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The name "Carnegie Mellon University" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For permission or any legal
 *    details, please contact
 *      Carnegie Mellon University
 *      Center for Technology Transfer and Enterprise Creation
 *      4615 Forbes Avenue
 *      Suite 302
 *      Pittsburgh, PA  15213
 *      (412) 268-7393, fax: (412) 268-7395
 *      innovation@andrew.cmu.edu
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Computing Services
 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <config.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include "cunit/cyrunit.h"
#include "times.h"
#include "cunit/timezones.h"

#define UNINIT_TIMET ((time_t)0xdeadbeef)

static int set_up(void)
{
    /*
     * Ensure that libc's idea of which timezone we're in is
     * predictable, in case any of the tests here depend on
     * it.  The timezone is faked up to simulate Australian
     * Eastern Standard time, only because I live there.
     */
    push_tz(TZ_MELBOURNE);
    return 0;
}

static int tear_down(void)
{
    restore_tz();
    return 0;
}


static void
test_rfc3501(void)
{
    time_t t;
    int r;

    /* Well-formed full RFC3501 format with 2-digit day
     * "dd-mmm-yyyy HH:MM:SS zzzzz" */
    t = UNINIT_TIMET;
    r = time_from_rfc3501("15-Oct-2010 03:19:52 +1100", &t);
    CU_ASSERT_EQUAL(r, 26);
    CU_ASSERT_EQUAL(t, 1287073192);

    /* Well-formed full RFC3501 format with 1-digit day
     * " d-mmm-yyyy HH:MM:SS zzzzz" */
    t = UNINIT_TIMET;
    r = time_from_rfc3501(" 5-Oct-2010 03:19:52 +1100", &t);
    CU_ASSERT_EQUAL(r, 26);
    CU_ASSERT_EQUAL(t, 1286209192);
}

static void
test_military_timezones(void)
{
    time_t t;
    int r;
    time_t zulu = 813727192;

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * uppercase 1-char timezone = UTC, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc3501("15-Oct-95 03:19:52 Z", &t);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * lowercase 1-char timezone = UTC, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc3501("15-Oct-95 03:19:52 z", &t);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * uppercase 1-char timezone = +0100, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc3501("15-Oct-95 03:19:52 A", &t);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu-1*3600);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * uppercase 1-char timezone = +0200, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc3501("15-Oct-95 03:19:52 B", &t);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu-2*3600);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * uppercase 1-char timezone = +0900, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc3501("15-Oct-95 03:19:52 I", &t);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu-9*3600);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * erroneous uppercase 1-char timezone, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc3501("15-Oct-95 03:19:52 J", &t);
    CU_ASSERT_EQUAL(r, -1);
    CU_ASSERT_EQUAL(t, UNINIT_TIMET);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * uppercase 1-char timezone = +1000, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc3501("15-Oct-95 03:19:52 K", &t);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu-10*3600);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * 1-char timezone = +1200, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc3501("15-Oct-95 03:19:52 M", &t);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu-12*3600);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * uppercase 1-char timezone = -0100, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc3501("15-Oct-95 03:19:52 N", &t);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu+1*3600);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * 1-char timezone = -1200, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc3501("15-Oct-95 03:19:52 Y", &t);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu+12*3600);
}

/*  " d-mmm-yy HH:MM:SS-z" */
/*  "dd-mmm-yy HH:MM:SS-zz" */
/*  " d-mmm-yy HH:MM:SS-zz" */
/*  "dd-mmm-yy HH:MM:SS-zzz" */
/*  " d-mmm-yy HH:MM:SS-zzz" */

/*
 * Test time_from_rfc822()
 */
static void test_parse_rfc822(void)
{
    static const char DATETIME[] = "Tue, 16 Nov 2010 12:46:49 +1100";
    static const time_t TIMET = 1289872009;
    time_t t;
    int r;

    /*
     * Convert the datetime string into a time_t, which is always
     * expressed in UTC regardless of the current timezone.
     */
    t = UNINIT_TIMET;
    r = time_from_rfc822(DATETIME, &t);
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, TIMET);

    push_tz(TZ_UTC);
    t = UNINIT_TIMET;
    r = time_from_rfc822(DATETIME, &t);
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, TIMET);
    pop_tz();

    push_tz(TZ_NEWYORK);
    t = UNINIT_TIMET;
    r = time_from_rfc822(DATETIME, &t);
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, TIMET);
    pop_tz();
}

/*
 * Test time_to_rfc822()
 */
static void test_gen_rfc822(void)
{
    static const char DATETIME_MEL[] = "Fri, 26 Nov 2010 14:22:02 +1100";
    static const char DATETIME_UTC[] = "Fri, 26 Nov 2010 03:22:02 +0000";
    static const char DATETIME_NYC[] = "Thu, 25 Nov 2010 22:22:02 -0500";
    static const time_t TIMET = 1290741722;
    int r;
    char buf[RFC822_DATETIME_MAX+1];

    memset(buf, 0x45, sizeof(buf));
    r = time_to_rfc822(TIMET, buf, sizeof(buf));
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_STRING_EQUAL(buf, DATETIME_MEL);

    push_tz(TZ_UTC);
    memset(buf, 0x45, sizeof(buf));
    r = time_to_rfc822(TIMET, buf, sizeof(buf));
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_STRING_EQUAL(buf, DATETIME_UTC);
    pop_tz();

    push_tz(TZ_NEWYORK);
    memset(buf, 0x45, sizeof(buf));
    r = time_to_rfc822(TIMET, buf, sizeof(buf));
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_STRING_EQUAL(buf, DATETIME_NYC);
    pop_tz();
}


static void test_ctime(void)
{
    static const char DATETIME[] = "16-Nov-2010 13:15:25 +1100";
    static const time_t TIMET = 1289873725;
    char buf[RFC3501_DATETIME_MAX+1];

    memset(buf, 0, sizeof(buf));
    time_to_rfc3501(TIMET, buf, sizeof(buf));
    CU_ASSERT_STRING_EQUAL(buf, DATETIME);
}


/*
 * Seen in the wild, generated by Apple Mail: a date
 * which is a valid UNIX time_t because it's in the first
 * hour of 1970 in UTC, but expressed in a timezone which
 * makes it be in the last day of 1969.  This is weird
 * and probably technically valid but does not need to
 * be supported, but we do need to fail gracefully.
 */
static void test_zerohour(void)
{
    static const char DATETIME_NY[] = "Wed, 31 Dec 1969 19:36:29 -0500";
    static const char DATETIME_MEL[] = " 1-Jan-1970 11:36:29 +1100";
    static const time_t TIMET = 2189;
    time_t t;
    int r;
    char buf[RFC3501_DATETIME_MAX+1];

    t = UNINIT_TIMET;
    r = time_from_rfc822(DATETIME_NY, &t);
    CU_ASSERT_EQUAL(r, -1);
    CU_ASSERT_EQUAL(t, UNINIT_TIMET);  /* fail gracefully */

    push_tz(TZ_UTC);
    t = UNINIT_TIMET;
    r = time_from_rfc822(DATETIME_NY, &t);
    CU_ASSERT_EQUAL(r, -1);
    CU_ASSERT_EQUAL(t, UNINIT_TIMET);  /* fail gracefully */
    pop_tz();

    push_tz(TZ_NEWYORK);
    t = UNINIT_TIMET;
    r = time_from_rfc822(DATETIME_NY, &t);
    CU_ASSERT_EQUAL(r, -1);
    CU_ASSERT_EQUAL(t, UNINIT_TIMET);  /* fail gracefully */
    pop_tz();

    memset(buf, 0, sizeof(buf));
    time_to_rfc3501(TIMET, buf, sizeof(buf));
    CU_ASSERT_STRING_EQUAL(buf, DATETIME_MEL);
}

static void
test_parse_iso8601(void)
{
    static const char DATETIME_MEL[] = "2010-11-26T14:22:02+11:00";
    static const char DATETIME_UTC[] = "2010-11-26T03:22:02Z";
    static const char DATETIME_NYC[] = "2010-11-25T22:22:02-05:00";
    static const time_t TIMET = 1290741722;
    time_t t;
    int r;

    t = UNINIT_TIMET;
    r = time_from_iso8601(DATETIME_MEL, &t);
    CU_ASSERT_EQUAL(r, 25);
    CU_ASSERT_EQUAL(t, TIMET);

    t = UNINIT_TIMET;
    r = time_from_iso8601(DATETIME_UTC, &t);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, TIMET);

    t = UNINIT_TIMET;
    r = time_from_iso8601(DATETIME_NYC, &t);
    CU_ASSERT_EQUAL(r, 25);
    CU_ASSERT_EQUAL(t, TIMET);

    static const char DATETIME_NEG[] = "1969-12-31T23:59:59Z";
    t = UNINIT_TIMET;
    r = time_from_iso8601(DATETIME_NEG, &t);
    CU_ASSERT_EQUAL(r, 20);
    struct tm *tm = gmtime(&t);
    CU_ASSERT_EQUAL(tm->tm_year, 69);

    static const char DATETIME_NEG2[] = "1965-01-02T03:04:05Z";
    t = UNINIT_TIMET;
    r = time_from_iso8601(DATETIME_NEG2, &t);
    CU_ASSERT_EQUAL(r, 20);
    tm = gmtime(&t);
    CU_ASSERT_EQUAL(tm->tm_year, 65);
}

static void
test_gen_iso8601(void)
{
    static const char DATETIME_UTC[] = "2010-11-26T03:22:02Z";
    static const time_t TIMET = 1290741722;
    char buf[30];
    int r;

    r = time_to_iso8601(TIMET, buf, sizeof(buf), 1);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_STRING_EQUAL(buf, DATETIME_UTC);
}

static void
test_gen_iso8601_secfrac(void)
{
    static const char DATETIME_STR[] = "2010-11-26T14:22:02.123+11:00";
    static const struct timeval TIMEVAL = { 1290741722, 123456 };
    char buf[33];
    int r;

    r = timeval_to_iso8601(&TIMEVAL, timeval_ms, buf, sizeof(buf));
    CU_ASSERT_EQUAL(r, strlen(DATETIME_STR));
    CU_ASSERT_STRING_EQUAL(buf, DATETIME_STR);
}

/* Test that the 29 Feb works in some actual leap years,
 * and not in some not-leap years */
static void
test_leapyear_rfc3501(void)
{
    /* 2000 is a leapyear */
    static const char FEB2000_STR[] = "29-Feb-2000 11:22:33 +1100";
    static const time_t FEB2000_TIMET = 951783753;
    /* 2001 is not a leapyear */
    static const char FEB2001_STR[] = "29-Feb-2001 11:22:33 +1100";
    /* 2004 is a leapyear */
    static const char FEB2004_STR[] = "29-Feb-2004 11:22:33 +1100";
    static const time_t FEB2004_TIMET = 1078014153;
    time_t t;
    int r;

    t = UNINIT_TIMET;
    r = time_from_rfc3501(FEB2000_STR, &t);
    CU_ASSERT_EQUAL(r, 26);
    CU_ASSERT_EQUAL(t, FEB2000_TIMET);

    t = UNINIT_TIMET;
    r = time_from_rfc3501(FEB2001_STR, &t);
    CU_ASSERT_EQUAL(r, -1);
    CU_ASSERT_EQUAL(t, UNINIT_TIMET);

    t = UNINIT_TIMET;
    r = time_from_rfc3501(FEB2004_STR, &t);
    CU_ASSERT_EQUAL(r, 26);
    CU_ASSERT_EQUAL(t, FEB2004_TIMET);
}

static void
test_leapyear_iso8601(void)
{
    /* 2000 is a leapyear */
    static const char FEB2000_STR[] = "2000-02-29T11:22:33+11:00";
    static const time_t FEB2000_TIMET = 951783753;
    /* 2001 is not a leapyear */
    static const char FEB2001_STR[] = "2001-02-29T11:22:33+11:00";
    /* 2004 is a leapyear */
    static const char FEB2004_STR[] = "2004-02-29T11:22:33+11:00";
    static const time_t FEB2004_TIMET = 1078014153;
    time_t t;
    int r;

    t = UNINIT_TIMET;
    r = time_from_iso8601(FEB2000_STR, &t);
    CU_ASSERT_EQUAL(r, 25);
    CU_ASSERT_EQUAL(t, FEB2000_TIMET);

    t = UNINIT_TIMET;
    r = time_from_iso8601(FEB2001_STR, &t);
    CU_ASSERT_EQUAL(r, -1);
    CU_ASSERT_EQUAL(t, UNINIT_TIMET);

    t = UNINIT_TIMET;
    r = time_from_iso8601(FEB2004_STR, &t);
    CU_ASSERT_EQUAL(r, 25);
    CU_ASSERT_EQUAL(t, FEB2004_TIMET);
}

static void
test_leapyear_rfc822(void)
{
    /* 2000 is a leapyear */
    static const char FEB2000_STR[] = "Tue, 29 Feb 2000 11:22:33 +1100";
    static const time_t FEB2000_TIMET = 951783753;
    /* 2001 is not a leapyear */
    static const char FEB2001_STR[] = "Thu, 29 Feb 2001 11:22:33 +1100";
    /* 2004 is a leapyear */
    static const char FEB2004_STR[] = "Sun, 29 Feb 2004 11:22:33 +1100";
    static const time_t FEB2004_TIMET = 1078014153;
    time_t t;
    int r;

    t = UNINIT_TIMET;
    r = time_from_rfc822(FEB2000_STR, &t);
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, FEB2000_TIMET);

    t = UNINIT_TIMET;
    r = time_from_rfc822(FEB2001_STR, &t);
    CU_ASSERT_EQUAL(r, -1);
    CU_ASSERT_EQUAL(t, UNINIT_TIMET);

    t = UNINIT_TIMET;
    r = time_from_rfc822(FEB2004_STR, &t);
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, FEB2004_TIMET);
}

static void
test_rfc5322(void)
{
    time_t t;
    int r;

    /* 1 Jan 1970 */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("Thu, 01 Jan 1970 00:00:00  ", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 27);
    CU_ASSERT_EQUAL(t, 0);

    t = UNINIT_TIMET;
    r = time_from_rfc5322("Thu, 01 Jan 1970 01:00:00 +0100", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, 0);

    /* Zero Hour - we don't allow it any more, because the date returns negative */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("WED, 31 DEC 1969 19:36:29 -0500", &t, DATETIME_FULL); /* NYC */
    CU_ASSERT_EQUAL(r, -1);

    t = UNINIT_TIMET;
    r = time_from_rfc5322(" 1-JAN-1970 11:36:29 +1100", &t, DATETIME_FULL); /* MEL */
    CU_ASSERT_EQUAL(r, 26);
    CU_ASSERT_EQUAL(t, 2189);

    /* Pre Jan 1 1970 - we don't allow */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("WED, 31 DEC 1969 19:36:29", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, -1);

    /* Well-formed full RFC5322 format with 2-digit day
     * "dd-mmm-yyyy HH:MM:SS zzzzz" */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("15-Oct-2010 03:19:52 +1100", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 26);
    CU_ASSERT_EQUAL(t, 1287073192);

    /* Well-formed full RFC5322 format with 1-digit day
     * " d-mmm-yyyy HH:MM:SS zzzzz" */
    t = UNINIT_TIMET;
    r = time_from_rfc5322(" 5-Oct-2010 03:19:52 +1100", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 26);
    CU_ASSERT_EQUAL(t, 1286209192);

    /* dd mmm yyyy HH:MM:SS zzzzz */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("20 Jun 2017 00:49:38 +0000", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 26);
    CU_ASSERT_EQUAL(t, 1497919778);

    /* dow, dd mmm yyyy HH:MM:SS zzzzz */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("Tue, 20 Jun 2017 00:49:38 +0000", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, 1497919778);

    t = UNINIT_TIMET;
    r = time_from_rfc5322("Tue, 20 Jun 2017 00:49:38 +0200", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, 1497912578);

    /* https://github.com/cyrusimap/cyrus-imapd/issues/2247 */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("Tue, 20  Jun  2017 00:49:38 +0000", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 33);
    CU_ASSERT_EQUAL(t, 1497919778);

    /* Timezone tests - same time different timezones*/
    t = UNINIT_TIMET;
    r = time_from_rfc5322("FRI, 26 NOV 2010 14:22:02 +1100", &t, DATETIME_FULL); /* MEL */
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, 1290741722);


    t = UNINIT_TIMET;
    r = time_from_rfc5322("FRI, 26 NOV 2010 03:22:02 +0000", &t, DATETIME_FULL); /* UTC */
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, 1290741722);

    t = UNINIT_TIMET;
    r = time_from_rfc5322("THU, 25 NOV 2010 22:22:02 -0500", &t, DATETIME_FULL); /* NYC */
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, 1290741722);

    t = UNINIT_TIMET;
    r = time_from_rfc5322("THU, 25 NOV 2010 22:22:02 EST", &t, DATETIME_FULL); /* NYC */
    CU_ASSERT_EQUAL(r, 29);
    CU_ASSERT_EQUAL(t, 1290741722);


    /* Time with period as separator */
    t = UNINIT_TIMET;
    r = time_from_rfc5322(" 3-jan-2009 04.05    -0400", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 26);
    CU_ASSERT_EQUAL(t, 1230969900);

#if SIZEOF_TIME_T >= 8
    /* Year 9999 */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("Fri, 31-Dec-9999 23:59:59 +0000", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, 253402300799);
#endif

    /* Year 1 - This will fail*/
    t = UNINIT_TIMET;
    r = time_from_rfc5322("1 Jan 1 00:00:00 +0000", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, -1);

#if SIZEOF_TIME_T >= 8
    /* 5 digit year */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("Sat, 1 Jan 10000 00:00:00", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 25);
    CU_ASSERT_EQUAL(t, 253402300800);
#endif

    /* Invalid date */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("foobar, 2 +5000", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, -1);

    t = UNINIT_TIMET;
    r = time_from_rfc5322("Sun, Hello, World!", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, -1);

    t = UNINIT_TIMET;
    r = time_from_rfc5322("-1000", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, -1);

    t = UNINIT_TIMET;
    r = time_from_rfc5322("Thu, 06 Jul 2017", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, -1);

    t = UNINIT_TIMET;
    r = time_from_rfc5322("This is some random text", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, -1);

    t = UNINIT_TIMET;
    r = time_from_rfc5322("0100200200300400502989587984579845", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, -1);
}

static void
test_military_timezones_using_rfc5322(void)
{
    time_t t;
    int r;
    time_t zulu = 813727192;

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * uppercase 1-char timezone = UTC, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("15-Oct-95 03:19:52 Z", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * lowercase 1-char timezone = UTC, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("15-Oct-95 03:19:52 z", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * uppercase 1-char timezone = +0100, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("15-Oct-95 03:19:52 A", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu-1*3600);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * uppercase 1-char timezone = +0200, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("15-Oct-95 03:19:52 B", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu-2*3600);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * uppercase 1-char timezone = +0900, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("15-Oct-95 03:19:52 I", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu-9*3600);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * erroneous uppercase 1-char timezone, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("15-Oct-95 03:19:52 J", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, 813727192);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * uppercase 1-char timezone = +1000, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("15-Oct-95 03:19:52 K", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu-10*3600);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * 1-char timezone = +1200, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("15-Oct-95 03:19:52 M", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu-12*3600);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * uppercase 1-char timezone = -0100, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("15-Oct-95 03:19:52 N", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu+1*3600);

    /* Well-formed legacy format with 2-digit day, 2-digit year,
     * 1-char timezone = -1200, "dd-mmm-yy HH:MM:SS z" */
    t = UNINIT_TIMET;
    r = time_from_rfc5322("15-Oct-95 03:19:52 Y", &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 20);
    CU_ASSERT_EQUAL(t, zulu+12*3600);
}

static void
test_leapyear_rfc5322(void)
{
    /* 2000 is a leapyear */
    static const char FEB2000_STR[] = "Tue, 29 Feb 2000 11:22:33 +1100";
    static const time_t FEB2000_TIMET = 951783753;
    /* 2001 is not a leapyear */
    static const char FEB2001_STR[] = "Thu, 29 Feb 2001 11:22:33 +1100";
    /* 2004 is a leapyear */
    static const char FEB2004_STR[] = "Sun, 29 Feb 2004 11:22:33 +1100";
    static const time_t FEB2004_TIMET = 1078014153;
    time_t t;
    int r;

    t = UNINIT_TIMET;
    r = time_from_rfc5322(FEB2000_STR, &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, FEB2000_TIMET);

    /* This will be converted to: Thu Mar  1 11:22:33 2001 */
    t = UNINIT_TIMET;
    r = time_from_rfc5322(FEB2001_STR, &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, 983406153);

    t = UNINIT_TIMET;
    r = time_from_rfc5322(FEB2004_STR, &t, DATETIME_FULL);
    CU_ASSERT_EQUAL(r, 31);
    CU_ASSERT_EQUAL(t, FEB2004_TIMET);
}

static void
test_offsettime_iso8601(void)
{
#define TESTCASE(ts, want_n, want_wday, want_yday) \
    { \
        const char *_ts = (ts); \
        int _want_n = (want_n); \
        int _want_wday = (want_wday); \
        int _want_yday = (want_yday); \
        struct offsettime _ot; \
        char buf[30]; \
        int r = offsettime_from_iso8601(_ts, &_ot); \
        CU_ASSERT_EQUAL(_want_n, r); \
        CU_ASSERT_EQUAL(_want_wday, _ot.tm.tm_wday); \
        CU_ASSERT_EQUAL(_want_yday, _ot.tm.tm_yday); \
        r = offsettime_to_iso8601(&_ot, buf, sizeof(buf), 1); \
        CU_ASSERT_EQUAL(_want_n, r); \
        CU_ASSERT_STRING_EQUAL(_ts, buf); \
    }

    TESTCASE("2019-05-02T03:15:00+07:00", 25, 4, 122);
    TESTCASE("2010-11-26T14:22:02+11:00", 25, 5, 330);
    TESTCASE("2010-11-26T03:22:02Z", 20, 5, 330);
    TESTCASE("2010-11-25T22:22:02-05:00", 25, 4, 329);
    TESTCASE("1969-12-31T23:59:59Z", 20, 3, 365);

#undef TESTCASE
}

static void
test_offsettime_rfc5322(void)
{
#define TESTCASE(ts, want_n, want_wday, want_yday) \
    { \
        const char *_ts = (ts); \
        int _want_n = (want_n); \
        int _want_wday = (want_wday); \
        int _want_yday = (want_yday); \
        struct offsettime _ot; \
        char buf[RFC822_DATETIME_MAX+1]; \
        int r = offsettime_from_rfc5322(_ts, &_ot, DATETIME_FULL); \
        CU_ASSERT_EQUAL(_want_n, r); \
        CU_ASSERT_EQUAL(_want_wday, _ot.tm.tm_wday); \
        CU_ASSERT_EQUAL(_want_yday, _ot.tm.tm_yday); \
        r = offsettime_to_rfc5322(&_ot, buf, sizeof(buf)); \
        CU_ASSERT_EQUAL(_want_n, r); \
        CU_ASSERT_STRING_EQUAL(_ts, buf); \
    }

    TESTCASE("Thu, 02 May 2019 03:15:00 +0700", 31, 4, 122);
    TESTCASE("Fri, 26 Nov 2010 14:22:02 +1100", 31, 5, 330);
    TESTCASE("Fri, 26 Nov 2010 03:22:02 +0000", 31, 5, 330);
    TESTCASE("Thu, 25 Nov 2010 22:22:02 -0500", 31, 4, 329);
    TESTCASE("Wed, 31 Dec 1969 23:59:59 +0000", 31, 3, 365);

#undef TESTCASE
}

static void
test_offsettime_rfc5322_ignore_wday(void)
{
#define TESTCASE(ts, want_wday, want_yday) \
    { \
        const char *_ts = (ts); \
        int _want_wday = (want_wday); \
        int _want_yday = (want_yday); \
        struct offsettime _ot; \
        int r = offsettime_from_rfc5322(_ts, &_ot, DATETIME_FULL); \
        CU_ASSERT(r > 0); \
        CU_ASSERT_EQUAL(_want_wday, _ot.tm.tm_wday); \
        CU_ASSERT_EQUAL(_want_yday, _ot.tm.tm_yday); \
    }

    /* Bogus week days are ignored */
    TESTCASE("Sat, 02 May 2019 03:15:00 +0700", 4, 122); // Should be "Thu"


#undef TESTCASE
}

/* vim: set ft=c: */
