// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.

#include "precomp.h"


// Exit macros
#define TimeExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_TIMEUTIL, x, s, __VA_ARGS__)
#define TimeExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_TIMEUTIL, x, s, __VA_ARGS__)
#define TimeExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_TIMEUTIL, x, s, __VA_ARGS__)
#define TimeExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_TIMEUTIL, x, s, __VA_ARGS__)
#define TimeExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_TIMEUTIL, x, s, __VA_ARGS__)
#define TimeExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_TIMEUTIL, x, s, __VA_ARGS__)
#define TimeExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_TIMEUTIL, p, x, e, s, __VA_ARGS__)
#define TimeExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_TIMEUTIL, p, x, s, __VA_ARGS__)
#define TimeExitOnNullDebugTrace(p, x, e, s, ...)  ExitOnNullDebugTraceSource(DUTIL_SOURCE_TIMEUTIL, p, x, e, s, __VA_ARGS__)
#define TimeExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_TIMEUTIL, p, x, s, __VA_ARGS__)
#define TimeExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_TIMEUTIL, e, x, s, __VA_ARGS__)
#define TimeExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_TIMEUTIL, g, x, s, __VA_ARGS__)

const LPCWSTR DAY_OF_WEEK[] = { L"Sun", L"Mon", L"Tue", L"Wed", L"Thu", L"Fri", L"Sat" };
const LPCWSTR MONTH_OF_YEAR[] = { L"None", L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" };
enum TIME_PARSER { DayOfWeek, DayOfMonth, MonthOfYear, Year, Hours, Minutes, Seconds, TimeZone };
enum TIME_PARSERRFC3339 { RFC3339_Year, RFC3339_Month, RFC3339_Day, RFC3339_Hours, RFC3339_Minutes, RFC3339_Seconds, RFC3339_TimeZone };

// prototypes
static HRESULT DayFromString(
    __in_z LPCWSTR wzDay,
    __out WORD* pwDayOfWeek
    );
static HRESULT MonthFromString(
    __in_z LPCWSTR wzMonth,
    __out WORD* pwMonthOfYear
    );


/********************************************************************
 TimeFromString - converts string to FILETIME

*******************************************************************/
extern "C" HRESULT DAPI TimeFromString(
    __in_z LPCWSTR wzTime,
    __out FILETIME* pFileTime
    )
{
    Assert(wzTime && pFileTime);

    HRESULT hr = S_OK;
    LPWSTR pwzTime = NULL;

    SYSTEMTIME sysTime = { };
    TIME_PARSER timeParser = DayOfWeek;

    LPCWSTR pwzStart = NULL;
    LPWSTR pwzEnd = NULL;

    hr = StrAllocString(&pwzTime, wzTime, 0);
    TimeExitOnFailure(hr, "Failed to copy time.");

    pwzStart = pwzEnd = pwzTime;
    while (pwzEnd && *pwzEnd)
    {
        if (L',' == *pwzEnd || L' ' == *pwzEnd || L':' == *pwzEnd)
        {
            *pwzEnd = L'\0'; // null terminate
            ++pwzEnd;

            while (L' ' == *pwzEnd)
            {
                ++pwzEnd; // and skip past the blank space
            }

            switch (timeParser)
            {
                case DayOfWeek:
                    hr = DayFromString(pwzStart, &sysTime.wDayOfWeek);
                    TimeExitOnFailure(hr, "Failed to convert string to day: %ls", pwzStart);
                    break;

                case DayOfMonth:
                    sysTime.wDay = (WORD)wcstoul(pwzStart, NULL, 10);
                    break;

                case MonthOfYear:
                    hr = MonthFromString(pwzStart, &sysTime.wMonth);
                    TimeExitOnFailure(hr, "Failed to convert to month: %ls", pwzStart);
                    break;

                case Year:
                    sysTime.wYear = (WORD)wcstoul(pwzStart, NULL, 10);
                    break;

                case Hours:
                    sysTime.wHour = (WORD)wcstoul(pwzStart, NULL, 10);
                    break;

                case Minutes:
                    sysTime.wMinute = (WORD)wcstoul(pwzStart, NULL, 10);
                    break;

                case Seconds:
                    sysTime.wSecond = (WORD)wcstoul(pwzStart, NULL, 10);
                    break;

                case TimeZone:
                    // TODO: do something with this in the future, but this should only hit outside of the while loop.
                    break;

                default:
                    break;
            }

            pwzStart = pwzEnd;
            timeParser = (TIME_PARSER)((int)timeParser + 1);
        }

        ++pwzEnd;
    }


    if (!::SystemTimeToFileTime(&sysTime, pFileTime))
    {
        TimeExitWithLastError(hr, "Failed to convert system time to file time.");
    }

LExit:
    ReleaseStr(pwzTime);

    return hr;
}

/********************************************************************
 TimeFromString3339 - converts string formated in accorance with RFC3339 to FILETIME
 http://tools.ietf.org/html/rfc3339
*******************************************************************/
extern "C" HRESULT DAPI TimeFromString3339(
    __in_z LPCWSTR wzTime,
    __out FILETIME* pFileTime
    )
{
    Assert(wzTime && pFileTime);

    HRESULT hr = S_OK;
    LPWSTR pwzTime = NULL;

    SYSTEMTIME sysTime = { };
    TIME_PARSERRFC3339 timeParser = RFC3339_Year;

    LPCWSTR pwzStart = NULL;
    LPWSTR pwzEnd = NULL;

    hr = StrAllocString(&pwzTime, wzTime, 0);
    TimeExitOnFailure(hr, "Failed to copy time.");

    pwzStart = pwzEnd = pwzTime;
    while (pwzEnd && *pwzEnd)
    {
        if (L'T' == *pwzEnd || L':' == *pwzEnd || L'-' == *pwzEnd)
        {
            *pwzEnd = L'\0'; // null terminate
            ++pwzEnd;

            switch (timeParser)
            {
                case RFC3339_Year:
                    sysTime.wYear = (WORD)wcstoul(pwzStart, NULL, 10);
                    break;

                case RFC3339_Month:
                    sysTime.wMonth = (WORD)wcstoul(pwzStart, NULL, 10);
                    break;

                case RFC3339_Day:
                    sysTime.wDay = (WORD)wcstoul(pwzStart, NULL, 10);
                    break;

                case RFC3339_Hours:
                    sysTime.wHour = (WORD)wcstoul(pwzStart, NULL, 10);
                    break;

                case RFC3339_Minutes:
                    sysTime.wMinute = (WORD)wcstoul(pwzStart, NULL, 10);
                    break;

                case RFC3339_Seconds:
                    sysTime.wSecond = (WORD)wcstoul(pwzStart, NULL, 10);
                    break;

                case RFC3339_TimeZone:
                    // TODO: do something with this in the future, but this should only hit outside of the while loop.
                    break;

                default:
                    break;
            }

            pwzStart = pwzEnd;
            timeParser = (TIME_PARSERRFC3339)((int)timeParser + 1);
        }

        ++pwzEnd;
    }


    if (!::SystemTimeToFileTime(&sysTime, pFileTime))
    {
        TimeExitWithLastError(hr, "Failed to convert system time to file time.");
    }

LExit:
    ReleaseStr(pwzTime);

    return hr;
}
/****************************************************************************
TimeCurrentTime - gets the current time in string format

****************************************************************************/
extern "C" HRESULT DAPI TimeCurrentTime(
    __deref_out_z LPWSTR* ppwz,
    __in BOOL fGMT
    )
{
    SYSTEMTIME st;

    if (fGMT)
    {
        ::GetSystemTime(&st);
    }
    else
    {
        SYSTEMTIME stGMT;
        TIME_ZONE_INFORMATION tzi;

        ::GetTimeZoneInformation(&tzi);
        ::GetSystemTime(&stGMT);
        ::SystemTimeToTzSpecificLocalTime(&tzi, &stGMT, &st);
    }

    return StrAllocFormatted(ppwz, L"%02d:%02d:%02d", st.wHour, st.wMinute, st.wSecond);
}


/****************************************************************************
TimeCurrentDateTime - gets the current date and time in string format,
  per format described in RFC 3339
****************************************************************************/
extern "C" HRESULT DAPI TimeCurrentDateTime(
    __deref_out_z LPWSTR* ppwz,
    __in BOOL fGMT
    )
{
    SYSTEMTIME st;

    ::GetSystemTime(&st);

    return TimeSystemDateTime(ppwz, &st, fGMT);
}


/****************************************************************************
TimeSystemDateTime - converts the provided system time struct to string format,
  per format described in RFC 3339
****************************************************************************/
extern "C" HRESULT DAPI TimeSystemDateTime(
    __deref_out_z LPWSTR* ppwz,
    __in const SYSTEMTIME *pst,
    __in BOOL fGMT
    )
{
    DWORD dwAbsBias = 0;

    if (fGMT)
    {
        return StrAllocFormatted(ppwz, L"%04hu-%02hu-%02huT%02hu:%02hu:%02huZ", pst->wYear, pst->wMonth, pst->wDay, pst->wHour, pst->wMinute, pst->wSecond);
    }
    else
    {
        SYSTEMTIME st;
        TIME_ZONE_INFORMATION tzi;

        ::GetTimeZoneInformation(&tzi);
        ::SystemTimeToTzSpecificLocalTime(&tzi, pst, &st);
        dwAbsBias = abs(tzi.Bias);

        return StrAllocFormatted(ppwz, L"%04hu-%02hu-%02huT%02hu:%02hu:%02hu%c%02u:%02u", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, 0 >= tzi.Bias ? L'+' : L'-', dwAbsBias / 60, dwAbsBias % 60);
    }
}


/****************************************************************************
TimeSystemToDateTimeString - converts the provided system time struct to
  string format representing date and time for the specified locale
****************************************************************************/
HRESULT DAPI TimeSystemToDateTimeString(
    __deref_out_z LPWSTR* ppwz,
    __in const SYSTEMTIME* pst,
    __in LCID locale
    )
{
    HRESULT hr = S_OK;
    const WCHAR * DATE_FORMAT = L"MMM dd',' yyyy',' ";
    const WCHAR * TIME_FORMAT = L"hh':'mm':'ss tt";
    int iLenDate = 0;
    int iLenTime = 0;

    iLenDate = ::GetDateFormatW(locale, 0, pst, DATE_FORMAT, NULL, 0);
    if (0 >= iLenDate)
    {
        TimeExitWithLastError(hr, "Failed to get date format with NULL");
    }

    iLenTime = ::GetTimeFormatW(locale, 0, pst, TIME_FORMAT, NULL, 0);
    if (0 >= iLenTime)
    {
        TimeExitWithLastError(hr, "Failed to get time format with NULL");
    }

    // Between both lengths we account for 2 null terminators, and only need one, so we subtract one
    hr = StrAlloc(ppwz, iLenDate + iLenTime - 1);
    TimeExitOnFailure(hr, "Failed to allocate string");

    if (!::GetDateFormatW(locale, 0, pst, DATE_FORMAT, *ppwz, iLenDate))
    {
        TimeExitWithLastError(hr, "Failed to get date format with buffer");
    }
    // Space to separate them
    (*ppwz)[iLenDate - 1] = ' ';

    if (!::GetTimeFormatW(locale, 0, pst, TIME_FORMAT, (*ppwz) + iLenDate - 1, iLenTime))
    {
        TimeExitWithLastError(hr, "Failed to get time format with buffer");
    }

LExit:
    return hr;
}

/********************************************************************
 DayFromString - converts string to day

*******************************************************************/
static HRESULT DayFromString(
    __in_z LPCWSTR wzDay,
    __out WORD* pwDayOfWeek
    )
{
    HRESULT hr = E_INVALIDARG; // assume we won't find a matching name

    for (WORD i = 0; i < countof(DAY_OF_WEEK); ++i)
    {
        if (0 == lstrcmpW(wzDay, DAY_OF_WEEK[i]))
        {
            *pwDayOfWeek = i;
            hr = S_OK;
            break;
        }
    }

    return hr;
}


/********************************************************************
 MonthFromString - converts string to month

*******************************************************************/
static HRESULT MonthFromString(
    __in_z LPCWSTR wzMonth,
    __out WORD* pwMonthOfYear
    )
{
    HRESULT hr = E_INVALIDARG; // assume we won't find a matching name

    for (WORD i = 0; i < countof(MONTH_OF_YEAR); ++i)
    {
        if (0 == lstrcmpW(wzMonth, MONTH_OF_YEAR[i]))
        {
            *pwMonthOfYear = i;
            hr = S_OK;
            break;
        }
    }

    return hr;
}