Расширение объекта Date языка JavaScript

VitaliyRodnenko, 29.07.2008

Перевод статьи о расширении объектов JavaScript.
Оригинал статьи тут

Большинство приложений, особенно бизнес-приложений, как правило, требуют множество операций с датой и временем. Код этих операций может быть упрощен, если объект Date ядра JavaScript расширить собственными дополнительными методами. Данная статья рассказывает о том, как добавлять в объект Date пользовательские методы, значительно упрощающие работу с датой в языке JavaScript и которые будут наследоваться каждым экземпляром данного объекта. Описанная в данной статье техника так же может быть применима ко всем объектам ядра JavaScript.

Практически все созданные нами методы могут быть использованы самостоятельно, без необходимости создания остальных. Но нужно помнить, что некоторые методы зависят от copy(), lastday(), getDaysBetween() и getMonthsBetween().

Безусловно, все наши методы могут быть реализованы как глобальные функции, но, расширяя класс Date, мы получаем несколько преимуществ:

  • при расширении объекта Date языка JavaScript, глобальное пространство имен меньше загромождается, код более читаем, и производительность немного выше (хотя это только наносекунды);
  • наши методы будут обладать всеми преимуществами объектно-ориентированного программирования;

Итак, приступим.

Как это работает?

Техника расширения использует преимущества реализации объектов в JavaScript. В основном, когда программисты говорят об объектно-ориентированном программировании (ООП), они подразумевают С++, Java или PHP. В этих языках строго типизированный класс определяет каждый объект – это все определяется до выполнения программы. Но JavaScript – это язык, основанный на прототипах. Функция-конструктор определяет каждый из объектов, а их методы могут быть расширены во время выполнения программы. Реализованные методы и свойства относятся к свойству прототипов функции конструктора, которое, по сути, также является объектом. Методы и свойства создаются объявлением, и новые или существующие экземпляры объектов наследуют их.

Date.prototype.method_name = function ([arguments]) {
  // Здесь идет код
};

В этом примере анонимная функция присваивается атрибуту method_name объекта prototype. Синтаксис вызова метода:

date_instance.method_name([arguments]);

Внутри кода метода ключевое слово this обращается к экземпляру объекта.
Теперь понятно, как это работает, давайте сделаем встроенный объект Date немного более полезным!

Копирование дат

В языке языке JavaScript присваивание объектов создает ссылку на объект, а не его копию. В следующем коде переменная date1 инициализирована значением 10 Февраля 2008 и это значение присвоено date2. Так как date2 не является копией date1, изменение числа на 20-ое касается только date1 и Alert выдает обе даты – 20 Февраля 2008. Они фактически являются одной и той же копией даты.

var date1 = new Date(2008, 1, 10);
var date2 = date1;
date2.setDate(20);
alert(date1 + "\n" + date2);

Для копирования даты её нужно преобразовать в базовое значение (строковое, числовое или булевое) и создать новую дату. Метод getTime() идеально подходит для этого. Он возвращает внутреннее представление даты с точностью до миллисекунды и может быть использован в качестве аргумента для конструктора даты:

var oldDate = new Date(); // сегодня
var dateCopy = new Date(oldDate.getTime());

Вместо написания этих строк везде, где они нужны, или создания глобальной функции мы добавим наследуемый метод copy() в конструктор Date:

Date.prototype.copy = function () {
  return new Date(this.getTime());
};

Вот пример его использования:

var oldDate = new Date(2008, 1, 10);
var newDate = oldDate.copy();

newDate.setDate(20);
alert(oldDate + "\n" + newDate);

Изменение newDate не влияет на oldDate. Даты, показанные в Alert это 10 февраля 2008 и 20 февраля 2008. Этот метод полезен, если вам нужно произвести несколько арифметических операций с датой, сохранив начальное её значение.

Получение информации о дате

Наши следующие четыре метода созданы для получения имени или аббревиатуры для недели или месяца. Создаются два массива: один для названий дней недели, второй для месяцев. Методы getDay() или getMonth() объекта Date возвращают указатель, использующийся для доступа к значению массива дней или месяцев.

