/*
* Copyright (c) 2002-2009 Nokia Corporation and/or its subsidiary(-ies). 
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: This file implements class CESMRICalValue.
*
*/


// Class include.
#include "emailtrace.h"
#include "cesmricalvalue.h" // CESMRICalValue

//debug

// User includes.
#include "esmricalkeywords.h"       // Literals
#include "cesmricalrulesegment.h"   // CESMRICalRuleSegment

// Constants.

namespace{
// Maximum number of characters to use for storing TInt.
// (The maximum number of characters for storing a decimal representation of
// KMaxTInt, KMinTInt or even KMaxTUint is 10 on current implementations.
// The maximum number of characters for storing a decimal representation of
// KMaxTUint64 is 20, so using this for future proofing.)
const TInt KICalTIntWidth = 20;

// Width of "HHMMSS"
const TInt KICalTimeWidth = 6;

// Width of "YYYYMMDD"
const TInt KICalDateWidth = 8;

// Width of "YYYYMMDDTHHMMSS" (note the 'T' in the middle)
const TInt KICalDateTimeWidth = KICalDateWidth + KICalTimeWidth + 1;

// Width of a duration.
const TInt KICalDurationWidth = 25;

// Width of a short format UTC offset.
const TInt KShortFormatUtcOffsetWidth = 5;  // The width of (e.g.) +1000.

// Width of a long format UTC offset.
const TInt KLongFormatUtcOffsetWidth = 7;   // The width of (e.g.) -013045.

// Time constants.
const TInt KICalSecondsPerMinute =     60;
const TInt KICalSecondsPerHour   =   3600; // That is:      60*60;
const TInt KICalSecondsPerDay    =  86400; // That is:   24*60*60;
const TInt KICalSecondsPerWeek   = 604800; // That is: 7*24*60*60;

// Time characters
const TUint KICalUtcChar('Z');      // UTC time.
const TUint KICalTimeChar('T');     // Date/time separator.
const TUint KICalPeriodChar('/');   // Period separator.
const TUint KICalMicroChar('.');    // Second/microsecond separator.
const TUint KICalDurationChar('P'); // Duration.
const TUint KICalWeekChar('W');     // Week.
const TUint KICalDayChar('D');      // Day.
const TUint KICalHourChar('H');     // Hour.
const TUint KICalMinuteChar('M');   // Minute.
const TUint KICalSecondChar('S');   // Second.
const TUint KICalPositiveChar('+'); // Positive values.
const TUint KICalNegativeChar('-'); // Negative values.

// Date format.
_LIT(KICalDateFormat, "%F%Y%M%D");
}//namespace

// ======== MEMBER FUNCTIONS ========

