From eee07894789ef68ec383f836f6de101854a03cf5 Mon Sep 17 00:00:00 2001 From: swoellauer Date: Fri, 21 Apr 2023 15:26:09 +0200 Subject: [PATCH] audio query with time zone; audio allow empty time zone; creator entries with time zone timestamps; update java dependencies; update js dependencies --- AudioApp/package-lock.json | 36 +++++++-------- AudioApp/src/components/browser.vue | 32 +++++++++++--- AudioApp/src/pages/project/list.vue | 6 ++- AudioApp/src/pages/project/main.vue | 16 +++---- AudioApp/src/store/project.js | 2 +- build.gradle | 10 ++--- src/audio/AudioProjectConfig.java | 2 +- src/audio/MetaCreator.java | 2 +- src/audio/SampleManagerConnector.java | 44 +++++++++++++++++++ src/audio/server/Webserver.java | 6 ++- .../server/api/Labeling_listHandler.java | 3 +- src/audio/server/api/LabelsHandler.java | 3 +- src/audio/server/api/ProjectHandler.java | 5 ++- src/audio/server/api/Sample2Handler.java | 4 +- .../task/Task_audio_sample_statistics.java | 10 ++--- src/util/AudioTimeUtil.java | 10 ++++- 16 files changed, 137 insertions(+), 54 deletions(-) diff --git a/AudioApp/package-lock.json b/AudioApp/package-lock.json index 7b1b436..0bdb733 100644 --- a/AudioApp/package-lock.json +++ b/AudioApp/package-lock.json @@ -3442,9 +3442,9 @@ } }, "node_modules/axios": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.5.tgz", - "integrity": "sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz", + "integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -4441,9 +4441,9 @@ } }, "node_modules/core-js": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.0.tgz", - "integrity": "sha512-hQotSSARoNh1mYPi9O2YaWeiq/cEB95kOrFb4NCrO4RIFt1qqNpKsaE+vy/L3oiqvND5cThqXzUU3r9F7Efztg==", + "version": "3.30.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.1.tgz", + "integrity": "sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -5141,9 +5141,9 @@ } }, "node_modules/electron": { - "version": "21.4.3", - "resolved": "https://registry.npmjs.org/electron/-/electron-21.4.3.tgz", - "integrity": "sha512-FB3yJom48sqOA2WXvneId3Y7Hp4YAPSdKH3l+UYDdD3d8SjcufBCPSZVipaxfLQhh54NqnuF+p0BRosX9+VibA==", + "version": "21.4.4", + "resolved": "https://registry.npmjs.org/electron/-/electron-21.4.4.tgz", + "integrity": "sha512-N5O7y7Gtt7mDgkJLkW49ETiT8M3myZ9tNIEvGTKhpBduX4WdgMj6c3hYeYBD6XW7SvbRkWEQaTl25RNday8Xpw==", "hasInstallScript": true, "dependencies": { "@electron/get": "^1.14.1", @@ -15713,9 +15713,9 @@ } }, "axios": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.5.tgz", - "integrity": "sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz", + "integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==", "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -16441,9 +16441,9 @@ } }, "core-js": { - "version": "3.30.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.0.tgz", - "integrity": "sha512-hQotSSARoNh1mYPi9O2YaWeiq/cEB95kOrFb4NCrO4RIFt1qqNpKsaE+vy/L3oiqvND5cThqXzUU3r9F7Efztg==" + "version": "3.30.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.1.tgz", + "integrity": "sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ==" }, "core-js-compat": { "version": "3.27.1", @@ -16931,9 +16931,9 @@ } }, "electron": { - "version": "21.4.3", - "resolved": "https://registry.npmjs.org/electron/-/electron-21.4.3.tgz", - "integrity": "sha512-FB3yJom48sqOA2WXvneId3Y7Hp4YAPSdKH3l+UYDdD3d8SjcufBCPSZVipaxfLQhh54NqnuF+p0BRosX9+VibA==", + "version": "21.4.4", + "resolved": "https://registry.npmjs.org/electron/-/electron-21.4.4.tgz", + "integrity": "sha512-N5O7y7Gtt7mDgkJLkW49ETiT8M3myZ9tNIEvGTKhpBduX4WdgMj6c3hYeYBD6XW7SvbRkWEQaTl25RNday8Xpw==", "requires": { "@electron/get": "^1.14.1", "@types/node": "^16.11.26", diff --git a/AudioApp/src/components/browser.vue b/AudioApp/src/components/browser.vue index 92d2d76..70e037b 100644 --- a/AudioApp/src/components/browser.vue +++ b/AudioApp/src/components/browser.vue @@ -230,6 +230,7 @@ export default defineComponent({ requestError: false, filteredLocations: [], filteredTimestamps: [], + timestamps_all_locations: undefined, timestamps_of_location: undefined, requestMetaLoading: false, requestMetaError: false, @@ -296,11 +297,11 @@ export default defineComponent({ return t.timestamp <= 0 ? {date: '(unknown)', value: 0, year: '(unknown)', month: '(unknown)', day: '(unknown)'} : {date: t.date, time: t.time, value: t.timestamp, year: t.year, month: t.month<10 ? '0'+t.month : ''+t.month, day: t.day<10 ? '0'+t.day : ''+t.day}; }); } else { - const d = this.$store.state.project.data; - if(d === undefined || d.dates === undefined || d.dates.length === 0) { + const d = this.timestamps_all_locations; + if(d === undefined || d.length === 0) { return [{date: '(no timestamps)', value: undefined}]; } - return d.dates.map(t => { + return d.map(t => { return t.timestamp <= 0 ? {date: '(unknown)', value: 0, year: '(unknown)', month: '(unknown)', day: '(unknown)'} : {date: t.date, value: t.timestamp, year: t.year, month: t.month<10 ? '0'+t.month : ''+t.month, day: t.day<10 ? '0'+t.day : ''+t.day}; }); } @@ -398,6 +399,12 @@ export default defineComponent({ } } }, + time_zone() { + if(this.time_zone) { + this.requestRefreshMeta(); + this.requestRefresh(); + } + }, }, methods: { async querySamples() { @@ -482,7 +489,13 @@ export default defineComponent({ if(this.selectedLocation) { //params.timestamps_of_location = this.selectedLocation.value; params.dates_of_location = this.selectedLocation.value; + } else { + params.dates = true; } + if(this.time_zone) { + params.tz = this.time_zone; + } + //this.timestamps_all_locations = undefined; // no clear this.timestamps_of_location = undefined; this.requestMetaError = false; this.requestMetaLoading = true; @@ -496,6 +509,10 @@ export default defineComponent({ } this.timestamps_of_location = tol; } + var td = response.data.project.dates; + if(td !== undefined) { + this.timestamps_all_locations = td; + } } catch(e) { this.requestMetaError = true; this.requestMetaLoading = false; @@ -504,19 +521,22 @@ export default defineComponent({ }, locationfilterFn(val, update, abort) { update(() => { - const needle = val.toLowerCase() + const needle = val.toLowerCase(); this.filteredLocations = this.locations.filter(v => v.label.toLowerCase().indexOf(needle) > -1) }); }, timestampfilterFn(val, update, abort) { update(() => { - const needle = val.toLowerCase() + const needle = val.toLowerCase(); this.filteredTimestamps = this.timestamps.filter(v => v.date.toLowerCase().indexOf(needle) > -1) }); }, }, mounted() { - this.requestRefresh(); + if(this.time_zone !== undefined) { + this.requestRefreshMeta(); + this.requestRefresh(); + } }, }); diff --git a/AudioApp/src/pages/project/list.vue b/AudioApp/src/pages/project/list.vue index 664c55c..47b2eb9 100644 --- a/AudioApp/src/pages/project/list.vue +++ b/AudioApp/src/pages/project/list.vue @@ -157,7 +157,7 @@ {{sample.location}} {{sample.date}} - {{sample.time}} + {{sample.time}} {{time_zone}} {{sample.device}} {{sample.id}} @@ -429,6 +429,7 @@ export default defineComponent({ player_fft_intensity_max: state => state.project.player_fft_intensity_max, //player_spectrum_shrink_Factor: state => state.project.player_spectrum_shrink_Factor, player_time_expansion_factor: state => state.project.player_time_expansion_factor, + time_zone: state => state.project.time_zone, }), listId() { return this.$route.query.list; @@ -617,6 +618,9 @@ export default defineComponent({ this.setActionStatus(true, undefined); var urlPath = 'samples2/' + this.workingEntry.sample; var params = {sample_rate: true, labels: true}; + if(this.time_zone) { + params.tz = this.time_zone; + } var response = await this.$api.get(urlPath, {params}); this.sample = response.data.sample; this.setActionStatus(false, undefined); diff --git a/AudioApp/src/pages/project/main.vue b/AudioApp/src/pages/project/main.vue index 1bdbeba..6fee3ad 100644 --- a/AudioApp/src/pages/project/main.vue +++ b/AudioApp/src/pages/project/main.vue @@ -6,13 +6,13 @@
- {{sample.location}} - {{sample.date}} - {{sample.time}} {{time_zone}} - {{sample.device}} - {{sample.id}} - {{Math.trunc(sampleRate/1000)}}.{{sampleRatemhz}} kHz - {{durationHH}}:{{durationMM}}:{{durationSS}}.{{durationMS}} + {{sample.location}} + {{sample.date}} + {{sample.time}} {{time_zone}} + {{sample.device}} + {{sample.id}} + {{Math.trunc(sampleRate/1000)}}.{{sampleRatemhz}} kHz + {{durationHH}}:{{durationMM}}:{{durationSS}}.{{durationMS}}
@@ -485,7 +485,7 @@ {{label.name}} {{label.creator}} - {{label.creation_date === undefined ? '' : label.creation_date.slice(0,16)}} + {{label.creation_date === undefined ? '' : label.creation_date.slice(0,16)}} diff --git a/AudioApp/src/store/project.js b/AudioApp/src/store/project.js index 113d028..7df783e 100644 --- a/AudioApp/src/store/project.js +++ b/AudioApp/src/store/project.js @@ -193,7 +193,7 @@ actions: { params.locations = true; params.devices = true; //params.timestamps = true; - params.dates = true; + //params.dates = true; // not static because of time zone params.samples_table_count = true; var response = await rootState.api.get('projects/' + rootState.projectId, {params}); commit('setData', response.data); diff --git a/build.gradle b/build.gradle index 59f039e..ca0d232 100644 --- a/build.gradle +++ b/build.gradle @@ -53,11 +53,11 @@ dependencies { implementation group: 'org.tinylog', name: 'tinylog-impl', version: '2.6.1' runtimeOnly group: 'org.tinylog', name: 'slf4j-tinylog', version: '2.6.1' - implementation group: 'org.eclipse.jetty', name: 'jetty-server', version: '11.0.14' - implementation group: 'org.eclipse.jetty', name: 'jetty-security', version: '11.0.14' - implementation group: 'org.eclipse.jetty.http2', name: 'http2-server', version: '11.0.14' - implementation group: 'org.eclipse.jetty', name: 'jetty-alpn-java-server', version: '11.0.14' - //implementation group: 'org.eclipse.jetty', name: 'jetty-alpn-conscrypt-server', version: '11.0.14' // ALPN support for Java 8 + implementation group: 'org.eclipse.jetty', name: 'jetty-server', version: '11.0.15' + implementation group: 'org.eclipse.jetty', name: 'jetty-security', version: '11.0.15' + implementation group: 'org.eclipse.jetty.http2', name: 'http2-server', version: '11.0.15' + implementation group: 'org.eclipse.jetty', name: 'jetty-alpn-java-server', version: '11.0.15' + //implementation group: 'org.eclipse.jetty', name: 'jetty-alpn-conscrypt-server', version: '11.0.15' // ALPN support for Java 8 implementation group: 'org.json', name: 'json', version: '20230227' implementation group: 'org.yaml', name: 'snakeyaml', version: '1.33' diff --git a/src/audio/AudioProjectConfig.java b/src/audio/AudioProjectConfig.java index 171c080..f784b85 100644 --- a/src/audio/AudioProjectConfig.java +++ b/src/audio/AudioProjectConfig.java @@ -66,7 +66,7 @@ public static class Builder { public Map profileMap = null; public int audio_cache_max_files = 20; - public String time_zone = "UTC"; + public String time_zone = ""; // possible values UTC, UTC+1, UTC-1, UTC+2 etc. missing value or empty string means no time zone, internally this is UTC but without time zone label. public Builder() {} diff --git a/src/audio/MetaCreator.java b/src/audio/MetaCreator.java index c46f0cd..3fc6411 100644 --- a/src/audio/MetaCreator.java +++ b/src/audio/MetaCreator.java @@ -44,7 +44,7 @@ public static boolean createYaml(File file, Path yamlPath) { Vec logList = new Vec(); LinkedHashMap logO = new LinkedHashMap(); logO.put("action", "create_yaml"); - logO.put("date", LocalDateTime.now().format(ISO_FORMATTER)); + logO.put("date", AudioTimeUtil.timeTextOfNow()); logList.add(logO); m.put("log", logList); YamlUtil.writeSafeYamlMap(yamlPath, m); diff --git a/src/audio/SampleManagerConnector.java b/src/audio/SampleManagerConnector.java index 4f9109c..bbfe9a6 100644 --- a/src/audio/SampleManagerConnector.java +++ b/src/audio/SampleManagerConnector.java @@ -117,15 +117,21 @@ public static enum SQL { QUERY_TIMESTAMPS_ALL("SELECT DISTINCT TIMESTAMP FROM SAMPLE ORDER BY TIMESTAMP"), QUERY_DATES_ALL("SELECT DISTINCT (TIMESTAMP - (TIMESTAMP % 86400)) AS DATE FROM SAMPLE ORDER BY DATE"), + + QUERY_ZONED_DATES_ALL("SELECT DISTINCT ((TIMESTAMP + ?) - ((TIMESTAMP + ?) % 86400)) AS DATE FROM SAMPLE ORDER BY DATE"), QUERY_TIMESTAMPS_AT_LOCATION("SELECT DISTINCT TIMESTAMP FROM SAMPLE WHERE LOCATION = ? ORDER BY TIMESTAMP"), QUERY_DATES_AT_LOCATION("SELECT DISTINCT (TIMESTAMP - (TIMESTAMP % 86400)) AS DATE FROM SAMPLE WHERE LOCATION = ? ORDER BY DATE"), + + QUERY_ZONED_DATES_AT_LOCATION("SELECT DISTINCT ((TIMESTAMP + ?) - ((TIMESTAMP + ?) % 86400)) AS DATE FROM SAMPLE WHERE LOCATION = ? ORDER BY DATE"), QUERY_TIMESTAMPS_AT_LOCATION_NULL("SELECT DISTINCT TIMESTAMP FROM SAMPLE WHERE LOCATION IS NULL ORDER BY TIMESTAMP"), QUERY_DATES_AT_LOCATION_NULL("SELECT DISTINCT (TIMESTAMP - (TIMESTAMP % 86400)) AS DATE FROM SAMPLE WHERE LOCATION IS NULL ORDER BY DATE"), + QUERY_ZONED_DATES_AT_LOCATION_NULL("SELECT DISTINCT ((TIMESTAMP + ?) - ((TIMESTAMP + ?) % 86400)) AS DATE FROM SAMPLE WHERE LOCATION IS NULL ORDER BY DATE"), + QUERY_DEVICES_ALL("SELECT DISTINCT DEVICE FROM SAMPLE WHERE NOT LOCKED ORDER BY DEVICE"), @@ -397,6 +403,21 @@ public void forEachDate(LongConsumer consumer) { throw new RuntimeException(e); } } + + public void forEachZonedDate(int timeZoneOffsetSeconds, LongConsumer consumer) { + try { + PreparedStatement stmt = getStatement(SQL.QUERY_ZONED_DATES_ALL); + stmt.setInt(1, timeZoneOffsetSeconds); + stmt.setInt(2, timeZoneOffsetSeconds); + ResultSet res = stmt.executeQuery(); + while(res.next()) { + long timestamp = res.getLong(1) - timeZoneOffsetSeconds; // convert back to UTC + consumer.accept(timestamp); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + } public void forEachTimestamp(String location, LongConsumer consumer) { PreparedStatement stmt; @@ -435,6 +456,29 @@ public void forEachDate(String location, LongConsumer consumer) { throw new RuntimeException(e); } } + + public void forEachZonedDate(String location, int timeZoneOffsetSeconds, LongConsumer consumer) { + PreparedStatement stmt; + try { + if(location == null) { + stmt = getStatement(SQL.QUERY_ZONED_DATES_AT_LOCATION_NULL); + stmt.setInt(1, timeZoneOffsetSeconds); + stmt.setInt(2, timeZoneOffsetSeconds); + } else { + stmt = getStatement(SQL.QUERY_ZONED_DATES_AT_LOCATION); + stmt.setInt(1, timeZoneOffsetSeconds); + stmt.setInt(2, timeZoneOffsetSeconds); + stmt.setString(3, location); + } + ResultSet res = stmt.executeQuery(); + while(res.next()) { + long timestamp = res.getLong(1) - timeZoneOffsetSeconds; // convert back to UTC + consumer.accept(timestamp); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + } public int count() { try { diff --git a/src/audio/server/Webserver.java b/src/audio/server/Webserver.java index a03c8b1..061c911 100644 --- a/src/audio/server/Webserver.java +++ b/src/audio/server/Webserver.java @@ -4,8 +4,11 @@ import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; +import java.util.TimeZone; import java.util.stream.StreamSupport; import jakarta.servlet.ServletException; @@ -62,6 +65,7 @@ import audio.server.api.WebAuthnHandler; import audio.server.api.WorklistsHandler; import photo2.api.PhotoDB2Handler; +import util.AudioTimeUtil; public class Webserver { @@ -213,7 +217,7 @@ public static void main(String[] args) throws Exception { if(httpsPort > 0) { s += " [at HTTPS port " + httpsPort + "]"; } - Logger.info(s); + Logger.info(s); server.join(); Logger.info("*** Server stopped ***"); } diff --git a/src/audio/server/api/Labeling_listHandler.java b/src/audio/server/api/Labeling_listHandler.java index c82477b..e501f9d 100644 --- a/src/audio/server/api/Labeling_listHandler.java +++ b/src/audio/server/api/Labeling_listHandler.java @@ -28,6 +28,7 @@ import audio.UserLabel; import audio.labeling.LabelingList; import audio.labeling.LabelingListEntry; +import util.AudioTimeUtil; import util.JsonUtil; import util.Web; import util.collections.vec.Vec; @@ -110,7 +111,7 @@ private void handleRoot_POST(String review_list_id, LabelingList labelingList, R int sample_label_index = sample.findLabelIndexOf(entry.label_start, entry.label_end); Label label = sample_label_index < 0 ? new Label(entry.label_start, entry.label_end) : sample.getLabel(sample_label_index); String username = account.username; - String timestamp = LocalDateTime.now().toString(); + String timestamp = AudioTimeUtil.timeTextOfNow(); Vec userLabels = new Vec(); for(String label_name : req_label_names) { UserLabel userLabel = label.userLabels.find((UserLabel us) -> label_name.equals(us.name)); diff --git a/src/audio/server/api/LabelsHandler.java b/src/audio/server/api/LabelsHandler.java index 916f071..4357cc1 100644 --- a/src/audio/server/api/LabelsHandler.java +++ b/src/audio/server/api/LabelsHandler.java @@ -22,6 +22,7 @@ import audio.Label; import audio.Sample; import audio.review.ReviewedLabel; +import util.AudioTimeUtil; import util.JsonUtil; import util.collections.vec.Vec; import util.yaml.YamlMap; @@ -116,7 +117,7 @@ private void handleRoot_POST(Sample sample, Request request, HttpServletResponse String actionName = jsonAction.getString("action"); switch(actionName) { case "add_label": { - Label label = Label.ofJSON(jsonAction.getJSONObject("label")).withCreator(account.username, LocalDateTime.now().toString()); + Label label = Label.ofJSON(jsonAction.getJSONObject("label")).withCreator(account.username, AudioTimeUtil.timeTextOfNow()); sample.getLabels().add(label); break; } diff --git a/src/audio/server/api/ProjectHandler.java b/src/audio/server/api/ProjectHandler.java index 3de9d25..1970e55 100644 --- a/src/audio/server/api/ProjectHandler.java +++ b/src/audio/server/api/ProjectHandler.java @@ -77,6 +77,7 @@ private void handleRoot(String project, Request request, HttpServletResponse res boolean fSamplesTableCount = Web.getFlagBoolean(request, "samples_table_count"); String reqTimeZone = Web.getString(request, "tz", "UTC"); int timeZoneOffsetSeconds = AudioTimeUtil.getTimeZoneOffsetSeconds(reqTimeZone); + Logger.info(reqTimeZone + " --> " + timeZoneOffsetSeconds); AudioProjectConfig config = broker.config().audioConfig; if(!config.project.equals(project)) { @@ -197,7 +198,7 @@ private void handleRoot(String project, Request request, HttpServletResponse res if(fDates) { json.key("dates"); json.array(); - sampleManager.tlSampleManagerConnector.get().forEachDate(timestampDateWriter); + sampleManager.tlSampleManagerConnector.get().forEachZonedDate(timeZoneOffsetSeconds, timestampDateWriter); json.endArray(); } if(timestamps_of_location != null) { @@ -220,7 +221,7 @@ private void handleRoot(String project, Request request, HttpServletResponse res json.value(loc); json.key("timestamps"); json.array(); - sampleManager.tlSampleManagerConnector.get().forEachDate(loc, timestampDateWriter); + sampleManager.tlSampleManagerConnector.get().forEachZonedDate(loc, timeZoneOffsetSeconds, timestampDateWriter); json.endArray(); json.endObject(); } diff --git a/src/audio/server/api/Sample2Handler.java b/src/audio/server/api/Sample2Handler.java index 328f11b..e2865f1 100644 --- a/src/audio/server/api/Sample2Handler.java +++ b/src/audio/server/api/Sample2Handler.java @@ -207,7 +207,7 @@ private void handleRoot_POST(Sample2 sample, Request request, HttpServletRespons for(String name : namesSet) { if(!retainNames.contains(name)) { String creator = account.username; - String creation_date = LocalDateTime.now().toString(); + String creation_date = AudioTimeUtil.timeTextOfNow(); UserLabel userLabel = new UserLabel(name, creator, creation_date); newUserlabels.add(userLabel); } @@ -241,7 +241,7 @@ private void handleRoot_POST(Sample2 sample, Request request, HttpServletRespons Vec newUserlabels = new Vec(); for(String name : names) { String creator = account.username; - String creation_date = LocalDateTime.now().toString(); + String creation_date = AudioTimeUtil.timeTextOfNow(); UserLabel userLabel = new UserLabel(name, creator, creation_date); newUserlabels.add(userLabel); } diff --git a/src/audio/task/Task_audio_sample_statistics.java b/src/audio/task/Task_audio_sample_statistics.java index 77c309d..47d1bdb 100644 --- a/src/audio/task/Task_audio_sample_statistics.java +++ b/src/audio/task/Task_audio_sample_statistics.java @@ -39,11 +39,11 @@ @Param(name = "col_temperature", type = Type.BOOLEAN, preset = "FALSE", description = "Include column 'temperature' in CSV output.") @Param(name = "col_file_size", type = Type.BOOLEAN, preset = "FALSE", description = "Include column 'file_size' in CSV output and sum up total file size in log message.") @Param(name = "filename", type = Type.STRING, preset = "samples.csv", description = "Filename of output CSV-file.") -@Param(name = "time_zone", type = Type.STRING, preset = "", description = "Set time zone of the 'time' column. e.g. UTC+1 If left empty, default time zone of project will be set.") -@Param(name = "include_time_zone", type = Type.BOOLEAN, preset = "FALSE", description = "In 'time' column, include the time zone marker. If false, time zone marker is not included in output, but time zone conversions are still applied.") +@Param(name = "time_zone", type = Type.STRING, preset = "", description = "Set time zone of the 'time' column. e.g. UTC+1 If left empty, default time zone of project will be set. (Only meaningful if the audio files include a time zone.)") +@Param(name = "include_time_zone", type = Type.BOOLEAN, preset = "FALSE", description = "In 'time' column, include the time zone marker. If false, time zone marker is not included in output, but time zone conversions are still applied. (Only meaningful if the audio files include a time zone.)") @Param(name = "filter_by_location", type = Type.STRING, preset = "", description = "(optional) Process the specified location only.") @Param(name = "filter_by_device", type = Type.STRING, preset = "", description = "(optional) Process the specified device id only.") -@Param(name = "filter_by_time", type = Type.STRING, preset = "", description = "(optional) Process the specified range of time only. Format: yyyy-MM-ddTHH:mm:ss A shortened format leads to a range of time. e.g. 2022 means all samples from year 2022. e.g. 2022-02 means all samples from February at year 2022.") +@Param(name = "filter_by_time", type = Type.STRING, preset = "", description = "(optional) Process the specified range of time only. Format: yyyy-MM-ddTHH:mm:ss A shortened format leads to a range of time. e.g. 2022 means all samples from year 2022. e.g. 2022-02 means all samples from February at year 2022. Time zone of 'time_zone' parameter will be used, or if empty, default time zone of project.") @Role("admin") public class Task_audio_sample_statistics extends Task { @@ -85,8 +85,8 @@ public void run() { boolean hasFilter_by_location = !filter_by_location.isBlank(); boolean hasFilter_by_device = !filter_by_device.isBlank(); boolean hasFilter_by_time = !filter_by_time.isBlank(); - long time_min = AudioTimeUtil.toAudiotimeStart(filter_by_time); - long time_max = AudioTimeUtil.toAudiotimeEnd(filter_by_time); + long time_min = AudioTimeUtil.toAudiotimeStart(filter_by_time) - timeZoneOffsetSeconds; + long time_max = AudioTimeUtil.toAudiotimeEnd(filter_by_time) - timeZoneOffsetSeconds; Path output_target = output_path.resolve(filename); try (CsvWriter csv = CsvWriter.builder().build(output_target)) { diff --git a/src/util/AudioTimeUtil.java b/src/util/AudioTimeUtil.java index 8fd1a64..8c24f88 100644 --- a/src/util/AudioTimeUtil.java +++ b/src/util/AudioTimeUtil.java @@ -4,6 +4,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.function.LongConsumer; @@ -15,6 +16,7 @@ public class AudioTimeUtil { public static final DateTimeFormatter DATE_SPACE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final DateTimeFormatter ZONED_TIME_TEXT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX"); private static final LocalDateTime UNIX_EPOCH = LocalDateTime.of(1970,1,1,0,0); @@ -281,7 +283,13 @@ public static int getTimeZoneOffsetSeconds(String timeZone) { int offset = Integer.parseInt(offsetText); return offset * HOUR_OFFSET; } else { - throw new RuntimeException("unknown time zone: |" + timeZone + "|" + offsetMatcher.matches()); + throw new RuntimeException("unknown time zone: |" + timeZone + "|"); } } + + public static String timeTextOfNow() { + ZonedDateTime zonedDateTime = ZonedDateTime.now(); + String s = zonedDateTime.format(ZONED_TIME_TEXT_FORMATTER); + return s; + } }