/* -*- indent-tabs-mode: nil -*- */
/*
  qore_date_private.cpp

  Qore Programming Language

  Copyright (C) 2003 - 2018 Qore Technologies, s.r.o.

  Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files (the "Software"),
  to deal in the Software without restriction, including without limitation
  the rights to use, copy, modify, merge, publish, distribute, sublicense,
  and/or sell copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  DEALINGS IN THE SOFTWARE.

  Note that the Qore library is released under a choice of three open-source
  licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
  information.
*/

#include <qore/Qore.h>
#include "qore/intern/qore_date_private.h"

#include <sys/time.h>
#include <errno.h>
#include <string.h>

const char *STATIC_UTC = "UTC";

const int qore_date_info::month_lengths[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
const int qore_date_info::positive_months[] = { 0,  31,  59,  90,  120,  151,  181,  212,  243,  273,  304,  334,  365 };
const int qore_date_info::negative_months[] = { 0, -31, -61, -92, -122, -153, -184, -214, -245, -275, -306, -334, -365 };

struct date_s {
   const char *long_name;
   const char *abbr;
   const char *upper_long_name;
   const char *upper_abbr;
};

// month names (in English)
static const struct date_s months[] = {
   { "January", "Jan", "JANUARY", "JAN" },
   { "February", "Feb", "FEBRUARY", "FEB" },
   { "March", "Mar", "MARCH", "MAR" },
   { "April", "Apr", "APRIL", "APR" },
   { "May", "May", "MAY", "MAY" },
   { "June", "Jun", "JUNE", "JUN" },
   { "July", "Jul", "JULY", "JUL" },
   { "August", "Aug", "AUGUST", "AUG" },
   { "September", "Sep", "SEPTEMBER", "SEP" },
   { "October", "Oct", "OCTOBER", "OCT" },
   { "November", "Nov", "NOVEMBER", "NOV" },
   { "December", "Dec", "DECEMBER", "DEC" }
};

// day names (in English!) FIXME: add locale-awareness!
static const struct date_s days[] = {
   { "Sunday", "Sun", "SUNDAY", "SUN" },
   { "Monday", "Mon", "MONDAY", "MON" },
   { "Tuesday", "Tue", "TUESDAY", "TUE" },
   { "Wednesday", "Wed", "WEDNESDAY", "WED" },
   { "Thursday", "Thu", "THURSDAY", "THU" },
   { "Friday", "Fri", "FRIDAY", "FRI" },
   { "Saturday", "Sat", "SATURDAY", "SAT" }
};

static int ampm(int hour) {
   int i = hour % 12;
   return i ? i : 12;
}

static int get_uint(const char *&p, int digits) {
   int rc = 0;
   for (int i = 0; i < digits; ++i) {
      if (*p < '0' || *p > '9')
         return -1;
      rc = (10 * rc) + ((*p) - '0');
      ++p;
   }
   return rc;
}

static int get_int(const char *&p, bool &err) {
   int rc = 0;

   int sign = 1;
   if (*p == '-') {
      sign = -1;
      ++p;
   }

   if (!isdigit(*p)) {
      err = true;
      return 0;
   }

   do {
      rc = (10 * rc) + ((*p) - '0');
      ++p;
   } while (isdigit(*p));

   return rc * sign;
}

bool qore_date_info::isLeapYear(int year) {
   if (!(year % 100))
      return !(year % 400) ? true : false;
   return (year % 4) ? false : true;
}

int qore_date_info::getMonthIxFromAbbr(const char* abbr) {
   for (int i = 0; i < 12; ++i) {
      if (!strcasecmp(months[i].abbr, abbr))
         return i;
   }
   // not found
   return -1;
}

void qore_absolute_time::set(const AbstractQoreZoneInfo* n_zone, const QoreValue v) {
   switch (v.type) {
      case QV_Bool: {
         zone = n_zone;
         epoch = v.v.b ? 1 : 0;
         us = 0;
         break;
      }
      case QV_Int: {
         set(n_zone, v.v.i, 0);
         break;
      }
      case QV_Float: {
         set(v.v.f, n_zone);
         break;
      }
      case QV_Node:
         switch (get_node_type(v.v.n)) {
            case NT_BOOLEAN: {
               zone = n_zone;
               epoch = reinterpret_cast<const QoreBoolNode*>(v.v.n)->getValue();
               us = 0;
               break;
            }
            case NT_INT: {
               set(n_zone, reinterpret_cast<const QoreBigIntNode*>(v.v.n)->val, 0);
               break;
            }
            case NT_FLOAT: {
               double f = reinterpret_cast<const QoreFloatNode*>(v.v.n)->f;
               set(f, n_zone);
               break;
            }
            case NT_STRING: {
               const char* str = reinterpret_cast<const QoreStringNode*>(v.v.n)->getBuffer();
               set(str, n_zone);
               break;
            }
            case NT_DATE: {
               const DateTimeNode* d = reinterpret_cast<const DateTimeNode*>(v.v.n);
               if (d->priv->relative) {
                  int64 us = d->priv->d.rel.getRelativeMicroseconds();
                  int64 s = us / 1000000;
                  us -= s * 1000000;
                  set(n_zone, s, us);
               }
               else
                  set(d->priv->d.abs);
               break;
            }
            default: {
               double f = v.v.n ? v.v.n->getAsFloat() : 0.0;
               set(f, n_zone);
               break;
            }
         }
         break;
      default:
         assert(false);
         break;
   }
}

void qore_absolute_time::set(const char* str, const AbstractQoreZoneInfo* n_zone, ExceptionSink* xsink) {
   size_t len = strlen(str);

   // we need at least YYYYMMDD
   if (len < 8) {
      if (xsink)
         xsink->raiseException("INVALID-DATE", "date '%s' is too short; need at least 8 digits for a date/time value (ex: YYYYMMDD)", str);
      set(n_zone, 0, 0);
      return;
   }

   const char* p = str;

   int year = get_uint(p, 4);
   if (year < 0) {
      if (xsink)
         xsink->raiseException("INVALID-DATE", "date '%s': cannot parse year value", str);
      set(n_zone, 0, 0);
      return;
   }

   bool needs_sep = false;
   if (*p == '-') {
      needs_sep = true;
      ++p;
   }

   int month = get_uint(p, 2);
   if (month < 0) {
      if (xsink)
         xsink->raiseException("INVALID-DATE", "date '%s': cannot parse month value; expecting 1 - 12 inclusive", str);
      set(n_zone, year, 1, 1, 0, 0, 0, 0);
      return;
   }

   if (xsink && (month < 1 || month > 12)) {
      xsink->raiseException("INVALID-DATE", "date '%s' provides an invalid month value: %d; expecting 1 - 12 inclusive", str, month);
      set(n_zone, year, month, 1, 0, 0, 0, 0);
      return;
   }

   if (needs_sep) {
      if (*p == '-')
         ++p;
      else {
         if (xsink) {
            if (*p)
               xsink->raiseException("INVALID-DATE", "cannot parse date string '%s'; encountered unknown char '%c'", str, *p);
            else
               xsink->raiseException("INVALID-DATE", "cannot parse date string '%s'; encountered end of data after month", str);
         }

         set(n_zone, year, month, 1, 0, 0, 0, 0);
         return;
      }
   }

   int day = get_uint(p, 2);
   if (day < 0) {
      if (xsink)
         xsink->raiseException("INVALID-DATE", "date '%s': cannot parse day value", str);
      set(n_zone, year, month, 1, 0, 0, 0, 0);
      return;
   }

   // check if day is valid
   if (xsink) {
      if (day < 1) {
         xsink->raiseException("INVALID-DATE", "date '%s' provides an invalid day of the month: %d; %04d-%02 has %d days", str, day, year, month);
         set(n_zone, year, month, day, 0, 0, 0, 0);
         return;
      }
      else if (day > 28) {
         // check how many days in the given month
         int dom = qore_date_info::getLastDayOfMonth(month, year);
         if (day > dom) {
            xsink->raiseException("INVALID-DATE", "date '%s' provides an invalid day of the month: %d; %04d-%02 has %d days", str, day, year, month, dom);
            set(n_zone, year, month, day, 0, 0, 0, 0);
            return;
         }
      }
   }

   //printd(5, "set: date: %04d-%02d-%02d\n", year, month, day);

   if (*p == 'Z' || (*p == ' ' && *(p + 1) == 'Z')) {
      set(0, year, month, day, 0, 0, 0, 0);
      return;
   }

   if (*p == ' ' || *p == 't' || *p == 'T' || *p == '-')
      ++p;

   int hour = get_uint(p, 2);
   if (hour < 0) {
      set(n_zone, year, month, day, 0, 0, 0, 0);
      return;
   }

   if (xsink && hour > 23) {
      xsink->raiseException("INVALID-DATE", "date '%s' provides an invalid hour value: %d; expecting 0 - 23 inclusive", str, hour);
      set(n_zone, year, month, day, hour, 0, 0, 0);
      return;
   }

   needs_sep = false;
   if (*p == ':') {
      needs_sep = true;
      ++p;
   }

   int minute = get_uint(p, 2);
   if (minute < 0) {
      if (xsink)
         xsink->raiseException("INVALID-DATE", "date '%s': cannot parse minute value; expecting 0 - 59 inclusive", str);
      set(n_zone, year, month, day, hour, 0, 0, 0);
      return;
   }

   if (xsink && minute > 59)
      xsink->raiseException("INVALID-DATE", "date '%s' provides an invalid minute value: %d; expecting 0 - 59 inclusive", str, minute);

   if (needs_sep) {
      if (*p == ':')
         ++p;
      else {
         if (*p && xsink)
            xsink->raiseException("INVALID-DATE", "cannot parse date/time string '%s'; encountered unknown char '%c'", str, *p);
         set(n_zone, year, month, day, hour, minute, 0, 0);
         return;
      }
   }

   int second = get_uint(p, 2);
   if (second < 0) {
      set(n_zone, year, month, day, hour, minute, 0, 0);
      return;
   }

   if (xsink && second > 59) {
      xsink->raiseException("INVALID-DATE", "date '%s' provides an invalid second value: %d; expecting 0 - 59 inclusive", str, second);
      set(n_zone, year, month, day, hour, minute, second, 0);
      return;
   }

   if (!*p) {
      set(n_zone, year, month, day, hour, minute, second, 0);
      return;
   }

   int us = 0;
   if (*p == '.') {
      ++p;
      if (!isdigit(*p)) {
         if (xsink) {
            if (*p)
               xsink->raiseException("INVALID-DATE", "cannot parse date/time string '%s'; encountered unknown char '%c' when parsing microseconds", str, *p);
            else
               xsink->raiseException("INVALID-DATE", "cannot parse date/time string '%s'; encountered end of data after decimal point", str);
         }
         set(n_zone, year, month, day, hour, minute, second, 0);
         return;
      }

      // read all digits
      int dlen = 0;
      while (isdigit(*p)) {
         // ignore excess digits beyond microsecond resolution
         if (dlen < 6) {
            us *= 10;
            us += *p - '0';
         }
         ++dlen;
         ++p;
      }

      // adjust to microseconds
      while (dlen < 6) {
         us *= 10;
         ++dlen;
      }

      if (!*p) {
         set(n_zone, year, month, day, hour, minute, second, us);
         return;
      }
   }

   // get time zone offset

   // read
   if (*p == ' ')
      ++p;

   if (*p == 'Z') {
      n_zone = 0;
      ++p;
   }
   else if (*p == '+' || *p == '-') {
      int mult = *p == '-' ? -1 : 1;

      ++p;
      if (!isdigit(*p)) {
         if (xsink) {
            if (*p)
               xsink->raiseException("INVALID-DATE", "cannot parse date/time string '%s'; encountered unknown char '%c' when parsing the UTC offset", str, *p);
            else
               xsink->raiseException("INVALID-DATE", "cannot parse date/time string '%s'; encountered end of data when parsing the UTC offset", str);
         }
         set(n_zone, year, month, day, hour, minute, second, us);
         return;
      }

      int utc_h = *p - '0';
      ++p;
      if (isdigit(*p)) {
         utc_h = utc_h * 10 + (*p - '0');
         ++p;
      }

      int offset = utc_h * 3600;

      if (*p) {
         if (*p == ':')
            ++p;

         if (!isdigit(*p)) {
            if (xsink) {
               if (*p)
                  xsink->raiseException("INVALID-DATE", "cannot parse date/time string '%s'; encountered unknown char '%c' when parsing the UTC offset minutes", str, *p);
               else
                  xsink->raiseException("INVALID-DATE", "cannot parse date/time string '%s'; encountered end of data when parsing the UTC offset minutes", str);
            }
            // ignore any time zone passed
            n_zone = findCreateOffsetZone(offset * mult);
            set(n_zone, year, month, day, hour, minute, second, us);
            return;
         }

         int utc_m = *p - '0';
         ++p;
         if (isdigit(*p)) {
            utc_m = utc_m * 10 + (*p - '0');
            ++p;
         }

         offset += utc_m * 60;

         if (*p) {
            if (*p == ':')
               ++p;

            if (!isdigit(*p)) {
               // ignore any time zone passed
               n_zone = findCreateOffsetZone(offset * mult);
               set(n_zone, year, month, day, hour, minute, second, us);
               return;
            }

            int utc_s = *p - '0';
            ++p;
            if (isdigit(*p)) {
               utc_s = utc_s * 10 + (*p - '0');
               ++p;
            }

            offset += utc_s;
         }
      }

      // ignore any time zone passed
      n_zone = findCreateOffsetZone(offset * mult);
   }

   set(n_zone, year, month, day, hour, minute, second, us);
}

int64 qore_absolute_time::getRelativeMicroseconds() const {
   // find the difference between gmtime and our time
   struct timeval tv;
   if (gettimeofday(&tv, 0)) {
      printd(0, "qore_absolute_time::getRelativeMicroseconds() gettimeofday() failed: %s\n", strerror(errno));
      return 0;
   }

   int64 ds = (epoch - tv.tv_sec) * 1000000 + (us - tv.tv_usec);
   return ds < 0 ? 0 : ds;
}

void qore_absolute_time::getAsString(QoreString &str) const {
   qore_time_info i;
   get(i);

   str.sprintf("%04d-%02d-%02d %02d:%02d:%02d.%06d", i.year, i.month, i.day, i.hour, i.minute, i.second, i.us);
   const char *wday = days[qore_date_info::getDayOfWeek(i.year, i.month, i.day)].abbr;
   str.sprintf(" %s ", wday);
   concatOffset(i.utcoffset, str);
   // only concat zone name if it's not the same as the offset just output
   if (*i.zname != '+' && *i.zname != '-')
      str.sprintf(" (%s)", i.zname);
}

qore_absolute_time &qore_absolute_time::operator+=(const qore_relative_time &dt) {
   int usecs;

   // break down date and do day, month, and year math
   if (dt.year || dt.month || dt.day) {
      // get the broken-down date values for the date in local time
      qore_simple_tm2 tm(epoch + AbstractQoreZoneInfo::getUTCOffset(zone, epoch), us);

#ifdef DEBUG
      // only needed by the debugging statement at the bottom
      //int64 oe=epoch;
#endif
      //printd(5, "absolute_time::operator+= this=%p %lld.%06d (%d) %04d-%02d-%02d %02d:%02d:%02d.%06d (+%dY %dM %dD %dh %dm %ds %dus)\n", this, epoch, us, zone ? zone->getUTCOffset(epoch) : 0, tm.year, tm.month, tm.day, tm.hour, tm.minute, tm.second, tm.us, dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.us);

      // add years, months, and days
      tm.year += dt.year;
      tm.month += dt.month;
      // normalize to the end of the month
      normalize_dm(tm.year, tm.month, tm.day);

      tm.day += dt.day;
      // normalize to the correct day, month, and year
      normalize_day(tm.year, tm.month, tm.day);

      // get epoch offset for same time on the target day
      epoch = qore_date_info::getEpochSeconds(tm.year, tm.month, tm.day, tm.hour, tm.minute, tm.second);

      // adjust for new UTC offset for target day at the original time
      epoch -= AbstractQoreZoneInfo::getUTCOffset(zone, epoch);

      usecs = tm.us;
   }
   else
      usecs = us;

   // get resulting microseconds
   usecs += dt.us;

   // add time component
   epoch += (SECS_PER_HOUR * dt.hour) + (SECS_PER_MINUTE * dt.minute) + dt.second;

   // normalize epoch and microseconds
   normalize_units2<int64, int>(epoch, usecs, 1000000);
   us = usecs;

   //printd(5, "absolute_time::operator+= new epoch=%lld.%06d (diff=%lld)\n", epoch, tm.us, epoch - oe);
   return *this;
}

qore_absolute_time &qore_absolute_time::operator-=(const qore_relative_time &dt) {
   qore_relative_time t(dt);
   t.unaryMinus();
   return *this += t;
}

qore_relative_time &qore_relative_time::operator+=(const qore_relative_time &dt) {
   year += dt.year;
   month += dt.month;
   day += dt.day;
   hour += dt.hour;
   minute += dt.minute;
   second += dt.second;
   us += dt.us;

   normalize();

   return *this;
}

qore_relative_time &qore_relative_time::operator-=(const qore_relative_time &dt) {
   year -= dt.year;
   month -= dt.month;
   day -= dt.day;
   hour -= dt.hour;
   minute -= dt.minute;
   second -= dt.second;
   us -= dt.us;

   normalize();

   return *this;
}

qore_relative_time &qore_relative_time::operator-=(const qore_absolute_time &dt) {
   second -= dt.epoch;
   us -= dt.us;

   normalize();

   return *this;
}

void qore_relative_time::set(const QoreValue v) {
   switch (v.type) {
      case QV_Bool: {
         set(0, 0, 0, 0, 0, (int)v.v.b, 0);
         break;
      }
      case QV_Int: {
         zero();
         addSecondsTo(v.v.i);
         break;
      }
      case QV_Float: {
         zero();
         addSecondsTo(v.v.f);
         break;
      }
      case QV_Node:
         switch (get_node_type(v.v.n)) {
            case NT_BOOLEAN: {
               set(0, 0, 0, 0, 0, (int)reinterpret_cast<const QoreBoolNode*>(v.v.n)->getValue(), 0);
               break;
            }
            case NT_INT: {
               // issue #2591: zero the value before adding
               zero();
               addSecondsTo(reinterpret_cast<const QoreBigIntNode*>(v.v.n)->val, 0);
               break;
            }
            case NT_FLOAT: {
               double f = reinterpret_cast<const QoreFloatNode*>(v.v.n)->f;
               zero();
               addSecondsTo(f);
               break;
            }
            case NT_STRING: {
               const char* str = reinterpret_cast<const QoreStringNode*>(v.v.n)->getBuffer();
               set(str);
               break;
            }
            case NT_DATE: {
               const DateTimeNode* d = reinterpret_cast<const DateTimeNode*>(v.v.n);
               if (d->priv->relative)
                  set(d->priv->d.rel);
               else {
                  zero();
                  addSecondsTo(d->priv->d.abs.epoch, d->priv->d.abs.us);
               }
               break;
            }
            default:
               double f = v.v.n ? v.v.n->getAsFloat() : 0.0;
               zero();
               addSecondsTo(f);
               break;
         }
         break;
   }
}

void qore_relative_time::set(const char* str) {
   if (*str == 'P' || *str == 'p') {
      setIso8601(str);
      return;
   }

#ifdef HAVE_STRTOLL
   int64 date = strtoll(str, 0, 10);
#else
   int64 date = atoll(str);
#endif
   const char *p = strchr(str, '.');

   int l = p ? p - str : strlen(str);
   // for date-only strings, move the date up to the right position
   if (l == 8)
      date *= 1000000;

   int us = p ? atoi(p + 1) : 0;
   if (us) {
      l = strlen(p + 1);
      assert(l < 7);
      us *= (int)pow((double)10, 6 - l);
   }

   setLiteral(date, us);
}

void concatOffset(int utcoffset, QoreString &str) {
   //printd(0, "concatOffset(%d)", utcoffset);

   // issue #2684 do not concatenate a "Z" with no offset, output +00:00
   str.concat(utcoffset < 0 ? '-' : '+');
   if (utcoffset < 0)
      utcoffset = -utcoffset;
   int h = utcoffset / SECS_PER_HOUR;
   // the remaining seconds after hours
   int r = utcoffset % SECS_PER_HOUR;
   // minutes
   int m = r / SECS_PER_MINUTE;
   // we have already output the hour sign above
   str.sprintf("%02d:%02d", h < 0 ? -h : h, m);
   // see if there are any seconds
   int s = utcoffset - h * SECS_PER_HOUR - m * SECS_PER_MINUTE;
   if (s)
      str.sprintf(":%02d", s);
}

void qore_date_private::format(QoreString &str, const char *fmt) const {
   qore_time_info i;
   get(i);

   const char *s = fmt;
   while (*s) {
      switch (*s) {
         case 'Y':
            if (s[1] != 'Y') {
               str.concat('Y');
               break;
            }
            s++;
            if ((s[1] == 'Y') && (s[2] == 'Y')) {
               str.sprintf("%04d", i.year);
               s += 2;
            }
            else
               str.sprintf("%02d", i.year - (i.year / 100) * 100);
            break;
         case 'M':
            if (s[1] == 'M') {
               str.sprintf("%02d", i.month);
               s++;
               break;
            }
            if ((s[1] == 'o') && (s[2] == 'n')) {
               s += 2;
               if ((s[1] == 't') && (s[2] == 'h')) {
                  s += 2;
                  if (i.month && (i.month <= 12))
                     str.sprintf("%s", months[(int)i.month - 1].long_name);
                  else
                     str.sprintf("Month%d", i.month - 1);
                  break;
               }
               if (i.month && (i.month <= 12))
                  str.sprintf("%s", months[(int)i.month - 1].abbr);
               else
                  str.sprintf("M%02d", i.month);
               break;
            }
            if ((s[1] == 'O') && (s[2] == 'N')) {
               s += 2;
               if ((s[1] == 'T') && (s[2] == 'H')) {
                  s += 2;
                  if (i.month && (i.month <= 12)) {
                     str.sprintf("%s", months[(int)i.month - 1].upper_long_name);
                  }
                  else
                     str.sprintf("MONTH%d", i.month);
                  break;
               }
               if (i.month && (i.month <= 12)) {
                  str.sprintf("%s", months[(int)i.month - 1].upper_abbr);
               }
               else
                  str.sprintf("M%02d", i.month);
               break;
            }
            str.sprintf("%d", i.month);
            break;
         case 'D':
            if (s[1] == 'D') {
               str.sprintf("%02d", i.day);
               s++;
               break;
            }
            if ((s[1] == 'a') && (s[2] == 'y')) {
               s += 2;
               int wday = qore_date_info::getDayOfWeek(i.year, i.month, i.day);
               str.sprintf("%s", days[wday].long_name);
               break;
            }
            if ((s[1] == 'A') && (s[2] == 'Y')) {
               s += 2;
               int wday = qore_date_info::getDayOfWeek(i.year, i.month, i.day);
               str.sprintf("%s", days[wday].upper_long_name);
               break;
            }
            if ((s[1] == 'y') || (s[1] == 'Y')) {
               s++;
               int wday = qore_date_info::getDayOfWeek(i.year, i.month, i.day);
               str.sprintf("%s", *s == 'Y' ? days[wday].upper_abbr : days[wday].abbr);
               break;
            }
            str.sprintf("%d", i.day);
            break;
         case 'H':
            if (s[1] == 'H') {
               str.sprintf("%02d", i.hour);
               s++;
            }
            else
               str.sprintf("%d", i.hour);
            break;
         case 'h':
            if (s[1] == 'h') {
               str.sprintf("%02d", ampm(i.hour));
               s++;
            }
            else
               str.sprintf("%d", ampm(i.hour));
            break;
         case 'P':
            if (i.hour > 11)
               str.sprintf("PM");
            else
               str.sprintf("AM");
            break;
         case 'p':
            if (i.hour > 11)
               str.sprintf("pm");
            else
               str.sprintf("am");
            break;
         case 'm':
            if (s[1] == 'm') {
               str.sprintf("%02d", i.minute);
               s++;
            }
            else if (s[1] == 's') {
               str.sprintf("%03d", i.us / 1000);
               s++;
            }
            else
               str.sprintf("%d", i.minute);
            break;
         case 'S':
            if (s[1] == 'S') {
               str.sprintf("%02d", i.second);
               s++;
            }
            else
               str.sprintf("%d", i.second);
            break;
         case 'u':
            if (s[1] == 'u') {
               str.sprintf("%03d", i.us / 1000);
               s++;
            }
            else if (s[1] == 's') {
               str.sprintf("%06d", i.us);
               s++;
            }
            else
               str.sprintf("%d", i.us / 1000);
            break;
         case 'x':
            if (s[1] == 'x') {
               str.sprintf("%06d", i.us);
               s++;
            }
            else
               str.sprintf("%d", i.us);
            break;
         case 'y':
            str.sprintf("%06d", i.us);
            str.trim_trailing('0');
            break;
         case 'z':
            str.sprintf("%s", i.zname);
            break;
            // add iso8601 UTC offset
         case 'Z':
            concatOffset(i.utcoffset, str);
            break;
         default:
            str.concat(*s);
            break;
      }
      s++;
   }
}

void qore_relative_time::setIso8601(const char* str) {
   const char *p = str;
   if (*p == 'P' || *p == 'p')
      ++p;

   bool time = false;
   zero();

   bool err = false;
   while (true) {
      if (*p == 'T' || *p == 't') {
         time = true;
         ++p;
      }
      int val = get_int(p, err);
      if (err)
         break;

      switch (*p) {
         case 'Y':
         case 'y':
            year += val;
            break;

         case 'M':
         case 'm':
            if (time)
               minute += val;
            else
               month += val;
            break;

         case 'D':
         case 'd':
            day += val;
            break;

         case 'H':
         case 'h':
            if (!time)
               return;

            hour += val;
            break;

         case 'S':
         case 's':
            if (!time)
               return;

            second += val;
            break;

            // non-ISO-8601 extension: <int>u for microseconds
         case 'u':
            if (!time)
               return;

            us += val;
            break;
         default:
            break;
      }
      ++p;
   }
}

void qore_date_private::setRelativeDate(const char *str) {
   relative = true;
   d.rel.set(str);
}

void qore_date_private::setAbsoluteDate(const char *str, const AbstractQoreZoneInfo *zone, ExceptionSink* xsink) {
   relative = false;
   d.abs.set(str, zone, xsink);
}

void qore_date_private::setDate(const char *str) {
   assert(str);
   if (*str == 'P' || *str == 'p')
      setRelativeDate(str);
   else
      setAbsoluteDate(str);
}

void qore_date_private::setDate(const char *str, ExceptionSink* xsink) {
   assert(str);
   if (*str == 'P' || *str == 'p')
      setRelativeDate(str);
   else
      setAbsoluteDate(str, currentTZ(), xsink);
}

void qore_simple_tm2::getISOWeek(int &yr, int &week, int &wday) const {
   // get day of week of jan 1 of this year
   int jan1 = qore_date_info::getDayOfWeek(year, 1, 1);

   // calculate day in calendar week
   int dn = qore_date_info::getDayNumber(year, month, day);
   int dow = (dn + jan1 - 1) % 7;
   wday = !dow ? 7 : dow;

   //printd(5, "qore_simple_tm2::getISOWeek() year=%d, start=%d, daw=%d dn=%d offset=%d\n", year, jan1, dow, dn, (jan1 > 4 ? 9 - jan1 : 2 - jan1));
   if ((!jan1 && dn == 1) || (jan1 == 5 && dn < 4) || (jan1 == 6 && dn < 3)) {
      yr = year - 1;
      jan1 = qore_date_info::getDayOfWeek(yr, 1, 1);
      //printd(5, "qore_simple_tm2::getISOWeek() previous year=%d, start=%d, leap=%d\n", yr, jan1, qore_date_info::isLeapYear(yr));
      if ((jan1 == 4 && !qore_date_info::isLeapYear(yr)) || (jan1 == 3 && qore_date_info::isLeapYear(yr)))
         week = 53;
      else
         week = 52;
      return;
   }
   yr = year;

   int offset = jan1 > 4 ? jan1 - 9 : jan1 - 2;
   week = ((dn + offset) / 7) + 1;
   if (week == 53) {
      if ((jan1 == 4 && !qore_date_info::isLeapYear(yr)) || (jan1 == 3 && qore_date_info::isLeapYear(yr)))
         return;
      else {
         ++yr;
         week = 1;
      }
   }
}

// normalize the given date to the last day of the month
void normalize_dm(int &year, int &month, int &day) {
   // normalize months and years
   if (month > 12 || month < 1) {
      --month;
      normalize_units2<int, int>(year, month, 12);
      ++month;
   }

   // fix resulting day of month; check for leap years
   if (month == 2 && day > 28)
      day = qore_date_info::isLeapYear(year) ? 29 : 28;
   else // otherwise set day to last day of month if necessary
      if (day > qore_date_info::month_lengths[month])
         day = qore_date_info::month_lengths[month];
}

// normalize to the correct day, month, and year
void normalize_day(int &year, int &month, int &day) {
   // assumes month is already normalized
   assert(month > 0 && month < 13);

   if (day > 0) {
      int i;
      while (day > (i = qore_date_info::getLastDayOfMonth(month, year))) {
         day -= i;
         ++month;
         if (month == 13) {
            month = 1;
            ++year;
         }
      }
      return;
   }

   while (day < 1) {
      --month;
      if (!month) {
         month = 12;
         --year;
      }
      day += qore_date_info::getLastDayOfMonth(month, year);
   }
}
