Григорианский календарь неправильный час - PullRequest
0 голосов
/ 02 ноября 2018

Итак, я начал немного тестировать с классом Java-GregorianCalendar и заметил странное поведение, возникающее при инициализации объекта за миллисекунды. Меня беспокоило то, что, хотя я установил миллисекунды на 0, время показывало 1 час.

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

Вот код, с которым я тестировал:

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class TestCalendar {

    public static void main(String[] args) {
        GregorianCalendar c = new GregorianCalendar();
        c.setTimeInMillis(-3600000);
        System.out.println(c.getTimeInMillis());
        System.out.println(c.get(Calendar.HOUR));
        String format = "mm:ss";
        if (c.get(Calendar.HOUR_OF_DAY) > 0) format = "HH:mm:ss";
        System.out.println(new SimpleDateFormat(format).format(c.getTime()));
    }

}

Это дало мне вывод

-3600000
0
0

Лучше всего было бы найти решение, которое не зависит от вычитания -3600000, как если бы на других компьютерах такой "ошибки" не существовало. Я не хочу иметь 23:00:00:)

EDIT:

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

c.setTimeZone(TimeZone.getTimeZone("GMT"));

Ответы [ 2 ]

0 голосов
/ 02 ноября 2018

ТЛ; др

Проблема: Вы ошибочно меняете дату, а не только время суток. Кроме того, часовой пояс неявно применяется.

Решение: Вместо этого используйте современные java.time классы.

LocalDate
.now()                 // Better to explicitly pass the desired/expected time zone as a `ZoneId` object.
.atStartOfDay()        // Again, better to explicitly pass the desired/expected time zone as a `ZoneId` object.

Возвращает LocalDateTime ( ВНИМАНИЕ: Не мгновение, не точка на временной шкале).

2018-11-01T00: 00

Намного лучше указать часовой пояс.

LocalDate
.now( 
    ZoneId.of( "Pacific/Auckland"  ) 
)                
.atStartOfDay(
    ZoneId.of( "Pacific/Auckland"  ) 
)   

Возвращает ZonedDateTime. Это является моментом, является точкой на временной шкале.

2018-11-02T00: 00 + 13: 00 [Pacific / Auckland]

GregorianCalendar::setTimeInMillis это не установка времени суток

Очевидно, вы ошибочно полагали, что GregorianCalendar::setTimeInMillis установит время суток, не влияя на дату. Среди многих недостатков унаследованных классов даты и времени есть несколько очень плохих вариантов именования классов и методов.

Но, нет, этот метод переопределяет момент как количество миллисекунд с эталонной даты 1970-01-01T00: 00Z.

Добавьте часовой пояс, неявно присвоенный GregorianCalendar, и вы получите неожиданные результаты.

Я начал немного тестировать с Java-GregorianCalendar

Не.

Эти старые классы даты и времени, включенные в ранние версии Java, ужасны . Они были вытеснены несколько лет назад классами java.time , определенными в JSR 310 .

В частности, чтобы отслеживать момент в UTC, используйте Instant.

инициализация объекта в миллисекундах

Не.

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

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

При обмене значениями даты и времени вместо этого используйте строки в стандартном формате ISO 8601.

Когда передается отсчет миллисекунд от эпохи Unix первого момента 1970 года в UTC, анализируется как Instant.

Instant instant = Instant.ofEpochMilli( … ) ;

java.time

Современное решение использует java.time классы вместо.

Получите ваше свидание.

LocalDate

Класс LocalDate представляет значение только для даты без времени суток и без часовой пояс или смещение от UTC .

Часовой пояс имеет решающее значение при определении даты. В любой момент времени дата меняется по всему земному шару в зависимости от зоны. Например, через несколько минут после полуночи в Париж Франция - это новый день, а еще «вчера» в Монреаль Квебек .

Если часовой пояс не указан, JVM неявно применяет свой текущий часовой пояс по умолчанию. Это значение по умолчанию может измениться в любой момент во время выполнения (!), Поэтому ваши результаты могут отличаться. Лучше указать в качестве аргумента ваш желаемый / ожидаемый часовой пояс .

Укажите собственное имя часового пояса в формате continent/region, например America/Montreal, Africa/Casablanca или Pacific/Auckland. Никогда не используйте 2-4-буквенное сокращение, такое как EST или IST, поскольку они не истинные часовые пояса, не стандартизированы и даже не уникальны (!).

ZoneId z = ZoneId.of( "America/Montreal" ) ;  
LocalDate today = LocalDate.now( z ) ;

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

ZoneId z = ZoneId.systemDefault() ;  // Get JVM’s current default time zone.

Или укажите дату. Вы можете установить месяц по номеру, с нормальным номером 1-12 для января-декабря.

LocalDate ld = LocalDate.of( 1986 , 2 , 23 ) ;  // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.

Или, беттну, используйте предварительно определенные объекты перечисления Month, по одному на каждый месяц года. Совет: используйте эти Month объекты по всей вашей кодовой базе, а не просто целое число, чтобы сделать ваш код более самодокументируемым, обеспечить допустимые значения и обеспечить безопасность типов .

LocalDate ld = LocalDate.of( 1986 , Month.FEBRUARY , 23 ) ;

ZonedDateTime

Видимо, вы хотите первый момент дня. Кстати, не думайте об этом как о «полуночи», так как этот термин неоднозначен.

Первый момент дня не может быть 00:00. Аномалии, такие как переход на летнее время (DST), означают, что первым моментом некоторых дат в некоторых зонах может быть другой момент, например 01:00. Пусть java.time определит первый момент.

Укажите часовой пояс.

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = localDate.atStartOfDay( z ) ;

Если вы хотите увидеть тот же момент в UTC, извлеките Instant.

Instant instant = zdt.toInstant() ;

О java.time

Фреймворк java.time встроен в Java 8 и более поздние версии. Эти классы вытесняют проблемные старые устаревшие классы даты и времени, такие как java.util.Date, Calendar, & SimpleDateFormat.

Проект Joda-Time , теперь в режиме обслуживания , рекомендует перейти на классы java.time .

Чтобы узнать больше, см. Oracle Tutorial . И поиск переполнения стека для многих примеров и объяснений. Спецификация JSR 310 .

Вы можете обмениваться java.time объектами непосредственно с вашей базой данных. Используйте драйвер JDBC , совместимый с JDBC 4.2 или более поздней версии. Нет необходимости в строках, нет необходимости в java.sql.* классах.

Где получить классы java.time?

Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является полигоном для возможных будущих дополнений к java.time. Здесь вы можете найти несколько полезных классов, таких как Interval, YearWeek, YearQuarter и more .

0 голосов
/ 02 ноября 2018

Настройка c.setTimeInMillis (0); устанавливает время на 1 января 1970 года в 00:00:00 по Гринвичу (1970-01-01 00:00:00 по Гринвичу), которое называется эпохой

https://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html#setTimeInMillis(long)

public void setTimeInMillis(long millis)

Sets this Calendar's current time from the given long value.

Parameters:
    millis - the new time in UTC milliseconds from the epoch.
See Also:
    setTime(Date), getTimeInMillis()

Если вы хотите установить время на полночь, я думаю, что вы хотите сделать.

    c.set(Calendar.HOUR_OF_DAY, 0);
    c.set(Calendar.MINUTE, 0);
    c.set(Calendar.SECOND, 0);
    c.set(Calendar.MILLISECOND, 0);
...