Кроме описанных выше проблем с использованием глобальных функций здесь есть другая угроза. Массивы (всех переменных) – это не константы, и они очень чувствительны к изменениям. Константы могут быть объявлены ключевым словом const в новых версиях языка JavaScript, но это не поддерживается большинством браузеров. К счастью, JavaScript предлагает альтернативные решения.

Массивы можно создать как свойства конструктора даты, исключая их из глобального пространства имен. Это можно сделать двумя способами: как статические атрибуты или наследуемые атрибуты. Примером статического атрибута служит PI, который является статическим атрибутом объекта Math. Массивы добавляются в конструктор Date с помощью присваивания, как это показано ниже:

Date.DAYNAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", ..., "Friday", "Saturday"];
Date.MONTHNAMES = ["January", "February", "March", "April", "May", "June", "July", ..., "August", "September", "October", "November", "December"];

Получить доступ к массивам можно следующим образом:

Date.DAYNAMES[index];

Второй вариант – это создание аттрибутов массива в объекте prototype конструктора даты:

Date.prototype.DAYNAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", ..., "Friday", "Saturday"];
Date.prototype.MONTHNAMES = ["January", "February", "March", "April", "May", "June", "July", ..., "August", "September", "October", "November", "December"];

Разница между этими двумя подходами в том, что они наследуются и доступны методам Date только при использовании ключевого слова this (этот метод будет использоваться на протяжении всей статьи).

Существует третий вариант – мы можем делать и то, и другое. Например:

Date.DAYNAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", ..., "Friday", "Saturday"];
Date.prototype.DAYNAMES = Date.DAYNAMES;

(Поднимая тему статических атрибутов и методов, необходимо упомянуть, что представленные методы могут быть не статическими, но относящимися к объекту prototype. Потребуются небольшие изменения, чтобы преобразовать эти методы в статические.)

А вот возможность сделать ваш код интернациональным. Код DAYNAMES и MONTHNAMES может быть отделен от других методов, так что можно установить названия дней и месяцев на разных языках:

Date.prototype.DAYNAMES = ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", ..., "Vendredi", "Samedi"];
Date.prototype.MONTHNAMES = ["Janvier", "Fevrier", "Mars", "Avril", "Mai", ..., "Juin", "Juillet", "Aout", "Septembre", "Octobre", "Novembre", "Decembre"];

С массивами на месте, четыре метода могут быть заданы следующим образом:

Date.prototype.getFullDay = function() {
  return this.DAYNAMES[this.getDay()];
};
Date.prototype.getDayAbbr = function() {
  return this.getFullDay().slice(0, 3);
};

Date.prototype.getFullMonth = function() {
  return this.MONTHNAMES[this.getMonth()];
};
Date.prototype.getMonthAbbr = function() {
  return this.getFullMonth().slice(0, 3);
};

А вот образец использования этих методов:

var example_date = new Date(2008, 1, 20);
alert("Day: " + example_date.getFullDay() +
  "\nDay abbr: " + example_date.getDayAbbr() +
  "\nMonth: " + example_date.getFullMonth() +
  "\nMonth Abbr: " + example_date.getMonthAbbr());

Alert покажет:
Day: Wednesday
Day abbr: Wed
Month: February
Month: Abbr Feb

Задание формата времени дня

Объект Date содержит 2 метода для получения строкового представления времени суток: toTimeString() и toLocaleTimeString(). Формат строки зависит от настроек клиента и может отличаться у разных пользователей. В общем, это все хорошо, но может не работать в вашем приложении. Следующие два метода возвращают определенный формат:
Первый возвращает время в формате “чч:мм:сс am/pm” с двумя цифрами в каждом числе.

Date.prototype.to12HourTimeString = function () {
  var h = thi  s.getHours();
  var m = "0" + this.getMinutes();
  var s = "0" + this.getSeconds();
  var ap = "am";

  if (h >= 12) {
      ap = "pm";

      if (h >= 13)
        h -= 12;
    } else if (h == 0)
      h = 12;
    }
  h = "0" + h;
  return h.slice(-2) + ":" +
           m.slice(-2) + ":" +
           s.slice(-2) + " " + ap;
};

