Сегодня расскажу как динамически обновлять время у комментариев, постов и т.п. при помощи jQuery.

Думаю многие видели что у социальной сети Вконтаке (а может и еще где), время меняется примерно каждые 5-10 сек 10 секунд назад, вот по этой схеме и будем писать наш скрипт обновления.
Что будет уметь плагин?
1. Собирать данные элементов, содержащих в себе время. (в формате timestamp)
2. Через заданный интервал времени, обновлять его у элементов, которых оно было собрано.
3. Секунды, минуты, часы будут в формате (столько то N назад), остальное (дд.мм.гггг в чч.мм)
К делу
Для начала нам понадобится сформировать HTML обертку у времени, для этого нам нужно поместить его в html контейнер, например в span, div, a и т.п. (не важно).
<span class="time" data-time="Время timestamp">Время</span>

Пример с PHP
<span class="time" data-time="<? echo time(); ?>"><? echo MyFunctionTime(); ?></span>

Описание атрибутов:
class — Для выборки по названию класса.
data-time — Будет хранить время в секундах с начала Эпохи Unix.
Собственно ВК, почти так же помечает время в комментариях.
Пишем и разбираем код
Для начала напишем функцию «Parser» которая соберет интересующие нас элементы, и поместит данные из них в массив.
function JPTimeMap(selector, maximum) 
{
    // Массив обновляемых элементов
    var timeMapArr = [];
    
    // Текущая дата
    var Time = new Date(); 

    // Время в формате timestamp
    var phptime = Math.floor(Time.getTime() / 1000);

    // Выбираем все элементы с указанным селектором
    $(selector).each(function() { 
    
        // Время в формате timestamp
        var uid = $(this).attr('data-time'); 
        
        // Помещаем время за последние 24 часа
        if (uid > (phptime - maximum))
            timeMapArr[uid] = uid;
    });
    
    return timeMapArr;   
}

Входящие параметры
selector — Селектор элемента в котором находится наше время (в примере это .time у class)
maximum — число секунд прошедшего времени, за которое будем собирать данные (в примере будет 86400 = 24 часа)
Далее пишем код который будет работать с этими данными, и инициализировать всю работу нашего плагина.
function JPTime(option) 
{
    // Копия текущего объекта
    var parent = this;
    
    // Опции работы скрипта
    var options = $.extend({
        interval: 5000,
        selector: '.time',
        maximum: 86400,
        autoInterval: false,
        timex: 60,
    }, option);
    
    /**
    * Cклонение слов по типу 
    * 1 секунда, 2 секунды, 5 секунд
    */
    this.case_number = function(num, one, two, more) {
        var l2 = num.substr(num.length - 2, 2);
        if ((l2 >= 5) && (l2 <= 20))
            return more;
        
        var l = num.substr(num.length - 1, 1);
        switch (l) {
            case '1':
                return one;
            case '2':
                return two;
            case '3':
                return two;
            case '4':
                return two;
            default:
                return more;
        }
    };
    
    /**
    * Формат времени (сек / мин / час назад)
    */
    this.timeOut = function(time) {
        var mTime = new Date();
        
        // Текущее время
        var realtime = Math.floor(mTime.getTime() / 1000 + 22);
        
        // Шаблон текста
        var text = {2: 'две', 3: 'три', 4: 'четыре'};
        
        // Время по секундам, минутам, часам и дате
        if (time == realtime) 
            return 'только что';
        else if (time > (realtime - 60)) {
            var sec = Math.floor(realtime - time);
            return (text[sec] ? text[sec] : sec) + ' секунд' + parent.case_number(String(sec), 'а', 'ы', '') + ' назад';
        } else if (time > (realtime - 3600)) {
            var minutes = Math.floor((realtime - time) / 60);
            return (text[minutes] ? text[minutes] : minutes) + ' минут' + parent.case_number(String(minutes), 'у', 'ы', '') + ' назад';
        } else if (time > (realtime - 86400)) {
            text = {2: 'два', 3: 'три', 4: 'четыре'};
            var hours = Math.floor((realtime - time) / 3600);
            return hours + ' час' + parent.case_number(String(hours), '', 'а', 'ов') + ' назад';
        } else {
            var t = new Date();
            t.setTime(time * 1000);
            return t.getDate()+'.'+
                   t.getMonth()+'.'+
                   t.getFullYear()+' в '+
                   t.getHours()+':'+
                   t.getMinutes()+':'+
                   t.getSeconds();
        }
    };
    
    /**
    * Запуск скрипта
    */
    this.start = function() {
        
        // Массив обновляемых элементов
        var timeMap = JPTimeMap(options.selector, options.maximum); 
        
        // Если есть что то в массиве, то запускаем обновление
        if (parent.count(timeMap) > '0') {
            interval = setInterval(function() {
                
                // Текущая метка времени
                jtime = Math.floor(new Date().getTime() / 1000 + 22);
                
                // Исполняющий цикл
                for(var p in timeMap) {
                    var newTime = parent.timeOut(p);
                    $('[data-time = "' + p + '"]').text(newTime);
                    endTime = p; 
                }
                
                // Наименьшее время из массива
                end = (jtime - endTime);
                
                // Корректировка интервала
                if (options.autoInterval === true) 
                    if (end > options.timex) {
                        options.interval = 60000, options.timex += end;   
                        clearInterval(interval);
                        parent.start();
                    }
            }, options.interval);
        }
    };
    
    /**
    * Псевдоним php функции count()
    */
    this.count = function(arr) {
        var cnt = 0;
        for (var i in arr) {
            if (i) 
               cnt++;
        }
        return cnt;
    };
}

