Модераторы: LSD, AntonSaburov

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Стандартные календарные расчеты 
:(
    Опции темы
arcsupport
Дата 7.1.2012, 22:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 725
Регистрация: 24.10.2008

Репутация: 1
Всего: 2



Существуют ли стандартные (встроенные в JRE) классы, объекты и методы, которые позволят рассчитать количество лет, месяцев, недель и дней между двумя датами по грегорианскому календарю?
По юлианскому календарю?
PM MAIL   Вверх
jk1
Дата 8.1.2012, 10:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник
Сообщений: 1168
Регистрация: 17.10.2008
Где: Санкт-Петербург

Репутация: 40
Всего: 75



Это как бы основа
Код

Calendar past = Calendar.getInstance();
past.set(2008, Calendar.AUGUST, 1);                // 2008-08-01
Calendar today = Calendar.getInstance();        // today
System.out.println(TimeUnit.DAYS.convert(today.getTimeInMillis() - past.getTimeInMillis(), TimeUnit.MILLISECONDS));

А конкретный тип календаря определяется конкретным наследником java.util.Calendar. Например там есть GregorianCalendar.


--------------------
Opinions are like assholes — everybody has one
PM MAIL   Вверх
arcsupport
Дата 8.1.2012, 11:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 725
Регистрация: 24.10.2008

Репутация: 1
Всего: 2



jk1, меня больше интересует не общее количество дней, а количество лет, месяцев, недель и дней между двумя датами.

PM MAIL   Вверх
Stolzen
Дата 9.1.2012, 14:56 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1041
Регистрация: 17.10.2005

Репутация: 23
Всего: 48



arcsupport, можно примерно так

Код

public static final long MILLIS_PER_SECOND = 1000;
public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR;

public static final long[] MILILIS = { MILLIS_PER_DAY, MILLIS_PER_HOUR, MILLIS_PER_MINUTE };

public static String difference(Calendar one, Calendar another) {
    long mls = Math.abs(one.getTimeInMillis() - another.getTimeInMillis());
    Object[] res = new Object[MILILIS.length];

    for (int i = 0; i < MILILIS.length; i++) {
        res[i] = mls / MILILIS[i];
        mls = mls % MILILIS[i];
    }

    return MessageFormat.format("{0} days, {1} hours, {2} minutes", res);
}

public static String differenceFromNow(Calendar one) {
    return difference(one, Calendar.getInstance());
}

public static void main(String[] args) {
    GregorianCalendar past1 = new GregorianCalendar(2008, Calendar.AUGUST, 1);
    System.out.println(differenceFromNow(past1));

    Calendar past2 = Calendar.getInstance();
    past2.add(Calendar.DAY_OF_MONTH, -1);
    System.out.println(differenceFromNow(past2));
}


Код

1 256 days, 22 hours, 55 minutes
1 days, 0 hours, 0 minutes


Оно?

Добавлено через 5 минут и 2 секунды
Года не добавлял, т.к. они могут быть високосные и невисокосные. Если это не важно, то в массив MILILIS можно добавить на первое место еще одну константу MILLIS_PER_YEAR = 355 * MILLIS_PER_DAY; и в MessageFormat.format("{0} days, {1} hours, {2} minutes", res);  добавить {0} years


--------------------
datatalks.ru - анализ данных, статистика, машинное обучение
PM MAIL WWW   Вверх
jk1
Дата 9.1.2012, 16:39 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник
Сообщений: 1168
Регистрация: 17.10.2008
Где: Санкт-Петербург

Репутация: 40
Всего: 75



Цитата

public static final long MILLIS_PER_SECOND = 1000;
public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR;

public static final long[] MILILIS = { MILLIS_PER_DAY, MILLIS_PER_HOUR, MILLIS_PER_MINUTE };


Не надо изобретать велосипедов. Класс TimeUnit, о котором я говорил выше, умеет выполнять преобразования единиц времени. В данном случае это будут преобразования милисекунд в минуты, часы, дни.


--------------------
Opinions are like assholes — everybody has one
PM MAIL   Вверх
Stolzen
Дата 9.1.2012, 17:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1041
Регистрация: 17.10.2005

Репутация: 23
Всего: 48



Преобразование он делать умеет, а вот делить с остатком - нет


--------------------
datatalks.ru - анализ данных, статистика, машинное обучение
PM MAIL WWW   Вверх
jk1
Дата 9.1.2012, 17:09 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник
Сообщений: 1168
Регистрация: 17.10.2008
Где: Санкт-Петербург

Репутация: 40
Всего: 75



Цитата

а вот делить с остатком - нет 


Умеет

Код

TimeUnit.SECONDS.convert(1900, TimeUnit.MILLISECONDS)


дает в результате 1


--------------------
Opinions are like assholes — everybody has one
PM MAIL   Вверх
Stolzen
Дата 9.1.2012, 17:09 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1041
Регистрация: 17.10.2005

Репутация: 23
Всего: 48



А остаток?


--------------------
datatalks.ru - анализ данных, статистика, машинное обучение
PM MAIL WWW   Вверх
jk1
Дата 9.1.2012, 17:32 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник
Сообщений: 1168
Регистрация: 17.10.2008
Где: Санкт-Петербург

Репутация: 40
Всего: 75



Вуаля
Код

Long time = 1900L;
Long seconds =  TimeUnit.SECONDS.convert(1900, TimeUnit.MILLISECONDS);
Long milliseconds = time - TimeUnit.MILLISECONDS.convert(seconds, TimeUnit.SECONDS);



--------------------
Opinions are like assholes — everybody has one
PM MAIL   Вверх
Mirkes
Дата 10.1.2012, 17:42 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 586
Регистрация: 18.8.2011
Где: Красноярск

Репутация: 7
Всего: 17



По моему умудренные советчики не поняли вопроса. Вопрос в исчислении стажа того или иного вида. То есть следует указать сколько лет, месяцев и дней прошло от одной даты до другой.
Нельзя говорить 100 дней, нужно сказать три месяца и ? дней.
С этого момента начинаются неприятности при подсчете стажей, поскольку разные месяцы содержат разное число дней.
Далее вопрос о том, какая точность нужна.
Ниже пример для грубого подсчета без делений, а средствами стандартных библиотек.
Еще ниже (по тексту)  пример точного подсчета. Текст не оптимизирован. Если задача регулярная, то стоит написать отдельную функцию и почистить код.
Код

import java.util.Calendar;
import java.util.concurrent.TimeUnit;

public class Days {

    public static void main(String[] arg) {
        Calendar past = Calendar.getInstance();
        Calendar diff = Calendar.getInstance();
        past.set(2008, Calendar.AUGUST, 1); // 2008-08-01
        Calendar today = Calendar.getInstance(); // today
        System.out.println(TimeUnit.DAYS.convert(today.getTimeInMillis() - past.getTimeInMillis(),
                                                 TimeUnit.MILLISECONDS));

        // грубый подсчет.
        diff.setTimeInMillis(today.getTimeInMillis() - past.getTimeInMillis());
        int year = diff.get(Calendar.YEAR) - 1970;
        int month = diff.get(Calendar.MONTH);
        int day = diff.get(Calendar.DAY_OF_MONTH);
        System.out.println("" + day + " days " + month + " month " + year + " years");


        // точный подсчет
        year=0;
        while (past.get(Calendar.YEAR)<today.get(Calendar.YEAR)){
            year++;
            past.add(Calendar.YEAR, 1);
        }
        month=0;
        if (past.get(Calendar.MONTH)>today.get(Calendar.MONTH)){
            past.add(Calendar.YEAR, -1);
            year--;
            while (past.get(Calendar.MONTH)>0){
                past.add(Calendar.MONTH, 1);
                month++;
            }
        }
        while (past.get(Calendar.MONTH)<today.get(Calendar.MONTH)){
            past.add(Calendar.MONTH, 1);
            month++;
        }
        day=0;
        if (past.get(Calendar.DAY_OF_MONTH)>today.get(Calendar.DAY_OF_MONTH)){
            past.add(Calendar.MONTH, -1);
            month--;
            while (past.get(Calendar.MONTH)<today.get(Calendar.MONTH)){
                past.add(Calendar.DAY_OF_MONTH, 1);
                day++;
            }
        }
        while (past.get(Calendar.DAY_OF_MONTH)<today.get(Calendar.DAY_OF_MONTH)){
            past.add(Calendar.DAY_OF_MONTH, 1);
            day++;
        }
        System.out.println("" + day + " days " + month + " month " + year + " years");
        
    }
}


пример вывода 
1257                                      вариант jk1 - просто число дней.
11 days 5 month 3 years         грубый расчет.
9 days 5 month 3 years           точный расчет

Забавно, но куда-то потерялось два дня. Я думал что разность будет в один день.




--------------------
Mirkes
PM MAIL   Вверх
arcsupport
Дата 10.1.2012, 18:13 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 725
Регистрация: 24.10.2008

Репутация: 1
Всего: 2



Mirkes, спасибо.
PM MAIL   Вверх
Dummy
Дата 10.1.2012, 22:09 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 263
Регистрация: 21.5.2007

Репутация: 9
Всего: 19



Mirkes, в вашей реализации я вижу пару моментов, с которыми хочется поспорить.
  •  Перемотка календаря past на один месяц назад/вперед может привести к накоплению погрешности по дням. Скажем, 30-е марта при перемотке месяца назад превратится в 28/29 февраля. Насколько это чревато, я не разбирался smile
  •  Постоянное использование Calendar.add() - это удар по производительности, т. к. внутри календаря может производится множество пересчетов. Насколько велика будет потеря в производительности, зависит от конкретной реализации Calendar.
И в целом - подсчет с использованием Calendar.add() не приводит к универсальной работе с календарями, в которых переменное количество месяцев в году (не знаю, применяются ли где-нибудь такие). Соответственно, нет смысла ее использовать. Для календарей с фиксированным количеством месяцев, к которым относятся григорианский и юлианский, разницу можно посчитать руками. Я тут набросал такое:
Код

void fastDiff(Calendar past, Calendar today) {        
    int years = today.get(Calendar.YEAR) - past.get(Calendar.YEAR);

    int monthsInYear = past.getMaximum(Calendar.MONTH) + 1;

    int pastMonth = past.get(Calendar.MONTH);
    int todayMonth = today.get(Calendar.MONTH);

    int months;

    if (pastMonth > todayMonth) {
        years--;
        months = monthsInYear - pastMonth + todayMonth;
    } else {
        months = todayMonth - pastMonth;
    }

    int pastDays = past.get(Calendar.DAY_OF_MONTH);
    int todayDays = today.get(Calendar.DAY_OF_MONTH);

    int days;

    if (pastDays > todayDays) {
        days = past.getActualMaximum(Calendar.DAY_OF_MONTH) - pastDays + todayDays;
        months--;
    } else {
        days = todayDays - pastDays;
    }

    if (months < 0) {
        years--;
        months = monthsInYear + months;
    }

    System.out.println("Fast diff:" + days + " days " + months + " month " + years + " years");
}

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

Это сообщение отредактировал(а) Dummy - 10.1.2012, 22:12
PM MAIL   Вверх
Mirkes
Дата 11.1.2012, 03:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 586
Регистрация: 18.8.2011
Где: Красноярск

Репутация: 7
Всего: 17



Есть подозрение, что вот этот фрагмент внесет погрешность. К сожалению пора на работу и нет времени проверить. Как только буду готов - напишу.
По поводу эффективности алгоритмов - я уже написал, что алгоритм не оптимизирован.
Успел набросать на основе метода Dummy свой метод.
Код

    public static void stag(Calendar past, Calendar today){
        int years = today.get(Calendar.YEAR) - past.get(Calendar.YEAR);

        int monthsInYear = past.getMaximum(Calendar.MONTH) + 1;

        int pastMonth = past.get(Calendar.MONTH);
        int todayMonth = today.get(Calendar.MONTH);

        int months;

        if (pastMonth > todayMonth) {
            years--;
            months = monthsInYear - pastMonth + todayMonth;
        } else {
            months = todayMonth - pastMonth;
        }

        int pastDays = past.get(Calendar.DAY_OF_MONTH);
        int todayDays = today.get(Calendar.DAY_OF_MONTH);

        int days;

        if (pastDays > todayDays) {
            months--;
        }
        if (months < 0) {
            years--;
            months = monthsInYear + months;
        }

        Calendar work = (Calendar)past.clone();
        work.add(Calendar.YEAR, years);
        work.add(Calendar.MONTH, months);
        days = (int)TimeUnit.DAYS.convert(today.getTimeInMillis() - work.getTimeInMillis(),
                                                 TimeUnit.MILLISECONDS);
        System.out.println("stag: " + days + " days " + months + " month " + years + " years");
    }


Нужно провести тест для проверки возникновения различий.
Разница точно возникла, но мое время истекло. Вернусь - проверю что правильно.


--------------------
Mirkes
PM MAIL   Вверх
Mirkes
Дата 11.1.2012, 13:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 586
Регистрация: 18.8.2011
Где: Красноярск

Репутация: 7
Всего: 17



Принял экзамен, теперь можно сдавать экзамен на владение датами.
В ходе ряда экспериментов установил, что пользоваться функцией TimeUnit.DAYS.convert для вычисления числа дней надо предельно акуратно, поскольку она имеет дурную привычку учитывать переоз на зимнее/летнее время, даже если его не было. Или перевод времени можно отключить?
Сравнение  FastDiff Dummy, и моего stag в следующей редакции:
Код

    public static void stag(Calendar past, Calendar today) {
        int years = today.get(Calendar.YEAR) - past.get(Calendar.YEAR);

        int monthsInYear = past.getMaximum(Calendar.MONTH) + 1;

        int pastMonth = past.get(Calendar.MONTH);
        int todayMonth = today.get(Calendar.MONTH);

        int months;

        if (pastMonth > todayMonth) {
            years--;
            months = monthsInYear - pastMonth + todayMonth;
        } else {
            months = todayMonth - pastMonth;
        }

        int pastDays = past.get(Calendar.DAY_OF_MONTH);
        int todayDays = today.get(Calendar.DAY_OF_MONTH);

        int days;

        if (pastDays > todayDays) {
            months--;
        }
        if (months < 0) {
            years--;
            months = monthsInYear + months;
        }
        Calendar work = (Calendar)past.clone();
        work.add(Calendar.YEAR, years);
        work.add(Calendar.MONTH, months);
        days = (int)TimeUnit.DAYS.convert(today.getTimeInMillis() - work.getTimeInMillis()+3600000, TimeUnit.MILLISECONDS);
        System.out.println("stag:" + days + " days " + months + " month " + years + " years");
    }

Сравнение производилось следующим способом.
Начальная дата выбиралась и задавалась вручную.
В качестве конечной даты рассматривались все даты начиная с 11.01.2012 в течении 370 дней - чуть больше года.
Все совпало при дате начала 01.08.2008.
Различия установлены в следующих случаях:
стартовая дата 02.08.2008
отличия в случаях (плюс по одному случаю совпадения)
Дата конца              FastDiff Dummy            Мой stag
29.02.2012    27 days 6 month 3 years    27 days 6 month 3 years
01.03.2012    30 days 6 month 3 years    28 days 6 month 3 years
02.03.2012    0 days 7 month 3 years    0 days 7 month 3 years

30.04.2012    28 days 8 month 3 years    28 days 8 month 3 years
01.05.2012    30 days 8 month 3 years    29 days 8 month 3 years
02.05.2012    0 days 9 month 3 years    0 days 9 month 3 years

Дальше видимо можно не продолжать.
Вопрос о том, что есть правильно, конечно спорный, но вывод моего метода мне гораздо приятней.
Если это нужно, то описание расчетов разницы дат можно добавить в статью по работе с датами в Java в FAQ


--------------------
Mirkes
PM MAIL   Вверх
Dummy
Дата 11.1.2012, 14:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 263
Регистрация: 21.5.2007

Репутация: 9
Всего: 19



Цитата(Mirkes @  11.1.2012,  03:22 Найти цитируемый пост)
По поводу эффективности алгоритмов - я уже написал, что алгоритм не оптимизирован.

И в изначальном варианте не мог быть оптимизирован. Кроме того, выдавал отрицательное количество месяцев на некоторых данных.

Цитата(Mirkes @  11.1.2012,  13:29 Найти цитируемый пост)
Вопрос о том, что есть правильно, конечно спорный, но вывод моего метода мне гораздо приятней.

Cогласен smile Ваш второй метод выдает более разумный результат, чем мой, т. к. логичнее считать количество дней по последнему не полностью доработанному месяцу.


PM MAIL   Вверх
Страницы: (3) Все [1] 2 3 
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Java"
LSD   AntonSaburov
powerOn   tux
javastic
  • Прежде, чем задать вопрос, прочтите это!
  • Книги по Java собираются здесь.
  • Документация и ресурсы по Java находятся здесь.
  • Используйте теги [code=java][/code] для подсветки кода. Используйтe чекбокс "транслит", если у Вас нет русских шрифтов.
  • Помечайте свой вопрос как решённый, если на него получен ответ. Ссылка "Пометить как решённый" находится над первым постом.
  • Действия модераторов можно обсудить здесь.
  • FAQ раздела лежит здесь.

Если Вам помогли, и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, LSD, AntonSaburov, powerOn, tux, javastic.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Java: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1448 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.