/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    Pseudo_QC_Date.qpp

    Qore Programming Language

    Copyright (C) 2003 - 2023 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/QC_TimeZone.h"
#include "qore/intern/QoreHashNodeIntern.h"

//! Methods in this pseudo-class can be executed on @ref date "date/time value types"
/**
 */
qclass <date> [arg=const DateTimeNode *dt = v.get<DateTimeNode>();vparent=<value>];

//! Returns Qore::NT_DATE
/**
    @return Qore::NT_DATE

    @par Example:
    @code{.py}
switch (d.typeCode()) {
    case NT_DATE:
        printf("%y: is a date\n", d);
	break;
}
    @endcode
 */
int <date>::typeCode() {
   return NT_DATE;
}

//! Returns @ref True if the date is an @ref absolute_dates "absolute date/time value"
/**
    @return @ref True if the date is an @ref absolute_dates "absolute date/time value"

    @par Example:
    @code{.py}
bool b = d.absolute();
    @endcode
*/
bool <date>::absolute() {
   return dt->isAbsolute();
}

//! Returns @ref True if the date is a @ref relative_dates "relative date/time value"
/**
    @return @ref True if the date is a @ref relative_dates "relative date/time value"

    @par Example:
    @code{.py}
bool b = d.relative();
    @endcode
*/
bool <date>::relative() {
   return dt->isRelative();
}

//! Returns an integer corresponding to the literal year value in the date (does not calculate a duration)
/**
    @return an integer corresponding to the literal year value in the date (does not calculate a duration)

    @par Example:
    @code{.py}
int n = d.years();
    @endcode

    @note equivalent to get_years(date)
*/
int <date>::years() [flags=CONSTANT] {
   return dt->getYear();
}

//! Returns an integer corresponding to the literal month value in the date (does not calculate a duration)
/**
    The date value can be either a @ref relative_dates "relative" or @ref absolute_dates "absolute" date.

    @return an integer corresponding to the literal month value in the date (does not calculate a duration)

    @par Example:
    @code{.py}
int n = d.months();
    @endcode

    @note equivalent to get_months(date)
*/
int <date>::months() [flags=CONSTANT] {
   return dt->getMonth();
}

//! Returns an integer corresponding to the literal day value in the date (does not calculate a duration)
/**
    The date value can be either a @ref relative_dates "relative" or @ref absolute_dates "absolute" date.

    @return an integer corresponding to the literal day value in the date (does not calculate a duration)

    @par Example:
    @code{.py}
int n = d.days();
    @endcode

    @note equivalent to get_days(date)
*/
int <date>::days() [flags=CONSTANT] {
   return dt->getDay();
}

//! Returns an integer corresponding to the literal hour value in the date (does not calculate a duration)
/**
    The date value can be either a @ref relative_dates "relative" or @ref absolute_dates "absolute" date.

    @return an integer corresponding to the literal hour value in the date (does not calculate a duration)

    @par Example:
    @code{.py}
int n = d.hours();
    @endcode

    @note equivalent to get_hours(date)
*/
int <date>::hours() [flags=CONSTANT] {
   return dt->getHour();
}

//! Returns an integer corresponding to the literal minute value in the date (does not calculate a duration)
/**
    The date value can be either a @ref relative_dates "relative" or @ref absolute_dates "absolute" date.

    @return an integer corresponding to the literal minute value in the date (does not calculate a duration)

    @par Example:
    @code{.py}
int n = d.minutes();
    @endcode

    @note equivalent to get_minutes(date)
*/
int <date>::minutes() [flags=CONSTANT] {
   return dt->getMinute();
}

//! Returns an integer corresponding to the literal second value in the date (does not calculate a duration)
/**
    The date value can be either a @ref relative_dates "relative" or @ref absolute_dates "absolute" date.

    @return an integer corresponding to the literal second value in the date (does not calculate a duration)

    @par Example:
    @code{.py}
int n = d.seconds();
    @endcode

    @note
    - equivalent to get_seconds(date)
    - to get the number of seconds of duration in a date/time value, use @ref <date>::durationSeconds() instead
*/
int <date>::seconds() [flags=CONSTANT] {
   return dt->getSecond();
}

//! Returns an integer corresponding to the literal millisecond value in the date (does not calculate a duration)
/**
    The date value can be either a @ref relative_dates "relative" or @ref absolute_dates "absolute" date.

    @return an integer corresponding to the literal millisecond value in the date (does not calculate a duration)

    @par Example:
    @code{.py}
int n = d.milliseconds();
    @endcode

    @note
    - equivalent to get_milliseconds(date)
    - to get the number of milliseconds of duration in a date/time value, use @ref <date>::durationMilliseconds() instead
*/
int <date>::milliseconds() [flags=CONSTANT] {
   return dt->getMillisecond();
}

