root/core/Date.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. Day
  2. DayFromYear
  3. TimeFromYear
  4. DaysInYear
  5. TimeWithinDay
  6. YearFromTime
  7. IsLeapYear
  8. TimeInLeapYear
  9. DayWithinYear
  10. MonthFromTime
  11. DateFromTime
  12. WeekDay
  13. UTC
  14. LocalTime
  15. GetTimezoneOffset
  16. HourFromTime
  17. DayFromMonth
  18. MinFromTime
  19. SecFromTime
  20. MsecFromTime
  21. MakeDate
  22. MakeTime
  23. MakeDay
  24. format
  25. toString
  26. getDateProperty
  27. setTime
  28. setTime
  29. setDate

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is [Open Source Virtual Machine.].
 *
 * The Initial Developer of the Original Code is
 * Adobe System Incorporated.
 * Portions created by the Initial Developer are Copyright (C) 2004-2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Adobe AS3 Team
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */


#include "avmplus.h"

namespace avmplus
{       
#ifdef FIX_RECIPROCAL_BUG
        //
        // Visual C++ 6.0 optimizes division by turning it into multiplication
        // by the reciprocal.  This loses precision and results in inaccuracies
        // in date calculations.  Avoid this "optimization" by putting the
        // numbers in static variables.
        // 
        static double kMsecPerDay       = 86400000;
        static double kMsecPerHour      = 3600000;
        static double kMsecPerSecond    = 1000;
        static double kMsecPerMinute    = 60000;
#else
        //
        // OK, we have a compiler that doesn't want to optimize our divisions.
        //
#define kMsecPerDay       86400000
#define kMsecPerHour      3600000
#define kMsecPerSecond    1000
#define kMsecPerMinute    60000
#endif

        //
        // We don't ever divide by these, so they should be fine.
        //
#define kSecondsPerMinute 60
#define kMinutesPerHour   60
#define kHoursPerDay      24
#define kMsecPerSecondInt 1000


        static double Day(double t)
        {
                return MathUtils::floor(t / (double)kMsecPerDay);
        }

        static double DayFromYear(double year)
        {
                return (365 * (year - 1970) +
                                MathUtils::floor((year - 1969) / 4) -
                                MathUtils::floor((year - 1901)/100) +
                                MathUtils::floor((year - 1601) / 400));
        }

        static inline double TimeFromYear(int year)
        {
                return (double)kMsecPerDay * DayFromYear(year);
        }

        static int DaysInYear(int year)
        {
                if (year % 4) {
                        return 365;
                }
                if (year % 100) {
                        return 366;
                }
                if (year % 400) {
                        return 365;
                }
                return 366;
        }

        static inline double TimeWithinDay(double t)
        {
                double result = MathUtils::mod(t, kMsecPerDay);
                if (result < 0)
            result += kMsecPerDay;
                return result;
        }

        // NOTE: this is used by the Player core code. Changing this will change legacy behavior.
        int YearFromTime(double t)
        {
                double day = Day(t);
                int lo, hi;
                lo = (int) MathUtils::floor((t < 0) ? (day / 365) : (day / 366)) + 1970;
                hi = (int) MathUtils::ceil((t < 0) ? (day / 366) : (day / 365)) + 1970;
                while (lo < hi) {

                        // 13may04 grandma :
                        // This was pivot = (lo + hi) / 2, but bug 89715 inadvertantly calls this with
                        // t = -6.5438017398347670e+019, which produces lo = -2075023950 and hi = -2069354479,
                        // and (lo + hi) overflows. The below expression won't overflow
                        //int pivot = (lo / 2) + (hi / 2) + (lo & hi & 1);

                        // 8/17/04 edsmith: 
                        // the above expression does overflow, with other large numbers.
                        // this one below uses double math to avoid overflow.
                        int pivot = (int) ((((double)lo) + ((double)hi)) / 2);

                        double pivotTime = TimeFromYear(pivot);
                        if (pivotTime <= t) {
                                if (TimeFromYear(pivot + 1) > t) { // R41
                                        return pivot;
                                } else {
                                        lo = pivot + 1;
                                }
                        } else if (pivotTime > t) {
                                hi = pivot - 1;
                        }
                }
                return lo;
        }

