diff --git a/plugins/org.jkiss.dbeaver.data.transfer/OSGI-INF/l10n/bundle.properties b/plugins/org.jkiss.dbeaver.data.transfer/OSGI-INF/l10n/bundle.properties index 7e4fd9e225eefa489c8af7d47ce3b0ffcd1133a2..0a2a90d9c485d9218a529f279594763b869c85cc 100644 --- a/plugins/org.jkiss.dbeaver.data.transfer/OSGI-INF/l10n/bundle.properties +++ b/plugins/org.jkiss.dbeaver.data.transfer/OSGI-INF/l10n/bundle.properties @@ -146,7 +146,11 @@ dataTransfer.producer.stream.processor.csv.property.emptyStringNull.description dataTransfer.producer.stream.processor.csv.property.nullString.name = NULL value mark dataTransfer.producer.stream.processor.csv.property.nullString.description = String literal used as NULL value mark. \nSuch strings will be converted into NULL during data import dataTransfer.producer.stream.processor.csv.property.timestampFormat.name = Date/time format -dataTransfer.producer.stream.processor.csv.property.timestampFormat.description = Date/time format pattern. Use this to clarify the date format present\n in the data source, not to change output data +dataTransfer.producer.stream.processor.csv.property.timestampFormat.description = Date/time format pattern. Use this to clarify the date format in CSV file, not to change output data.\nSearch for 'java DateTimeFormatter' for format details. +dataTransfer.producer.stream.processor.csv.property.timestampZone.name = Timezone ID +dataTransfer.producer.stream.processor.csv.property.timestampZone.description = Timezone ID. By default local machine timezone is used.\n3 ways to specify zone:\n\t-Local zone offset (+3, -04:30)\n\t-Specific zone offset (GMT+2, UTC+01:00)\n\t-Region based (UTC, ECT, PST, etc) + + task.category.name.common = Common task.category.description.common = Common database tasks task.name.export = Data export diff --git a/plugins/org.jkiss.dbeaver.data.transfer/plugin.xml b/plugins/org.jkiss.dbeaver.data.transfer/plugin.xml index b92c935aa69e038216f54429ee2a2fc879fed75c..4d51a4d28fb457de30722d6ec705fe21be88d49d 100644 --- a/plugins/org.jkiss.dbeaver.data.transfer/plugin.xml +++ b/plugins/org.jkiss.dbeaver.data.transfer/plugin.xml @@ -51,6 +51,7 @@ + diff --git a/plugins/org.jkiss.dbeaver.data.transfer/src/org/jkiss/dbeaver/tools/transfer/stream/StreamTransferResultSet.java b/plugins/org.jkiss.dbeaver.data.transfer/src/org/jkiss/dbeaver/tools/transfer/stream/StreamTransferResultSet.java index 3d5927c2b710788827ce9289b782b1d01367de27..accfad51a170d9e61705a661a1b396372853ff8c 100644 --- a/plugins/org.jkiss.dbeaver.data.transfer/src/org/jkiss/dbeaver/tools/transfer/stream/StreamTransferResultSet.java +++ b/plugins/org.jkiss.dbeaver.data.transfer/src/org/jkiss/dbeaver/tools/transfer/stream/StreamTransferResultSet.java @@ -28,6 +28,7 @@ import org.jkiss.utils.CommonUtils; import java.sql.Timestamp; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; @@ -50,6 +51,7 @@ public class StreamTransferResultSet implements DBCResultSet { private Object[] streamRow; private final List attributeMappings; private DateTimeFormatter dateTimeFormat; + private ZoneId dateTimeZoneId; public StreamTransferResultSet(DBCSession session, DBCStatement statement, StreamEntityMapping entityMapping) { this.session = session; @@ -97,6 +99,14 @@ public class StreamTransferResultSet implements DBCResultSet { value = java.util.Date.from(zdt.toInstant()); } catch (Exception e) { LocalDateTime localDT = LocalDateTime.from(ta); + if (dateTimeZoneId != null) { + // Shift LocalDateTime to specified zone + // https://stackoverflow.com/questions/42280454/changing-localdatetime-based-on-time-difference-in-current-time-zone-vs-eastern + localDT = localDT + .atZone(ZoneId.systemDefault()) + .withZoneSameInstant(dateTimeZoneId) + .toLocalDateTime(); + } // We use java.sql.Timestamp.valueOf because classic date/time conversion turns "pre-historic" Gregorian // dates into incorrect SQL timestamps (in Julian calendar). E.g. 0001-01-01->0001-01-03 value = Timestamp.valueOf(localDT); @@ -160,7 +170,16 @@ public class StreamTransferResultSet implements DBCResultSet { return dateTimeFormat; } - public void setDateTimeFormat(DateTimeFormatter dateTimeFormat) { + public void setDateTimeFormat(DateTimeFormatter dateTimeFormat, ZoneId dateTimeZoneId) { this.dateTimeFormat = dateTimeFormat; + this.dateTimeZoneId = dateTimeZoneId; + if (this.dateTimeFormat != null && this.dateTimeZoneId != null) { + // Set zone to the format. + // FIXME: it looks like a good idea but in fact iti s not. We can't convert ZonedDateTime into + // FIXME: proper SQL timestamp for pre-historic (pre-Gregorian) dates. + // FIXME: so we will shift LocalDateTime in getAttributeValue instead + // FIXME: https://stackoverflow.com/questions/23975205/why-does-converting-java-dates-before-1582-to-localdate-with-instant-give-a-diff + //this.dateTimeFormat = this.dateTimeFormat.withZone(dateTimeZoneId); + } } } diff --git a/plugins/org.jkiss.dbeaver.data.transfer/src/org/jkiss/dbeaver/tools/transfer/stream/importer/DataImporterCSV.java b/plugins/org.jkiss.dbeaver.data.transfer/src/org/jkiss/dbeaver/tools/transfer/stream/importer/DataImporterCSV.java index caaf000dc556a99aa78c2730d47370fdfb747d32..eb3e0fa9dc0024604aee32435cd3fe8a1a44ef97 100644 --- a/plugins/org.jkiss.dbeaver.data.transfer/src/org/jkiss/dbeaver/tools/transfer/stream/importer/DataImporterCSV.java +++ b/plugins/org.jkiss.dbeaver.data.transfer/src/org/jkiss/dbeaver/tools/transfer/stream/importer/DataImporterCSV.java @@ -49,7 +49,6 @@ public class DataImporterCSV extends StreamImporterAbstract { private static final String PROP_NULL_STRING = "nullString"; private static final String PROP_EMPTY_STRING_NULL = "emptyStringNull"; private static final String PROP_ESCAPE_CHAR = "escapeChar"; - private static final String PROP_TIMESTAMP_FORMAT = "timestampFormat"; enum HeaderPosition { none, @@ -135,7 +134,7 @@ public class DataImporterCSV extends StreamImporterAbstract { consumer.fetchStart(producerSession, resultSet, -1, -1); - applyTransformHints(resultSet, consumer, getTimeStampFormat(properties, PROP_TIMESTAMP_FORMAT)); + applyTransformHints(resultSet, consumer, properties, PROP_TIMESTAMP_FORMAT, PROP_TIMESTAMP_ZONE); try (Reader reader = openStreamReader(inputStream, properties)) { try (CSVReader csvReader = openCSVReader(reader, properties)) { diff --git a/plugins/org.jkiss.dbeaver.data.transfer/src/org/jkiss/dbeaver/tools/transfer/stream/importer/StreamImporterAbstract.java b/plugins/org.jkiss.dbeaver.data.transfer/src/org/jkiss/dbeaver/tools/transfer/stream/importer/StreamImporterAbstract.java index b0c0e92af82e6b1e175c6e718bb8fc36cd9b0134..cb51e51b14b15cfc12635087112e3bec9383de7e 100644 --- a/plugins/org.jkiss.dbeaver.data.transfer/src/org/jkiss/dbeaver/tools/transfer/stream/importer/StreamImporterAbstract.java +++ b/plugins/org.jkiss.dbeaver.data.transfer/src/org/jkiss/dbeaver/tools/transfer/stream/importer/StreamImporterAbstract.java @@ -31,6 +31,7 @@ import org.jkiss.dbeaver.tools.transfer.stream.StreamDataImporterColumnInfo; import org.jkiss.dbeaver.tools.transfer.stream.StreamTransferResultSet; import org.jkiss.utils.CommonUtils; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Map; @@ -41,6 +42,9 @@ public abstract class StreamImporterAbstract implements IStreamDataImporter { private static final Log log = Log.getLog(StreamImporterAbstract.class); + protected static final String PROP_TIMESTAMP_FORMAT = "timestampFormat"; + protected static final String PROP_TIMESTAMP_ZONE = "timestampZone"; + private IStreamDataImporterSite site; public IStreamDataImporterSite getSite() @@ -61,10 +65,10 @@ public abstract class StreamImporterAbstract implements IStreamDataImporter { } @Nullable - protected DateTimeFormatter getTimeStampFormat(Map properties, String propName) { + protected DateTimeFormatter getTimeStampFormat(Map properties, String formatPropName) { DateTimeFormatter tsFormat = null; - String tsFormatPattern = CommonUtils.toString(properties.get(propName)); + String tsFormatPattern = CommonUtils.toString(properties.get(formatPropName)); if (!CommonUtils.isEmpty(tsFormatPattern)) { try { tsFormat = DateTimeFormatter.ofPattern(tsFormatPattern); @@ -75,9 +79,17 @@ public abstract class StreamImporterAbstract implements IStreamDataImporter { return tsFormat; } - protected void applyTransformHints(StreamTransferResultSet resultSet, IDataTransferConsumer consumer, DateTimeFormatter tsFormat) throws DBException { + protected void applyTransformHints(StreamTransferResultSet resultSet, IDataTransferConsumer consumer, Map properties, String formatPropName, String zoneIdPropName) throws DBException { + DateTimeFormatter tsFormat = formatPropName == null ? null : getTimeStampFormat(properties, formatPropName); + ZoneId tsZoneId = null; + if (zoneIdPropName != null) { + String zoneId = CommonUtils.toString(properties.get(zoneIdPropName)); + if (!CommonUtils.isEmpty(zoneId)) { + tsZoneId = ZoneId.of(zoneId); + } + } if (tsFormat != null) { - resultSet.setDateTimeFormat(tsFormat); + resultSet.setDateTimeFormat(tsFormat, tsZoneId); } // Try to find source/target attributes