Bug 1278186 - Implement valueAsNumber and valueAsDate for <input type=week>. r=smaug,Waldo
authorJessica Jong <jjong@mozilla.com>
Thu, 29 Sep 2016 01:08:00 +0200
changeset 315819 2614d75c2100368dd92c6fe40490fe573c12c759
parent 315818 98eb5919336965816379b64f68c732e4d19b66fe
child 315820 6e6856e101c6e70c5f2f19089719f3e6df2bc3db
push id30757
push usercbook@mozilla.com
push dateFri, 30 Sep 2016 10:02:43 +0000
treeherdermozilla-central@5ffed033557e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, Waldo
bugs1278186
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1278186 - Implement valueAsNumber and valueAsDate for <input type=week>. r=smaug,Waldo
dom/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
dom/html/test/forms/test_valueasdate_attribute.html
dom/html/test/forms/test_valueasnumber_attribute.html
js/public/Date.h
js/src/jsdate.cpp
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -210,19 +210,23 @@ const Decimal HTMLInputElement::kStepSca
 const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1);
 const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000);
 const Decimal HTMLInputElement::kStepScaleFactorMonth = Decimal(1);
 const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0);
 const Decimal HTMLInputElement::kDefaultStep = Decimal(1);
 const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60);
 const Decimal HTMLInputElement::kStepAny = Decimal(0);
 
+const double HTMLInputElement::kMinimumYear = 1;
 const double HTMLInputElement::kMaximumYear = 275760;
-const double HTMLInputElement::kMinimumYear = 1;
+const double HTMLInputElement::kMaximumWeekInMaximumYear = 37;
+const double HTMLInputElement::kMaximumDayInMaximumYear = 13;
+const double HTMLInputElement::kMaximumMonthInMaximumYear = 9;
 const double HTMLInputElement::kMaximumWeekInYear = 53;