Второй метод возвращает время в 24-часовом формате, также с двумя цифрами в каждом числе.

Date.prototype.to24HourTimeString = function () {
  var h = "0" + this.getHours();
  var m = "0" + this.getMinutes();
  var s = "0" + this.getSeconds();
  return h.slice(-2) + ":" + m.slice(-2) + ":" + s.slice(-2);
};

Наверное, в этом коде интересно только использование slice(). Чтобы сделать числа двузначными, к ним добавляется ведущий ноль, и затем slice() используется для захвата крайних справа 2 цифр. Если число меньше 10, включается дополнительный ведущий ноль. Эти методы используются вот так:

var dte = new Date(2008, 1, 20, 19, 30, 5);
alert(dte.to12HourTimeString() + "\n" + dte.to24HourTimeString());

Alert покажет:
07:30:05 pm
19:30:05

Получение количества дней в месяце

Бывает, что необходимо узнать количество дней в месяце. Для этого можно использовать массив с дополнительным кодом для Февраля, но есть способ проще. JavaScript переполнит даты, созданные с недопустимыми значениями. Например, если мы создадим дату 33 февраля 2008 как:

var d = new Date(2008, 1, 33);
alert(d);

Дата, созданная и отображенная в окне, — это 4 марта 2008 (2008 год – високосный).
Следовательно, мы можем сказать, что последний день месяца – и число дней в месяце – это «нулевой» день следующего месяца – один день до первого. Следующий месяц вычисляется как текущий месяц (date.getMonth()) плюс один. JavaScript возвратит Январь следующего месяца, если прибавить 1 к Декабрю, то есть года переводятся так же, как и месяцы. Последний день месяца вычисляется так:

Date.prototype.lastday = function() {
  var d = new Date(this.getFullYear(), this.getMonth() + 1, 0);
  return d.getDate();
};

Число дней в месяце любой даты находится следующим образом:

var d = new Date(2008, 1, 5);
alert(d.lastday());

Окно предупреждения покажет число дней в месяце: в этом примере – 29. (Второй аргумент, 1, в конструкторе Date это Февраль, Январь – 0.)

Поиск количества дней между двумя датами

Деловые проекты часто нуждаются в определении количества дней между двумя датами. Мы создадим метод getDaysBetween() – но сначала давайте добавим ещё один атрибут к прототипу конструктора даты, названный msPERDAY. Его значение – это число миллисекунд в 24 часах. Все что нам нужно – эта строка кода:

Date.prototype.msPERDAY = 1000 ∗ 60 ∗ 60 ∗ 24;

Другими словами, 1000 миллисекунд в секунде, 60 секунд в минуте, 60 минут в одном часе, 24 часа в сутках.

В большинстве случаев, определение количества дней между двумя датами исключает учет времени. Это требование мы будем использовать. Между 1 февраля и 10 февраля 9 дней, не обращая внимания на время – в одной дате может быть 01 00 часов, а в другой 23 00, но разница — всё равно 9 дней. Несмотря на это даты JavaScript включают время, так что необходимо корректирование:

Date.prototype.getDaysBetween = function(d) {
  d = d.copy();

  d.setHours(this.getHours(), this.getMinutes(), this.getSeconds(), ...
    this.getMilliseconds());

  var diff = d.getTime() - this.getTime();
  return (diff)/this.msPERDAY;
};

В данном коде, новая дата создается с таким же значением, что и аргумент, чтобы избежать изменения исходной даты. Временная часть новой даты поставлена, чтобы соответствовать времени даты, вызывающей метод, исключающий влияние времени дня.
Метод используется следующим образом:

var today = new Date();
var birthday = new Date(2008, 9, 31);
var days = today.getDaysBetween(birthday);

if (days > 0)
  alert(days + " days 'til my birthday.");
else if (days < 0)
  alert(days + " days since my birthday.");
