Как я могу проверить с помощью joda-time, это первая, вторая или последняя неделя месяца по заданной дате?

Например:

  • 5 February 2016 - первая неделя,
  • 12 February 2016- вторая неделя,
  • 28 February 2016- на прошлой неделе

person Anatoly    schedule 04.02.2016    source источник
comment
Это помечено java-time. Возможно, вы также ищете решение для Java Time? (новый API времени Java 8)?   -  person Tunaki    schedule 05.02.2016
comment
На самом деле нет, я ищу решение joda-time. Просто подумал, что это может быть сходство между API и решением для Java-Time, подходящим и для Joda-Time.   -  person Anatoly    schedule 05.02.2016
comment
Боюсь, Joda-Time не поддерживает неделю месяца из коробки... Придется придумывать хитрую реализацию. Однако он включен в Java Time.   -  person Tunaki    schedule 05.02.2016
comment
@Tunaki Я боюсь, что это даже невозможно с Java 8 (JSR-310) без написания собственного сложного настройщика или поля из-за того, что weekOfMonth также определяет нулевую неделю, что, вероятно, не то, что хочет OP. Месяц, эквивалентный weekOfWeekBasedYear(), отсутствует в java.time.   -  person Meno Hochschild    schedule 05.02.2016
comment
@MenoHochschild Я заметил, что использовалась неделя 0, да. Это действительно зависит от того, какое определение недели месяца хочет иметь ОП. ISO-8601, который имеет Java Time, говорит, что первая неделя имеет минимум 4 дня...   -  person Tunaki    schedule 05.02.2016


Ответы (2)


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

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

Поскольку начало недели в понедельник не обязательно совпадает с первым днем ​​месяца, неделя может начинаться в предыдущем месяце или относиться к следующему месяцу.

Классы JDK SimpleDateFormat с символом шаблона поля w (а также новое поле JSR-310 WeekFields.weekOfMonth()) используют следующую стратегию:

Если первый день месяца приходится на период с понедельника по четверг, то соответствующая неделя имеет не менее 4 дней в текущем месяце и будет считаться неделей 1, в противном случае - неделей 0 (нулем). Последовательно последний день месяца всегда будет использовать возрастающее число, даже если он относится к первой неделе следующего месяца.

В отличие от этого определения, спецификация шаблона даты и времени CLDR и ISO-8601 почти ничего не говорят о деталях в контексте недели месяца. Однако эти стандарты не умалчивают о неделе года, когда они описывают другую стратегию. И CLDR явно говорит о неделе месяца (раздел 8.4 ):

8.4 Неделя года

Значения, рассчитанные для поля «Неделя года», находятся в диапазоне от 1 до 53 для григорианского календаря (они могут иметь другие диапазоны для других календарей). Неделя 1 в году – это первая неделя, содержащая как минимум указанное минимальное количество дней в этом году. Недели между 1-й неделей одного года и 1-й неделей следующего года нумеруются последовательно от 2 до 52 или 53 (при необходимости). Например, 1 января 1998 года был четверг. Если первый день недели — ПОНЕДЕЛЬНИК, а минимальное количество дней в неделе — 4 (это значения, отражающие ISO 8601 и многие национальные стандарты), то первая неделя 1998 года начинается 29 декабря 1997 года и заканчивается 4 января. 1998 г. Однако, если первый день недели - ВОСКРЕСЕНЬЕ, то первая неделя 1998 г. начинается 4 января 1998 г. и заканчивается 10 января 1998 г. Тогда первые три дня 1998 г. являются частью 53-й недели 1997 г.

Значения рассчитываются аналогичным образом для недели месяца.

Разница между обеими стратегиями, примененными на дату 2016-02-29, составит:

  • 5 неделя по JDK
  • 1-я неделя согласно CLDR/ISO

Теперь я представляю решение для Joda-Time.

public static void main(String[] args) throws Throwable {

    System.out.println(getWeekOfMonth(false)); // CLDR/ISO-spec
    // 1 for today=2016-02-05
    // 2 for today=2016-02-12
    // 4 for today=2016-02-28
    // 1 for today=2016-02-29

    System.out.println(getWeekOfMonth(true)); // JDK-behaviour
    // 1 for today=2016-02-05
    // 2 for today=2016-02-12
    // 4 for today=2016-02-28
    // 5 for today=2016-02-29

}

private static int getWeekOfMonth(boolean bounded) {
    int weekOfMonth;
    LocalDate today = LocalDate.now();

    LocalDate first = today.dayOfMonth().withMinimumValue();
    int dowFirst = first.getDayOfWeek();

    if (dowFirst <= DateTimeConstants.THURSDAY) {
        // we are in week 1 and go to Monday as start of week
        first = first.minusDays(dowFirst - DateTimeConstants.MONDAY);

        // first try: we determine the week of current month
        weekOfMonth = Days.daysBetween(first, today).getDays() / 7 + 1;

        if (!bounded) {
            // edge case: are we in first week of next month?
            LocalDate next = first.plusMonths(1);
            int dowNext = next.getDayOfWeek();

            if (dowNext <= DateTimeConstants.THURSDAY) {
                next = next.minusDays(dowNext - DateTimeConstants.MONDAY);
                if (!next.isAfter(today)) {
                    weekOfMonth = 1;
                }
            }
        }
    } else if (bounded) {
        weekOfMonth = 0;
    } else {
        // we are in last week of previous month so let's check the start of previous month
        LocalDate previous = first.minusMonths(1);
        int dowPrevious = previous.getDayOfWeek();

        if (dowPrevious <= DateTimeConstants.THURSDAY) {
            previous = previous.minusDays(dowPrevious - DateTimeConstants.MONDAY);
        } else {
            previous = previous.plusDays(DateTimeConstants.MONDAY - dowPrevious + 7);
        }

        weekOfMonth = Days.daysBetween(previous, today).getDays() / 7 + 1;
    }

    return weekOfMonth;
}