//! Returns an integer corresponding to the literal microsecond value in the date (does not calculate a duration)
/**
    The date value can be either a @ref relative_dates "relative" or @ref absolute_dates "absolute" date.

    @return an integer corresponding to the literal microsecond value in the date (does not calculate a duration)

    @par Example:
    @code{.py}
int n = d.microseconds();
    @endcode

    @note
    - equivalent to get_microseconds(date)
    - to get the number of microseconds of duration in a date/time value, use @ref <date>::durationMicroseconds() instead
*/
int <date>::microseconds() [flags=CONSTANT] {
   return dt->getMicrosecond();
}

//! Returns an integer value representing the the number of seconds of time duration in the date value (can be either a @ref relative_dates "relative" or @ref absolute_dates "absolute" date)
/** The duration in seconds is calculated and any fractional seconds are truncated (no rounding is performed)

    @return an integer value representing the the number of seconds in the date value; if the value is a
    @ref relative_dates "relative date", the value is normalized to seconds and the integer seconds value is returned,
    if the value is an @ref absolute_dates "absolute date", the duration in seconds is calculated from the present time;
    so if the present time is sent as an argument, 0 is returned, if a future date is used, the number of seconds
    from the present time to the future date is returned, if an @ref absolute_dates "absolute date" in the past is used,
    also 0 is returned (the pseudo-method does not calculate second differences for @ref absolute_dates "absolute dates" in the past (for
    this use @ref date_time_arithmetic instead); this method can only return a negative value if passed a relative date/time value

    @par Example:
    @code{.py}
int us = PT2M15S3u.durationSeconds(); # returns 135
    @endcode

    @note
    - equivalent to get_duration_seconds()
    - to get the literal seconds integer value from a date/time value without calculating a duration, use @ref <date>::seconds()

    @see
    - <date>::durationSecondsFloat()
    - <date>::durationMilliseconds()
    - <date>::durationMicroseconds()

    @since %Qore 0.8.7
*/
int <date>::durationSeconds() [flags=CONSTANT] {
   return dt->getRelativeSeconds();
}

//! Returns an integer value representing the the number of milliseconds of time duration in the date value (can be either a @ref relative_dates "relative" or @ref absolute_dates "absolute" date)
/** The duration in milliseconds is calculated and any fractional milliseconds are truncated (no rounding is performed)

    @return an integer value representing the the number of milliseconds in the date value; if the value is a
    @ref relative_dates "relative date", the value is normalized to milliseconds and the integer milliseconds value is returned,
    if the value is an @ref absolute_dates "absolute date", the duration in milliseconds is calculated from the present time;
    so if the present time is sent as an argument, 0 is returned, if a future date is used, the number of milliseconds
    from the present time to the future date is returned, if an @ref absolute_dates "absolute date" in the past is used,
    also 0 is returned (the pseudo-method does not calculate millisecond differences for @ref absolute_dates "absolute dates" in the past (for
    this use @ref date_time_arithmetic instead); this method can only return a negative value if passed a relative date/time value

    @par Example:
    @code{.py}
int us = PT2M15S3u.durationMilliseconds(); # returns 135000
    @endcode

    @note
    - equivalent to get_duration_milliseconds()
    - to get the literal milliseconds integer value from a date/time value without calculating a duration, use @ref <date>::milliseconds()

    @see
    - <date>::durationSecondsFloat()
    - <date>::durationSeconds()
    - <date>::durationMicroseconds()

    @since %Qore 0.8.7
*/
int <date>::durationMilliseconds() [flags=CONSTANT] {
   return dt->getRelativeMilliseconds();
}

//! Returns an integer value representing the the number of microseconds of time duration in the date value (can be either a @ref relative_dates "relative" or @ref absolute_dates "absolute" date)
/** @return an integer value representing the the number of microseconds in the date value; if the value is a
    @ref relative_dates "relative date", the value is normalized to microseconds and the integer microseconds value is returned,
    if the value is an @ref absolute_dates "absolute date", the duration in microseconds is calculated from the present time;
    so if the present time is sent as an argument, 0 is returned, if a future date is used, the number of microseconds
    from the present time to the future date is returned, if an @ref absolute_dates "absolute date" in the past is used,
    also 0 is returned (the pseudo-method does not calculate microsecond differences for @ref absolute_dates "absolute dates" in the past (for
    this use @ref date_time_arithmetic instead); this method can only return a negative value if passed a relative date/time value

    @par Example:
    @code{.py}
int us = PT2M15S3u.durationMicroseconds(); # returns 135000003
    @endcode

    @note
    - equivalent to get_duration_microseconds()
    - to get the literal microseconds integer value from a date/time value without calculating a duration, use @ref <date>::microseconds()

    @see
    - <date>::durationSecondsFloat()
    - <date>::durationSeconds()
    - <date>::durationMilliseconds()

    @since %Qore 0.8.7
*/
int <date>::durationMicroseconds() [flags=CONSTANT] {
   return dt->getRelativeMicroseconds();
}