+const double HTMLInputElement::kMsPerDay = 24 * 60 * 60 * 1000;
 
 #define NS_INPUT_ELEMENT_STATE_IID                 \
 { /* dc3b3d14-23e2-4479-b513-7b369343e3a0 */       \
   0xdc3b3d14,                                      \
   0x23e2,                                          \
   0x4479,                                          \
   {0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \
 }
@@ -1875,29 +1879,49 @@ HTMLInputElement::ConvertStringToNumber(
       return true;
     case NS_FORM_INPUT_MONTH:
       {
         uint32_t year, month;
         if (!ParseMonth(aValue, &year, &month)) {
           return false;
         }
 
-        // Maximum valid month is 275760-09.
         if (year < kMinimumYear || year > kMaximumYear) {
           return false;
         }
 
-        if (year == kMaximumYear && month > 9) {
+        // Maximum valid month is 275760-09.
+        if (year == kMaximumYear && month > kMaximumMonthInMaximumYear) {
           return false;
         }
 
         int32_t months = MonthsSinceJan1970(year, month);
         aResultValue = Decimal(int32_t(months));
         return true;
       }
+    case NS_FORM_INPUT_WEEK:
+      {
+        uint32_t year, week;
+        if (!ParseWeek(aValue, &year, &week)) {
+          return false;
+        }
+
+        if (year < kMinimumYear || year > kMaximumYear) {
+          return false;
+        }
+
+        // Maximum week is 275760-W37, the week of 275760-09-13.
+        if (year == kMaximumYear && week > kMaximumWeekInMaximumYear) {
+          return false;
+        }
+
+        double days = DaysSinceEpochFromWeek(year, week);
+        aResultValue = Decimal::fromDouble(days * kMsPerDay);
+        return true;
+      }
     default:
       MOZ_ASSERT(false, "Unrecognized input type");
       return false;
   }
 }
 
 Decimal
 HTMLInputElement::GetValueAsDecimal() const
@@ -2126,28 +2150,62 @@ HTMLInputElement::ConvertNumberToString(
 
         if (year == kMaximumYear && month > 8) {
           return false;
         }
 
         aResultString.AppendPrintf("%04.0f-%02.0f", year, month + 1);
         return true;
       }
+    case NS_FORM_INPUT_WEEK:
+      {
+        aValue = aValue.floor();
+
+        // Based on ISO 8601 date.
+        double year = JS::YearFromTime(aValue.toDouble());
+        double month = JS::MonthFromTime(aValue.toDouble());
+        double day = JS::DayFromTime(aValue.toDouble());
+        // Adding 1 since day starts from 0.
+        double dayInYear = JS::DayWithinYear(aValue.toDouble(), year) + 1;
+
+        // Adding 1 since month starts from 0.
+        uint32_t isoWeekday = DayOfWeek(year, month + 1, day, true);
+        // Target on Wednesday since ISO 8601 states that week 1 is the week
+        // with the first Thursday of that year.
+        uint32_t week = (dayInYear - isoWeekday + 10) / 7;
+
+        if (week < 1) {
+          year--;
+          if (year < 1) {
+            return false;
+          }
+          week = MaximumWeekInYear(year);
+        } else if (week > MaximumWeekInYear(year)) {
+          year++;
+          if (year > kMaximumYear ||
+              (year == kMaximumYear && week > kMaximumWeekInMaximumYear)) {
+            return false;
+          }
+          week = 1;
+        }
+
+        aResultString.AppendPrintf("%04.0f-W%02d", year, week);
+        return true;
+      }
     default:
       MOZ_ASSERT(false, "Unrecognized input type");
       return false;
   }
 }
 
 
 Nullable<Date>
 HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
 {
-  // TODO: this is temporary until bug 888316 is fixed.
-  if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_WEEK) {
+  if (!IsDateTimeInputType(mType)) {
     return Nullable<Date>();
   }
 
   switch (mType) {
     case NS_FORM_INPUT_DATE:
     {
       uint32_t year, month, day;
       nsAutoString value;
@@ -2181,28 +2239,41 @@ HTMLInputElement::GetValueAsDate(ErrorRe
       GetValueInternal(value);
       if (!ParseMonth(value, &year, &month)) {
         return Nullable<Date>();
       }
 
       JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, 1));
       return Nullable<Date>(Date(time));
     }
+    case NS_FORM_INPUT_WEEK:
+    {
+      uint32_t year, week;
+      nsAutoString value;
+      GetValueInternal(value);
+      if (!ParseWeek(value, &year, &week)) {
+        return Nullable<Date>();
+      }
+
+      double days = DaysSinceEpochFromWeek(year, week);
+      JS::ClippedTime time = JS::TimeClip(days * kMsPerDay);
+
+      return Nullable<Date>(Date(time));
+    }
   }
 
   MOZ_ASSERT(false, "Unrecognized input type");
   aRv.Throw(NS_ERROR_UNEXPECTED);
   return Nullable<Date>();
 }
 
 void
 HTMLInputElement::SetValueAsDate(Nullable<Date> aDate, ErrorResult& aRv)
 {
-  // TODO: this is temporary until bug 888316 is fixed.
-  if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_WEEK) {
+  if (!IsDateTimeInputType(mType)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   if (aDate.IsNull() || aDate.Value().IsUndefined()) {
     aRv = SetValue(EmptyString());
     return;
   }
@@ -5091,34 +5162,41 @@ HTMLInputElement::IsLeapYear(uint32_t aY
 {
   if ((aYear % 4 == 0 && aYear % 100 != 0) || ( aYear % 400 == 0)) {
     return true;
   }
   return false;
 }
 
 uint32_t
-HTMLInputElement::DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay) const
+HTMLInputElement::DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay,
+                            bool isoWeek) const
 {
   // Tomohiko Sakamoto algorithm.
   int monthTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
   aYear -= aMonth < 3;
 
-  return (aYear + aYear / 4 - aYear / 100 + aYear / 400 +
-          monthTable[aMonth - 1] + aDay) % 7;
+  uint32_t day = (aYear + aYear / 4 - aYear / 100 + aYear / 400 +
+                  monthTable[aMonth - 1] + aDay) % 7;
+
+  if (isoWeek) {
+    return ((day + 6) % 7) + 1;
+  }
+
+  return day;
 }
 
 uint32_t
 HTMLInputElement::MaximumWeekInYear(uint32_t aYear) const
 {
-  int day = DayOfWeek(aYear, 1, 1); // January 1.
+  int day = DayOfWeek(aYear, 1, 1, true); // January 1.
   // A year starting on Thursday or a leap year starting on Wednesday has 53
   // weeks. All other years have 52 weeks.
-  return day == 4 || (day == 3 && IsLeapYear(aYear)) ? kMaximumWeekInYear
-                                                     : kMaximumWeekInYear - 1;
+  return day == 4 || (day == 3 && IsLeapYear(aYear)) ?
+    kMaximumWeekInYear : kMaximumWeekInYear - 1;
 }
 
 bool
 HTMLInputElement::IsValidWeek(const nsAString& aValue) const
 {
   uint32_t year, week;
   return ParseWeek(aValue, &year, &week);
 }
@@ -5223,16 +5301,35 @@ bool HTMLInputElement::ParseDate(const n
   if (!ParseMonth(yearMonthStr, aYear, aMonth)) {
     return false;
   }
 
   return DigitSubStringToNumber(aValue, endOfMonthOffset + 1, 2, aDay) &&
          *aDay > 0 && *aDay <= NumberOfDaysInMonth(*aMonth, *aYear);
 }
 
+double
+HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear, uint32_t aWeek) const
+{
+  double days = JS::DayFromYear(aYear) + (aWeek - 1) * 7;
+  uint32_t dayOneIsoWeekday = DayOfWeek(aYear, 1, 1, true);
+
+  // If day one of that year is on/before Thursday, we should subtract the
+  // days that belong to last year in our first week, otherwise, our first
+  // days belong to last year's last week, and we should add those days
+  // back.
+  if (dayOneIsoWeekday <= 4) {
+    days -= (dayOneIsoWeekday - 1);
+  } else {
+    days += (7 - dayOneIsoWeekday + 1);
+  }
+
+  return days;
+}
+
 uint32_t
 HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth, uint32_t aYear) const
 {
 /*
  * Returns the number of days in a month.
  * Months that are |longMonths| always have 31 days.
  * Months that are not |longMonths| have 30 days except February (month 2).
  * February has 29 days during leap years which are years that are divisible by 400.
@@ -5246,18 +5343,17 @@ HTMLInputElement::NumberOfDaysInMonth(ui
   if (longMonths[aMonth-1]) {
     return 31;
   }
 
   if (aMonth != 2) {
     return 30;
   }
 
-  return (aYear % 400 == 0 || (aYear % 100 != 0 && aYear % 4 == 0))
-          ? 29 : 28;
+  return IsLeapYear(aYear) ? 29 : 28;
 }
 
 /* static */ bool
 HTMLInputElement::DigitSubStringToNumber(const nsAString& aStr,
                                          uint32_t aStart, uint32_t aLen,
                                          uint32_t* aRetVal)
 {
   MOZ_ASSERT(aStr.Length() > (aStart + aLen - 1));
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -1044,21 +1044,17 @@ protected:
   /**
    * Returns if stepDown and stepUp methods apply for the current type.
    */
   bool DoStepDownStepUpApply() const { return DoesStepApply(); }
 
   /**
    * Returns if valueAsNumber attribute applies for the current type.
    */
-  bool DoesValueAsNumberApply() const
-  {
-    // TODO: this is temporary until bug 888316 is fixed.
-    return DoesMinMaxApply() && mType != NS_FORM_INPUT_WEEK;
-  }
+  bool DoesValueAsNumberApply() const { return DoesMinMaxApply(); }
 
   /**
    * Returns if autocomplete attribute applies for the current type.
    */
   bool DoesAutocompleteApply() const;
 
   /**
    * Returns if the minlength or maxlength attributes apply for the current type.
@@ -1227,30 +1223,38 @@ protected:
    * @return whether the parsing was successful.
    */
   bool ParseDate(const nsAString& aValue,
                  uint32_t* aYear,
                  uint32_t* aMonth,
                  uint32_t* aDay) const;
 
   /**
+   * This methods returns the number of days since epoch for a given year and
+   * week.
+   */
+  double DaysSinceEpochFromWeek(uint32_t aYear, uint32_t aWeek) const;
+
+  /**
    * This methods returns the number of days in a given month, for a given year.
    */
   uint32_t NumberOfDaysInMonth(uint32_t aMonth, uint32_t aYear) const;
 
   /**
    * This methods returns the number of months between January 1970 and the
    * given year and month.
    */
   int32_t MonthsSinceJan1970(uint32_t aYear, uint32_t aMonth) const;
 
   /**
-   * This methods returns the day of the week given a date, note that 0 = Sunday.
+   * This methods returns the day of the week given a date. If @isoWeek is true,
+   * 7=Sunday, otherwise, 0=Sunday.
    */
-  uint32_t DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay) const;
+  uint32_t DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay,
+                     bool isoWeek) const;
 
   /**
    * This methods returns the maximum number of week in a given year, the
    * result is either 52 or 53.
    */
   uint32_t MaximumWeekInYear(uint32_t aYear) const;
 
   /**
@@ -1485,22 +1489,31 @@ protected:
 
   // Default step used when there is no specified step.
   static const Decimal kDefaultStep;
   static const Decimal kDefaultStepTime;
 
   // Float value returned by GetStep() when the step attribute is set to 'any'.
   static const Decimal kStepAny;
 
+  // Minimum year limited by HTML standard, year >= 1.
+  static const double kMinimumYear;
   // Maximum year limited by ECMAScript date object range, year <= 275760.
   static const double kMaximumYear;
-  // Minimum year limited by HTML standard, year >= 1.
-  static const double kMinimumYear;
+  // Maximum valid week is 275760-W37.
+  static const double kMaximumWeekInMaximumYear;
+  // Maximum valid day is 275760-09-13.
+  static const double kMaximumDayInMaximumYear;
+  // Maximum valid month is 275760-09.
+  static const double kMaximumMonthInMaximumYear;
   // Long years in a ISO calendar have 53 weeks in them.
   static const double kMaximumWeekInYear;
+  // Milliseconds in a day.
+  static const double kMsPerDay;
+
 
   /**
    * The type of this input (<input type=...>) as an integer.
    * @see nsIFormControl.h (specifically NS_FORM_INPUT_*)
    */
   uint8_t                  mType;
   nsContentUtils::AutocompleteAttrState mAutocompleteAttrState;
   bool                     mDisabledChanged     : 1;
