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 315827 2614d75c2100368dd92c6fe40490fe573c12c759
parent 315826 98eb5919336965816379b64f68c732e4d19b66fe
child 315828 6e6856e101c6e70c5f2f19089719f3e6df2bc3db
push id20634
push usercbook@mozilla.com
push dateFri, 30 Sep 2016 10:10:13 +0000
treeherderfx-team@afe79b010d13 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, Waldo
bugs1278186
milestone52.0a1
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