Class Timestamp
A thin wrapper around java.util.Date that allows the JDBC API to identify this as an SQL TIMESTAMP value. It adds the ability to hold the SQL TIMESTAMP fractional seconds value, by allowing the specification of fractional seconds to a precision of nanoseconds. A Timestamp also provides formatting and parsing operations to support the JDBC escape syntax for timestamp values.
- 19 , which is the number of characters in yyyy-mm-dd hh:mm:ss
- 20 + s , which is the number of characters in the yyyy-mm-dd hh:mm:ss.[fff. ] and s represents the scale of the given Timestamp, its fractional seconds precision.
Note: This type is a composite of a java.util.Date and a separate nanoseconds value. Only integral seconds are stored in the java.util.Date component. The fractional seconds — the nanos — are separate. The Timestamp.equals(Object) method never returns true when passed an object that isn’t an instance of java.sql.Timestamp , because the nanos component of a date is unknown. As a result, the Timestamp.equals(Object) method is not symmetric with respect to the java.util.Date.equals(Object) method. Also, the hashCode method uses the underlying java.util.Date implementation and therefore does not include nanos in its computation.
Due to the differences between the Timestamp class and the java.util.Date class mentioned above, it is recommended that code not view Timestamp values generically as an instance of java.util.Date . The inheritance relationship between Timestamp and java.util.Date really denotes implementation inheritance, and not type inheritance.
Работа со временем
Со времен, когда придумали JDBC и стандартизировали его интерфейсы, прошло лет 20, и за это время много чего изменилось.
Во-первых, мир стал глобальным и теперь один сервер может обслуживать пользователей со всего мира. Скорость интернета порешала. Поэтому в SQL был добавлен еще один тип данных для работы со временем. Теперь типы выглядят вот так:
- DATE – хранит дату: год, месяц, день.
- TIME – хранит время: часы, минуты, секунды.
- TIMESTAMP – хранит конкретный момент времени: дата, время и миллисекунды.
- TIMESTAMP WITH TIME ZONE – TIMESTAMP и временная зона (имя зоны или смещение).
Во-вторых, в Java появился DateTime API для глобальной работы со временем. В нем появились такие классы:
- Дата и время:
- LocalDate
- LocalTime
- java.time.Instant
- java.time.LocalDateTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
Третий интересный момент состоит в том, что очень многим SQL-клиентам хотелось бы получать время с сервера уже в своей локальное зоне . Преобразовывать время на лету конечно можно, но это не удобно, да и ошибки бывают.
Например, я хочу получить из базы все задания на сегодня. У SQL-сервера есть функция CURDATE() для этого дела. Только вот сервер находится в США, а я – в Японии. И хотелось бы чтобы он мне вернул все записи за “мое сегодня”, а не “его сегодня”.
В общем, SQL-сервер тоже должен уметь по-умному работать с клиентами в разных временных зонах.
Современные проблемы требуют современных решений
В принципе, новые типы из Java DateTime API и типы из SQL можно удобно сопоставлять. Для того чтобы представить тип DATE в Java нужно использовать класс java.time.LocalDate из JDK 8 DateTime API.
Тип TIME из базы данных можно представить двумя типами из Java: java.time.LocalTime и java.time.OffsetTime . Тоже ничего сложного.
Конкретный момент времени, представленный типом TIMESTAMP в базе, в Java можно представить 4 типами:
- java.time.Instant
- java.time.LocalDateTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
Ну, и наконец, TIMESTAMP WITH TIME ZONE можно представить двумя типами:
Так как ты уже знаком с DateTime API, то запомнить это дело тебе труда не составит 🙂
Запишу в виде таблицы, так будет проще:
SQL TYPE Java Type DATE java.time.LocalDate TIME java.time.LocalTime
java.time.OffsetTimeTIMESTAMP java.time.Instant
java.time.LocalDateTime
java.time.OffsetDateTime
java.time.ZonedDateTimeTIMESTAMP WITH TIME ZONE java.time.OffsetDateTime
java.time.ZonedDateTimeПолучение даты
У меня для тебя хорошая новость. Первая за долгое время. Мы можем обойти ограничение метода getDate() , который возвращает тип java.sql Date.
Дело в том, что у объекта ResultSet есть еще один интересный метод — getObject() . Этот метод принимает два параметра: колонку и тип, и возвращает значение колонки, преобразованное к заданному типу. Общий вид метода такой:
ИмяКласса имя = getObject(column, ИмяКласса);
И если вы хотите преобразовать тип DATE к типу java.time.LocalDate , то нужно написать что-то типа:
LocalDate localDate = results.getObject(4, LocalDate.class);
А любой TIMESTAMP вообще можно преобразовать к куче типов:
java.time.Instant instant = results.getObject(9, java.time.Instant.class); java.time.LocalDateTime local = results.getObject(9, java.time. LocalDateTime.class); java.time.OffsetDateTime offset = results.getObject(9, java.time. OffsetDateTime.class); java.time.ZonedDateTime zoned = results.getObject(9, java.time. ZonedDateTime.class);
Важно! Этот код не будет работать если у вас устаревший MySQL JDBC Driver . Обрати внимание на версию “mysql-connector-java”, прописанную в твоем pom.xml, или добавленную в Libraries в настройках проекта.
Кстати, таким же способом можно обойти и невозможность хранить null у примитивных типов. Если колонка таблицы имеет тип INT, то есть пара способов получить из нее null. Смотри пример ниже:
Integer id1 = results.getObject(8, Integer.class); //так будет работать Integer id2 = results.getObject(8, int.class); //так тоже будет работать int id3 = results.getObject(8, Integer.class); //метод вернет null, JVM кинет NPE int id4 = results.getObject(8, int.class); //метод вернет null, JVM кинет NPE
Настройка часового пояса в MySQL
С MySQL тоже произошло много всего интересного. Как ты знаешь, при создании соединения с MySQL к нему можно добавлять различные параметры:
mysql://localhost:3306/db_scheme?имя=значение&имя=значение
Так вот, для работы с временными зонами в MySQL добавили три параметра. Эти параметры ты можешь передавать, когда устанавливаешь соединение с сервером.
Ниже я приведу таблицу с ними:
Параметр Значения Значение по умолчанию connectionTimeZone LOCAL | SERVER | user-zone SERVER forceConnectionTimeZoneToSession true | false true preserveInstants true | false false С помощью параметра connectionTimeZone мы выбираем временную зону (часовой пояс), в котором будут выполняться все запросы. С точки зрения клиента, сервер работает в указанной временной зоне.
Параметр forceConnectionTimeZoneToSession заставляет игнорировать переменную time_zone сессии и заменить ее на connectionTimeZone.
И наконец параметр preserveInstants управляет преобразование точного-момента-времени между JVM timeZone и connectionTimeZone.
Самые частые конфигурации такие:
- connectionTimeZone=LOCAL & forceConnectionTimeZoneToSession=false — соответствует старому MySQL JDBC драйверу версии 5.1 с параметром useLegacyDatetimeCode=true.
- connectionTimeZone=LOCAL & forceConnectionTimeZoneToSession=true — новый режим, обеспечивающий наиболее естественный способ обработки значений даты и времени.
- connectionTimeZone=SERVER & preserveInstants=true — соответствует старому MySQL JDBC драйверу версии 5.1 с параметром useLegacyDatetimeCode=false.
- connectionTimeZone=user_defined & preserveInstants=true — помогает преодолеть ситуацию, когда часовой пояс сервера не может быть распознан коннектором, потому что он установлен как общая аббревиатура, такая как CET/CEST.
Да, даты — тема интересная и проблем с ними много. Как говорится: это конечно страшно, но и я не сыкло! 🙂
Display Localized Timestamp in User’s Timezone
For any application supporting multiple locales and timezones, it is often the requirement to show the date and timestamp adjusted to the user’s local timezone offset. In this tutorial, we will learn to display the date and time in the user timezone.
To display localized timestamps, we have primarily two options:
- Modify the date-time on the server side and return the string to render directly on the client screen
- Return the GMT date to client and let client handle the timezone adjustment and display on the screen
Above both options are easy to implement. The only differences are design and usability challenges that vary from case to case.
2. Adjust the Timezone on Server Side
Timezone adjustment on the server-side can be done for the applications, that are served globally to all users, to display the server output as it is to the screen. In such cases, clients generally do not process the server output and are merely used for displaying the information sent by the server.
Note that HttpRequest object does not support the timezone information directly, and so many websites ask the user’s timezone at the time of the registration process.
//Suppose this is UTC timestamp fetched from database ZonedDateTime instant = ZonedDateTime.now(ZoneId.of("UTC")); ZonedDateTime instantInUTC = instant.withZoneSameInstant(ZoneId.of("Asia/Kolkata")); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMM, yyyy 'at' HH:mm a"); //Send this to the client for displaying on screen String formattedZdt = instantInUTC.format(formatter); System.out.println(formattedZdt); //21 Feb, 2022 at 22:03 pm
3. Adjust the Timezone on Client Side
This is the recommended and most used approach as it does not require the sniffing of the user’s timezone on the server-side. The server sends the UTC date time to all the users across the globe and a client-side script changes the UTC time to local time.
Getting the timezone of the user on the client-side is a rather easy approach and most browsers support query timezone information, including the native javascript.
The getTimezoneOffset() returns the time difference, in minutes, between UTC time and local time. The returned value is positive if the local time zone is behind UTC and negative if the local time zone is ahead of UTC.
For example, if your time zone is UTC+5, the getTimezoneOffset() method will return -300 minutes:
var date = new Date(); var offset = date.getTimezoneOffset(); // -300
Remember that the getTimezoneOffset() method only gives the local time zone offset from UTC time and not the actual time zone.
The value of returned offset varies according to the Daylight Saving Time (DST) rules. We should not try to find the timezone using the offset because the calculation is not straightforward and may give incorrect results.
We should just use the above offset to find the local date and time from the server response containing UTC timestamps.
If you really with to get the timezone information then use the Intl.DateTimeFormat object is available in all modern browsers.
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; console.log(timezone); // Asia/Kolkata
In this tutorial, we learned different options to show the date and time to the users according to their timezone and offset rules. Though Java supports numerous ways to convert and format the timestamps from one timezone to another, there is no easy way to get the timezone of the user from an HTTP request.
For this reason, sending UTC timestamp to the client is recommended approach where clients are free to choose the display format of the information as well.