--- a/dom/html/test/forms/test_valueasdate_attribute.html
+++ b/dom/html/test/forms/test_valueasdate_attribute.html
@@ -41,30 +41,29 @@ var validTypes =
   ["reset", false],
   ["button", false],
   ["number", false],
   ["range", false],
   ["date", true],
   ["time", true],
   ["color", false],
   ["month", true],
-  // TODO: temporary set to false until bug 888316 is fixed.
-  ["week", false],
+  ["week", true],
 ];
 
 var todoTypes =
 [
   ["datetime", true],
   ["datetime-local", true],
 ];
 
 function checkAvailability()
 {
 
-  for (data of validTypes) {
+  for (let data of validTypes) {
     var exceptionCatched = false;
     element.type = data[0];
     try {
       element.valueAsDate;
     } catch (e) {
       exceptionCatched = true;
     }
     is(exceptionCatched, false,
@@ -75,17 +74,17 @@ function checkAvailability()
       element.valueAsDate = new Date();
     } catch (e) {
       exceptionCatched = true;
     }
     is(exceptionCatched, !data[1], "valueAsDate for " + data[0] +
                                    " availability is not correct");
   }
 
-  for (data of todoTypes) {
+  for (let data of todoTypes) {
     var exceptionCatched = false;
     element.type = data[0];
     try {
       element.valueAsDate;
     } catch (e) {
       exceptionCatched = true;
     }
     is(exceptionCatched, false,
@@ -99,17 +98,17 @@ function checkAvailability()
     }
     todo_is(exceptionCatched, !data[1],
             "valueAsDate for " + data[0] + " availability is not correct");
   }
 }
 
 function checkGarbageValues()
 {
-  for (type of validTypes) {
+  for (let type of validTypes) {
     if (!type[1]) {
       continue;
     }
     type = type[0];
 
     var element = document.createElement('input');
     element.type = type;
 
@@ -124,17 +123,17 @@ function checkGarbageValues()
     element.value = "test";
     element.valueAsDate = new Date(NaN);
     is(element.value, "", "valueAsDate should set the value to the empty string");
 
     var illegalValues = [
       "foobar", 42, {}, function() { return 42; }, function() { return Date(); }
     ];
 
-    for (value of illegalValues) {
+    for (let value of illegalValues) {
       try {
         var caught = false;
         element.valueAsDate = value;
       } catch(e) {
         is(e.name, "TypeError", "Exception should be 'TypeError'.");
         caught = true;
       }
       ok(caught, "Assigning " + value + " to .valueAsDate should throw");
@@ -172,24 +171,24 @@ function checkDateGet()
     // This date is valid for the input element, but is out of
     // the date object range. In this case, on getting valueAsDate,
     // a Date object will be created, but it will have a NaN internal value,
     // and will return the string "Invalid Date".
     [ "275760-09-14", true ],
   ];
 
   element.type = "date";
-  for (data of validData) {
+  for (let data of validData) {
     element.value = data[0];
     is(element.valueAsDate.valueOf(), data[1],
        "valueAsDate should return the " +
        "valid date object representing this date");
   }
 
-  for (data of invalidData) {
+  for (let data of invalidData) {
     element.value = data[0];
     if (data[1]) {
       is(String(element.valueAsDate), "Invalid Date",
          "valueAsDate should return an invalid Date object "  +
          "when the element value is not a valid date");
     } else {
       is(element.valueAsDate, null,
          "valueAsDate should return null "  +
@@ -220,17 +219,17 @@ function checkDateSet()
     [ 86400000,          "1970-01-02" ],
     // Negative years, this is out of range for the input element,
     // the corresponding date string is the empty string
     [ -62135596800001,   "" ],
     // Invalid dates.
   ];
 
   element.type = "date";
-  for (data of testData) {
+  for (let data of testData) {
     element.valueAsDate = new Date(data[0]);
     is(element.value, data[1], "valueAsDate should set the value to "
                                 + data[1]);
     element.valueAsDate = new testFrame.Date(data[0]);
     is(element.value, data[1], "valueAsDate with other-global date should " +
                                "set the value to " + data[1]);
   }
 }
@@ -271,17 +270,17 @@ function checkTimeGet()
     { value: "00:00:00.14", result: { time: 140, hours: 0, minutes: 0, seconds: 0, ms: 140 } },
     { value: "13:37:42.7", result: { time: 49062700, hours: 13, minutes: 37, seconds: 42, ms: 700 } },
     { value: "23:31:12.23", result: { time: 84672230, hours: 23, minutes: 31, seconds: 12, ms: 230 } },
   ];
 
   var element = document.createElement('input');
   element.type = 'time';
 
-  for (test of tests) {
+  for (let test of tests) {
     element.value = test.value;  
     if (test.result === null) {
       is(element.valueAsDate, null, "element.valueAsDate should return null");
     } else {
       var date = element.valueAsDate;
       isnot(date, null, "element.valueAsDate should not be null");
 
       is(date.getTime(), test.result.time);
@@ -314,26 +313,26 @@ function checkTimeSet()
     { value: -86400001, result: "23:59:59.999" },
     { value: -56789, result: "23:59:03.211" },
     { value: 0.9, result: "00:00" },
   ];
 
   var element = document.createElement('input');
   element.type = 'time';
 
-  for (test of tests) {
+  for (let test of tests) {
     element.valueAsDate = new Date(test.value);
     is(element.value, test.result,
        "element.value should have been changed by setting valueAsDate");
   }
 }
 
 function checkWithBustedPrototype()
 {
-  for (type of validTypes) {
+  for (let type of validTypes) {
     if (!type[1]) {
       continue;
     }
 
     type = type[0];
 
     var element = document.createElement('input');
     element.type = type;
@@ -438,24 +437,24 @@ function checkMonthGet()
     // This month is valid for the input element, but is out of
     // the date object range. In this case, on getting valueAsDate,
     // a Date object will be created, but it will have a NaN internal value,
     // and will return the string "Invalid Date".
     [ "275760-10", true ],
   ];
 
   element.type = "month";
-  for (data of validData) {
+  for (let data of validData) {
     element.value = data[0];
     is(element.valueAsDate.valueOf(), data[1],
        "valueAsDate should return the " +
        "valid date object representing this month");
   }
 
-  for (data of invalidData) {
+  for (let data of invalidData) {
     element.value = data[0];
     if (data[1]) {
       is(String(element.valueAsDate), "Invalid Date",
          "valueAsDate should return an invalid Date object "  +
          "when the element value is not a valid month");
     } else {
       is(element.valueAsDate, null,
          "valueAsDate should return null "  +
@@ -485,17 +484,166 @@ function checkMonthSet()
     [ -86400000,          "1969-12" ],
     [ 86400000,           "1970-01" ],
     // Negative years, this is out of range for the input element,
     // the corresponding month string is the empty string
     [ -62135596800001,    "" ],
   ];
 
   element.type = "month";
-  for (data of testData) {
+  for (let data of testData) {
+    element.valueAsDate = new Date(data[0]);
+    is(element.value, data[1], "valueAsDate should set the value to "
+                                + data[1]);
+    element.valueAsDate = new testFrame.Date(data[0]);
+    is(element.value, data[1], "valueAsDate with other-global date should " +
+                               "set the value to " + data[1]);
+  }
+}
+
+function checkWeekGet()
+{
+  var validData =
+  [
+    // Common years starting on different days of week.
+    [ "2007-W01", Date.UTC(2007, 0, 1)   ], // Mon
+    [ "2013-W01", Date.UTC(2012, 11, 31) ], // Tue
+    [ "2014-W01", Date.UTC(2013, 11, 30) ], // Wed
+    [ "2015-W01", Date.UTC(2014, 11, 29) ], // Thu
+    [ "2010-W01", Date.UTC(2010, 0, 4)   ], // Fri
+    [ "2011-W01", Date.UTC(2011, 0, 3)   ], // Sat
+    [ "2017-W01", Date.UTC(2017, 0, 2)   ], // Sun
+    // Common years ending on different days of week.
+    [ "2007-W52", Date.UTC(2007, 11, 24) ], // Mon
+    [ "2013-W52", Date.UTC(2013, 11, 23) ], // Tue
+    [ "2014-W52", Date.UTC(2014, 11, 22) ], // Wed
+    [ "2015-W53", Date.UTC(2015, 11, 28) ], // Thu
+    [ "2010-W52", Date.UTC(2010, 11, 27) ], // Fri
+    [ "2011-W52", Date.UTC(2011, 11, 26) ], // Sat
+    [ "2017-W52", Date.UTC(2017, 11, 25) ], // Sun
+    // Leap years starting on different days of week.
+    [ "1996-W01", Date.UTC(1996, 0, 1)   ], // Mon
+    [ "2008-W01", Date.UTC(2007, 11, 31) ], // Tue
+    [ "2020-W01", Date.UTC(2019, 11, 30) ], // Wed
+    [ "2004-W01", Date.UTC(2003, 11, 29) ], // Thu
+    [ "2016-W01", Date.UTC(2016, 0, 4)   ], // Fri
+    [ "2000-W01", Date.UTC(2000, 0, 3)   ], // Sat
+    [ "2012-W01", Date.UTC(2012, 0, 2)   ], // Sun
+    // Leap years ending on different days of week.
+    [ "2012-W52", Date.UTC(2012, 11, 24) ], // Mon
+    [ "2024-W52", Date.UTC(2024, 11, 23) ], // Tue
+    [ "1980-W52", Date.UTC(1980, 11, 22) ], // Wed
+    [ "1992-W53", Date.UTC(1992, 11, 28) ], // Thu
+    [ "2004-W53", Date.UTC(2004, 11, 27) ], // Fri
+    [ "1988-W52", Date.UTC(1988, 11, 26) ], // Sat
+    [ "2000-W52", Date.UTC(2000, 11, 25) ], // Sun
+    // Other normal cases.
+    [ "2016-W36",   1473033600000    ],
+    [ "1969-W52",   -864000000       ],
+    [ "1970-W01",   -259200000       ],
+    [ "275760-W37", 8639999568000000 ],
+  ];
+
+  var invalidData =
+  [
+    [ "invalidweek" ],
+    [ "0000-W01"     ],
+    [ "2016-W00"     ],
+    [ "123-W01"      ],
+    [ "2016-W53"     ],
+    [ ""             ],
+    // This week is valid for the input element, but is out of
+    // the date object range. In this case, on getting valueAsDate,
+    // a Date object will be created, but it will have a NaN internal value,
+    // and will return the string "Invalid Date".
+    [ "275760-W38", true ],
+  ];
+
+  element.type = "week";
+  for (let data of validData) {
+    element.value = data[0];
+    is(element.valueAsDate.valueOf(), data[1],
+       "valueAsDate should return the " +
+       "valid date object representing this week");
+  }
+
+  for (let data of invalidData) {
+    element.value = data[0];
+    if (data[1]) {
+      is(String(element.valueAsDate), "Invalid Date",
+         "valueAsDate should return an invalid Date object "  +
+         "when the element value is not a valid week");
+    } else {
+      is(element.valueAsDate, null,
+         "valueAsDate should return null "  +
+         "when the element value is not a valid week");
+    }
+  }
+}
+
+function checkWeekSet()
+{
+  var testData =
+  [
+    // Common years starting on different days of week.
+    [ Date.UTC(2007, 0, 1), "2007-W01"   ], // Mon
+    [ Date.UTC(2013, 0, 1), "2013-W01"   ], // Tue
+    [ Date.UTC(2014, 0, 1), "2014-W01"   ], // Wed
+    [ Date.UTC(2015, 0, 1), "2015-W01"   ], // Thu
+    [ Date.UTC(2010, 0, 1), "2009-W53"   ], // Fri
+    [ Date.UTC(2011, 0, 1), "2010-W52"   ], // Sat
+    [ Date.UTC(2017, 0, 1), "2016-W52"   ], // Sun
+    // Common years ending on different days of week.
+    [ Date.UTC(2007, 11, 31), "2008-W01" ], // Mon
+    [ Date.UTC(2013, 11, 31), "2014-W01" ], // Tue
+    [ Date.UTC(2014, 11, 31), "2015-W01" ], // Wed
+    [ Date.UTC(2015, 11, 31), "2015-W53" ], // Thu
+    [ Date.UTC(2010, 11, 31), "2010-W52" ], // Fri
+    [ Date.UTC(2011, 11, 31), "2011-W52" ], // Sat
+    [ Date.UTC(2017, 11, 31), "2017-W52" ], // Sun
+    // Leap years starting on different days of week.
+    [ Date.UTC(1996, 0, 1), "1996-W01"   ], // Mon
+    [ Date.UTC(2008, 0, 1), "2008-W01"   ], // Tue
+    [ Date.UTC(2020, 0, 1), "2020-W01"   ], // Wed
+    [ Date.UTC(2004, 0, 1), "2004-W01"   ], // Thu
+    [ Date.UTC(2016, 0, 1), "2015-W53"   ], // Fri
+    [ Date.UTC(2000, 0, 1), "1999-W52"   ], // Sat
+    [ Date.UTC(2012, 0, 1), "2011-W52"   ], // Sun
+    // Leap years ending on different days of week.
+    [ Date.UTC(2012, 11, 31), "2013-W01" ], // Mon
+    [ Date.UTC(2024, 11, 31), "2025-W01" ], // Tue
+    [ Date.UTC(1980, 11, 31), "1981-W01" ], // Wed
+    [ Date.UTC(1992, 11, 31), "1992-W53" ], // Thu
+    [ Date.UTC(2004, 11, 31), "2004-W53" ], // Fri
+    [ Date.UTC(1988, 11, 31), "1988-W52" ], // Sat
+    [ Date.UTC(2000, 11, 31), "2000-W52" ], // Sun
+    // Other normal cases.
+    [ Date.UTC(2016, 8, 9),  "2016-W36" ],
+    [ Date.UTC(2010, 0, 3),  "2009-W53" ],
+    [ Date.UTC(2010, 0, 4),  "2010-W01" ],
+    [ Date.UTC(2010, 0, 10), "2010-W01" ],
+    [ Date.UTC(2010, 0, 11), "2010-W02" ],
+    [ 0,                     "1970-W01" ],
+    // Maximum valid month (limited by the ecma date object range).
+    [ 8640000000000000,      "275760-W37" ],
+    // Minimum valid month (limited by the input element minimum valid value).
+    [ -62135596800000 ,      "0001-W01" ],
+    // "Values must be truncated to valid week"
+    [ 42.1234,               "1970-W01" ],
+    [ 123.123456789123,      "1970-W01" ],
+    [ 1e-1,                  "1970-W01" ],
+    [ -1.1,                  "1970-W01" ],
+    [ -345600000,            "1969-W52" ],
+    // Negative years, this is out of range for the input element,
+    // the corresponding week string is the empty string
+    [ -62135596800001,       "" ],
+  ];
+
+  element.type = "week";
+  for (let data of testData) {
     element.valueAsDate = new Date(data[0]);
     is(element.value, data[1], "valueAsDate should set the value to "
                                 + data[1]);
     element.valueAsDate = new testFrame.Date(data[0]);
     is(element.value, data[1], "valueAsDate with other-global date should " +
                                "set the value to " + data[1]);
   }
 }
@@ -511,12 +659,17 @@ checkDateSet();
 // Test <input type='time'>.
 checkTimeGet();
 checkTimeSet();
 
 // Test <input type='month'>.
 checkMonthGet();
 checkMonthSet();
 
+// Test <input type='week'>.
+checkWeekGet();
+checkWeekSet();
+
 </script>
 </pre>
 </body>
 </html>
+
--- a/dom/html/test/forms/test_valueasnumber_attribute.html
+++ b/dom/html/test/forms/test_valueasnumber_attribute.html
@@ -41,28 +41,28 @@ function checkAvailability()
     ["button", false],
     ["number", true],
     ["range", true],
     ["date", true],
     ["time", true],
     ["color", false],
     ["month", true],
     // TODO: temporary set to false until bug 888316 is fixed.
-    ["week", false],
+    ["week", true],
   ];
 
   var todoList =
   [
     ["datetime", true],
     ["datetime-local", true],
   ];
 
   var element = document.createElement('input');
 
-  for (data of testData) {
+  for (let data of testData) {
     var exceptionCatched = false;
     element.type = data[0];
     try {
       element.valueAsNumber;
     } catch (e) {
       exceptionCatched = true;
     }
     is(exceptionCatched, false,
@@ -73,17 +73,17 @@ function checkAvailability()
       element.valueAsNumber = 42;
     } catch (e) {
       exceptionCatched = true;
     }
     is(exceptionCatched, !data[1], "valueAsNumber for " + data[0] +
                                    " availability is not correct");
   }
 
-  for (data of todoList) {
+  for (let data of todoList) {
     var exceptionCatched = false;
     element.type = data[0];
     try {
       element.valueAsNumber;
     } catch (e) {
       exceptionCatched = true;
     }
     is(exceptionCatched, false,
@@ -117,17 +117,17 @@ function checkNumberGet()
     ["1e0.1", null],
     ["", null], // the empty string is not a number
     ["foo", null],
     ["42,13", null], // comma can't be used as a decimal separator
   ];
 
   var element = document.createElement('input');
   element.type = "number";
-  for (data of testData) {
+  for (let data of testData) {
     element.value = data[0];
 
     // Given that NaN != NaN, we have to use null when the expected value is NaN.
     if (data[1] != null) {
       is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
          "floating point representation of the value");
     } else {
       ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN "  +
@@ -157,17 +157,17 @@ function checkNumberSet()
     [Infinity, "42", true],
     [-Infinity, "42", true],
     // Setting NaN should change the value to the empty string.
     [NaN, ""],
   ];
 
   var element = document.createElement('input');
   element.type = "number";
-  for (data of testData) {
+  for (let data of testData) {
     var caught = false;
     try {
       element.valueAsNumber = data[0];
       is(element.value, data[1],
          "valueAsNumber should be able to set the value");
     } catch (e) {
       caught = true;
     }
@@ -208,17 +208,17 @@ function checkRangeGet()
     ["42,13", defaultValue],
   ];
 
   var element = document.createElement('input');
   element.type = "range";
   element.setAttribute("min", min); // avoids out of range sanitization
   element.setAttribute("max", max);
   element.setAttribute("step", "any"); // avoids step mismatch sanitization
-  for (data of testData) {
+  for (let data of testData) {
     element.value = data[0];
 
     // Given that NaN != NaN, we have to use null when the expected value is NaN.
     is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
        "floating point representation of the value");
   }
 }
 
@@ -248,17 +248,17 @@ function checkRangeSet()
     [NaN, defaultValue],
   ];
 
   var element = document.createElement('input');
   element.type = "range";
   element.setAttribute("min", min); // avoids out of range sanitization
   element.setAttribute("max", max);
   element.setAttribute("step", "any"); // avoids step mismatch sanitization
-  for (data of testData) {
+  for (let data of testData) {
     var caught = false;
     try {
       element.valueAsNumber = data[0];
       is(element.value, data[1],
          "valueAsNumber should be able to set the value");
     } catch (e) {
       caught = true;
     }
@@ -300,23 +300,23 @@ function checkDateGet()
     "1901-12-32",
     "1901-00-12",
     "1901-01-00",
     "1900-02-29",
   ];
 
   var element = document.createElement('input');
   element.type = "date";
-  for (data of validData) {
+  for (let data of validData) {
     element.value = data[0];
     is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
        "timestamp representing this date");
   }
 
-  for (data of invalidData) {
+  for (let data of invalidData) {
     element.value = data;
     ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN "  +
        "when the element value is not a valid date");
   }
 }
 
 function checkDateSet()
 {
@@ -356,28 +356,28 @@ function checkDateSet()
     // Infinity will keep the current value and throw (so we need to set a current value).
     [ 1298851200010, "2011-02-28" ],
     [ Infinity, "2011-02-28", true ],
     [ -Infinity, "2011-02-28", true ],
   ];
 
   var element = document.createElement('input');
   element.type = "date";
-  for (data of testData) {
+  for (let data of testData) {
     var caught = false;
 
     try {
       element.valueAsNumber = data[0];
       is(element.value, data[1], "valueAsNumber should set the value to " + data[1]);
     } catch(e) {
       caught = true;
     }
 
     if (data[2]) {
-      ok(caught, "valueAsNumber should have trhown"); 
+      ok(caught, "valueAsNumber should have thrown");
       is(element.value, data[1], "the value should not have changed");
     } else {
       ok(!caught, "valueAsNumber should not have thrown");
     }
   }
 
 }
 
@@ -417,17 +417,17 @@ function checkTimeGet()
     { value: "00:00:00.14", result: 140 },
     { value: "13:37:42.7", result: 49062700 },
     { value: "23:31:12.23", result: 84672230 },
   ];
 
   var element = document.createElement('input');
   element.type = 'time';
 
-  for (test of tests) {
+  for (let test of tests) {
     element.value = test.value;
     if (isNaN(test.result)) {
       ok(isNaN(element.valueAsNumber),
          "invalid value should have .valueAsNumber return NaN");
     } else {
       is(element.valueAsNumber, test.result,
          ".valueAsNumber should return " + test.result);
     }
@@ -464,17 +464,17 @@ function checkTimeSet()
     { value: -86400001, result: "23:59:59.999" },
     { value: -56789, result: "23:59:03.211" },
     { value: 0.9, result: "00:00" },
   ];
 
   var element = document.createElement('input');
   element.type = 'time';
 
-  for (test of tests) {
+  for (let test of tests) {
     try {
       var caught = false;
       element.valueAsNumber = test.value;
       is(element.value, test.result, "value should return " + test.result);
     } catch(e) {
       caught = true;
     }
 
@@ -483,17 +483,16 @@ function checkTimeSet()
     }
 
     is(caught, test.throw, "the test throwing status should be " + test.throw);
   }
 }
 
 function checkMonthGet()
 {
-  dump("checkMonthGet\n");
   var validData =
   [
     [ "2016-07", 558       ],
     [ "1970-01", 0         ],
     [ "1969-12", -1        ],
     [ "0001-01", -23628    ],
     [ "10000-12", 96371    ],
     [ "275760-09", 3285488 ],
@@ -506,23 +505,23 @@ function checkMonthGet()
     "2000-00",
     "2012-13",
     // Out of range.
     "275760-10",
   ];
 
   var element = document.createElement('input');
   element.type = "month";
-  for (data of validData) {
+  for (let data of validData) {
     element.value = data[0];
     is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
        "integer value representing this month");
   }
 
-  for (data of invalidData) {
+  for (let data of invalidData) {
     element.value = data;
     ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN "  +
        "when the element value is not a valid month");
   }
 }
 
 function checkMonthSet()
 {
@@ -559,28 +558,184 @@ function checkMonthSet()
     // Infinity will keep the current value and throw (so we need to set a current value)
     [ 558,               "2016-07"   ],
     [ Infinity,          "2016-07", true ],
     [ -Infinity,         "2016-07", true ],
   ];
 
   var element = document.createElement('input');
   element.type = "month";
-  for (data of testData) {
+  for (let data of testData) {
     var caught = false;
 
     try {
       element.valueAsNumber = data[0];
       is(element.value, data[1], "valueAsNumber should set the value to " + data[1]);
     } catch(e) {
       caught = true;
     }
 
     if (data[2]) {
-      ok(caught, "valueAsNumber should have trhown");
+      ok(caught, "valueAsNumber should have thrown");
+      is(element.value, data[1], "the value should not have changed");
+    } else {
+      ok(!caught, "valueAsNumber should not have thrown");
+    }
+  }
+}
+
+function checkWeekGet()
+{
+  var validData =
+  [
+    // Common years starting on different days of week.
+    [ "2007-W01", Date.UTC(2007, 0, 1)   ], // Mon
+    [ "2013-W01", Date.UTC(2012, 11, 31) ], // Tue
+    [ "2014-W01", Date.UTC(2013, 11, 30) ], // Wed
+    [ "2015-W01", Date.UTC(2014, 11, 29) ], // Thu
+    [ "2010-W01", Date.UTC(2010, 0, 4)   ], // Fri
+    [ "2011-W01", Date.UTC(2011, 0, 3)   ], // Sat
+    [ "2017-W01", Date.UTC(2017, 0, 2)   ], // Sun
+    // Common years ending on different days of week.
+    [ "2007-W52", Date.UTC(2007, 11, 24) ], // Mon
+    [ "2013-W52", Date.UTC(2013, 11, 23) ], // Tue
+    [ "2014-W52", Date.UTC(2014, 11, 22) ], // Wed
+    [ "2015-W53", Date.UTC(2015, 11, 28) ], // Thu
+    [ "2010-W52", Date.UTC(2010, 11, 27) ], // Fri
+    [ "2011-W52", Date.UTC(2011, 11, 26) ], // Sat
+    [ "2017-W52", Date.UTC(2017, 11, 25) ], // Sun
+    // Leap years starting on different days of week.
+    [ "1996-W01", Date.UTC(1996, 0, 1)   ], // Mon
+    [ "2008-W01", Date.UTC(2007, 11, 31) ], // Tue
+    [ "2020-W01", Date.UTC(2019, 11, 30) ], // Wed
+    [ "2004-W01", Date.UTC(2003, 11, 29) ], // Thu
+    [ "2016-W01", Date.UTC(2016, 0, 4)   ], // Fri
+    [ "2000-W01", Date.UTC(2000, 0, 3)   ], // Sat
+    [ "2012-W01", Date.UTC(2012, 0, 2)   ], // Sun
+    // Leap years ending on different days of week.
+    [ "2012-W52", Date.UTC(2012, 11, 24) ], // Mon
+    [ "2024-W52", Date.UTC(2024, 11, 23) ], // Tue
+    [ "1980-W52", Date.UTC(1980, 11, 22) ], // Wed
+    [ "1992-W53", Date.UTC(1992, 11, 28) ], // Thu
+    [ "2004-W53", Date.UTC(2004, 11, 27) ], // Fri
+    [ "1988-W52", Date.UTC(1988, 11, 26) ], // Sat
+    [ "2000-W52", Date.UTC(2000, 11, 25) ], // Sun
+    // Other normal cases.
+    [ "2015-W53", Date.UTC(2015, 11, 28)   ],
+    [ "2016-W36", Date.UTC(2016, 8, 5)     ],
+    [ "1970-W01", Date.UTC(1969, 11, 29)   ],
+    [ "275760-W37", Date.UTC(275760, 8, 8) ],
+  ];
+
+  var invalidData =
+  [
+    "invalidweek",
+    "0000-W01",
+    "2016-W00",
+    "2016-W53",
+    // Out of range.
+    "275760-W38",
+  ];
+
+  var element = document.createElement('input');
+  element.type = "week";
+  for (let data of validData) {
+    dump("Test: " + data[0]);
+    element.value = data[0];
+    is(element.valueAsNumber, data[1], "valueAsNumber should return the " +
+       "integer value representing this week");
+  }
+
+  for (let data of invalidData) {
+    element.value = data;
+    ok(isNaN(element.valueAsNumber), "valueAsNumber should return NaN "  +
+       "when the element value is not a valid week");
+  }
+}
+
+function checkWeekSet()
+{
+  var testData =
+  [
+    // Common years starting on different days of week.
+    [ Date.UTC(2007, 0, 1), "2007-W01"   ], // Mon
+    [ Date.UTC(2013, 0, 1), "2013-W01"   ], // Tue
+    [ Date.UTC(2014, 0, 1), "2014-W01"   ], // Wed
+    [ Date.UTC(2015, 0, 1), "2015-W01"   ], // Thu
+    [ Date.UTC(2010, 0, 1), "2009-W53"   ], // Fri
+    [ Date.UTC(2011, 0, 1), "2010-W52"   ], // Sat
+    [ Date.UTC(2017, 0, 1), "2016-W52"   ], // Sun
+    // Common years ending on different days of week.
+    [ Date.UTC(2007, 11, 31), "2008-W01" ], // Mon
+    [ Date.UTC(2013, 11, 31), "2014-W01" ], // Tue
+    [ Date.UTC(2014, 11, 31), "2015-W01" ], // Wed
+    [ Date.UTC(2015, 11, 31), "2015-W53" ], // Thu
+    [ Date.UTC(2010, 11, 31), "2010-W52" ], // Fri
+    [ Date.UTC(2011, 11, 31), "2011-W52" ], // Sat
+    [ Date.UTC(2017, 11, 31), "2017-W52" ], // Sun
+    // Leap years starting on different days of week.
+    [ Date.UTC(1996, 0, 1), "1996-W01"   ], // Mon
+    [ Date.UTC(2008, 0, 1), "2008-W01"   ], // Tue
+    [ Date.UTC(2020, 0, 1), "2020-W01"   ], // Wed
+    [ Date.UTC(2004, 0, 1), "2004-W01"   ], // Thu
+    [ Date.UTC(2016, 0, 1), "2015-W53"   ], // Fri
+    [ Date.UTC(2000, 0, 1), "1999-W52"   ], // Sat
+    [ Date.UTC(2012, 0, 1), "2011-W52"   ], // Sun
+    // Leap years ending on different days of week.
+    [ Date.UTC(2012, 11, 31), "2013-W01" ], // Mon
+    [ Date.UTC(2024, 11, 31), "2025-W01" ], // Tue
+    [ Date.UTC(1980, 11, 31), "1981-W01" ], // Wed
+    [ Date.UTC(1992, 11, 31), "1992-W53" ], // Thu
+    [ Date.UTC(2004, 11, 31), "2004-W53" ], // Fri
+    [ Date.UTC(1988, 11, 31), "1988-W52" ], // Sat
+    [ Date.UTC(2000, 11, 31), "2000-W52" ], // Sun
+    // Other normal cases.
+    [ Date.UTC(2008, 8, 26),  "2008-W39" ],
+    [ Date.UTC(2016, 0, 4),   "2016-W01" ],
+    [ Date.UTC(2016, 0, 10),  "2016-W01" ],
+    [ Date.UTC(2016, 0, 11),  "2016-W02" ],
+    // Maximum valid week (limited by the ecma date object range).
+    [ 8640000000000000,  "275760-W37" ],
+    // Minimum valid week (limited by the input element minimum valid value)
+    [ -62135596800000,   "0001-W01" ],
+    // "Values must be truncated to valid weeks"
+    [ 0.3,               "1970-W01"  ],
+    [ 1e-1,              "1970-W01"  ],
+    [ -1.1,              "1970-W01"  ],
+    [ -345600000,        "1969-W52"  ],
+    // Invalid numbers.
+    // Those are implicitly converted to numbers
+    [ "",                "1970-W01" ],
+    [ true,              "1970-W01" ],
+    [ false,             "1970-W01" ],
+    [ null,              "1970-W01" ],
+    // Those are converted to NaN, the corresponding week string is the empty string
+    [ "invalidweek",     "" ],
+    [ NaN,               "" ],
+    [ undefined,         "" ],
+    // Infinity will keep the current value and throw (so we need to set a current value).
+    [ Date.UTC(2016, 8, 8), "2016-W36" ],
+    [ Infinity,             "2016-W36", true ],
+    [ -Infinity,            "2016-W36", true ],
+  ];
+
+  var element = document.createElement('input');
+  element.type = "week";
+  for (let data of testData) {
+    var caught = false;
+
+    try {
+      element.valueAsNumber = data[0];
+      is(element.value, data[1], "valueAsNumber should set the value to " + data[1]);
+    } catch(e) {
+      caught = true;
+    }
+
+    if (data[2]) {
+      ok(caught, "valueAsNumber should have thrown");
       is(element.value, data[1], "the value should not have changed");
     } else {
       ok(!caught, "valueAsNumber should not have thrown");
     }
   }
 }
 
 checkAvailability();
@@ -600,12 +755,16 @@ checkDateSet();
 // <input type='time'> test
 checkTimeGet();
 checkTimeSet();
 
 // <input type='month'> test
 checkMonthGet();
 checkMonthSet();
 
+// <input type='week'> test
+checkWeekGet();
+checkWeekSet();
+
 </script>
 </pre>
 </body>
 </html>
--- a/js/public/Date.h
+++ b/js/public/Date.h
@@ -144,11 +144,27 @@ YearFromTime(double time);
 JS_PUBLIC_API(double)
 MonthFromTime(double time);
 
 // Takes an integer number of milliseconds since the epoch and returns the
 // day (1-based).  Can return NaN, and will do so if NaN is passed in.
 JS_PUBLIC_API(double)
 DayFromTime(double time);
 
+// Takes an integer year and returns the number of days from epoch to the given
+// year.
+// NOTE: The calculation performed by this function is literally that given in
+// the ECMAScript specification.  Nonfinite years, years containing fractional
+// components, and years outside ECMAScript's date range are not handled with
+// any particular intelligence.  Garbage in, garbage out.
+JS_PUBLIC_API(double)
+DayFromYear(double year);
+
+// Takes an integer number of milliseconds since the epoch and an integer year,
+// returns the number of days in that year. If |time| is nonfinite, returns NaN.
+// Otherwise |time| *must* correspond to a time within the valid year |year|.
+// This should usually be ensured by computing |year| as |JS::DayFromYear(time)|.
+JS_PUBLIC_API(double)
+DayWithinYear(double time, double year);
+
 } // namespace JS
 
 #endif /* js_Date_h */
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -370,16 +370,28 @@ JS::MonthFromTime(double time)
 }
 
 JS_PUBLIC_API(double)
 JS::DayFromTime(double time)
 {
     return DateFromTime(time);
 }
 
+JS_PUBLIC_API(double)
+JS::DayFromYear(double year)
+{
+    return ::DayFromYear(year);
+}
+
+JS_PUBLIC_API(double)
+JS::DayWithinYear(double time, double year)
+{
+    return ::DayWithinYear(time, year);
+}
+
 /*
  * Find a year for which any given date will fall on the same weekday.
  *
  * This function should be used with caution when used other than
  * for determining DST; it hasn't been proven not to produce an
  * incorrect year for times near year boundaries.
  */
 static int