caddr_t
bif_dateadd (caddr_t * qst, caddr_t * err_ret, state_slot_t ** args)
{
  caddr_t res;
  caddr_t part = bif_string_arg (qst, args, 0, "dateadd");
  boxint n = bif_long_arg (qst, args, 1, "dateadd");
  caddr_t dt = bif_date_arg (qst, args, 2, "dateadd");
  TIMESTAMP_STRUCT ts;
  int dt_type = DT_DT_TYPE (dt);
  int year_or_month_tz_tweak = (((!strcmp ("year", part)) || (!strcmp ("month", part))) ? DT_TZ (dt) : 0);
  DT_AUDIT_FIELDS (dt);
  dt_to_GMTimestamp_struct (dt, &ts);
  if (year_or_month_tz_tweak)
    ts_add (&ts, year_or_month_tz_tweak, "minute");
  ts_add (&ts, n, part);
  if (year_or_month_tz_tweak)
    ts_add (&ts, -year_or_month_tz_tweak, "minute");
  res = dk_alloc_box (DT_LENGTH, DV_DATETIME);
  GMTimestamp_struct_to_dt (&ts, res);
  DT_SET_TZ (res, DT_TZ (dt));
  if (DT_TYPE_DATE == dt_type
      && (0 == stricmp (part, "year") || 0 == stricmp (part, "month") || 0 == stricmp (part, "day")))
    DT_SET_DT_TYPE (res, dt_type);
  DT_AUDIT_FIELDS (dt);
  return res;
}
caddr_t
bif_dt_set_tz (caddr_t * qst, caddr_t * err_ret, state_slot_t ** args)
{
  caddr_t arg = bif_date_arg (qst, args, 0, "dt_set_tz");
  long tz = (long) bif_long_arg (qst, args, 1, "dt_set_tz");
  caddr_t res = box_copy (arg);
  DT_SET_TZ (res, tz);
  return res;
}
caddr_t
bif_date_string_GMT (caddr_t * qst, caddr_t * err_ret, state_slot_t ** args)
{
  char temp[100];
  caddr_t arg = bif_date_arg (qst, args, 0, "datestring");
  char dt2[DT_LENGTH];
  memcpy (dt2, arg, DT_LENGTH);
  DT_SET_TZ (dt2, 0);
  dt_to_string (dt2, temp, sizeof (temp));
  return (box_dv_short_string (temp));
}
caddr_t
bif_timestampadd (caddr_t * qst, caddr_t * err_ret, state_slot_t ** args)
{
  caddr_t res;
  ptrlong part = bif_long_arg (qst, args, 0, "timestampadd");
  int n = (int) bif_long_arg (qst, args, 1, "timestampadd");
  caddr_t dt = bif_date_arg (qst, args, 2, "timestampadd");
  int saved_tz = DT_TZ (dt);
  GMTIMESTAMP_STRUCT ts;
  dt_to_GMTimestamp_struct (dt, &ts);
  ts_add (&ts, n, interval_odbc_to_text (part, "timestampadd"));
  res = dk_alloc_box (DT_LENGTH, DV_DATETIME);
  GMTimestamp_struct_to_dt (&ts, res);
  DT_SET_TZ (res, saved_tz);
  return res;
}
int /* Returns number of chars parsed. */
dt_scan_from_buffer (const char *buf, int mode, caddr_t *dt_ret, const char **err_msg_ret)
{
  const char *tail = buf;
  int fld_len, acc, ymd_found = 0, hms_found = 0, msec_factor;
  TIMESTAMP_STRUCT ts;
  memset (&ts, 0, sizeof (TIMESTAMP_STRUCT));
  dt_ret[0] = NULL;
  err_msg_ret[0] = NULL;
  fld_len = 0; acc = 0; while (isdigit (tail[0]) && (4 > fld_len)) { acc = acc * 10 + (tail++)[0] - '0'; fld_len++; }
  if ('-' == tail[0])
    { /* Date delimiter, let's parse date part */
      if (((DT_PRINT_MODE_YMD | DT_PRINT_MODE_HMS) & mode) && !(DT_PRINT_MODE_YMD & mode))
        {
          err_msg_ret[0] = "Time field is expected but date field delimiter is found";
          return 0;
        }
      if (4 != fld_len)
        {
          err_msg_ret[0] = "Year field should have 4 digits";
          return 0;
        }
      ymd_found = 1;
      ts.year = acc;
      tail++;
      fld_len = 0; acc = 0; while (isdigit (tail[0]) && (2 > fld_len)) { acc = acc * 10 + (tail++)[0] - '0'; fld_len++; }
      if (2 != fld_len)
        {
          err_msg_ret[0] = "Month field should have 2 digits";
          return 0;
        }
      if ('-' != tail[0])
        {
          err_msg_ret[0] = "Minus sign is expected after month";
          return 0;
        }
      ts.month = acc;
      tail++;
      fld_len = 0; acc = 0; while (isdigit (tail[0]) && (2 > fld_len)) { acc = acc * 10 + (tail++)[0] - '0'; fld_len++; }
      if (2 != fld_len)
        {
          err_msg_ret[0] = "Day of month field should have 2 digits";
          return 0;
        }
      ts.day = acc;
      if ('T' != tail[0])
        goto scan_tz; /* see below */
      tail++;
      fld_len = 0; acc = 0; while (isdigit (tail[0]) && (2 > fld_len)) { acc = acc * 10 + (tail++)[0] - '0'; fld_len++; }
    }
  if (':' == tail[0])
    { /* Time delimiter, let's parse time part */
      if (((DT_PRINT_MODE_YMD | DT_PRINT_MODE_HMS) & mode) && !(DT_PRINT_MODE_HMS & mode))
        {
          err_msg_ret[0] = "Date field is expected but time field delimiter is found";
          return 0;
        }
      if (2 != fld_len)
        {
          err_msg_ret[0] = "Hour field should have 2 digits";
          return 0;
        }
      hms_found = 1;
      ts.hour = acc;
      tail++;
      fld_len = 0; acc = 0; while (isdigit (tail[0]) && (2 > fld_len)) { acc = acc * 10 + (tail++)[0] - '0'; fld_len++; }
      if (2 != fld_len)
        {
          err_msg_ret[0] = "Minute field should have 2 digits";
          return 0;
        }
      if (':' != tail[0])
        {
          err_msg_ret[0] = "Colon is expected after minute";
          return 0;
        }
      ts.minute = acc;
      tail++;
      fld_len = 0; acc = 0; while (isdigit (tail[0]) && (2 > fld_len)) { acc = acc * 10 + (tail++)[0] - '0'; fld_len++; }
      if (2 != fld_len)
        {
          err_msg_ret[0] = "Second field should have 2 digits";
          return 0;
        }
      ts.second = acc;
      if ('.' == tail[0])
        {
          tail++;
          msec_factor = 1000000000;
          acc = 0;
          if (!isdigit (tail[0]))
            {
              err_msg_ret[0] = "Fraction of second is expected after decimal dot";
              return 0;
            }
          do
            {
              if (msec_factor)
                acc = acc * 10 + (tail[0] - '0');
              tail++;
              msec_factor /= 10;
            } while (isdigit (tail[0]));
          ts.fraction = acc * (msec_factor ? msec_factor : 1);
        }
      if ('Z' != tail[0] && strncmp (tail, " GMT", 4))
	{
	  err_msg_ret[0] = "Colon or time zone is expected after minute";
	  return 0;
	}
    }
  else
    {
      err_msg_ret[0] = "Generic syntax error in date/time";
      return 0;
    }

scan_tz:
/* Now HMS part is complete (or skipped) */
  if ('Z' == tail[0])
    tail++;
  else if (!strncmp (tail, " GMT", 4))
    tail += 4;
  else
    {
      err_msg_ret[0] = "Generic syntax error in date/time";
      return 0;
    }
  if ((DT_PRINT_MODE_YMD & mode) && !ymd_found)
    {
      err_msg_ret[0] = "Datetime expected but time-only string is found";
      return 0;
    }
  if ((DT_PRINT_MODE_HMS & mode) && !hms_found)
    {
      err_msg_ret[0] = "Datetime expected but date-only string is found";
      return 0;
    }
  dt_ret[0] = dk_alloc_box (DT_LENGTH, DV_DATETIME);
  {
    uint32 day;
    day = date2num (ts.year, ts.month, ts.day);
    DT_SET_DAY (dt_ret[0], day);
    DT_SET_HOUR (dt_ret[0], ts.hour);
    DT_SET_MINUTE (dt_ret[0], ts.minute);
    DT_SET_SECOND (dt_ret[0], ts.second);
    DT_SET_FRACTION (dt_ret[0], ts.fraction);
    DT_SET_TZ (dt_ret[0], dt_local_tz);
  }
  SET_DT_TYPE_BY_DTP (dt_ret[0], (ymd_found ? (hms_found ? DV_DATETIME : DV_DATE) : DV_TIME));
  return (tail - buf);
}
caddr_t
arithm_dt_add_num (ccaddr_t box1, ccaddr_t box2, int subtraction, caddr_t *err_ret)
{
  int dt_type = DT_DT_TYPE (box1);
  dtp_t dtp2 = DV_TYPE_OF (box2);
  boxint whole_seconds = 0;
  boxint nanoseconds = 0;
  TIMESTAMP_STRUCT ts;
  caddr_t res;
  switch (dtp2)
    {
    case DV_LONG_INT:
      whole_seconds = unbox (box2);
      break;
    case DV_DOUBLE_FLOAT:
      {
        double n = unbox_double (box2);
        double rest;
        whole_seconds = (n >= 0.0) ? floor(n + 0.5) : ceil(n - 0.5);
        rest = n - whole_seconds;
        if (abs(rest/n) > (3 * DBL_EPSILON))
          nanoseconds = (n - whole_seconds) * 1000000000L;
        break;
      }
    case DV_NUMERIC:
      {
        numeric_t n = (numeric_t)box2;
        if (NUMERIC_STS_SUCCESS != numeric_to_int64 (n, &whole_seconds))
          {
            err_ret[0] = srv_make_new_error ("22003", "SR087", "Wrong arguments for datetime arithmetic: decimal is out of range.");
            return NULL;
          }
        if (n->n_scale > 0)
          {
            char *nptr = n->n_value + n->n_len;
            int ctr;
            int mult = 1;
            for (ctr = 9; ctr > n->n_scale; ctr--) mult *= 10;
            while (ctr--)
              {
                nanoseconds += mult * nptr[ctr];
                mult *= 10;
              }
          }
        break;
      }
    default:
      return NULL;
    }
  DT_AUDIT_FIELDS (dt);
  dt_to_GMTimestamp_struct (box1, &ts);
  ts_add (&ts, (subtraction ? -whole_seconds : whole_seconds), "second");
  if (nanoseconds)
    ts_add (&ts, (subtraction ? -nanoseconds : nanoseconds), "nanosecond");
  res = dk_alloc_box (DT_LENGTH, DV_DATETIME);
  GMTimestamp_struct_to_dt (&ts, res);
  DT_SET_TZ (res, DT_TZ (box1));
  if ((DT_TYPE_DATE == dt_type) && (0 == (((whole_seconds * 1000000000L) + nanoseconds) % (SPERDAY * 1000000000L))))
    DT_SET_DT_TYPE (res, dt_type);
  DT_AUDIT_FIELDS (dt);
  return res;
}