//! Returns a floating-point value representing the the number of seconds of duration in the value of the date passed (can be either a @ref relative_dates "relative" or @ref absolute_dates "absolute" date)
/** The duration in seconds is calculated and any fractional seconds are returned as digits to the right of the decimal point.

    @return a floating-point vaaue representing the the number of seconds of duration in the value of the date passed; if the argument is a @ref relative_dates "relative date", the value is normalized to seconds and the floating-point seconds value is returned with microseconds providing the digits to the right of the decimal place, if the argument is an @ref absolute_dates "absolute date", the duration in seconds is calculated from the present time; so if the present time is sent as an argument, 0 is returned, if a future date is used, the number of seconds from the present time to the future date is returned, if an @ref absolute_dates "absolute date" in the past is passed as an argument, also 0 is returned (the function does not calculate second differences for @ref absolute_dates "absolute dates" in the past (for this use @ref date_time_arithmetic instead); this function can only return a negative value if passed a relative date/time value

    @par Example:
    @code{.py}
float dt = PT2M15S3u.durationSecondsFloat(); # returns 135.000003
    @endcode

    @note
    - equivalent to get_duration_seconds_f()
    - to get the literal seconds integer value from a date/time value without calculating a duration, use @ref <date>::seconds()

    @see
    - <date>::durationSeconds()
    - <date>::durationMilliseconds()
    - <date>::durationMicroseconds()

    @since %Qore 0.8.12
*/
float <date>::durationSecondsFloat() [flags=CONSTANT] {
   return dt->getRelativeSecondsDouble();
}

//! Returns a formatted string for the date value
/**
    @param format a string giving the format for the date; see @ref date_formatting for more information about this string

    @return a formatted string for a date argument passed

    @par Example:
    @code{.py}
string str = d.format("Day, Mon D, YYYY-MM-DD HH:mm:SS");
    @endcode

    @bug there is no locale support; day and month names and abbreviations are only returned in English

    @note equivalent to format_date(string, date)
*/
string <date>::format(string format) [flags=CONSTANT] {
   QoreStringNode *rv = new QoreStringNode;
   dt->format(*rv, format->getBuffer());
   return rv;
}

//! Returns @ref True if the current date/time value is currently in daylight savings time
/**
    @return @ref True if the current date/time value is currently in daylight savings time; always returns @ref False for @ref relative_dates "relative date/time values"

    @par Example:
    @code{.py}
bool b = d.isDst();
    @endcode
 */
bool <date>::isDst() [flags=CONSTANT] {
   bool is_dst;

   AbstractQoreZoneInfo::getUTCOffset(dt->getZone(), dt->getEpochSecondsUTC(), is_dst);
   return is_dst;
}

//! Returns a Qore::TimeZone object for the time zone of the date/time value; returns @ref nothing for @ref relative_dates "relative date/time values"
/**
    @return a Qore::TimeZone object for the time zone of the date/time value; returns @ref nothing for @ref relative_dates "relative date/time values"

    @par Example:
    @code{.py}
*TimeZone zone = d.zone();
    @endcode
 */
*TimeZone <date>::zone() [flags=CONSTANT] {
   return dt->isAbsolute() ? new QoreObject(QC_TIMEZONE, 0, new TimeZoneData(dt->getZone())) : 0;
}

//! Returns the name of the current time zone for the current @ref absolute_dates "absolute date/time value" (ex: \c "CEST" for Central European Summer Time for a time during summer time or \c "CET" for Central European Time for the same time zone while daylight savings time is not active) or a UTC offset (like \c "+01") or \c "UTC" or @ref nothing for @ref relative_dates "relative date/time values"
/**
    @return the name of the current time zone for the current @ref absolute_dates "absolute date/time value" (ex: \c "CEST" for Central European Summer Time for a time during summer time or \c "CET" for Central European Time for the same time zone while daylight savings time is not active) or a UTC offset (like \c "+01") or \c "UTC" or @ref nothing for @ref relative_dates "relative date/time values"

    @par Example:
    @code{.py}
*string zn = d.currentZoneName();
    @endcode
 */