else
  alert("It's my birthday!!");

Если дата, используемая как аргумент позже, чем дата, вызывающая метод, то возвращается положительное значение. Отрицательное значение возвращается, если аргумент – более ранняя дата. Может возвращаться и абсолютное значение, но знание того, какая дата была раньше, может быть важным, как в примере.

Использование представлений данных как аргумента

Метод getDaysBetween(d), показанный выше, принимает объект Date в качестве своего аргумента, но может быть удобнее использовать другие типы данных для представления даты. Конструктор Date имеет четыре различных процедуры:

new Date(); // без аргумента, использует текущее время и дату
new Date("Feb 20, 2008 10:20:05"); // символьное представление даты
new Date(2008, 1, 20, 10, 20, 05); // числовое представление даты
new Date(milliseconds); // число миллисекунд

Любой из методов, которые мы создаем, использующий дату как аргумент, может быть преобразован, чтобы принимать любые аргументы, распознанные конструктором данных, с добавлением следующего кода:

Date.prototype.getDaysBetween = function(d) {
  var d2;

  // дополнительный код для свойств аргументов
  if (arguments.length == 0) {
    d2 = new Date():
  } else if (d instanceof Date) {
    d2 = new Date(d.getTime());
  } else if (typeof d == "string") {
    d2 = new Date(d);
  } else if (arguments.length >= 3) {
    var dte = [0, 0, 0, 0, 0, 0];
    for (var i = 0; i < arguments.length; i++) {
      dte  [i] = arguments[i];
    }
    d2 = new Date(dte[0], dte[1], dte[2], dte[3], dte[4], ...
      dte[5]);
  } else if (typeof d == "number") {
    d2 = new Date(d);
  } else {
    return null;
  }

  if (d2 == "Invalid Date")
    return null;
  // Конец дополнительного кода

  d2.setHours(this.getHours(), this.getMinutes(), ...
    this.getSeconds(), this.getMilliseconds());

  var diff = d2.getTime() - this.getTime();
  return (diff)/this.msPERDAY;
};

Если используемый аргумент не преобразуется в дату, конструктор создает экземпляр даты со значением “Invalid Date”. В этом случае возвращается ноль.
(Мы всегда будем считать, что аргумент – это дата, чтобы уберечь статью от путаницы.)

Добавление дней, недель, месяцев и лет к дате

Для изменения даты прибавлением (или вычитанием) числа к ней используются 4 метода – addDays(), addWeeks(), addMonths() и addYears(). Каждый метод берет числовой аргумент, означающий количество дней, месяцев или лет, которые нужно добавить как подходящие к методу. Код для этих методов следующий:

Date.prototype.addDays = function(d) {
  this.setDate( this.getDate() + d );
};

Date.prototype.addWeeks = function(w) {
  this.addDays(w ∗ 7);
};

Date.prototype.addMonths= function(m) {
  var d = this.getDate();
  this.setMonth(this.getMonth() + m);

  if (this.getDate() < d)
    this.setDate(0);
};

Date.prototype.addYears = function(y) {
  var m = this.getMonth();
  this.setFullYear(this.getFullYear() + y);

  if (m < this.getMonth()) {
    this.setDate(0);
  }
};

Первый метод, addDays(), использует тот же принцип, что и метод lastDay(). Метод getDate() объекта даты возвращает число дней в месяце (от 1 до 31). К нему прибавляется количество дополнительных дней, и затем метод setDate объекта Date устанавливает день месяца на новое значение. Когда число, используемое как дата превышает (или становится меньше чем) количество дней в месяце, JavaScript отсчитывает дни вперед(или назад) до правильной даты.
Метод addWeeks() преобразует недели в дни, а затем использует метод addDays() для изменения даты. Если, например, дата – Среда, то новая дата будет – Среда.

Методы addMonths() и addYears() работают также, как addDays(). Они используют методы объекта Date getMonth и setMonth() или getFullYear и setFullYear() соответствено. У этих методов есть корректировка для согласования неравных длин месяцев или лет.

