/* ***** 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 #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) { return (t - OSDep::localTZA(t) - OSDep::daylightSavingTA(t - OSDep::localTZA(t))); } static double LocalTime(double t) { return (t + OSDep::localTZA(t) + OSDep::daylightSavingTA(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::nan(); } 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::nan(); 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::nan(); 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::nan(); 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 = OSDep::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); wchar intbuf[256]; int len; MathUtils::convertIntegerToString(value, intbuf, len); wchar *uintptr = intbuf; while (*uintptr) { *buffer++ = *uintptr++; } } 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 ); 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::nan(); } 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)); } }