Теперь когда все написали, сохраняем это все в js файл, например jptime.js и подключаем в шапку сайта, или страницу с выводом обновляемого времени.
Инициализация:
var JPTime = new JPTime({
  interval: 5000, 
  selector: '.time',
  maximum: 86400,
  autoInterval: true,
});

JPTime.start();

Параметры:
interval — Колличество миллисекунд, через которое будут обновляться элементы времени.
selector — метка атрибута, для идентификации элемента времени, я выбирал по .time в class
maximum — Максимальное колличество секунд, за которое будет выбираться элемент для создания массива.
autoInterval — Принимает значение false или true (по умолчанию false), если true, то при достижении минимального времени в 60 сек. интервал обновления становится 1 раз в минуту, если false, по заданному интервалу.

Ну, собственно, это все, что хотел написать.
Пример можно увидеть здесь, скачать готовый jptime.js можно по этой ссылке, полный пример demo можно скачать по этой ссылке.
Всем благ, и удачных разработок!
UPD: Дописал перезапуск интервала.

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


  1. WebSpider
    24.07.2015 00:20

    > 41 секунда назад
    > 1 минуту назад
    > две минуты назад

    Вам бы привести всё к единому стилю как минимум


    1. alex-borisi Автор
      24.07.2015 01:06
      -2

      Ну я не делал акцент на стиле времени, буквенный вывод чисто для разнообразия. )
      // Можно указать все слова чисел, а можно вообще убрать. Думаю для примера самое оно.


  1. istui
    24.07.2015 00:45
    +1

    Посмотрите вот это, там намного больше вариантов (для вашей функции timeOut)


  1. Sayonji
    24.07.2015 09:06
    +7

    Какой ужас. Особенно разбираться лень, но:
    1.

    parent.count(timeMap) > '0'
    Ладно ноль как строка, но зачем делать самописную считалку вместо .length?
    2.
    interval =…
    endTime =…
    end = ...
    Вы отправляете в глобал переменные с названиями interval, end. Не надо так. Да и вообще, подумайте о механизме сброса интервала, если, например, контент на странице изменится (после того как перестанете записывать interval в свойства окна). Заодно придумайте что-то на случай добавления нового span[data-time] на страницу. Не подвешивать же на каждый новый пост по своему интервалу?
    3.
    setInterval(function() {… options.interval = 60000}, options.interval);
    К сожалению, это не сработает. Как вообще можно подумать, что это сработает? Передача числа по ссылке по умолчанию в js?
    4. Придирка, но всё же возьмите на заметку:
    this.timeOut = function(time) {
    this.start = function() {
    Погуглите js operator new, есть такая штука как JPTime.prototype={...}.


  1. Sayonji
    24.07.2015 09:06
    +1

    Какой ужас. Особенно разбираться лень, но:
    1.

    parent.count(timeMap) > '0'
    Ладно ноль как строка, но зачем делать самописную считалку вместо .length?
    2.
    interval =…
    endTime =…
    end = ...
    Вы отправляете в глобал переменные с названиями interval, end. Не надо так. Да и вообще, подумайте о механизме сброса интервала, если, например, контент на странице изменится (после того как перестанете записывать interval в свойства окна). Заодно придумайте что-то на случай добавления нового span[data-time] на страницу. Не подвешивать же на каждый новый пост по своему интервалу?
    3.
    setInterval(function() {… options.interval = 60000}, options.interval);
    К сожалению, это не сработает. Как вообще можно подумать, что это сработает? Передача числа по ссылке по умолчанию в js?
    4. Придирка, но всё же возьмите на заметку:
    this.timeOut = function(time) {
    this.start = function() {
    Погуглите js operator new, есть такая штука как JPTime.prototype={...}.


    1. Sayonji
      24.07.2015 10:10

      Когда отправка комментария не сработала, я специально перед повторной отправкой обновил страницу и поверил. Прошу прощения, это все хабр.


  1. limonte
    24.07.2015 09:08
    +2

    Вашу библиотеку ровным счетом никто не будет использовать и скорее всего даже она даже внимания не заслужит пока она не будет на GitHub и в bower репизитории.


  1. kovalevsky
    24.07.2015 09:21
    +5

    moment.js


  1. jonasas
    24.07.2015 09:32
    +2

    Есть один очень важный момент, который необходимо учесть. Что будет, если на клиентской машине установлено неверное время? Особенно, если на пару часов в будущем.


  1. rznELVIS
    24.07.2015 10:10
    +1

    Коллега, было бы круто добавить обзор аналогов. А то как то маловато инфы (


  1. kashey
    24.07.2015 13:08

    Не самое правильное решение — забыли разбивать элементы на частоты обновлений, в том числе динамически.
    Представим что у вас 100 комментов, 80 из них написаны вчера («вчера»), 10 пару часов назад («2 часа назад»), 9 недавно («12 минут назад») и парочка «только что».
    У всех свои интервалы проверок и обновлений. Все могут переходить из класса в класс.
    И где метод .stop?