*string <date>::currentZoneName() [flags=CONSTANT] {
   if (dt->isRelative())
      return QoreValue();

   bool is_dst;
   const char *zone_name;

   AbstractQoreZoneInfo::getUTCOffset(dt->getZone(), dt->getEpochSecondsUTC(), is_dst, zone_name);
   return new QoreStringNode(zone_name);
}

//! Returns the time zone offset for the current time in seconds east of UTC or -1 for @ref relative_dates "relative date/time values"
/**
    @return the time zone offset for the current time in seconds east of UTC or -1 for @ref relative_dates "relative date/time values"

    @par Example:
    @code{.py}
int utcoffset = d.getUtcOffset();
    @endcode
 */
int <date>::getUtcOffset() [flags=CONSTANT] {
   return AbstractQoreZoneInfo::getUTCOffset(dt->getZone());
}

//! Returns @ref False if the date value is all zeros, @ref True if not
/** @return @ref False if the date value is all zeros, @ref True if not

    @par Example:
    @code{.py}
bool b = d.val();
    @endcode

    @see
    - @ref perl-bool-eval "%perl-bool-eval"
    - @ref strict-bool-eval "%strict-bool-eval"
*/
bool <date>::val() [flags=CONSTANT] {
   return dt->hasValue();
}

//! Returns @ref True because date values can be converted to integers
/**
    @return @ref True because date values can be converted to integers

    @par Example:
    @code{.py}
if (n.intp())
    printf("%y: can be converted to an integer: %d\n", n, int(n));
    @endcode
*/
bool <date>::intp() [flags=CONSTANT] {
   return true;
}

//! Returns @ref True because boolean values can be converted to strings
/**
    @return @ref True because boolean values can be converted to strings

    @par Example:
    @code{.py}
if (n.strp())
    printf("%y: can be converted to a string: '%s'\n", n, string(n));
    @endcode
*/
bool <date>::strp() [flags=CONSTANT] {
   return true;
}

//! Returns midnight on the given date (strips the time component on the new value)
/**
    @return midnight on the given date (strips the time component on the new value)

    @par Example:
    @code{.py}
date midnight = dt.midnight();
    @endcode

    @note equivalent to get_midnight()

    @since %Qore 0.8.5
*/
date <date>::midnight() [flags=CONSTANT] {
   DateTimeNode* rv = new DateTimeNode(*dt);
   rv->setTime(0, 0, 0);
   return rv;
}

//! Returns the number of seconds since the start of the epoch (1970-01-01Z) for the current date for @ref absolute_dates "absolute date/time values"; returns 0 for @ref relative_dates "relative date/time values"
/** @par Example:
    @code{.py}
int secs = d.getEpochSeconds();
    @endcode

    @return the number of seconds since the start of the epoch (1970-01-01Z) for the current date for @ref absolute_dates "absolute date/time values"; returns 0 for @ref relative_dates "relative date/time values"

    @since %Qore 0.8.8
 */
int <date>::getEpochSeconds() [flags=CONSTANT] {
   return dt->isAbsolute() ? dt->getEpochSecondsUTC() : 0;
}

//! Returns the number of seconds since the start of the epoch (1970-01-01) for the current date in the local time zone for @ref absolute_dates "absolute date/time values"; returns 0 for @ref relative_dates "relative date/time values"
/** @par Example:
    @code{.py}
int secs = d.getEpochSecondsLocalTime();
    @endcode

    @return the number of seconds since the start of the epoch (1970-01-01) for the current date in the local time zone for @ref absolute_dates "absolute date/time values"; returns 0 for @ref relative_dates "relative date/time values"

    @since %Qore 0.8.8
 */
int <date>::getEpochSecondsLocalTime() [flags=CONSTANT] {
   return dt->isAbsolute() ? dt->getEpochSeconds() : 0;
}

//! Returns a @ref DateTimeInfo hash for the date (can be either a @ref relative_dates "relative" or @ref absolute_dates "absolute" date)
/** @par Example:
    @code{.py}
hash<DateTimeInfo> h = d.info(date);
    @endcode

    @return a @ref DateTimeInfo hash for the given date argument

    @note equivalent to date_info(date)

    @since %Qore 0.8.8
*/
hash<DateTimeInfo> <date>::info() [flags=CONSTANT] {
   return date_info(*dt);
}

