Browse Source

Migrate to Java 25

Co-Authored-By: Claude <noreply@anthropic.com>
pull/14976/head
Viacheslav Klimov 8 months ago
parent
commit
8f9c4b70d7
  1. 2
      .github/workflows/license-header-format.yml
  2. 10
      application/src/main/data/json/edge/instructions/install/centos/instructions.md
  3. 10
      application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md
  4. 134
      common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java
  5. 36
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
  6. 12
      dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java
  7. 2
      msa/tb/docker-cassandra/Dockerfile
  8. 2
      msa/tb/docker-postgres/Dockerfile
  9. 4
      packaging/java/build.gradle
  10. 8
      packaging/java/scripts/windows/install.bat
  11. 2
      packaging/js/build.gradle
  12. 20
      pom.xml
  13. 81
      tbel-date-note.md

2
.github/workflows/license-header-format.yml

@ -35,7 +35,7 @@ jobs:
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: 'corretto' # https://github.com/actions/setup-java?tab=readme-ov-file#supported-distributions distribution: 'corretto' # https://github.com/actions/setup-java?tab=readme-ov-file#supported-distributions
java-version: '21' java-version: '25'
cache: 'maven' # https://github.com/actions/setup-java?tab=readme-ov-file#caching-sbt-dependencies cache: 'maven' # https://github.com/actions/setup-java?tab=readme-ov-file#caching-sbt-dependencies
- name: License header format - name: License header format

10
application/src/main/data/json/edge/instructions/install/centos/instructions.md

@ -8,15 +8,15 @@ sudo yum install -y nano wget && sudo yum install -y https://dl.fedoraproject.or
{:copy-code} {:copy-code}
``` ```
#### Step 1. Install Java 17 (OpenJDK) #### Step 1. Install Java 25 (OpenJDK)
ThingsBoard service is running on Java 17. To install OpenJDK 17, follow these instructions: ThingsBoard service is running on Java 25. To install OpenJDK 25, follow these instructions:
```bash ```bash
sudo dnf install java-17-openjdk sudo dnf install java-25-openjdk
{:copy-code} {:copy-code}
``` ```
Configure your operating system to use OpenJDK 17 by default. You can configure the default version by running the following command: Configure your operating system to use OpenJDK 25 by default. You can configure the default version by running the following command:
```bash ```bash
sudo update-alternatives --config java sudo update-alternatives --config java
@ -33,7 +33,7 @@ java -version
The expected result is: The expected result is:
```text ```text
openjdk version "17.x.xx" openjdk version "25.x.xx"
OpenJDK Runtime Environment (...) OpenJDK Runtime Environment (...)
OpenJDK 64-Bit Server VM (build ...) OpenJDK 64-Bit Server VM (build ...)
``` ```

10
application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md

@ -1,14 +1,14 @@
Here is the list of commands that can be used to quickly install ThingsBoard Edge on Ubuntu Server and connect to the server. Here is the list of commands that can be used to quickly install ThingsBoard Edge on Ubuntu Server and connect to the server.
#### Step 1. Install Java 17 (OpenJDK) #### Step 1. Install Java 25 (OpenJDK)
ThingsBoard service is running on Java 17. To install OpenJDK 17, follow these instructions: ThingsBoard service is running on Java 25. To install OpenJDK 25, follow these instructions:
```bash ```bash
sudo apt update && sudo apt install openjdk-17-jdk sudo apt update && sudo apt install openjdk-25-jdk
{:copy-code} {:copy-code}
``` ```
Configure your operating system to use OpenJDK 17 by default. You can configure the default version by running the following command: Configure your operating system to use OpenJDK 25 by default. You can configure the default version by running the following command:
```bash ```bash
sudo update-alternatives --config java sudo update-alternatives --config java
@ -25,7 +25,7 @@ java -version
The expected result is: The expected result is:
```text ```text
openjdk version "17.x.xx" openjdk version "25.x.xx"
OpenJDK Runtime Environment (...) OpenJDK Runtime Environment (...)
OpenJDK 64-Bit Server VM (build ...) OpenJDK 64-Bit Server VM (build ...)
``` ```

134
common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbDateTest.java