Надеюсь, это не слишком сложно для вас.


Кстати, если вам интересно, как выглядят простые альтернативы, применимые на платформах старше Java-8:

Time4J (моя библиотека)

private static int time4j(boolean bounded) { // supports both definitions
  PlainDate today = SystemClock.inLocalView().today(); // using system timezone
    return today.get(
          (bounded ? Weekmodel.ISO.boundedWeekOfMonth() : Weekmodel.ISO.weekOfMonth()));
}

Threeten-BP (бэкпорт Java-8):

private static int threeten() { // only JDK-definition (code similar to Java-8)
    org.threeten.bp.LocalDate today = org.threeten.bp.LocalDate.now();
    return today.get(WeekFields.ISO.weekOfMonth());
}

Старый JDK:

private static int oldJDK() { // only JDK-definition
    GregorianCalendar gcal = new GregorianCalendar();
    gcal.setMinimalDaysInFirstWeek(4);
    gcal.setFirstDayOfWeek(Calendar.MONDAY);
    return gcal.get(Calendar.WEEK_OF_MONTH);
}

Как видите, с этими альтернативами очень легко изменить базовые недельные модели на случаи, отличные от ISO (например, американские недели). Если вы хотите, чтобы это было в Joda-Time, то я оставляю вам задачу переписать представленное Joda-решение.

Обновление в связи с комментарием ниже по теме:

Итак, все дело в дне недели в месяце. Joda-Time также не поддерживает этот элемент/поле из коробки. Извини. Но вы можете изучить необходимый алгоритм для такого поля используется в других библиотеках.

Демонстрационный пример в Time4J для моделирования правила rfc2445, упомянутого в комментарии:

    PlainDate dtStart = PlainDate.of(2016, Month.FEBRUARY, 4);
    int count = 5;
    Weekday byday = Weekday.FRIDAY; // first
    int interval = 1;
    CalendarUnit frequency = CalendarUnit.MONTHS;

    List<PlainDate> sequence = new ArrayList<>(count);
    PlainDate wim = dtStart;
    for (int i = 0; i < count; i++) {
        wim = wim.with(PlainDate.WEEKDAY_IN_MONTH.setToFirst(byday));
        sequence.add(wim);
        wim = wim.with(PlainDate.DAY_OF_MONTH, 1).plus(interval, frequency);
    }
    if (!sequence.isEmpty() && !sequence.get(0).equals(dtStart)) {
        sequence.remove(0); // Not quite sure - do you need another condition?
    }
    System.out.println(sequence); // [2016-03-04, 2016-04-01, 2016-05-06, 2016-06-03]

В Java-8 также есть поддержка через специализированный корректор, чтобы вы могли легко перенести данный демонстрационный пример на Java-8 с помощью java.time.LocalDate.

person Meno Hochschild    schedule 05.02.2016
comment
Спасибо за ваш подробный ответ. На самом деле этот ответ является продолжением моего предыдущего ответа здесь: stackoverflow.com/questions/35197441/ Мне нужно исключить первый день, определенный DTSTART, если не среди RRULE. В rfc-2445 я могу определить правило повторения как 1st monday, 2nd monday, last monday на 1MO,2MO,-1MO. Чтобы узнать, есть ли DTSTART среди этих понедельников, и оставить его, а если нет, то удалить его, я должен знать, какой день и на какой неделе DTSTART. - person Anatoly; 05.02.2016
comment
@ Анатолий Хм, ты все еще говоришь о неделе месяца или ты имеешь в виду день недели в месяце? Это не совсем то же самое. Ваше правило повторения звучит скорее как SimpleDateFormat-символ F шаблона или специальный JSR-310-adjuster или в Time4J этот интерфейс.. Для Joda-Time такого эквивалента тоже нет, поэтому необходим другой обходной путь. - person Meno Hochschild; 06.02.2016
comment
Я приведу короткий пример. У меня есть следующее RRULE: FREQ=MONTHLY;COUNT=5;BYDAY=1FR;INTERVAL=1; Где DTSTART — 04 февраля 2016 г. Т.е. каждый месяц, первую пятницу месяца следует выбирать 5 раз, даты: 5 февраля 2016 г., 4 марта 2016 г., 1 апреля 2016 г., 6 мая 2016 г., 3 июня 2016 г. Но пользователь выбрал начало этой последовательности 4 февраля 2016 года, что является не 1-й пятницей, а 1-м четвергом. Мне нужно проверить, что это и оставить его или удалить. Я реализую ваши примеры завтра, чтобы увидеть, что мне больше подходит, спасибо. Единственная проблема связана с лицензией LPGL вашей библиотеки, не знаю, смогу ли я ее использовать. - person Anatoly; 06.02.2016
comment
О... извините, но мне действительно нужно DAY_OF_WEEK_IN_MONTH, как описано здесь: docs.oracle.com/javase/7/docs/api/java/util/ Мне стыдно, но я неправильно сформулировал свой вопрос. В любом случае, ваш ответ на мой вопрос, как я его задал, был правильным, и, по крайней мере, я надеюсь, что он кому-то поможет. - person Anatoly; 06.02.2016
comment
@Анатолий Спасибо за разъяснение. Что касается LGPL, меня просто беспокоит ситуация, когда у любого пользователя может быть устаревшая версия, и он может попросить вас установить более новую версию Time4J, встроенную в ваше приложение. Но, конечно, это открытый и бесплатный исходный код. - person Meno Hochschild; 06.02.2016

В jdk 8 вы можете реализовать свои собственные TemporalAdjusters и использовать с LocalDate.with()

person nantitv    schedule 05.02.2016