//! Returns an integer representing the ordinal day number in the year (corresponding to the <a href="http://en.wikipedia.org/wiki/ISO_8601#Ordinal_dates">ISO-8601 day number</a>) for the @ref absolute_dates "absolute date" value
/**
    @return an integer representing the ordinal day number in the year (corresponding to the <a href="http://en.wikipedia.org/wiki/ISO_8601#Ordinal_dates">ISO-8601 day number</a>) for the @ref absolute_dates "absolute date" value; if a @ref relative_dates "relative date" value is used, then this method will return 0

    @par Example:
    @code{.py}
int dn = dt.dayNumber();
    @endcode

    @note equivalent to @ref Qore::get_day_number()

    @since %Qore 0.8.12
*/
int <date>::dayNumber() [flags=CONSTANT] {
  return dt->getDayNumber();
}

//! Returns an integer representing the day of the week for the @ref absolute_dates "absolute date" value (0=Sunday, 6=Saturday)
/**
    @return an integer representing the day of the week for the @ref absolute_dates "absolute date" value (0=Sunday, 6=Saturday); if a @ref relative_dates "relative date" value is used, then this method will return 0

    @par Example:
    @code{.py}
int dn = dt.dayOfWeek();
    @endcode

    @note equivalent to @ref Qore::get_day_of_week()

    @see <date>::isoDayOfWeek()

    @since %Qore 0.8.12
*/
int <date>::dayOfWeek() [flags=CONSTANT] {
   return dt->getDayOfWeek();
}

//! Returns an integer representing the ISO-8601 day of the week for the @ref absolute_dates "absolute date" value (1=Monday, 7=Sunday)
/**
    @return an integer representing the day of the week for the @ref absolute_dates "absolute date" passed (1=Monday, 7=Sunday); if a @ref relative_dates "relative date" value is used, then this method will return 7

    @par Example:
    @code{.py}
int dn = dt.isoDayOfWeek();
    @endcode

    @note equivalent to @ref Qore::get_iso_day_of_week()

    @see <date>::dayOfWeek()

    @since %Qore 0.8.12
*/
int <date>::isoDayOfWeek() [flags=CONSTANT] {
   int d = dt->getDayOfWeek();
   return (!d ? 7 : d);
}

//! Returns an @ref Qore::IsoWeekInfo "IsoWeekInfo" hash representing the ISO-8601 calendar week information for the @ref absolute_dates "absolute date" (hash keys: \c "year", \c "week", \c "day")
/** @note that the ISO-8601 year does not always correspond with the calendar year at the end and the beginning of every year (for example 2006-01-01 in ISO-8601 calendar week format is: year=2005, week=52, day=7)

    @return an @ref Qore::IsoWeekInfo "IsoWeekInfo" hash representing the ISO-8601 calendar week information for the @ref absolute_dates "absolute date" (hash keys: \c "year", \c "week", \c "day"); if a @ref relative_dates "relative date" value is used, then this method will return year=1970, week=1, day=1

    @par Example:
    @code{.py}
hash<IsoWeekInfo> h = 2007-05-15.isoWeekHash(); # returns year=2007, week=20, day=2
    @endcode

    @note equivalent to @ref Qore::get_iso_week_hash()

    @see <date>::isoWeekString()

    @since %Qore 0.8.12
*/
hash<IsoWeekInfo> <date>::isoWeekHash() [flags=CONSTANT] {
    int year, week, day;
    dt->getISOWeek(year, week, day);

    QoreHashNode* h = new QoreHashNode(hashdeclIsoWeekInfo, xsink);
    qore_hash_private* ph = qore_hash_private::get(*h);
    ph->setKeyValueIntern("year", year);
    ph->setKeyValueIntern("week", week);
    ph->setKeyValueIntern("day", day);

    return h;
}

//! Returns a string representing the ISO-8601 calendar week information for the @ref absolute_dates "absolute date" (ex: 2006-01-01 = "2005-W52-7")
/**
    @return a string representing the ISO-8601 calendar week information for the @ref absolute_dates "absolute date" (ex: 2006-01-01 = "2005-W52-7"); if a @ref relative_dates "relative date" value is used, then this method will return \c "1970-W01-1"

    @par Example:
    @code{.py}
string str = 2007-05-15.isoWeekString(); # returns "2007-W20-2"
    @endcode

    @note equivalent to @ref Qore::get_iso_week_string()

    @see <date>::isoWeekHash()

    @since %Qore 0.8.12
*/
string <date>::isoWeekString() [flags=CONSTANT] {
   int year, week, day;
   dt->getISOWeek(year, week, day);
   QoreStringNode *str = new QoreStringNode();
   str->sprintf("%04d-W%02d-%d", year, week, day);
   return str;
}