Добавляя месяцы, нужно ожидать, что новая дата будет тем же днем месяца – если месяц прибавлен ко 2 января какого-либо года, то результат должен быть 2 февраля - но, если месяц добавлен к 31 января, то результат без корректировки это 3 марта( 2 марта если год – високосный). Для наших целей должен был получиться последний день Февраля, поэтому есть тест для проверки является ли день месяца, полученный в результате, меньше, чем исходная дата. Если получилось так, это показывает, что дата отодвинулась в месяц, следующий за тем, который нам нужен. Отладка использует тот же способ, что и метод lastDay(): дата устанавливается на нулевой день.

Метод addYears имеет похожие проблемы в високосные годы. Прибавляя год к 29 февраля, получим 1 марта, поэтому необходимо вернуться в 28.
Методы могут использоваться вот так:

var date = new Date(2008, 1, 1); // 1ое февраля 2008
alert(date.addDays(7));
alert(date.addWeeks(3));
alert(date.addMonths(2));
alert(date.addYears(-2));

Alert-ы покажут 8 февраля, 29 февраля, 29 апрель.

Добавление дней недели

В некоторых бизнес приложениях считаются только дни недели – может появиться проблема во время подсчета даты окончания проекта, а вы не хотите работать в выходные! Следующий метод добавляет количество дней недели, считающихся выходными, так что период между начальной и новой датами включает в себя требуемое число дней в неделе.

Date.prototype.addWeekDays = function(d) {
  var startDay = this.getDay();  //текущий день недели от 0 до 6
  var wkEnds = 0;  //число нужных выходных
  var partialWeek = d % 5;  //количество дней для неполной недели

  if (d < 0) {  //вычитание дней недели
    wkEnds = Math.ceil(d/5); //отрицательное количество выходных

    switch (startDay) {
    case 6:  //начинаем с субботы, на 1 выходной меньше
      if (partialWeek  0 && wkEnds < 0)
        wkEnds++;
      break;
    case 0:  //начальный день - воскресенье
      if (partialWeek  0)
        d++;  //уменьшаем добавленные дни
      else
        d—;  // увеличиваем добавленные дни
      break;
    default:
      if (partialWeek <= -startDay)
      wkEnds—;
    }
  }
  else if (d > 0) {  //adding weekdays
    wkEnds = Math.floor(d/5);
    var w = wkEnds;
    switch (startDay) {
    case 6:
      // Если начальный день – суббота и 
      // неделя полная, нужно уменьшить дни на 1 
      // неделя неполная, нужно увеличить дни на 1
      if (partialWeek 0)
        d--;
      else
        d++;
      break;
    case 0:
      //Sunday
      if (partialWeek 0 && wkEnds > 0)
        wkEnds—;
      break;
    default:
      if (5 – day < partialWeek)
        wkEnds++;
    }
  }

  d += wkEnds ∗ 2;
  this.addDays(d);
};

Метод addWeekDays() вычисляет количество выходных дней, которые будут нужны в диапазоне дат, и добавляет его к требуемым дням недели. Эта сумма прибавляется к дате, с помощью нашего метода addDays(). addWeekDays() всегда вычисляет дату с понедельника по пятницу включительно. Например, сейчас суббота и мы завершаем планирование проекта, который начинается в понедельник. Мы определили, что проект займет 50 дней и хотим вычислить дату завершения. Следующий код сделает это:

var today = new Date(2008, 1, 23);
var endDate = today.copy();
endDate.addWeekDays(50);
alert(endDate);
Alert покажет 2 мая 2008.
К несчастью, менеджеры решили, что проект должен быть перенесен, так что он закончится 31 октября. Они хотят знать, когда вам нужно начать.
var endDate = new Date(2008, 9, 31);
var startDate = endDate.copy();
startDate.addWeekDays(-50);
alert(startDate);

Alert покажет 22 августа 2008, последний день, когда проект можно начать, разумно ожидая закончить его в последний день октября.