@ -209,7 +209,9 @@ class TbDateTest {
.put("timeZone", "America/New_York") .put("timeZone", "America/New_York")
.put("dateStyle", "full") .put("dateStyle", "full")
.toString())); .toString()));
Assertions.assertEquals("середа, 6 вересня 2023 р.", d.toLocaleDateString("uk-UA", JacksonUtil.newObjectNode() // Java 21+ uses narrow no-break space (U+202F) before "р." per CLDR 42+
String expectedDate_ukUA = Runtime.version().feature() >= 21 ? "середа, 6 вересня 2023\u202Fр." : "середа, 6 вересня 2023 р.";
Assertions.assertEquals(expectedDate_ukUA, d.toLocaleDateString("uk-UA", JacksonUtil.newObjectNode()
.put("timeZone", "Europe/Kiev") .put("timeZone", "Europe/Kiev")
.put("dateStyle", "full") .put("dateStyle", "full")
.toString())); .toString()));
@ -244,12 +246,18 @@ class TbDateTest {
Assertions.assertNotNull(d.toLocaleTimeString()); Assertions.assertNotNull(d.toLocaleTimeString());
Assertions.assertNotNull(d.toLocaleTimeString("en-US")); Assertions.assertNotNull(d.toLocaleTimeString("en-US"));
Assertions.assertEquals("9:04:05 PM", d.toLocaleTimeString("en-US", "America/New_York")); // Java 21+ uses narrow no-break space (U+202F) before AM/PM per CLDR 42+
String expectedTime_enUS = Runtime.version().feature() >= 21 ? "9:04:05\u202FPM" : "9:04:05 PM";
Assertions.assertEquals(expectedTime_enUS, d.toLocaleTimeString("en-US", "America/New_York"));
Assertions.assertEquals("오후 9:04:05", d.toLocaleTimeString("ko-KR", "America/New_York")); Assertions.assertEquals("오후 9:04:05", d.toLocaleTimeString("ko-KR", "America/New_York"));
Assertions.assertEquals("04:04:05", d.toLocaleTimeString( "uk-UA", "Europe/Kiev")); Assertions.assertEquals("04:04:05", d.toLocaleTimeString( "uk-UA", "Europe/Kiev"));
Assertions.assertEquals("9:04:05 م", d.toLocaleTimeString( "ar-EG", "America/New_York")); Assertions.assertEquals("9:04:05 م", d.toLocaleTimeString( "ar-EG", "America/New_York"));
Assertions.assertEquals("9:04:05 PM Eastern Daylight Time", d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode() // Java 21+ uses narrow no-break space (U+202F) before AM/PM per CLDR 42+
String expectedFullTime_enUS = Runtime.version().feature() >= 21
? "9:04:05\u202FPM Eastern Daylight Time"
: "9:04:05 PM Eastern Daylight Time";
Assertions.assertEquals(expectedFullTime_enUS, d.toLocaleTimeString("en-US", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York") .put("timeZone", "America/New_York")
.put("timeStyle", "full") .put("timeStyle", "full")
.toString())); .toString()));
@ -292,14 +300,27 @@ class TbDateTest {
Assertions.assertNotNull(d.toLocaleString()); Assertions.assertNotNull(d.toLocaleString());
Assertions.assertNotNull(d.toLocaleString("en-US")); Assertions.assertNotNull(d.toLocaleString("en-US"));
Assertions.assertEquals("9/5/23, 9:04:05 PM", d.toLocaleString("en-US", "America/New_York")); // Java 21+ uses narrow no-break space (U+202F) before AM/PM per CLDR 42+
String expectedLocale_enUS = Runtime.version().feature() >= 21 ? "9/5/23, 9:04:05\u202FPM" : "9/5/23, 9:04:05 PM";
Assertions.assertEquals(expectedLocale_enUS, d.toLocaleString("en-US", "America/New_York"));
Assertions.assertEquals("23. 9. 5. 오후 9:04:05", d.toLocaleString("ko-KR", "America/New_York")); Assertions.assertEquals("23. 9. 5. 오후 9:04:05", d.toLocaleString("ko-KR", "America/New_York"));
Assertions.assertEquals("06.09.23, 04:04:05", d.toLocaleString( "uk-UA", "Europe/Kiev")); Assertions.assertEquals("06.09.23, 04:04:05", d.toLocaleString( "uk-UA", "Europe/Kiev"));
String expected_ver = Runtime.version().feature() == 11 ? "5\u200F/9\u200F/2023 9:04:05 م" : // Java 11 has no comma, Java 17 uses ASCII comma, Java 21+ uses Arabic comma (U+060C)
"5\u200F/9\u200F/2023, 9:04:05 م"; String expected_ar;
Assertions.assertEquals(expected_ver, d.toLocaleString( "ar-EG", "America/New_York")); if (Runtime.version().feature() == 11) {
expected_ar = "5\u200F/9\u200F/2023 9:04:05 م";
} else if (Runtime.version().feature() >= 21) {
expected_ar = "5\u200F/9\u200F/2023\u060C 9:04:05 م";
} else {
expected_ar = "5\u200F/9\u200F/2023, 9:04:05 م";
}
Assertions.assertEquals(expected_ar, d.toLocaleString( "ar-EG", "America/New_York"));
Assertions.assertEquals("Tuesday, September 5, 2023 at 9:04:05 PM Eastern Daylight Time", d.toLocaleString("en-US", JacksonUtil.newObjectNode() // Java 21+ changed "at" to "," and uses NNBSP before AM/PM per CLDR 42+
String expected_enUS_full = Runtime.version().feature() >= 21
? "Tuesday, September 5, 2023, 9:04:05\u202FPM Eastern Daylight Time"
: "Tuesday, September 5, 2023 at 9:04:05 PM Eastern Daylight Time";
Assertions.assertEquals(expected_enUS_full, d.toLocaleString("en-US", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York") .put("timeZone", "America/New_York")
.put("dateStyle", "full") .put("dateStyle", "full")
.put("timeStyle", "full") .put("timeStyle", "full")
@ -309,15 +330,26 @@ class TbDateTest {
.put("dateStyle", "full") .put("dateStyle", "full")
.put("timeStyle", "full") .put("timeStyle", "full")
.toString())); .toString()));
Assertions.assertEquals("середа, 6 вересня 2023 р. о 04:04:05 за східноєвропейським літнім часом", d.toLocaleString("uk-UA", JacksonUtil.newObjectNode() // Java 21+ changed Ukrainian locale: "о" replaced with "," and NNBSP before "р." per CLDR 42+
String expected_ukUA_full_summer = Runtime.version().feature() >= 21
? "середа, 6 вересня 2023\u202Fр., 04:04:05 за східноєвропейським літнім часом"
: "середа, 6 вересня 2023 р. о 04:04:05 за східноєвропейським літнім часом";
Assertions.assertEquals(expected_ukUA_full_summer, d.toLocaleString("uk-UA", JacksonUtil.newObjectNode()
.put("timeZone", "Europe/Kiev") .put("timeZone", "Europe/Kiev")
.put("dateStyle", "full") .put("dateStyle", "full")
.put("timeStyle", "full") .put("timeStyle", "full")
.toString())); .toString()));
expected_ver = Runtime.version().feature() == 11 ? "الثلاثاء، 5 سبتمبر 2023 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية" : // Java 11 has no connector, Java 17 uses "في", Java 21+ uses Arabic comma "،"
"الثلاثاء، 5 سبتمبر 2023 في 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية"; String expected_ar_full;
Assertions.assertEquals(expected_ver, d.toLocaleString("ar-EG", JacksonUtil.newObjectNode() if (Runtime.version().feature() == 11) {
expected_ar_full = "الثلاثاء، 5 سبتمبر 2023 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية";
} else if (Runtime.version().feature() >= 21) {
expected_ar_full = "الثلاثاء، 5 سبتمبر 2023\u060C 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية";
} else {
expected_ar_full = "الثلاثاء، 5 سبتمبر 2023 في 9:04:05 م التوقيت الصيفي الشرقي لأمريكا الشمالية";
}
Assertions.assertEquals(expected_ar_full, d.toLocaleString("ar-EG", JacksonUtil.newObjectNode()
.put("timeZone", "America/New_York") .put("timeZone", "America/New_York")
.put("dateStyle", "full") .put("dateStyle", "full")
.put("timeStyle", "full") .put("timeStyle", "full")
@ -825,16 +857,44 @@ class TbDateTest {
@Test @Test
public void toStringAsJs() { public void toStringAsJs() {
TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"-04:00"); TbDate d1 = new TbDate(1975, 12, 31, 23,15,30, 567,"-04:00");
Assertions.assertEquals("четвер, 1 січня 1976 р. о 06:15:30 за східноєвропейським стандартним часом", d1.toString("uk-UA", "Europe/Kyiv")); // Java 21+ changed Ukrainian locale: "о" (at) replaced with "," and NNBSP before "р." per CLDR 42+
Assertions.assertEquals("Thursday, January 1, 1976 at 6:15:30 AM Eastern European Standard Time", d1.toString("en-US", "Europe/Kyiv")); String expected_ukUA_full = Runtime.version().feature() >= 21
Assertions.assertEquals("1976 Jan 1, Thu 06:15:30 Eastern European Time", d1.toString("UTC", "Europe/Kyiv")); ? "четвер, 1 січня 1976\u202Fр., 06:15:30 за східноєвропейським стандартним часом"
Assertions.assertEquals("Wednesday, December 31, 1975 at 10:15:30 PM Eastern Standard Time", d1.toString("en-US", "America/New_York")); : "четвер, 1 січня 1976 р. о 06:15:30 за східноєвропейським стандартним часом";
Assertions.assertEquals("1975 Dec 31, Wed 22:15:30 Eastern Standard Time", d1.toString("GMT", "America/New_York")); Assertions.assertEquals(expected_ukUA_full, d1.toString("uk-UA", "Europe/Kyiv"));
Assertions.assertEquals("1975 Dec 31, Wed 22:15:30 Eastern Standard Time", d1.toString("UTC", "America/New_York")); // Java 21+ changed "at" to "," and uses NNBSP before AM/PM per CLDR 42+
String expected_enUS_Kyiv = Runtime.version().feature() >= 21
? "Thursday, January 1, 1976, 6:15:30\u202FAM Eastern European Standard Time"
: "Thursday, January 1, 1976 at 6:15:30 AM Eastern European Standard Time";
Assertions.assertEquals(expected_enUS_Kyiv, d1.toString("en-US", "Europe/Kyiv"));
// Java 21+ changed timezone display for UTC locale
String expected_UTC_Kyiv = Runtime.version().feature() >= 21
? "1976 Jan 1, Thu 06:15:30 Kyiv (+0)"
: "1976 Jan 1, Thu 06:15:30 Eastern European Time";
Assertions.assertEquals(expected_UTC_Kyiv, d1.toString("UTC", "Europe/Kyiv"));
// Java 21+ changed "at" to "," and uses NNBSP before AM/PM per CLDR 42+
String expected_enUS_NY = Runtime.version().feature() >= 21
? "Wednesday, December 31, 1975, 10:15:30\u202FPM Eastern Standard Time"
: "Wednesday, December 31, 1975 at 10:15:30 PM Eastern Standard Time";
Assertions.assertEquals(expected_enUS_NY, d1.toString("en-US", "America/New_York"));
// Java 21+ changed timezone display for GMT/UTC locales
String expected_GMT_NY = Runtime.version().feature() >= 21
? "1975 Dec 31, Wed 22:15:30 New York (+0)"
: "1975 Dec 31, Wed 22:15:30 Eastern Standard Time";
Assertions.assertEquals(expected_GMT_NY, d1.toString("GMT", "America/New_York"));
Assertions.assertEquals(expected_GMT_NY, d1.toString("UTC", "America/New_York"));
Assertions.assertEquals(d1.toUTCString("UTC"), d1.toUTCString()); Assertions.assertEquals(d1.toUTCString("UTC"), d1.toUTCString());
Assertions.assertEquals("четвер, 1 січня 1976 р., 03:15:30", d1.toUTCString("uk-UA")); // Java 21+ uses NNBSP before "р." per CLDR 42+
Assertions.assertEquals("Thursday, January 1, 1976, 3:15:30 AM", d1.toUTCString("en-US")); String expected_ukUA_utc = Runtime.version().feature() >= 21
? "четвер, 1 січня 1976\u202Fр., 03:15:30"
: "четвер, 1 січня 1976 р., 03:15:30";
Assertions.assertEquals(expected_ukUA_utc, d1.toUTCString("uk-UA"));
// Java 21+ uses NNBSP before AM/PM per CLDR 42+
String expected_enUS_utc = Runtime.version().feature() >= 21
? "Thursday, January 1, 1976, 3:15:30\u202FAM"
: "Thursday, January 1, 1976, 3:15:30 AM";
Assertions.assertEquals(expected_enUS_utc, d1.toUTCString("en-US"));
Assertions.assertEquals("1976-01-01T03:15:30.567Z", d1.toJSON()); Assertions.assertEquals("1976-01-01T03:15:30.567Z", d1.toJSON());
Assertions.assertEquals("1976-01-01T03:15:30.567Z", d1.toISOString()); Assertions.assertEquals("1976-01-01T03:15:30.567Z", d1.toISOString());
@ -842,22 +902,44 @@ class TbDateTest {
Assertions.assertEquals("1976-01-01 06:15:30", d1.toLocaleString("UTC", "Europe/Kyiv")); Assertions.assertEquals("1976-01-01 06:15:30", d1.toLocaleString("UTC", "Europe/Kyiv"));
Assertions.assertEquals("01.01.76, 06:15:30", d1.toLocaleString("uk-UA", "Europe/Kyiv")); Assertions.assertEquals("01.01.76, 06:15:30", d1.toLocaleString("uk-UA", "Europe/Kyiv"));
Assertions.assertEquals("1975-12-31 22:15:30", d1.toLocaleString("UTC", "America/New_York")); Assertions.assertEquals("1975-12-31 22:15:30", d1.toLocaleString("UTC", "America/New_York"));
Assertions.assertEquals("12/31/75, 10:15:30 PM", d1.toLocaleString("en-US", "America/New_York")); // Java 21+ uses NNBSP before AM/PM per CLDR 42+
String expected_enUS_locale_NY = Runtime.version().feature() >= 21
? "12/31/75, 10:15:30\u202FPM"
: "12/31/75, 10:15:30 PM";
Assertions.assertEquals(expected_enUS_locale_NY, d1.toLocaleString("en-US", "America/New_York"));
Assertions.assertEquals("1976 Jan 1, Thu", d1.toDateString("UTC", "Europe/Kyiv")); Assertions.assertEquals("1976 Jan 1, Thu", d1.toDateString("UTC", "Europe/Kyiv"));
Assertions.assertEquals("четвер, 1 січня 1976 р.", d1.toDateString("uk-UA", "Europe/Kyiv")); // Java 21+ uses NNBSP before "р." per CLDR 42+
String expected_ukUA_date = Runtime.version().feature() >= 21
? "четвер, 1 січня 1976\u202Fр."
: "четвер, 1 січня 1976 р.";
Assertions.assertEquals(expected_ukUA_date, d1.toDateString("uk-UA", "Europe/Kyiv"));
Assertions.assertEquals("1975 Dec 31, Wed", d1.toDateString("UTC", "America/New_York")); Assertions.assertEquals("1975 Dec 31, Wed", d1.toDateString("UTC", "America/New_York"));
Assertions.assertEquals("Wednesday, December 31, 1975", d1.toDateString("en-US", "America/New_York")); Assertions.assertEquals("Wednesday, December 31, 1975", d1.toDateString("en-US", "America/New_York"));
Assertions.assertEquals("06:15:30", d1.toLocaleTimeString("uk-UA", "Europe/Kyiv")); Assertions.assertEquals("06:15:30", d1.toLocaleTimeString("uk-UA", "Europe/Kyiv"));
Assertions.assertEquals("06:15:30", d1.toLocaleTimeString("UTC", "Europe/Kyiv")); Assertions.assertEquals("06:15:30", d1.toLocaleTimeString("UTC", "Europe/Kyiv"));
Assertions.assertEquals("10:15:30 PM", d1.toLocaleTimeString("en-US", "America/New_York")); // Java 21+ uses NNBSP before AM/PM per CLDR 42+
String expected_enUS_time = Runtime.version().feature() >= 21 ? "10:15:30\u202FPM" : "10:15:30 PM";
Assertions.assertEquals(expected_enUS_time, d1.toLocaleTimeString("en-US", "America/New_York"));
Assertions.assertEquals("22:15:30", d1.toLocaleTimeString("UTC", "America/New_York")); Assertions.assertEquals("22:15:30", d1.toLocaleTimeString("UTC", "America/New_York"));
Assertions.assertEquals("06:15:30 за східноєвропейським стандартним часом", d1.toTimeString("uk-UA", "Europe/Kyiv")); Assertions.assertEquals("06:15:30 за східноєвропейським стандартним часом", d1.toTimeString("uk-UA", "Europe/Kyiv"));
Assertions.assertEquals("06:15:30 Eastern European Time", d1.toTimeString("UTC", "Europe/Kyiv")); // Java 21+ changed timezone display for UTC locale
Assertions.assertEquals("10:15:30 PM Eastern Standard Time", d1.toTimeString("en-US", "America/New_York")); String expected_UTC_time_Kyiv = Runtime.version().feature() >= 21
Assertions.assertEquals("22:15:30 Eastern Standard Time", d1.toTimeString("UTC", "America/New_York")); ? "06:15:30 Kyiv (+0)"
: "06:15:30 Eastern European Time";
Assertions.assertEquals(expected_UTC_time_Kyiv, d1.toTimeString("UTC", "Europe/Kyiv"));
// Java 21+ uses NNBSP before AM/PM per CLDR 42+
String expected_enUS_timeStr = Runtime.version().feature() >= 21
? "10:15:30\u202FPM Eastern Standard Time"
: "10:15:30 PM Eastern Standard Time";
Assertions.assertEquals(expected_enUS_timeStr, d1.toTimeString("en-US", "America/New_York"));
// Java 21+ changed timezone display for UTC locale
String expected_UTC_time_NY = Runtime.version().feature() >= 21
? "22:15:30 New York (+0)"
: "22:15:30 Eastern Standard Time";
Assertions.assertEquals(expected_UTC_time_NY, d1.toTimeString("UTC", "America/New_York"));
} }
@Test @Test

36
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java

@ -98,10 +98,8 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"AND ea.entityType = :affectedEntityType " + "AND ea.entityType = :affectedEntityType " +
"AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " + "AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " +
"AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " + "AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " +
"AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " +
"AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " +
// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " +
// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " +
"AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " +
"AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " +
"AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " + "AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " +
@ -119,10 +117,8 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"AND ea.entityType = :affectedEntityType " + "AND ea.entityType = :affectedEntityType " +
"AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " + "AND (:startTime IS NULL OR (a.createdTime >= :startTime AND ea.createdTime >= :startTime)) " +
"AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " + "AND (:endTime IS NULL OR (a.createdTime <= :endTime AND ea.createdTime <= :endTime)) " +
"AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " +
"AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " +
// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " +
// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " +
"AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " +
"AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " +
"AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " + "AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " +
@ -183,10 +179,8 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"WHERE a.tenantId = :tenantId " + "WHERE a.tenantId = :tenantId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " +
"AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " +
// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " +
// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " +
"AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " +
"AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " +
"AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " + "AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " +
@ -199,10 +193,8 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"WHERE a.tenantId = :tenantId " + "WHERE a.tenantId = :tenantId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " +
"AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " +
// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " +
// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " +
"AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " +
"AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " +
"AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " + "AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " +
@ -263,10 +255,8 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"WHERE a.tenantId = :tenantId AND a.customerId = :customerId " + "WHERE a.tenantId = :tenantId AND a.customerId = :customerId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " +
"AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " +
// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " +
// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " +
"AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " +
"AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " +
"AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " + "AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " +
@ -280,10 +270,8 @@ public interface AlarmRepository extends JpaRepository<AlarmEntity, UUID> {
"WHERE a.tenantId = :tenantId AND a.customerId = :customerId " + "WHERE a.tenantId = :tenantId AND a.customerId = :customerId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:#{#alarmTypes == null} = true) OR a.type IN (:alarmTypes)) " + //HHH-15968 "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " +
"AND ((:#{#alarmSeverities == null} = true) OR a.severity IN (:alarmSeverities)) " + //HHH-15968 "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " +
// "AND ((:alarmTypes) IS NULL OR a.type IN (:alarmTypes)) " +
// "AND ((:alarmSeverities) IS NULL OR a.severity IN (:alarmSeverities)) " +
"AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " + "AND ((:clearFilterEnabled) = FALSE OR a.cleared = :clearFilter) " +
"AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " + "AND ((:ackFilterEnabled) = FALSE OR a.acknowledged = :ackFilter) " +
"AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " + "AND (:assigneeId IS NULL OR a.assigneeId = :assigneeId) " +

12
dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java

@ -33,8 +33,7 @@ public interface AuditLogRepository extends JpaRepository<AuditLogEntity, UUID>
"a.tenantId = :tenantId " + "a.tenantId = :tenantId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:#{#actionTypes == null} = true) OR a.actionType IN (:actionTypes)) " + //HHH-15968 "AND ((:actionTypes) IS NULL OR a.actionType IN (:actionTypes)) " +
// "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " +
"AND (:textSearch IS NULL OR ilike(a.entityType, CONCAT('%', :textSearch, '%')) = true " + "AND (:textSearch IS NULL OR ilike(a.entityType, CONCAT('%', :textSearch, '%')) = true " +
"OR ilike(a.entityName, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.entityName, CONCAT('%', :textSearch, '%')) = true " +
"OR ilike(a.userName, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.userName, CONCAT('%', :textSearch, '%')) = true " +
@ -54,8 +53,7 @@ public interface AuditLogRepository extends JpaRepository<AuditLogEntity, UUID>
"AND a.entityType = :entityType AND a.entityId = :entityId " + "AND a.entityType = :entityType AND a.entityId = :entityId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:#{#actionTypes == null} = true) OR a.actionType IN (:actionTypes)) " + //HHH-15968 "AND ((:actionTypes) IS NULL OR a.actionType IN (:actionTypes)) " +
// "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " +
"AND (:textSearch IS NULL OR ilike(a.entityName, CONCAT('%', :textSearch, '%')) = true " + "AND (:textSearch IS NULL OR ilike(a.entityName, CONCAT('%', :textSearch, '%')) = true " +
"OR ilike(a.userName, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.userName, CONCAT('%', :textSearch, '%')) = true " +
"OR ilike(a.actionType, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.actionType, CONCAT('%', :textSearch, '%')) = true " +
@ -75,8 +73,7 @@ public interface AuditLogRepository extends JpaRepository<AuditLogEntity, UUID>
"AND a.customerId = :customerId " + "AND a.customerId = :customerId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:#{#actionTypes == null} = true) OR a.actionType IN (:actionTypes)) " + //HHH-15968 "AND ((:actionTypes) IS NULL OR a.actionType IN (:actionTypes)) " +
// "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " +
"AND (:textSearch IS NULL OR ilike(a.entityType, CONCAT('%', :textSearch, '%')) = true " + "AND (:textSearch IS NULL OR ilike(a.entityType, CONCAT('%', :textSearch, '%')) = true " +
"OR ilike(a.entityName, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.entityName, CONCAT('%', :textSearch, '%')) = true " +
"OR ilike(a.userName, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.userName, CONCAT('%', :textSearch, '%')) = true " +
@ -96,8 +93,7 @@ public interface AuditLogRepository extends JpaRepository<AuditLogEntity, UUID>
"AND a.userId = :userId " + "AND a.userId = :userId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:#{#actionTypes == null} = true) OR a.actionType IN (:actionTypes)) " + //HHH-15968 "AND ((:actionTypes) IS NULL OR a.actionType IN (:actionTypes)) " +
// "AND ((:actionTypes) IS NULL OR a.actionType in (:actionTypes)) " +
"AND (:textSearch IS NULL OR ilike(a.entityType, CONCAT('%', :textSearch, '%')) = true " + "AND (:textSearch IS NULL OR ilike(a.entityType, CONCAT('%', :textSearch, '%')) = true " +
"OR ilike(a.entityName, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.entityName, CONCAT('%', :textSearch, '%')) = true " +
"OR ilike(a.actionType, CONCAT('%', :textSearch, '%')) = true " + "OR ilike(a.actionType, CONCAT('%', :textSearch, '%')) = true " +

2
msa/tb/docker-cassandra/Dockerfile

@ -14,7 +14,7 @@
# limitations under the License. # limitations under the License.
# #
FROM thingsboard/openjdk17:bookworm-slim FROM thingsboard/openjdk25:trixie-slim
ENV PG_MAJOR=16 ENV PG_MAJOR=16

2
msa/tb/docker-postgres/Dockerfile

@ -14,7 +14,7 @@
# limitations under the License. # limitations under the License.
# #
FROM thingsboard/openjdk17:bookworm-slim FROM thingsboard/openjdk25:trixie-slim
ENV PG_MAJOR 12 ENV PG_MAJOR 12

4
packaging/java/build.gradle

@ -16,7 +16,7 @@
import org.apache.tools.ant.filters.ReplaceTokens import org.apache.tools.ant.filters.ReplaceTokens
plugins { plugins {
id "nebula.ospackage" version "8.6.3" id "com.netflix.nebula.ospackage" version "12.1.1"
} }
buildDir = projectBuildDir buildDir = projectBuildDir
@ -92,7 +92,7 @@ buildRpm {
archiveVersion = projectVersion.replace('-', '') archiveVersion = projectVersion.replace('-', '')
archiveFileName = "${pkgName}.rpm" archiveFileName = "${pkgName}.rpm"
// Support Java 17 (existing), plus Java 21 and Java 25 for RPM-based distros // Support Java 25 (current), plus Java 21 and Java 17 (backward compatibility) for RPM-based distros
// Keep using RPM boolean expression syntax since .or() chaining is for DEB only // Keep using RPM boolean expression syntax since .or() chaining is for DEB only
requires("(java-17 or java-17-headless or jre-17 or jre-17-headless or " + requires("(java-17 or java-17-headless or jre-17 or jre-17-headless or " +
"java-21 or java-21-headless or jre-21 or jre-21-headless or " + "java-21 or java-21-headless or jre-21 or jre-21-headless or " +

8
packaging/java/scripts/windows/install.bat

@ -7,11 +7,11 @@ setlocal ENABLEEXTENSIONS
for /f tokens^=2-5^ delims^=.-_^" %%j in ('java -fullversion 2^>^&1') do set "jver=%%j%%k" for /f tokens^=2-5^ delims^=.-_^" %%j in ('java -fullversion 2^>^&1') do set "jver=%%j%%k"
@ECHO CurrentVersion %jver% @ECHO CurrentVersion %jver%
if %jver% NEQ 170 GOTO JAVA_NOT_INSTALLED if %jver% NEQ 250 GOTO JAVA_NOT_INSTALLED
:JAVA_INSTALLED :JAVA_INSTALLED
@ECHO Java 17 found! @ECHO Java 25 found!
@ECHO Installing thingsboard ... @ECHO Installing thingsboard ...
SET loadDemo=false SET loadDemo=false
@ -50,8 +50,8 @@ POPD
GOTO END GOTO END
:JAVA_NOT_INSTALLED :JAVA_NOT_INSTALLED
@ECHO Java 17 is not installed. Only Java 17 is supported @ECHO Java 25 is not installed. Only Java 25 is supported
@ECHO Please go to https://adoptopenjdk.net/index.html and install Java 17. Then retry installation. @ECHO Please go to https://adoptopenjdk.net/index.html and install Java 25. Then retry installation.
PAUSE PAUSE
GOTO END GOTO END

2
packaging/js/build.gradle

@ -16,7 +16,7 @@
import org.apache.tools.ant.filters.ReplaceTokens import org.apache.tools.ant.filters.ReplaceTokens
plugins { plugins {
id "nebula.ospackage" version "8.6.3" id "com.netflix.nebula.ospackage" version "12.1.1"
} }
buildDir = projectBuildDir buildDir = projectBuildDir

20
pom.xml

@ -28,8 +28,8 @@
<inceptionYear>2016</inceptionYear> <inceptionYear>2016</inceptionYear>
<properties> <properties>
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.target>25</maven.compiler.target>
<main.dir>${basedir}</main.dir> <main.dir>${basedir}</main.dir>
<pkg.disabled>true</pkg.disabled> <pkg.disabled>true</pkg.disabled>
<pkg.process-resources.phase>none</pkg.process-resources.phase> <pkg.process-resources.phase>none</pkg.process-resources.phase>
@ -65,7 +65,7 @@
<protobuf.version>3.25.5</protobuf.version> <!-- A Major v4 does not support by the pubsub yet--> <protobuf.version>3.25.5</protobuf.version> <!-- A Major v4 does not support by the pubsub yet-->
<grpc.version>1.76.0</grpc.version> <grpc.version>1.76.0</grpc.version>
<tbel.version>1.2.8</tbel.version> <tbel.version>1.2.8</tbel.version>
<lombok.version>1.18.38</lombok.version> <lombok.version>1.18.40</lombok.version>
<paho.client.version>1.2.5</paho.client.version> <paho.client.version>1.2.5</paho.client.version>
<paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version> <paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version>
<os-maven-plugin.version>1.7.1</os-maven-plugin.version> <os-maven-plugin.version>1.7.1</os-maven-plugin.version>
@ -111,6 +111,7 @@
<jakarta.el.version>4.0.2</jakarta.el.version> <jakarta.el.version>4.0.2</jakarta.el.version>
<antisamy.version>1.7.5</antisamy.version> <antisamy.version>1.7.5</antisamy.version>
<snmp4j.version>3.8.0</snmp4j.version> <snmp4j.version>3.8.0</snmp4j.version>
<bytebuddy.version>1.18.4</bytebuddy.version>
<langchain4j.version>1.8.0-TB</langchain4j.version> <langchain4j.version>1.8.0-TB</langchain4j.version>
<error_prone_annotations.version>2.38.0</error_prone_annotations.version> <error_prone_annotations.version>2.38.0</error_prone_annotations.version>
<animal-sniffer-annotations.version>1.24</animal-sniffer-annotations.version> <animal-sniffer-annotations.version>1.24</animal-sniffer-annotations.version>
@ -605,7 +606,7 @@
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version> <version>3.11.0</version>
<configuration> <configuration>
<release>17</release> <release>25</release>
<compilerArgs> <compilerArgs>
<arg>-Xlint:deprecation</arg> <arg>-Xlint:deprecation</arg>
<arg>-Xlint:removal</arg> <arg>-Xlint:removal</arg>
@ -663,6 +664,7 @@
<configuration> <configuration>
<argLine> <argLine>
-XX:+UseStringDeduplication -XX:MaxGCPauseMillis=200 -XX:+UseStringDeduplication -XX:MaxGCPauseMillis=200
-XX:+EnableDynamicAgentLoading
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
-Dqueue.edqs.local.rocksdb_path="target/rocksdb/fork_${surefire.forkNumber}/edqs" -Dqueue.edqs.local.rocksdb_path="target/rocksdb/fork_${surefire.forkNumber}/edqs"
-Dqueue.calculated_fields.rocks_db_path="target/rocksdb/fork_${surefire.forkNumber}/cf" -Dqueue.calculated_fields.rocks_db_path="target/rocksdb/fork_${surefire.forkNumber}/cf"
@ -913,6 +915,16 @@
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>${bytebuddy.version}</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>${bytebuddy.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.thingsboard.langchain4j</groupId> <groupId>org.thingsboard.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId> <artifactId>langchain4j-bom</artifactId>

81
tbel-date-note.md

@ -0,0 +1,81 @@
## Release Note: TBEL Date Formatting Changes (Java 21+)
### Summary
When upgrading ThingsBoard to Java 21 or later, users may notice changes in locale-formatted date/time strings produced by the `TbDate` class in TBEL scripts. These changes are caused by updates to the Unicode CLDR (Common Locale Data Repository) in Java 21+.
### What Changed
| Format | Java 17 | Java 21+ |
|--------|---------|----------|
| Time with AM/PM | `9:04:05 PM` | `9:04:05 PM` (narrow no-break space before AM/PM) |
| Full datetime (English) | `Tuesday, September 5, 2023 at 9:04:05 PM` | `Tuesday, September 5, 2023, 9:04:05 PM` |
| Full datetime (Ukrainian) | `середа, 6 вересня 2023 р. о 04:04:05` | `середа, 6 вересня 2023 р., 04:04:05` |
| Short datetime (Arabic) | `5/9/2023, 9:04:05 م` | `5/9/2023، 9:04:05 م` (Arabic comma) |
| UTC timezone display | `Eastern European Time` | `Kyiv (+0)` |
### Impact
TBEL scripts that rely on exact string matching or parsing of locale-formatted dates may behave differently after upgrading. For example:
```javascript
// ⚠️ May break after upgrade - string comparison
var dateStr = new Date(ts).toLocaleString("en-US", "America/New_York");
if (dateStr == "9/5/23, 9:04:05 PM") {
// Will fail - invisible character difference
}
// ⚠️ May break after upgrade - string splitting
var parts = new Date(ts).toLocaleTimeString("en-US", tz).split(" ");
// "PM" now preceded by narrow no-break space (U+202F), not regular space
```
### Recommendations
1. **Use ISO formats for comparisons and storage**
```javascript
// ✅ Stable across Java versions
var dateStr = new Date(ts).toISOString(); // "2023-09-05T21:04:05Z"
var dateJson = new Date(ts).toJSON(); // "2023-09-05T21:04:05.000Z"
```
2. **Use explicit patterns for consistent formatting**
```javascript
// ✅ Explicit pattern - stable output
var dateStr = new Date(ts).toLocaleString("en-US", '{"pattern": "M/d/yyyy, h:mm:ss a"}');
```
3. **Use numeric getters for comparisons**
```javascript
// ✅ Compare numeric values instead of strings
var d = new Date(ts);
if (d.getHours() == 21 && d.getMinutes() == 4) { ... }
```
4. **Avoid string equality checks on formatted dates**
```javascript
// ❌ Avoid
if (date.toLocaleString() == storedDateString) { ... }
// ✅ Prefer
if (date.getTime() == storedTimestamp) { ... }
```
### Affected Methods
The following `TbDate` methods may produce different output:
- `toLocaleString()`
- `toLocaleDateString()`
- `toLocaleTimeString()`
- `toString()`
- `toDateString()`
- `toTimeString()`
- `toUTCString()`
### Unaffected Methods
These methods produce consistent output across Java versions:
- `toISOString()`
- `toJSON()`
- `getTime()` / `valueOf()`
- All numeric getters (`getFullYear()`, `getMonth()`, `getDate()`, `getHours()`, etc.)
Loading…
Cancel
Save