// ---------------------------------------------------------------------------
// CESMRICalValue::NewL
// ---------------------------------------------------------------------------
//
EXPORT_C CESMRICalValue* CESMRICalValue::NewL()
    {
    FUNC_LOG;

    CESMRICalValue* self  = CESMRICalValue::NewLC();
    CleanupStack::Pop(self);

    return self;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::NewLC
// ---------------------------------------------------------------------------
//
EXPORT_C CESMRICalValue* CESMRICalValue::NewLC()
    {
    FUNC_LOG;

    CESMRICalValue* self  = new (ELeave) CESMRICalValue;
    CleanupStack::PushL(self);
    self->ConstructL();

    return self;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::~CESMRICalValue
// ---------------------------------------------------------------------------
//
CESMRICalValue::~CESMRICalValue()
    {
    FUNC_LOG;
    delete iValue;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::BinaryLC
// ---------------------------------------------------------------------------
//
EXPORT_C HBufC8* CESMRICalValue::BinaryLC() const
    {
    FUNC_LOG;

    CheckNullValueL();
    HBufC8* buf = HBufC8::NewLC(iValue->Length());
    buf->Des().Copy(*iValue);

    return buf;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetBinaryL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetBinaryL(const TDesC8& aBuffer)
    {
    FUNC_LOG;

    PrepareValuePointer();
    iValue = HBufC::NewL(aBuffer.Length());
    iValue->Des().Copy(aBuffer);
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::BooleanL
// ---------------------------------------------------------------------------
//
EXPORT_C TBool CESMRICalValue::BooleanL() const
    {
    FUNC_LOG;

    CheckNullValueL();

    if (iValue->Des() == KICalTrue)
        {
        return ETrue;
        }
    else if (iValue->Des() == KICalFalse)
        {
        return EFalse;
        }

    // Else...
    User::Leave(KErrCorrupt);

    return EFalse;  // Never reached.
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetBooleanL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetBooleanL(TBool aBool)
    {
    FUNC_LOG;

    PrepareValuePointer();

    if (aBool) // ETrue
        {
        iValue = KICalTrue().AllocL();
        }
    else    // EFalse
        {
        iValue = KICalFalse().AllocL();
        }

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetDateL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::GetDateL(TTime& aDate) const
    {
    FUNC_LOG;

    CheckNullValueL();
    GetDateFromValueL(aDate);

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetDateL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetDateL(const TTime& aDate)
    {
    FUNC_LOG;

    PrepareValuePointer();
    iValue = HBufC::NewL(KICalDateWidth);
    AppendDateToValueL(aDate);

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetTimeL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::GetTimeL(TTime& aTime, TTimeZoneType& aTzType) const
    {
    FUNC_LOG;

    CheckNullValueL();
    GetTimeFromValueL(aTime, aTzType);

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetTimeL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetTimeL(const TTime& aTime, TTimeZoneType aTzType)
    {
    FUNC_LOG;

    PrepareValuePointer();

    if (aTzType == EUtcTime)
        {
        iValue = HBufC::NewL(KICalTimeWidth + 1);
        AppendTimeToValueL(aTime);
        iValue->Des().Append(KICalUtcChar); // Append a 'Z' to signify UTC.
        }
    else if (aTzType == ESpecifiedTimeZone || aTzType == EFloatingTime)
        {
        iValue = HBufC::NewL(KICalTimeWidth);
        AppendTimeToValueL(aTime);
        }

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetDateTimeL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::GetDateTimeL(TTime& aDateTime, TTimeZoneType& aTzType, TInt aFirstCharacterNum) const
    {
    FUNC_LOG;

    CheckNullValueL();

    // The GetTimeFromValueL() function explicitly sets the TTime passed
    // in, so create a new TTime for the date and merge with the time later.
    TTime theDate;
    GetDateFromValueL(theDate, aFirstCharacterNum);

    // Only start looking for the time at character position KICalDateWidth
    // plus one for the 'T' separator.
    if (iValue->Length() > aFirstCharacterNum + KICalDateWidth + 1)
        {
        TTime theTime;
        if ((*iValue)[aFirstCharacterNum + KICalDateWidth] != KICalDateTimeSeparator)
            {
            User::Leave(KErrCorrupt);
            }

        GetTimeFromValueL(theTime, aTzType, aFirstCharacterNum + KICalDateWidth + 1);

        // Set the time component.
        TDateTime dateValues(theDate.DateTime());
        TDateTime combinedValues(theTime.DateTime());

        // Set the date component.
        combinedValues.SetYear(dateValues.Year());
        combinedValues.SetMonth(dateValues.Month());
        combinedValues.SetDay(dateValues.Day());

        // Set the value to return.
        aDateTime = TTime(combinedValues);
        }
    else
        {
        // There is no time component specified.  For compatibility with MS Outlook,
        // which can export RECURRENCE-ID fields with no time component, we use
        // a default time of '00:00'
        aDateTime = theDate;
        }

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetDateTimeL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetDateTimeL(const TTime& aDateTime, TTimeZoneType aTzType)
    {
    FUNC_LOG;

    PrepareValuePointer();

    if (aTzType == EUtcTime)
        {
        iValue = HBufC::NewL(KICalDateTimeWidth + 1);
        AppendDateToValueL(aDateTime);

        iValue->Des().Append(KICalTimeChar);
        AppendTimeToValueL(aDateTime);

        iValue->Des().Append(KICalUtcChar); // For UTC Time.
        }
    else if (aTzType == ESpecifiedTimeZone || aTzType == EFloatingTime)
        {
        iValue = HBufC::NewL(KICalDateTimeWidth);
        AppendDateToValueL(aDateTime);

        iValue->Des().Append(KICalTimeChar);
        AppendTimeToValueL(aDateTime);
        }

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::DurationL
// ---------------------------------------------------------------------------
//
EXPORT_C TTimeIntervalSeconds CESMRICalValue::DurationL() const
    {
    FUNC_LOG;

    CheckNullValueL();
    TTimeIntervalSeconds theTis;
    GetTimeIntervalFromValueL(theTis);

    return theTis;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetDurationL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetDurationL(TTimeIntervalSeconds aDuration)
    {
    FUNC_LOG;

    // e.g. P15DT5H0M20S
    PrepareValuePointer();

    iValue = HBufC::NewL(KICalDurationWidth);
    TInt durInt(aDuration.Int());

    if (durInt < 0)
        {
        // First character is a '-' for negative values
        iValue->Des().Append(KICalNegativeChar);

        // Now we've set the sign, change the durInt to +ve
        durInt =- durInt;
        }

    iValue->Des().Append(KICalDurationChar);

    // Add day portion.
    TInt numDays(durInt / KICalSecondsPerDay);
    durInt -= (numDays * KICalSecondsPerDay);
    iValue->Des().AppendNum(numDays);
    iValue->Des().Append(KICalDayChar);
    iValue->Des().Append(KICalTimeChar);

    // Add hour portion.
    TInt numHours(durInt / KICalSecondsPerHour);
    durInt -= (numHours * KICalSecondsPerHour);
    iValue->Des().AppendNum(numHours);
    iValue->Des().Append(KICalHourChar);

    // Add minute portion.
    TInt numMinutes(durInt / KICalSecondsPerMinute);
    durInt -= (numMinutes * KICalSecondsPerMinute);
    iValue->Des().AppendNum(numMinutes);
    iValue->Des().Append(KICalMinuteChar);

    // Add second portion.
    TInt numSeconds(durInt);
    iValue->Des().AppendNum(numSeconds);
    iValue->Des().Append(KICalSecondChar);

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetFloatL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::GetFloatL(TReal& aFloat) const
    {
    FUNC_LOG;

    CheckNullValueL();
    TLex stringLex(iValue->Des());
    User::LeaveIfError(stringLex.Val(aFloat));

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetFloatL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetFloatL(const TReal& aFloat)
    {
    FUNC_LOG;

    PrepareValuePointer();

    iValue = HBufC::NewL(KDefaultRealWidth);
    TRealFormat format;
    iValue->Des().Num(aFloat, format);

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::IntegerL
// ---------------------------------------------------------------------------
//
EXPORT_C TInt CESMRICalValue::IntegerL() const
    {
    FUNC_LOG;

    CheckNullValueL();

    return ReadIntL(iValue->Des());
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetIntegerL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetIntegerL(TInt aInt)
    {
    FUNC_LOG;

    PrepareValuePointer();
    iValue = HBufC::NewL(KICalTIntWidth);
    iValue->Des().Num(aInt);

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetPeriodL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::GetPeriodL(
    TTime& aStartTime,
    TTimeZoneType& aStartTzType,
    TTime& aEndTime,
    TTimeZoneType& aEndTzType) const
    {
    FUNC_LOG;

    // Example :   19970101T180000Z/19970102T070000Z
    // that is : yyyymmddThhmmss[Z]/yyyymmddThhmmss[Z]

    // Example2:   19970101T180000Z/PT5H30M
    // that is2: yyyymmddThhmmss[Z]/[+/-]P[lots of stuff]

    // As the first part is always a date-time, get this out to start off with
    GetDateTimeL(aStartTime, aStartTzType, 0);

    // Next we need to get the position of the separator.
    TInt charNumber(iValue->Des().Locate(KICalPeriodChar));
    User::LeaveIfError(charNumber);

    // If the character after the separator is a 'P', '+' or '-' the second part is a duration.
    ++charNumber;
    if (charNumber >= iValue->Length())
        {
        User::Leave(KErrCorrupt);
        }

    TChar theChar(iValue->Des()[charNumber]);

    if ((theChar == KICalDurationChar) ||
        (theChar == KICalPositiveChar) ||
        (theChar == KICalNegativeChar))
        {
        // Call a function to change the duration into a TTime (date + time)
        TTimeIntervalSeconds interval;
        GetTimeIntervalFromValueL(interval, charNumber);

        // Convert from this TimeInterval to a TTime
        aEndTime = aStartTime;
        aEndTime += interval;

        // The timezone will be the same as the first.
        aEndTzType = aStartTzType;
        }
    else // Presume it's a date-time
        {
        GetDateTimeL(aEndTime, aEndTzType, charNumber);
        }

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetPeriodL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetPeriodL(
    const TTime& aStartTime,
    TTimeZoneType aStartTzType,
    const TTime& aEndTime,
    TTimeZoneType aEndTzType)
    {
    FUNC_LOG;

    // Example:   19970101T180000Z/19970102T070000Z
    // that is: yyyymmddThhmmss[Z]/yyyymmddThhmmss[Z]

    PrepareValuePointer();

    SetDateTimeL(aStartTime, aStartTzType);

    if (aEndTzType == EUtcTime)
        {
        iValue->ReAllocL(iValue->Length() + 2 + KICalDateTimeWidth);
        iValue->Des().Append(KICalPeriodChar);

        AppendDateToValueL(aEndTime);
        iValue->Des().Append(KICalTimeChar);

        AppendTimeToValueL(aEndTime);
        iValue->Des().Append(KICalUtcChar);
        }
    else if (aEndTzType == ESpecifiedTimeZone || aEndTzType == EFloatingTime)
        {
        iValue->ReAllocL(iValue->Length() + 1 + KICalDateTimeWidth);

        iValue->Des().Append(KICalPeriodChar);
        AppendDateToValueL(aEndTime);

        iValue->Des().Append(KICalTimeChar);
        AppendTimeToValueL(aEndTime);
        }

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetDayL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::GetDayL(TDay& aDay, TInt& aPos) const
    {
    FUNC_LOG;

    CheckNullValueL();

    // Find the end of the numeric part.
    _LIT(KICalDayNumeric, "-+0123456789");
    TInt endNumeric(0);

    while (endNumeric < iValue->Length())
        {
        if (KICalDayNumeric().Locate((*iValue)[endNumeric]) == KErrNotFound)
            {
            break;
            }

        ++endNumeric;
        }

    // Set the numeric part.
    if (endNumeric != 0)
        {
        aPos = ReadIntL(iValue->Left(endNumeric));
        }
    else
        {
        aPos = 0;
        }

    // Set the day part.
    HBufC* dayStrBuf = iValue->Mid(endNumeric).AllocLC();
    TPtr dayStr( dayStrBuf->Des() );
    dayStr.TrimLeft();

    if (dayStr.CompareF(KICalMonday) == 0)
        {
        aDay = EMonday;
        }
    else if (dayStr.CompareF(KICalTuesday) == 0)
        {
        aDay = ETuesday;
        }
    else if (dayStr.CompareF(KICalWednesday) == 0)
        {
        aDay = EWednesday;
        }
    else if (dayStr.CompareF(KICalThursday) == 0)
        {
        aDay = EThursday;
        }
    else if (dayStr.CompareF(KICalFriday) == 0)
        {
        aDay = EFriday;
        }
    else if (dayStr.CompareF(KICalSaturday) == 0)
        {
        aDay = ESaturday;
        }
    else if (dayStr.CompareF(KICalSunday) == 0)
        {
        aDay = ESunday;
        }
    else
        {
        User::Leave(KErrCorrupt);   // Invalid day.
        }

    CleanupStack::PopAndDestroy( dayStrBuf );
    dayStrBuf = NULL;

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetDayL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetDayL(TDay aDay, TInt aPos)
    {
    FUNC_LOG;

    PrepareValuePointer();

    // Get the day as a descriptor.
    TPtrC dayName;

    switch (aDay)
        {
        case EMonday:
            dayName.Set(KICalMonday());
            break;
        case ETuesday:
            dayName.Set(KICalTuesday());
            break;
        case EWednesday:
            dayName.Set(KICalWednesday());
            break;
        case EThursday:
            dayName.Set(KICalThursday());
            break;
        case EFriday:
            dayName.Set(KICalFriday());
            break;
        case ESaturday:
            dayName.Set(KICalSaturday());
            break;
        case ESunday:
            dayName.Set(KICalSunday());
            break;
        default:
            User::Leave(KErrUnknown);
            break;
        }

    // We need space for a number and a day name.
    iValue = HBufC::NewL(KICalTIntWidth + dayName.Length());
    iValue->Des().AppendNum(aPos);
    iValue->Des().Append(dayName);

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::MonthL
// ---------------------------------------------------------------------------
//
EXPORT_C TMonth CESMRICalValue::MonthL() const
    {
    FUNC_LOG;

    CheckNullValueL();

    TInt num(IntegerL());

    if ((num < 1) || (num > 12))
        {
        User::Leave(KErrCorrupt);
        }

    return static_cast<TMonth>(num - 1);
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetMonthL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetMonthL(TMonth aMonth)
    {
    FUNC_LOG;

    PrepareValuePointer();
    SetIntegerL(aMonth + 1);

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetRecurrenceRuleL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::GetRecurrenceRuleL(RPointerArray<CESMRICalRuleSegment>& aRuleSegmentArray) const
    {
    FUNC_LOG;

    CheckNullValueL();

    // Find the first semicolon:
    TInt nextSemiColon(iValue->Locate(KICalSemiColonChar));

    if (nextSemiColon == KErrNotFound)
        {
        // This means there is only one segment, pretend the semicolon exists at the end of the buffer
        nextSemiColon = iValue->Length();
        }

    TUint charNum(0);

    do
        {
        // Create a rule segment from characters charNum to nextSemiColon
        CESMRICalRuleSegment* theRule = CESMRICalRuleSegment::NewLC(iValue->Mid(charNum, nextSemiColon - charNum));
        User::LeaveIfError(aRuleSegmentArray.Append(theRule));  // Takes ownership.
        CleanupStack::Pop(theRule);

        // Put the character marker past the current semicolon
        charNum = nextSemiColon + 1;

        // We only want to check for any more semicolons in the area to the right of charNum.
        // First check if there are ANY characters to the right of charNum
        if (charNum < iValue->Length())
            {
            // Find the location of the next semi colon in this area.
            nextSemiColon = iValue->Right(iValue->Length() - charNum).Locate(KICalSemiColonChar);

            if (nextSemiColon != KErrNotFound)
                {
                // Set the semicolon to be in it's correct position, shifted to take into account the fact
                // that we were only looking in the right part of the original descriptor.
                nextSemiColon += charNum;
                }
            else
                {
                // There are no more semicolons, read in the last value and then exit the loop
                nextSemiColon = iValue->Length();
                }
            }
        }
    while (charNum < iValue->Length());

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetRecurrenceRuleL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetRecurrenceRuleL(const TDesC& aRuleValue)
    {
    FUNC_LOG;

    PrepareValuePointer();
    iValue = aRuleValue.AllocL();

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::TextL
// ---------------------------------------------------------------------------
//
EXPORT_C const TDesC& CESMRICalValue::TextL() const
    {
    FUNC_LOG;

    CheckNullValueL();

    return *iValue;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetTextL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetTextL(const TDesC& aText)
    {
    FUNC_LOG;

    PrepareValuePointer();
    iValue = aText.AllocL();

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::UtcOffsetL
// ---------------------------------------------------------------------------
//
EXPORT_C TTimeIntervalSeconds CESMRICalValue::UtcOffsetL() const
    {
    FUNC_LOG;

    // Format is (e.g.) +1000 (10 hours ahead) or -013045 (1.5 hours and 45 seconds behind).
    CheckNullValueL();

    const TInt length = iValue->Length();

    if ((length != KLongFormatUtcOffsetWidth) && (length != KShortFormatUtcOffsetWidth))
        {
        // Invalid UTC Offset - we can't really test more than this, so the output may be garbage
        User::Leave(KErrCorrupt);
        }

    // Get the value of the hour component.
    TInt hours(ReadIntL(iValue->Des().Left(3)));    // '+' or '-' plus the hours component.

    // Get the value of the minute component.
    TInt minutes(ReadIntL(iValue->Des().Mid(3, 2)));    // The minutes component.

    // Get the value of the (optional) second component.
    TInt seconds(0);

    if (length == KLongFormatUtcOffsetWidth)    // Long format.
        {
        seconds = ReadIntL(iValue->Des().Mid(5, 2));    // The second component.
        }

    // Convert to TTimeIntervalSeconds.
    return TTimeIntervalSeconds((hours * KICalSecondsPerHour) + (minutes * KICalSecondsPerMinute) + seconds);
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::SetUtcOffsetL
// ---------------------------------------------------------------------------
//
EXPORT_C void CESMRICalValue::SetUtcOffsetL(TTimeIntervalSeconds aOffset)
    {
    FUNC_LOG;

    // Format is (e.g.) +1000 (10 hours ahead) or -013045 (1.5 hours and 45 seconds behind).
    PrepareValuePointer();

    // Create a buffer long enough to hold the widest format.
    iValue = HBufC::NewL(KLongFormatUtcOffsetWidth);

    // Get the offset as an integer.
    TInt offset(aOffset.Int());

    // Set the sign.
    if (offset < 0)
        {
        iValue->Des().Append(KICalNegativeChar);

        // Set offset to positive for calculations.
        offset =- offset;
        }
    else
        {
        iValue->Des().Append(KICalPositiveChar);
        }

    // Add hour portion.
    TInt numHours(offset / KICalSecondsPerHour);
    offset -= (numHours * KICalSecondsPerHour);
    iValue->Des().AppendNumFixedWidth(numHours, EDecimal, 2);

    // Add minute portion.
    TInt numMinutes(offset / KICalSecondsPerMinute);
    offset -= (numMinutes * KICalSecondsPerMinute);
    iValue->Des().AppendNumFixedWidth(numMinutes, EDecimal, 2);

    // Add second portion if necessary.
    if (offset > 0)
        {
        iValue->Des().AppendNumFixedWidth(offset, EDecimal, 2);
        }

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::CESMRICalValue
// ---------------------------------------------------------------------------
//
CESMRICalValue::CESMRICalValue()
    {
    FUNC_LOG;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::ConstructL
// ---------------------------------------------------------------------------
//
void CESMRICalValue::ConstructL()
    {
    FUNC_LOG;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::CheckNullValueL
// ---------------------------------------------------------------------------
//
/**
Checks for a NULL value. Should be called before accessing iValue.
@leave Leaves with KErrCorrupt if the value is NULL.
@internalTechnology
*/
void CESMRICalValue::CheckNullValueL() const
    {
    FUNC_LOG;

    if (!iValue)
        {
        User::Leave(KErrCorrupt);
        }

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::PrepareValuePointer
// ---------------------------------------------------------------------------
//
/**
Prepares iValue pointer for writing. Deletes existing pointer if it exists.
@internalTechnology
*/
void CESMRICalValue::PrepareValuePointer()
    {
    FUNC_LOG;

    if (iValue)
        {
        delete iValue;
        iValue = NULL;
        }

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::ReadIntL
// ---------------------------------------------------------------------------
//
/**
Reads a TInt from the given descriptor.
@param aDes The descriptor to convert
@return The integer conversion.
@leave Leaves if there is an error reading an integer.
@internalTechnology
*/
TInt CESMRICalValue::ReadIntL(const TDesC& aDes) const
    {
    FUNC_LOG;

    TLex stringLex(aDes);
    TInt returnValue(0);
    User::LeaveIfError(stringLex.Val(returnValue));

    return returnValue;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::AppendDateToValueL
// ---------------------------------------------------------------------------
//
/**
Appends the date to iValue. Member iValue must be initialised before calling.
@param aDate TTime to append to the value.
@internalTechnology
*/
void CESMRICalValue::AppendDateToValueL(const TTime& aDate)
    {
    FUNC_LOG;

    TBuf<KICalDateWidth> formattedDate;
    aDate.FormatL(formattedDate, KICalDateFormat);
    iValue->Des().Append(formattedDate);

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetDateFromValueL
// ---------------------------------------------------------------------------
//
/**
Gets the date from iValue descriptor. Should be in the format YYYYMMDD
@param aDate A modifiable reference returning a date.
@param aFirstCharacterNum Skip this many characters at the start of the value.
@leave Leaves with KErrCorrupt if the format of the value is not a valid date.
@internalTechnology
*/
void CESMRICalValue::GetDateFromValueL(TTime& aDate, TInt aFirstCharacterNum) const
    {
    FUNC_LOG;

    if (aFirstCharacterNum + KICalDateWidth > iValue->Length())
        {
        User::Leave(KErrCorrupt);
        }

    // Get the year as an int.
    TInt year(ReadIntL(iValue->Des().Mid(aFirstCharacterNum, 4)));  // YYYYmmdd

    if (year < 0)   // All positive years are valid
        {
        User::Leave(KErrCorrupt);
        }

    // Get the month as an int.
    TInt month(ReadIntL(iValue->Mid(aFirstCharacterNum + 4, 2)));   // yyyMMdd

    if ((month < 1) || (month > 12))
        {
        User::Leave(KErrCorrupt);
        }

    // Get the day as an int.
    TInt day(ReadIntL(iValue->Mid(aFirstCharacterNum + 6, 2))); // yyyymmDD

    if ((day < 1) || (day > Time::DaysInMonth(year, static_cast<TMonth>(month - 1))))   // Zero-offset month.
        {
        User::Leave(KErrCorrupt);
        }

    // Set the date from its component parts.
    aDate = 0;
    aDate += TTimeIntervalYears(year);
    aDate += TTimeIntervalMonths(month - 1);    // Zero-offset.
    aDate += TTimeIntervalDays(day - 1);        // Zero-offset.

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::AppendTimeToValueL
// ---------------------------------------------------------------------------
//
/**
Appends the time to iValue. Member iValue must be initialised before calling.
@param aTime TTime to append to the value.
@internalTechnology
*/
void CESMRICalValue::AppendTimeToValueL(const TTime& aTime)
    {
    FUNC_LOG;

    // Format is HHMMSS followed optionally by a Z for UTC time.
    // Note that the 'Z' is not appended here.
    TDateTime fullTime(aTime.DateTime());

    iValue->Des().AppendNumFixedWidth(fullTime.Hour(), EDecimal, 2);
    iValue->Des().AppendNumFixedWidth(fullTime.Minute(), EDecimal, 2);
    iValue->Des().AppendNumFixedWidth(fullTime.Second(), EDecimal, 2);

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetTimeFromValueL
// ---------------------------------------------------------------------------
//
/**
Gets the time from iValue descriptor, starting from a pre-determined point.
Should be in the format HHMMSS[Z]
@param aTime TTime to store result of read in
@param aTzType Enumeration showing whether the time represents local time (not
supported), UTC or a custom time zone.
@param aFirstCharacterNum The character number to start searching from.
@leave Leaves with KErrCorrupt if the value does not hold a valid time.
@internalTechnology
*/
void CESMRICalValue::GetTimeFromValueL(TTime& aTime, TTimeZoneType& aTzType, TInt aFirstCharacterNum) const
    {
    FUNC_LOG;

    if (aFirstCharacterNum + KICalTimeWidth > iValue->Length())
        {
        User::Leave(KErrCorrupt);
        }

    // Create a new descriptor containing just the first KICalTimeWidth characters of iValue
    // It's size is one larger as we will need to add a full stop (see below)
    HBufC* formattedTime = HBufC::NewLC(iValue->Des().Mid(aFirstCharacterNum, KICalTimeWidth).Length() + 1);
    *formattedTime = iValue->Des().Mid(aFirstCharacterNum, KICalTimeWidth);

    // The only formatting needed is to append a full stop
    formattedTime->Des().Append(KICalMicroChar);

    if (aTime.Set(*formattedTime))
        {
        User::Leave(KErrCorrupt);
        }

    if ((iValue->Length() > (KICalTimeWidth + aFirstCharacterNum)) &&
        (iValue->Des()[KICalTimeWidth + aFirstCharacterNum] == KICalUtcChar))
        {
        aTzType = EUtcTime;
        }
    else
        {//DAVIDTODO:
        // If there is no 'Z' character then assume that there
        // is a specified time zone - EFloatingTime is not used.
        aTzType = ESpecifiedTimeZone;
        }

    CleanupStack::PopAndDestroy(formattedTime);

    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetTimeIntervalFromValueL
// ---------------------------------------------------------------------------
//
/**
Retrieves a time interval stored as a duration as per the RFC 2445
specification.
@param aTimeInterval TTimeIntervalSeconds to store the result in.
@param aFirstCharacterNum The position in the value containing the first
character of the duration.
@leave Leaves with KErrCorrupt if the value does not hold a valid interval.
@internalTechnology
*/
void CESMRICalValue::GetTimeIntervalFromValueL(TTimeIntervalSeconds& aTimeInterval, TInt aFirstCharacterNum) const
    {
    FUNC_LOG;

    //     dur-value  = (["+"] / "-") "P" (dur-date / dur-time / dur-week)
    //     dur-date   = dur-day [dur-time]
    //     dur-time   = "T" (dur-hour / dur-minute / dur-second)
    //     dur-week   = 1*DIGIT "W"
    //     dur-hour   = 1*DIGIT "H" [dur-minute]
    //     dur-minute = 1*DIGIT "M" [dur-second]
    //     dur-second = 1*DIGIT "S"
    //     dur-day    = 1*DIGIT "D"

    // Example: A duration of 15 days, 5 hours and 20 seconds would be:
    //     P15DT5H0M20S
    // A duration of 7 weeks would be:
    //     P7W

    TInt intervalMultiplier(1); // This variable sets the interval to be positive or negative.

    // Length should be at least two.
    if ((aFirstCharacterNum >= iValue->Length()) || (iValue->Length() < 2 + aFirstCharacterNum))
        {
        User::Leave(KErrCorrupt);
        }

    // Check that the first character is a 'P', '+' or '-' and adjust the interval accordingly.
    TChar firstCharacter(iValue->Des()[aFirstCharacterNum]);

    if (firstCharacter == KICalDurationChar)
        {
        intervalMultiplier = 1;
        aFirstCharacterNum += 1; // Skip the "P"
        }
    else if (firstCharacter == KICalPositiveChar)
        {
        intervalMultiplier = 1;
        aFirstCharacterNum += 2; // Skip the "+P"
        }
    else if (firstCharacter == KICalNegativeChar)
        {
        intervalMultiplier = -1;
        aFirstCharacterNum += 2; // Skip the "-P"
        }
    else
        {
        User::Leave(KErrCorrupt);
        }

    // It can only contain a dur-date, or a dur-time, or a dur-week
    TInt theInterval(0);
    if (!GetDurDateL(theInterval, aFirstCharacterNum))
        {
        if (!GetDurTimeL(theInterval, aFirstCharacterNum))
            {
            if (!GetDurWeekL(theInterval, aFirstCharacterNum))
                {
                User::Leave(KErrCorrupt);
                }
            }
        }

    theInterval *= intervalMultiplier;

    // Now we've done the multiply we can convert it into a TTimeIntervalSeconds
    aTimeInterval = theInterval;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetDurDateL
// ---------------------------------------------------------------------------
//
/**
Reads in and converts a dur-day into a number of seconds
@param aIntervalSecs Function adds number of seconds to this variable.
@param aCurrentCharNumber Character number to start looking in the value.
@return ETrue if the value could be interpreted as a dur-date, EFalse otherwise.
@internalTechnology
*/
TBool CESMRICalValue::GetDurDateL(TInt& aIntervalSecs, TInt& aCurrentCharNumber) const
    {
    FUNC_LOG;

    //  dur-date   = dur-day [dur-time]
    //  dur-day    = 1*DIGIT "D"

    if (aCurrentCharNumber >= iValue->Length())
        {
        return EFalse;
        }

    // Create a new TPtrC containing the data from iValue which we need.
    TPtrC data(iValue->Mid(aCurrentCharNumber));

    // Find the next 'D' in the descriptor.
    TInt nextDPos(data.Locate(KICalDayChar));

    // Check it exists
    if (nextDPos != KErrNotFound)
        {
        // If character found, construct a number from currentCharNumber to nextDPos
        TInt theNum(ReadIntL(data.Left(nextDPos)));

        // Convert this time from days to seconds
        theNum *= KICalSecondsPerDay;
        aIntervalSecs += theNum;
        aCurrentCharNumber += nextDPos + 1; // Extra increment to get past the 'D'

        // Check if a dur-time follows. It's optional
        // so we can ignore it's return value.
        GetDurTimeL(aIntervalSecs, aCurrentCharNumber);
        return ETrue;
        }

    // If no character found, return EFalse
    return EFalse;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetDurTimeL
// ---------------------------------------------------------------------------
//
/**
Reads in and converts a dur-time into a number of seconds
@param aIntervalSecs Function adds number of seconds to this variable.
@param aCurrentCharNumber Character number to start looking in the value.
@return ETrue if the value could be interpreted as a dur-time, EFalse otherwise.
@leave Leaves with KErrCorrupt if the value does not hold a valid time.
@internalTechnology
*/
TBool CESMRICalValue::GetDurTimeL(TInt& aIntervalSecs, TInt& aCurrentCharNumber) const
    {
    FUNC_LOG;

    //  dur-time   = "T" (dur-hour / dur-minute / dur-second) e.g. T5H0M20S

    if (aCurrentCharNumber >= iValue->Length())
        {
        return EFalse;
        }

    if (iValue->Des()[aCurrentCharNumber] == KICalTimeChar)
        {
        ++aCurrentCharNumber; // Increment to get past 'T'

        if (!GetDurHourL(aIntervalSecs, aCurrentCharNumber))
            {
            if (!GetDurMinuteL(aIntervalSecs, aCurrentCharNumber))
                {
                if (!GetDurSecondL(aIntervalSecs, aCurrentCharNumber))
                    {
                    // We should not have read a 'T' and failed to match hour/minute/second
                    User::Leave(KErrCorrupt);
                    }
                }
            }
        }
    else
        {
        // First character is not a 'T', therefore value is not a dur-time
        return EFalse;
        }

    return ETrue;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetDurHourL
// ---------------------------------------------------------------------------
//
/**
Reads in and converts a dur-hour into a number of seconds. There is no range
check on the number of hours.
@param aIntervalSecs Function adds number of seconds to this variable.
@param aCurrentCharNumber Character number to start looking in the value.
@return ETrue if the value could be interpreted as a dur-hour, EFalse otherwise.
@internalTechnology
*/
TBool CESMRICalValue::GetDurHourL(TInt& aIntervalSecs, TInt& aCurrentCharNumber) const
    {
    FUNC_LOG;

    //  dur-hour   = 1*DIGIT "H" [dur-minute]

    if (aCurrentCharNumber >= iValue->Length())
        {
        return EFalse;
        }

    // Create a new TPtrC containing the data from iValue which we need
    TPtrC data(iValue->Mid(aCurrentCharNumber));

    // Find the next 'H' in the descriptor
    TInt nextHPos(data.Locate(KICalHourChar));

    // Check it exists
    if (nextHPos != KErrNotFound)
        {
        // If character found, construct a number from currentCharNumber to nextHPos
        TInt theNum(ReadIntL(data.Left(nextHPos)));

        // Convert this time from hours to seconds
        theNum *= KICalSecondsPerHour;

        aIntervalSecs += theNum;
        aCurrentCharNumber += nextHPos + 1; // Extra increment to get past 'H'

        // Check if a dur-minute follows. It's optional
        // so we can ignore it's return value.
        GetDurMinuteL(aIntervalSecs,aCurrentCharNumber);
        return ETrue;
        }

    // If no character found, return EFalse
    return EFalse;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetDurMinuteL
// ---------------------------------------------------------------------------
//
/**
Reads in and converts a dur-minute into a number of seconds. There is no range
check on the number of minutes.
@param aIntervalSecs Function adds number of seconds to this variable.
@param aCurrentCharNumber Character number to start looking in the value.
@return ETrue if the value could be interpreted as a dur-minute, EFalse
otherwise.
@internalTechnology
*/
TBool CESMRICalValue::GetDurMinuteL(TInt& aIntervalSecs, TInt& aCurrentCharNumber) const
    {
    FUNC_LOG;

    //  dur-minute = 1*DIGIT "M" [dur-second]

    if (aCurrentCharNumber >= iValue->Length())
        {
        return EFalse;
        }

    // Create a new TPtrC containing the data from iValue which we need
    TPtrC data(iValue->Mid(aCurrentCharNumber));

    // Find the next 'M' in the descriptor
    TInt nextMPos(data.Locate(KICalMinuteChar));

    // Check it exists
    if (nextMPos != KErrNotFound)
        {
        // If character found, construct a number from currentCharNumber to nextMPos
        TInt theNum(ReadIntL(data.Left(nextMPos)));

        // Convert this time from minutes to seconds
        theNum *= KICalSecondsPerMinute;

        aIntervalSecs += theNum;
        aCurrentCharNumber += nextMPos + 1; // Extra increment to get past 'M'

        // Check if a dur-second follows. It's optional
        // so we can ignore it's return value.
        GetDurSecondL(aIntervalSecs,aCurrentCharNumber);
        return ETrue;
        }

    // If no character found, return EFalse
    return EFalse;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetDurSecondL
// ---------------------------------------------------------------------------
//
/**
Reads in and converts a dur-second into a number of seconds. There is no range
check on the number of seconds.
@param aIntervalSecs Function adds number of seconds to this variable.
@param aCurrentCharNumber Character number to start looking in the value.
@return ETrue if the value could be interpreted as a dur-second, EFalse
otherwise.
@internalTechnology
*/
TBool CESMRICalValue::GetDurSecondL(TInt& aIntervalSecs, TInt& aCurrentCharNumber) const
    {
    FUNC_LOG;

    //  dur-second = 1*DIGIT "S"

    if (aCurrentCharNumber >= iValue->Length())
        {
        return EFalse;
        }

    // Create a new TPtrC containing the data from iValue which we need
    TPtrC data(iValue->Mid(aCurrentCharNumber));

    // Find the next 'S' in the descriptor
    TInt nextSPos(data.Locate(KICalSecondChar));

    // Check it exists
    if (nextSPos != KErrNotFound)
        {
        // If character found, construct a number from currentCharNumber to nextSPos
        TInt theNum(ReadIntL(data.Left(nextSPos)));

        aIntervalSecs += theNum;
        aCurrentCharNumber += nextSPos;

        return ETrue;
        }

    // If no character found, return EFalse
    return EFalse;
    }

// ---------------------------------------------------------------------------
// CESMRICalValue::GetDurWeekL
// ---------------------------------------------------------------------------
//
/**
Reads in and converts a dur-week into a number of seconds
@param aIntervalSecs Function adds number of seconds to this variable
@param aCurrentCharNumber Character number to start looking in the value.
@return ETrue if the value could be interpreted as a dur-week, EFalse
otherwise.
@internalTechnology
*/
TBool CESMRICalValue::GetDurWeekL(TInt& aIntervalSecs, TInt& aCurrentCharNumber) const
    {
    FUNC_LOG;

    //  dur-week   = 1*DIGIT "W"

    if (aCurrentCharNumber >= iValue->Length())
        {
        return EFalse;
        }

    // Create a new TPtrC containing the data from iValue which we need
    TPtrC data(iValue->Mid(aCurrentCharNumber));

    // Find the next 'W' in the descriptor
    TInt nextWPos(data.Locate(KICalWeekChar));

    // Check it exists
    if (nextWPos != KErrNotFound)
        {
        // If character found, construct a number from currentCharNumber to nextWPos
        TInt theNum(ReadIntL(data.Left(nextWPos)));

        // Convert this time from weeks to seconds
        theNum *= KICalSecondsPerWeek;

        aIntervalSecs += theNum;
        aCurrentCharNumber += nextWPos;

        return ETrue;
        }

    // If no character found, return EFalse
    return EFalse;
    }


// End of File