Этот код не считает выходные или другие дни, когда фирма не работает. Для подсчета выходных мы можем добавить в addWeekDays() цикл, который повышает число требуемых дней в диапазоне от начальной даты до конечной на 1 в каждый выходной день. Вот пример функции:

function due_date(start_date, workDays) {
  var dueDate = new Date();
  var days = 0;
  var holidays = 0;

  do {
    dueDate.setYear(start_date.getFullYear());
    dueDate.setMonth(start_date.getMonth());
    dueDate.setDate(start_date.getDate());

    days = workDays + holidays;
    dueDate.addWeekDays(days);

    // Ваш собственный поиск.
    // Поиск по базе данных или массиву
    // выходных дней между  start_date и dueDate
    holidays = [query-results]

  } while (days != workDays + holidays);

  return dueDate;
}

(Функция due_date() не является средством объекта Date, так как она требует внешний источник данных, который может измениться.)
Функция due_date() предполагает, что существует источник данных (массив или база данных), содержащий даты всех выходных дней вашей компании, и что у вас есть возможность сделать запрос к этой таблице, возвращающий количество выходных дней, встретившихся между двумя датами.

Цикл начинается с установки dueDate на start_date. Он добавляет число выходных дней (с нулевым начальным значением) к рабочим дням и использует метод addWeekDays() для получения срока выполнения. Запрос получает количество выходных дней между начальной датой и сроком завершения( исключая те, что выпадают на субботу и воскресенье). Если количество выходных в сумме с начальным числом рабочих дней даёт количество дней, использующихся для вычисления значения конечной даты, то работа окончена. В противном случае, значение срока завершения сбрасывается, и цикл запускается ещё раз с новым количеством выходных.

Вычисление времени между двумя датами

Во многих деловых приложениях необходимо определить время между двумя датами в днях, месяцах, годах или днях недели. Мы уже создали метод для подсчета дней между двуся датами: getDaysBetween().

Вместе с addWeekDays() может работать getWeekDays(). Она вычисляет количество дней между двумя датами. Так же, как addWeekDays, этот метод не учитывает выходные дни при вычислении, но его можно включить в функцию, которая сделает это.

Date.prototype.getWeekDays = function(d) {

  var wkEnds = 0;
  var days = Math.abs(this.getDaysBetween(d));
  var startDay = 0, endDay = 0;

  if (days) {
    if (d < this) {
      startDay = d.getDay();
      endDay = this.getDay();
    } else {
      startDay = this.getDay();
      endDay = d.getDay();
    }
    wkEnds = Math.floor(days/7);

    if (startDay != 6 && startDay > endDay)
      wkEnds++;

    if (startDay != endDay && (startDay  6 || endDay  6) )
      days-;

    days -= (wkEnds ∗ 2);
  }
  return days;
};

Этот код определяет, какое количество выходных дней необходимо вычесть из общего количества дней между двумя датами. Кроме того, в отличие от других методов, он возвращает абсолютное значение вместо числа со знаком. Вот пример его использования:

var endDate = new Date(2007, 11, 5); // 5-е декабря 2007
var startDate = new Date(2008, 1, 20); // 20-е февраля 2008
alert(startDate.getWeekDays(endDate));

Alert покажет 55, число выходных дней между двумя датами.

Простой алгоритм, вычисляющий количество месяцев между двумя датами может выглядеть так:

var d1 = this.getFullYear() ∗ 12 + this.getMonth();
var d2 = d.getFullYear() ∗ 12 + d.getMonth();
return d2 - d1;

Данный алгоритм будет работать для дат, содержащих в себе несколько лет, когда разбиение на месяцы не так существенно.
Подсчет количества месяцев между двумя датами осложняется, так как бывают неполные месяцы для каждой из дат – очевидно, что между 1 января и 28 февраля больше 1 месяца. Дни, включаемые в часть месяца, зависят от хронологического порядка дат. Нам требуются только дни от даты до конца месяца или с 1-го дня месяца до даты. Нам также нужно возвратить отрицательное значение, если дата, используемая как аргумент, стоит раньше другой.

