-
Bug
-
Resolution: Unresolved
-
Major
-
None
-
9.6.2
-
- OpenJDK 1.8.0_345
- Tomcat 9.0.62
- ZK 9.6.2
Steps to Reproduce
Given following view model and zul
public class OldDateVM { private static String formatAtUTC(Date date) { final SimpleDateFormat df = new SimpleDateFormat("yyyy.M.d.H.m.s.S", Locale.US); df.setTimeZone(TimeZone.getTimeZone("UTC")); return df.format(date); } private static String formatAtUTC(ZonedDateTime zonedDateTime) { final DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy.M.d.H.m.s.S"); zonedDateTime = zonedDateTime.withZoneSameInstant(ZoneId.of("UTC")); return df.format(zonedDateTime); } private static String formatLocal(Date date) { final SimpleDateFormat df = new SimpleDateFormat("yyyy.M.d.H.m.s.S", Locale.US); return df.format(date); } private static String formatLocal(ZonedDateTime zonedDateTime) { final DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy.M.d.H.m.s.S"); return df.format(zonedDateTime); } private String isoDate; private ZonedDateTime zonedDateTime; private Date date; @DependsOn("isoDate") public Date getDate() { return date; } @DependsOn("date") public String getDateLocal() { return Optional.ofNullable(date).map(OldDateVM::formatLocal).orElse(null); } @DependsOn("date") public Long getDateMilliseconds() { return Optional.ofNullable(date).map(Date::getTime).orElse(null); } @DependsOn("date") public String getDateUTC() { return Optional.ofNullable(date).map(OldDateVM::formatAtUTC).orElse(null); } public String getIsoDate() { return isoDate; } public String getJavaVendor() { return System.getProperty("java.vendor"); } public String getJavaVersion() { return System.getProperty("java.version"); } public String getTimezone() { return System.getProperty("user.timezone"); } @DependsOn("isoDate") public ZonedDateTime getZonedDateTime() { return zonedDateTime; } @DependsOn("zonedDateTime") public String getZonedDateTimeLocal() { return Optional.ofNullable(zonedDateTime).map(OldDateVM::formatLocal).orElse(null); } @DependsOn("zonedDateTime") public Long getZonedDateTimeMilliseconds() { return Optional.ofNullable(zonedDateTime).map(ZonedDateTime::toInstant).map(Instant::toEpochMilli).orElse(null); } @DependsOn("zonedDateTime") public String getZonedDateTimeUTC() { return Optional.ofNullable(zonedDateTime).map(OldDateVM::formatAtUTC).orElse(null); } public String getZoneId() { return ZoneId.systemDefault().toString(); } @Init public void init() throws ParseException { isoDate = "1893-11-01"; updateDates(); } public void setDate(Date date) { this.date = date; } public void setIsoDate(String isoDate) throws ParseException { this.isoDate = isoDate; updateDates(); } public void setZonedDateTime(ZonedDateTime zonedDateTime) { this.zonedDateTime = zonedDateTime; } private void updateDates() throws ParseException { zonedDateTime = LocalDate.parse(isoDate).atStartOfDay(ZoneId.systemDefault()); date = new SimpleDateFormat("yyyy-MM-dd").parse(isoDate); } }
<window viewModel="@id('vm') @init('OldDateVM')"> <vlayout> <grid hflex="min"> <columns> <column label="Java Vendor" width="200px" /> <column label="Java Version" width="190px" /> <column label="User Time Zone" width="200px" /> <column label="Zone Id" width="200px" /> </columns> <rows> <row> <label value="@load(vm.javaVendor)" /> <label value="@load(vm.javaVersion)" /> <label value="@load(vm.timezone)" /> <label value="@load(vm.zoneId)" /> </row> </rows> </grid> <hlayout valign="middle"> <label value="ISO 8601 Date" /> <textbox value="@bind(vm.isoDate)" constraint="/\d{4}-\d{2}-\d{2}/" /> </hlayout> <grid hflex="min"> <columns> <column label="Java class" width="210px" /> <column label="Milliseconds" width="160px" /> <column label="Local Date Time" width="200px" /> <column label="ZK JSON format" width="200px" /> <column label="DateBox (dd/MM/yyyy HH:mm:ss)" width="270px" /> </columns> <rows> <row> <label value="java.time.ZonedDateTime" /> <label value="@load(vm.zonedDateTimeMilliseconds)" /> <label value="@load(vm.zonedDateTimeLocal)" /> <label value="@load(vm.zonedDateTimeUTC)" /> <datebox valueInZonedDateTime="@bind(vm.zonedDateTime)" cols="21" format="dd/MM/yyyy HH:mm:ss" buttonVisible="true" displayedTimeZones="Europe/Rome" /> </row> <row> <label value="java.util.Date" /> <label value="@load(vm.dateMilliseconds)" /> <label value="@load(vm.dateLocal)" /> <label value="@load(vm.dateUTC)" /> <datebox value="@bind(vm.date)" cols="21" format="dd/MM/yyyy HH:mm:ss" buttonVisible="true" displayedTimeZones="Europe/Rome" /> </row> </rows> </grid> </vlayout> </window>
- Enter "1893-10-31" as ISO 8601 date
Current Result
- datebox using "valueInZonedDateTime" shows "31/10/1893 00:00:00"
- datebox using "value" shows "30/10/1893 23:49:56"
Expected Result
Both datebox shall show "31/10/1893 00:00:00".
Debug Information
When countries adopted TimeZone standard time adjustments were required.
Italy, for example, adjusted its clock as "1893-10-31 23:49:55" (iso format) to "1893-11-01 00:00:00".
Most countries did analogous adjustments.
Client-side JS and java.time API support these transitions, while GregorianCalendar API does not. See JDK bugs:
Since latest versions of ZK use JDK 1.8, (de)serialization in zcommon/src/org/zkoss/json/JSONs.java shall use java.time API.
Conversion between java.time API and GregorianCalendar (java.util.Date) shall not use milliseconds. It shall extract fields (year, month ecc) from one API and use these fields to create an object in the other.
Workaround
none