        static inline bool IsLeapYear(int year)
        {
                return DaysInYear(year) == 366;
        }

        static inline bool TimeInLeapYear(double t)
        {
                return IsLeapYear(YearFromTime(t));
        }

        static inline int DayWithinYear(double t)
        {
                return (int) (Day(t) - DayFromYear((int) YearFromTime(t)));
        }

        static const uint16 kMonthOffset[2][13] = {
                //    Jan Feb Mar Apr May  Jun  Jul  Aug  Sep  Oct  Nov  Dec  Total
                { 0,  31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
                { 0,  31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
        };

        static int MonthFromTime(double t)
        {
                int day = DayWithinYear(t);
                int leap = (int) TimeInLeapYear(t);
                int i;

                for (i=0; i<11; i++) {
                        if (day < kMonthOffset[leap][i+1]) {
                                break;
                        }
                }
                return i;
        }

        static int DateFromTime(double t)
        {
                int month = MonthFromTime(t);
                return DayWithinYear(t) - kMonthOffset[(int) TimeInLeapYear(t)][month] + 1;
        }

        //mds: gcc has problems when this is inlined.
        int WeekDay(double t)
        {
                int result = (int) MathUtils::mod(Day(t) + 4,  7);
                if (result < 0) {
                        result = 7 + result;
                }
                return result;
        }

        /* This needs to be publicly available, for Mac edge code. (srj) */
        double UTC(double t)
        {
                double adj = VMPI_getLocalTimeOffset();
                return (t - adj - VMPI_getDaylightSavingsTA(t - adj));
        }

        static double LocalTime(double t)
        {
                return (t + VMPI_getLocalTimeOffset() + VMPI_getDaylightSavingsTA(t));
        }

        // this needs to be publicly available, for data paraser
        double GetTimezoneOffset(double t)
        {
                return (t - LocalTime(t)) / kMsecPerMinute;
        }

        static int HourFromTime(double t)
        {
                int result = (int) MathUtils::mod(MathUtils::floor((t + 0.5) / kMsecPerHour), kHoursPerDay);
                if (result < 0) {
                        result += kHoursPerDay;
                }
                return result;
        }

        static double DayFromMonth(double year, double month)
        {
                int iMonth = (int) MathUtils::floor(month);
                if (iMonth < 0 || iMonth >= 12) {
                        return MathUtils::kNaN;
                }
                return DayFromYear((int)year) + kMonthOffset[(int)IsLeapYear((int)year)][iMonth];
        }

        static int MinFromTime(double time)
        {
                int result = (int) MathUtils::mod(MathUtils::floor(time / kMsecPerMinute), kMinutesPerHour);
                if (result < 0) {
                        result += kMinutesPerHour;
                }
                return result;
        }

        static int SecFromTime(double time)
        {
                int result = (int) MathUtils::mod(MathUtils::floor(time / kMsecPerSecond), kSecondsPerMinute);
                if (result < 0) {
                        result += kSecondsPerMinute;
                }
                return result;
        }

        static int MsecFromTime(double time)
        {
                int result = (int) MathUtils::mod(time, kMsecPerSecond);
                if (result < 0) {
                        result += kMsecPerSecondInt;
                }
                return result;
        }

        double MakeDate(double day, double time)
        {
                // if any value is not finite, return NaN
                if (MathUtils::isInfinite(day) || MathUtils::isInfinite(time) ||
                        day != day || time != time)
                        return MathUtils::kNaN;

                day = MathUtils::toInt(day);
                time = MathUtils::toInt(time);
                
                return day * kMsecPerDay + time;
        }

        double MakeTime(double hour, double min, double sec, double ms)
        {
                // if any value is not finite, return NaN
                if (MathUtils::isInfinite(hour) || MathUtils::isInfinite(min) || MathUtils::isInfinite(sec) || MathUtils::isInfinite(ms) ||
                        hour != hour || min != min || sec != sec || ms != ms)
                        return MathUtils::kNaN;

                hour = MathUtils::toInt(hour);
                min  = MathUtils::toInt(min);
                sec  = MathUtils::toInt(sec);
                ms   = MathUtils::toInt(ms);
                
                return hour * (double)kMsecPerHour + min * (double)kMsecPerMinute + sec * (double)kMsecPerSecond + ms;
        }

        double MakeDay(double year, double month, double date)
        {
                // if any value is not finite, return NaN
                if (MathUtils::isInfinite(year) || MathUtils::isInfinite(month) || MathUtils::isInfinite(date) ||
                        year != year || month != month || date != date)
                        return MathUtils::kNaN;

                year  = MathUtils::toInt(year);
                month = MathUtils::toInt(month);
                date  = MathUtils::toInt(date);

                year += MathUtils::floor(month / 12);
                month = MathUtils::mod(month, 12);
                if (month < 0) {
                        month += 12;
                }
                return DayFromMonth(year, month) + (date - 1);
        }

        Date::Date()
        {
                m_time = VMPI_getDate();
        }
                
        Date::Date(double year,
                           double month,
                           double date,
                           double hours,
                           double min,
                           double sec,
                           double msec,
                           bool utcFlag)
        {
                if (year < 100) {
                        year += 1900;
                }
                m_time = MakeDate(MakeDay(year, month, date),
                                                  MakeTime(hours, min, sec, msec));
                if (!utcFlag) {
                        m_time = UTC(m_time);
                }
        }

        void Date::format(wchar *buffer,
                                          const char *format,
                                          ...) const
        {
                va_list ap;

                va_start(ap, format);
                while (*format) {
                        if (*format == '%') {
                                switch (*++format) {
                                case 's':
                                        {
                                                char *str = va_arg(ap, char *);
                                                while (*str) {
                                                        *buffer++ = *str++;
                                                }
                                        }
                                        break;
                                case '2':
                                        {
                                                int value = va_arg(ap, int);
                                                *buffer++ = (wchar)((value/10) + '0');
                                                *buffer++ = (wchar)((value%10) + '0');
                                        }
                                        break;
                                case '3':
                                        {
                                                char *str = va_arg(ap, char *);
                                                *buffer++ = *str++;
                                                *buffer++ = *str++;
                                                *buffer++ = *str++;
                                        }
                                        break;
                                case 'c':
                                        {
                                                // gcc complains if you put va_arg(ap, char)
                                                char value = (char)(va_arg(ap, int));
                                                *buffer++ = value;
                                        }
                                        break;
                                case 'd':
                                        {
                                                int value = va_arg(ap, int);
                                                char intbuf[MathUtils::kMinSizeForInt32_t_base10_toString];
                                                int32_t len = MathUtils::kMinSizeForInt32_t_base10_toString;
                                                char* intptr = MathUtils::convertIntegerToStringBuffer(value, intbuf, len, 10, MathUtils::kTreatAsSigned);
                                                while (*intptr) {
                                                        *buffer++ = *intptr++;
                                                }
                                        }
                                        break;
                                }
                        } else {
                                *buffer++ = *format;
                        }
                        format++;
                }
                *buffer = 0;
                va_end(ap);
        }
                                          
        bool Date::toString(wchar *buffer,
                                                int formatIndex, int &len) const
        {
                // todo we could try to do a much better job on
                // localized date stuff
                
                static const char kMonths[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
                static const char kDaysOfWeek[] = "SunMonTueWedThuFriSat";

                if (MathUtils::isNaN(m_time)) {
                        UnicodeUtils::Utf8ToUtf16((const uint8*)"Invalid Date", 
                                                                          12,
                                                                          buffer, 
                                                                          12, true );
                        buffer[len=12] = 0;
                        return true;
                }
                
                double time = m_time;

                if (formatIndex != kToUTCString) {
                        time = LocalTime(m_time);
                }

                int year = YearFromTime(time);
                int month = MonthFromTime(time);
                int day = WeekDay(time);
                if (month < 0 || month >= 12 || day < 0 || day >= 7) {
                        return false;
                }
                
                int delta = (int) ((time - m_time) / kMsecPerMinute);
                char signChar = '+';
                if (delta < 0) {
                        delta = -delta;
                        signChar = '-';
                }
                int deltaH = (delta / 60);
                int deltaM = (delta % 60);

                int date = (int)DateFromTime(time);

                int hour24 = (int)HourFromTime(time);
                int hour12 = hour24 % 12;
                if (hour12 == 0) {
                        hour12 = 12;
                }
                char ampm = (hour24 >= 12) ? 'P' : 'A';

                int min = (int)MinFromTime(time);
                int seconds = (int)SecFromTime(time);

                const char *dayOfWeekStr = kDaysOfWeek + day * 3;
                const char *monthStr = kMonths + month * 3;
                
                switch (formatIndex) {
                        /* CN:  ecma3 leaves the string format implementation dependant, as long
                        /   as it contains all the info below.  As a result, IE and Mozilla
                        /   had different formats for date.  In 2002, Mozilla changed their date
                        /   format to make it easier to write code which parses dates in string format 
                        /   regardless of the implementation which produced it.
                        /   http://bugzilla.mozilla.org/show_bug.cgi?id=118266 
                        /   We are not required by the standard to follow suit, but the one of the goals
                        /   of compliance is easy porting of code / techniques from ecmascript.  This change
                        /   should be done in a AS2/AS3 conditional manner, however:

                        // CN: 1/8/05 well, maybe this does break some existing user code (like the ATS).  Since
                        //  we aren't required to match Spidermonkey by the ES3 spec, lets decide to break
                        //  existing ECMAscript code over existing Actionscript code.

                        //AS2.0 format:

                        */
                case kToString:
                        format(buffer,
                                   "%3 %3 %d %2:%2:%2 GMT%c%2%2 %d",
                                   dayOfWeekStr,
                                   monthStr,
                                   date,
                                   hour24,
                                   min,
                                   seconds,
                                   signChar,
                                   deltaH,
                                   deltaM,
                                   year);
                        break;
                case kToLocaleString:
                        format(buffer,
                                   "%3 %3 %d %d %2:%2:%2 %cM",
                                   dayOfWeekStr,
                                   monthStr,
                                   date,
                                   year,
                                   hour12,
                                   min,
                                   seconds,
                                   ampm);
                        break;                  
                case kToUTCString:
                        format(buffer,
                                   "%3 %3 %d %2:%2:%2 %d UTC",
                                   dayOfWeekStr,
                                   monthStr,
                                   date,
                                   hour24,
                                   min,
                                   seconds,
                                   year);
                        break;
                                
                /*  This would be the SpiderMonkey / IE format (well, logically at least in that
                /   toString() == toDateString() + toTimeString().  See long comment above...
                        
                case kToString:
                        format(buffer,
                                   "%3 %3 %d %d %2:%2:%2 GMT%c%2%2",
                                   dayOfWeekStr,
                                   monthStr,
                                   date,
                                   year,
                                   hour24,
                                   min,
                                   seconds,
                                   signChar,
                                   deltaH,
                                   deltaM);
                        break;
                case kToLocaleString:
                        format(buffer,
                                   "%3 %3 %d %d %2:%2:%2 %cM",
                                   dayOfWeekStr,
                                   monthStr,
                                   date,
                                   year,
                                   hour12,
                                   min,
                                   seconds,
                                   ampm);
                        break;                  
                case kToUTCString:
                        format(buffer,
                                   "%3 %3 %d %d %2:%2:%2 UTC",
                                   dayOfWeekStr,
                                   monthStr,
                                   date,
                                   year,
                                   hour24,
                                   min,
                                   seconds);
                        break;
                */

                case kToDateString:
                case kToLocaleDateString:
                        format(buffer,
                                   "%3 %3 %d %d",
                                   dayOfWeekStr,
                                   monthStr,
                                   (int)DateFromTime(time),
                                   (int)YearFromTime(time));
                        break;
                case kToTimeString:
                        format(buffer,
                                   "%2:%2:%2 GMT%c%2%2",
                                   hour24,
                                   min,
                                   seconds,
                                   signChar,
                                   deltaH,
                                   deltaM);
                        break;                  
                case kToLocaleTimeString:
                        format(buffer,
                                   "%2:%2:%2 %cM",
                                   hour12,
                                   min,
                                   seconds,
                                   ampm);
                        break;
                default:
                        return false;
                }
                len = String::Length(buffer);
                return true;
        }

        double Date::getDateProperty(int index)
        {
                double t = m_time;

                // Short-circuit and return NaN if the date
                // is NaN
                if (MathUtils::isNaN(t)) {
                        return MathUtils::kNaN;
                }
                
                switch (index) {
                case kUTCFullYear:
                        return YearFromTime(t);
                case kUTCMonth:
                        return MonthFromTime(t);
                case kUTCDate:
                        return DateFromTime(t);
                case kUTCDay:
                        return WeekDay(t);
                case kUTCHours:
                        return HourFromTime(t);
                case kUTCMinutes:
                        return MinFromTime(t);
                case kUTCSeconds:
                        return SecFromTime(t);
                case kUTCMilliseconds:
                        return MsecFromTime(t);
                case kFullYear:
                        return YearFromTime(LocalTime(t));
                case kMonth:
                        return MonthFromTime(LocalTime(t));
                case kDate:
                        return DateFromTime(LocalTime(t));
                case kDay:
                        return WeekDay(LocalTime(t));
                case kHours:
                        return HourFromTime(LocalTime(t));
                case kMinutes:
                        return MinFromTime(LocalTime(t));
                case kSeconds:
                        return SecFromTime(LocalTime(t));
                case kMilliseconds:
                        return MsecFromTime(LocalTime(t));
                case kTimezoneOffset:
                        return (t - LocalTime(t)) / kMsecPerMinute;
                case kTime:
                        return t;
                }

                AvmAssert(false);
                return 0;
        }

        void Date::setTime(double value)
        {
                m_time = TimeClip(value);
        }

        // To not change a particular value, pass NaN.
        void Date::setTime(double hours,
                                           double min,
                                           double sec,
                                           double msec,
                                           bool utcFlag)
        {
                double t = utcFlag ? m_time : LocalTime(m_time);

                if (MathUtils::isNaN(hours)) {
                        hours = HourFromTime(t);
                }
                if (MathUtils::isNaN(min)) {
                        min = MinFromTime(t);
                }
                if (MathUtils::isNaN(sec)) {
                        sec = SecFromTime(t);
                }
                if (MathUtils::isNaN(msec)) {
                        msec = MsecFromTime(t);
                }
                t = MakeDate(Day(t),
                                         MakeTime(hours,
                                                          min,
                                                          sec,
                                                          msec));

                m_time = TimeClip(utcFlag ? t : UTC(t));
        }

        void Date::setDate(double year,
                                           double month,
                                           double date,
                                           bool utcFlag)
        {
                double t = utcFlag ? m_time : LocalTime(m_time);

                // date may already be NaN.  It stays as NaN unless we are setting the year
                if (MathUtils::isNaN(m_time))
                {
                        if (MathUtils::isNaN(year))
                                return;
                        else 
                                t = 0; // treat time as zero.
                }

                if (MathUtils::isNaN(year)) {
                        year = YearFromTime(t);
                }
                if (MathUtils::isNaN(month)) {
                        month = MonthFromTime(t);
                }
                if (MathUtils::isNaN(date)) {
                        date = DateFromTime(t);
                }

                // cn 2/14/06  Not sure whent this was added, but its not ECMAScript compatible.
                //   Yes, we will unexpectedly roll over into the next month and that's what
                //   the spec says to do.  Disabling this "correction"
                /*
                // If we are setting the month on the 31st to a month that has 30 days,
                // this will have the unexpected effect of rolling the date over into
                // the next month.  Correct for that here.
                int iMonth = (int)month;
                int iDate  = (int)date;
                int leap   = IsLeapYear((int)year);
                if (iMonth >= 0 && iMonth <= 11 && iDate >= 1 && iDate <= 31 ) {
                        if (iDate >= DaysInMonth(leap, iMonth)) {
                                iDate = DaysInMonth(leap, iMonth);
                                date = (double)iDate;
                        }
                }
                */
                
                t = MakeDate(MakeDay(year, month, date), TimeWithinDay(t));
                m_time = TimeClip(utcFlag ? t : UTC(t));
        }
}


/* [<][>][^][v][top][bottom][index][help] */