Date.prototype.getMonthsBetween = function(d) {
  var sDate, eDate;
  var d1 = this.getFullYear() ∗ 12 + this.getMonth();
  var d2 = d.getFullYear() ∗ 12 + d.getMonth();
  var sign;
  var months = 0;

  if (this == d) {
    months = 0;
  } else if (d1 == d2) { //тот же год и месяц
    months = (d.getDate() - this.getDate()) / this.lastday();
  } else {
    if (d1 <  d2) {
      sDate = this;
      eDate = d;
      sign = 1;
    } else {
      sDate = d;
      eDate = this;
      sign = -1;
    }

    var sAdj = sDate.lastday() - sDate.getDate();
    var eAdj = eDate.getDate();
    var adj = (sAdj + eAdj) / sDate.lastday() - 1;
    months = Math.abs(d2 - d1) + adj;
    months = (months ∗ sign)
  }
  return months;
};

Две даты конвертируются в количество полных месяцев от общей точки – год 1. Затем происходит разветвление кода, в зависимости от хронологического порядка дат. Если даты одинаковы, то номер месяцев - ноль. Если даты относятся к одному году и месяцу, то число месяцев – это разница между датами, поделенная на количество дней в месяце. Иначе, создаются переменные ссылочного типа, показывающие, какая дата более ранняя. После этого вычисляется количество дней, оставшихся в начальной дате, и количество дней в последнем месяце, которое является днем месяца, затем полученная сумма делится на количество дней в более раннем из двух месяцев.

Количество дней в начальном месяце используется как делитель, потому что сумма будет равна номеру дней в начальном месяце, если даты одинаковы.
После создания getMonthsBetween() можно легко написать метод, вычисляющий количество лет между двумя датами. Только необходимо разделить количество месяцев между ними на 12:

Date.prototype.getYearsBetween = function(d) {
  var months = this.getMonthsBetween(d);
  return months/12;
};

Метод особого назначения getAge() может быть написан, используя getYearsBetween() следующим образом:

Date.prototype.getAge = function() {
  var today = new Date();
  return this.getYearsBetween(today).toFixed(2);
};

А использовать его можно вот так:

var dateOfBirth = new Date(yob, mob, dob);
var age = dateOfBirth.getAge();

Последний метод, sameDayEachWeek(), немного отличается от остальных. Он возвращает массив с датами для определенного дня недели – от воскресенья до субботы – для каждой недели между двумя датами. Его можно использовать в приложении, которое требует планирование постоянных событий.

Date.prototype.sameDayEachWeek = function (day, date) {
  var aDays = new Array();
  var eDate, nextDate, adj;

  if (this > date) {
    eDate = this;
    nextDate = date.copy();
  } else {
    eDate = date;
    nextDate = this.copy();
  }

  adj = (day - nextDate.getDay() + 7) % 7;
  nextDate.setDate(nextDate.getDate() + adj);

  while (nextDate < eDate) {
    aDays[aDays.length] = nextDate.copy();
    nextDate.setDate(nextDate.getDate() + 7);
  }
  return aDays;
};

Метод использует в качестве аргументов значение дня недели (от 0 - для воскресенья, до 6 - для субботы) и дату.
Первая часть кода вычисляет, какая дата является ранней. В ней генерируется дата для увеличения, nextDate, которая является копией более ранней даты, как начальной точки, и ссылку к более поздней дате, как конечной точки. Новый объект Date используется для nextDate, так что он может быть изменен без влияния на начальную дату.
Затем вычисляется дата первого дня недели, который нам необходим. Перед добавлением необходимо произвести вычитание из переменной date, так как вычитание приводит строку в числовую форму (если просматривается переменная строкового типа), таким будет произведено сложение чисел, а не их конкатенация (т. е. объединение строк, а не арифметическая операция сложения).
После этого, в цикле выполняется nextDate для каждого из семи дней, до тех пор, пока дата не выйдет из диапазона. Каждый проход добавляет новый объект Date в возвращаемый массив.
Метод можно использовать следующим образом:

var enddate = new Date(2008, 0, 5); // 5ое января 2008
var date = new Date(2008, 1, 10); // 10ое февраля 2008

var listofdays = date.sameDayEachWeek(2, enddate); // 2 = вторник
alert("Tuedays in range are:\n\n" + listofdays.join("\n"));

Команда Alert выдаст следующее:
Tuedays in range are:

Tue Jan 08 2008 00:00:00 GMT-0600 (Central Standard Time)
Tue Jan 15 2008 00:00:00 GMT-0600 (Central Standard Time)
Tue Jan 22 2008 00:00:00 GMT-0600 (Central Standard Time)
Tue Jan 29 2008 00:00:00 GMT-0600 (Central Standard Time)
Tue Feb 05 2008 00:00:00 GMT-0600 (Central Standard Time)

Заключение

Многие из показанных методов могут быть использованы самостоятельно, без остальных. Главное помнить о том, что некоторые методы зависят от copy(), lastday(), getDaysBetween() и getMonthsBetween(), а некоторые – от трех созданных свойств. Таким образом, Вы сможете написать только нужный Вашему проекту код.

Также, методы могут быть добавлены в любой другой встроенный конструктор объектов – Array, String, Math, Object – используя такой же способ: присваивать их атрибуту prototype.

Надеемся, Вы найдете эту статью полезной в Вашей работе. Вы с легкостью сможете расширить встроенные объекты JavaScript с помощью вашего собственного кода и добавить Ваши собственные методы. Добавление методов через prototype делает Ваш код более понятным и добавляет ему преимущества в выполнении (благодаря способу обработки ссылок на объекты в JavaScript – сначала он ищет локальные объекты и затем проходит по цепочке прототипов, глобальное пространство – это последнее место, где JavaScript выполнит поиск).

Спонсор поста: Агентство «Web++» — создание и продвижение сайтов в Волгограде.

Подписаться на обновления блога

Вам понравился наш блог, хотите следить за обновлениями? Подпишитесь на RSS рассылку или рассылку по электронной почте. Так же вы можете следить за нами в Twitter.

Категории: JavaScript, Переводы | Комментировать

Комментарии (6)

  1. Ivan / 01.11.2009 в 13:52

    Респект!

  2. АНВАР / 03.11.2009 в 14:58

    Благодарю)

  3. Виктор / 27.11.2009 в 23:54

    В принципе, полезно! Вообще удивительно, что до сих пор стандартный JS не был расширен множеством необходимых функций класса Date! Ведь там и половины нету того, что нужно для нормальной работы с датой!

  4. ULiX / 08.09.2010 в 03:27

    Статья очень хорошая.

    Вот только подсчет числа месяцев для меня всё равно остался спорным вопросом.
    В приведенном примере для вычисления дробной части используется делитель равный числу дней в стартовом месяце.
    Лучше объяснять на примерах…
    Между 29 Января и 28 Февраля скрипт выдаст неполный месяц (30 дней).
    Но стоит конечную дату увеличить всего на 1 день, выбрав 1 марта (31 день), и алгоритм уже выдаст больше одного месяца, а точнее 1 месяц и 3 дня.

    Подсчитать точно, число месяцев я считаю не реально. Это всегда будет неоднозначно. Скорее это это информация для приближенного понимания, нежели для точных предпосылок, но если это приближение, то зачем выдумывать сложные алгоритмы, когда можно просто задастся константой числа дней в месяце и делить число дней между датами на эту константу.

  5. nalai / 25.01.2013 в 12:04

    В коде функции addWeekDays на 44 строке используется нигде до этого не объявленная переменная days

  6. vlad / 02.07.2013 в 17:18

    При таком прототипировании:
    Date.prototype.addDays = function(d) {
    this.setDate( this.getDate() + d );
    };
    var date = new Date(2008, 1, 1); // 1ое февраля 2008
    alert(date.addDays(7)); // выдает undefined
    Надо, видимо, было бы так:
    date.addDays(7);alert(date);
    Поскольку прототип меняет this.

Оставить комментарий

480×60
480×60