Вычисление проблем с неделей года с помощью ZonedDateTime и API календаря

Я пытался вычислить неделю года из ввода строки формата даты ISO-8601. Сначала я пробовал это с java.time.ZonedDateTime, но это дает неверный результат для даты ввода - 2 января 2049 года. Затем я попробовал использовать API календаря, он также дает неверный ответ на 31 декабря 2049 года.

Я прикрепил пример тестового кода

public class ZonedDateTimeTest {        
    public static void main(String[] args) throws InterruptedException {
        System.out.println("======================================");
        String instantStr1 = "2049-01-02T03:48:00Z";
        printYearAndWeekOfYear(instantStr1);
        System.out.println("======================================");
        String instantStr2 = "2049-12-31T03:48:00Z";
        printYearAndWeekOfYear(instantStr2);
        System.out.println("======================================");
    }

    public static void printYearAndWeekOfYear(String ISODate) {
        System.out.println("Date provided -> " + ISODate);

        ZonedDateTime utcTimestamp = parseToInstant(ISODate).atZone(ZoneOffset.UTC);
        int year = utcTimestamp.getYear();
        int weekOfYear = utcTimestamp.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
        System.out.println("Using ZonedDateTime API:: Year " + year + " weekOfYear " + weekOfYear);


        Date d1 = Date.from(parseToInstant(ISODate));
        Calendar cl = Calendar.getInstance();
        cl.setTime(d1);
        int year1 = cl.get(Calendar.YEAR);
        int weekOfYear1 = cl.get(Calendar.WEEK_OF_YEAR);
        System.out.println("Using Calendar API:: Year " + year1 + " weekOfYear " + weekOfYear1);
    }

    public static Instant parseToInstant(String ISODate) {
        return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(ISODate, Instant::from);
    }
}

Вывод из кода выше

======================================
Date provided  2049-01-02T03:48:00Z
Using ZonedDateTime API: Year 2049 weekOfYear 53
Using Calendar API: Year 2049 weekOfYear 1    
======================================    
Date provided 2049-12-31T03:48:00Z
Using ZonedDateTime API: Year 2049 weekOfYear 52
Using Calendar API: Year 2049 weekOfYear 1
======================================

person kanchan    schedule 16.01.2017    source источник
comment
Пожалуйста, поместите этот комментарий в вопрос - логически это часть вопроса. Также жаль, что вы нигде не указали часовой пояс в части Calendar, что может легко повлиять на результаты.   -  person Jon Skeet    schedule 16.01.2017
comment
Пожалуйста, перестаньте нарушать форматирование. Я уже дважды исправлял форматирование вашего кода, и оба раза вы его отменяли. Затем я переформатировал результаты, чтобы их было легче читать, и снова вы это отменили.   -  person Jon Skeet    schedule 16.01.2017


Ответы (2)


Для начала в вашем коде есть четыре проблемы:

  • Вы используете системный часовой пояс по умолчанию, когда используете Calendar, что вполне может изменить дату, на которую приходится Instant. Если вы настроите календарь на использование UTC, вы сделаете его более согласованным.
  • Вы используете Calendar.YEAR, что даст вам календарный год, а не недельный год. Вместо этого вам нужно использовать Calendar.getWeekYear().
  • Вы используете ZonedDateTime.getYear(), что снова является календарным годом. Вы должны использовать utcTimestamp.get(IsoFields.WEEK_BASED_YEAR)
  • Вы используете Calendar.getInstance(), который может дать вам негригорианский календарь, или первый день недели может быть установлен неподходящим образом для вычислений, которые вы хотите выполнить.

Исправляя эти проблемы (и соглашения об именах), мы получаем:

import java.util.*;
import java.time.*;
import java.time.format.*;
import java.time.chrono.*;
import java.time.temporal.*;

public class ZonedDateTimeTest {

    public static void main(String[] args) {
        printYearAndWeekOfYear("2049-01-02T03:48:00Z");
        String instantStr2 = "2049-12-31T03:48:00Z";
        printYearAndWeekOfYear("2049-12-31T03:48:00Z");
    }

    public static void printYearAndWeekOfYear(String isoDate) {
        System.out.println("Date provided -> " + isoDate);

        Instant instant = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(isoDate, Instant::from);
        ZonedDateTime utcTimestamp = instant.atZone(ZoneOffset.UTC);
        int year = utcTimestamp.get(IsoFields.WEEK_BASED_YEAR);
        int weekOfYear = utcTimestamp.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
        System.out.println("ZonedDateTime: Year " + year + " weekOfYear " + weekOfYear);

        // Force the Gregorian calendar with ISO rules and using UTC
        Calendar calendar = new GregorianCalendar();
        calendar.setFirstDayOfWeek(Calendar.MONDAY);
        calendar.setMinimalDaysInFirstWeek(4);
        calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
        calendar.setTime(Date.from(instant));

        int calYear = calendar.getWeekYear();
        int calWeekOfYear = calendar.get(Calendar.WEEK_OF_YEAR);
        System.out.println("Calendar: Year " + calYear + " weekOfYear " + calWeekOfYear);
        System.out.println();
    }
}

Выход:

Date provided -> 2049-01-02T03:48:00Z
ZonedDateTime: Year 2048 weekOfYear 53
Calendar: Year 2048 weekOfYear 53

Date provided -> 2049-12-31T03:48:00Z
ZonedDateTime: Year 2049 weekOfYear 52
Calendar: Year 2049 weekOfYear 52

Оба они выглядят хорошо для меня.

person Jon Skeet    schedule 16.01.2017
comment
Мне интересно узнать расчеты weekOfYear. И ZonedDateTime, и Calendar дают возможный неверный ответ. - person kanchan; 16.01.2017
comment
@kanchan: Нет, не знают. См. мое редактирование - они оба дают правильный результат ISO за неделю-неделю-год, но Calendar не позволяет вам получить неделю-год. - person Jon Skeet; 16.01.2017
comment
Класс GregorianCalendar был обновлен в Java-7, чтобы обеспечить эквивалентное решение проблемы, см. мой ответ. - person Meno Hochschild; 16.01.2017
comment
@MenoHochschild: Ах, действительно, я не заметил getWeekYear() в Calendar, так как привык указывать номер поля для get... - person Jon Skeet; 16.01.2017

Старый материал Calendar действительно позволяет найти решение, начиная с Java-7, поэтому я показываю его как дополнение к ответу Джона Скита, связанному с Java-8:

String instantStr1 = "2049-01-02T03:48:00Z";
String instantStr2 = "2049-12-31T03:48:00Z";

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
Date d1 = sdf.parse(instantStr1);
Date d2 = sdf.parse(instantStr2);

GregorianCalendar gcal = new GregorianCalendar();
gcal.setFirstDayOfWeek(Calendar.MONDAY);
gcal.setMinimalDaysInFirstWeek(4);

gcal.setTime(d1);
System.out.println(
    "Using Calendar API: Year " + gcal.getWeekYear() + " weekOfYear "
    + gcal.get(Calendar.WEEK_OF_YEAR)
); // Using Calendar API: Year 2048 weekOfYear 53

gcal.setTime(d2);
System.out.println(
    "Using Calendar API: Year " + gcal.getWeekYear() + " weekOfYear "
    + gcal.get(Calendar.WEEK_OF_YEAR)
); // Using Calendar API: Year 2049 weekOfYear 52

Для пользователей Android, для которых этот API является стандартным: метод getWeekYear( ) доступен, начиная с уровня API 24.

person Meno Hochschild    schedule 16.01.2017
comment
Большое спасибо Meno Hochschild и Jon Skeet за вашу помощь. - person kanchan; 17.01.2017