From 2589b0c9a48102fe5b4c3b4e55e93c5f63eb4c8e Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 27 Feb 2025 09:41:42 +0800 Subject: [PATCH 01/15] Using timezone settings to display datetime. --- Directory.Packages.props | 2 +- .../datatables/datatables-extensions.js | 4 +-- .../AbpApplicationConfigurationAppService.cs | 8 ++---- .../TimeZoneSettingsAppService.cs | 2 +- .../TimeZoneSettingGroup/Default.cshtml | 8 +++--- .../TimeZoneSettingGroup/Default.js | 21 ++++++++++++-- .../TimeZoneSettingGroupViewComponent.cs | 28 ++----------------- npm/packs/core/src/abp.js | 23 ++++++++++----- 8 files changed, 47 insertions(+), 49 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 7f3b3065d9e..d783ac80cb9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -174,7 +174,7 @@ - + diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js index e3e1f8fe6cc..6a59ec8a65a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js @@ -516,7 +516,7 @@ var abp = abp || {}; if (!value) { return value; } else { - return (ISOStringToDateTimeLocaleString())(value); + return abp.clock.normalizeToLocaleString(value, { year: 'numeric', month: '2-digit', day: '2-digit' }); } }; @@ -524,7 +524,7 @@ var abp = abp || {}; if (!value) { return value; } else { - return (ISOStringToDateTimeLocaleString(luxon.DateTime.DATETIME_SHORT))(value); + return abp.clock.normalizeToLocaleString(value); } }; diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs index 7f41f731651..8dfa51768d2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs @@ -311,7 +311,7 @@ protected virtual Task GetGlobalFeatur protected virtual async Task GetTimingConfigAsync() { - var windowsTimeZoneId = await _settingProvider.GetOrNullAsync(TimingSettingNames.TimeZone); + var timeZone = await _settingProvider.GetOrNullAsync(TimingSettingNames.TimeZone); return new TimingDto { @@ -319,13 +319,11 @@ protected virtual async Task GetTimingConfigAsync() { Windows = new WindowsTimeZone { - TimeZoneId = windowsTimeZoneId + TimeZoneId = timeZone.IsNullOrWhiteSpace() ? null : _timezoneProvider.IanaToWindows(timeZone) }, Iana = new IanaTimeZone { - TimeZoneName = windowsTimeZoneId.IsNullOrWhiteSpace() - ? null - : _timezoneProvider.WindowsToIana(windowsTimeZoneId!) + TimeZoneName = timeZone } } }; diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Application/Volo/Abp/SettingManagement/TimeZoneSettingsAppService.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Application/Volo/Abp/SettingManagement/TimeZoneSettingsAppService.cs index ee8580ac22f..781a1a27d14 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Application/Volo/Abp/SettingManagement/TimeZoneSettingsAppService.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Application/Volo/Abp/SettingManagement/TimeZoneSettingsAppService.cs @@ -30,7 +30,7 @@ public virtual async Task GetAsync() public virtual Task> GetTimezonesAsync() { - return Task.FromResult(TimeZoneHelper.GetTimezones(TimezoneProvider.GetWindowsTimezones())); + return Task.FromResult(TimeZoneHelper.GetTimezones(TimezoneProvider.GetIanaTimezones())); } public virtual async Task UpdateAsync(string timezone) diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.cshtml b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.cshtml index c146118cb34..11d9af9637e 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.cshtml +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.cshtml @@ -1,18 +1,18 @@ @using Microsoft.AspNetCore.Mvc.Localization @using Volo.Abp.SettingManagement.Localization @inject IHtmlLocalizer L -@model Volo.Abp.SettingManagement.Web.Pages.SettingManagement.Components.TimeZoneSettingGroup.TimeZoneSettingGroupViewComponent.UpdateTimezoneSettingsViewModel -
- + +
+
@L["TimezoneHelpText"].Value
- + @L["Save"]
diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.js b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.js index d6e720dbe31..10dfc1a0d60 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.js +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.js @@ -3,13 +3,28 @@ var l = abp.localization.getResource('AbpSettingManagement'); + var select2 = null; + volo.abp.settingManagement.timeZoneSettings.getTimezones().then(function (result) { + var data = $.map(result, function (obj) { + obj.id = obj.value; + obj.text = obj.name; + return obj; + }); + + select2 = $("#timezone-select").select2({ + data: data + }); + + volo.abp.settingManagement.timeZoneSettings.get().then(function (result) { + select2.val(result).trigger("change"); + }); + }); + $("#TimeZoneSettingsForm").on('submit', function (event) { event.preventDefault(); - - volo.abp.settingManagement.timeZoneSettings.update($("#Timezone").val()).then(function (result) { + volo.abp.settingManagement.timeZoneSettings.update(select2.find(':selected')[0].value).then(function (result) { $(document).trigger("AbpSettingSaved"); }); - }); }); })(jQuery); diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/TimeZoneSettingGroupViewComponent.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/TimeZoneSettingGroupViewComponent.cs index eaf24ab2f23..9c22152a6f8 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/TimeZoneSettingGroupViewComponent.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/TimeZoneSettingGroupViewComponent.cs @@ -1,42 +1,18 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Rendering; using Volo.Abp.AspNetCore.Mvc; -using Volo.Abp.Auditing; -using Volo.Abp.Timing.Localization.Resources.AbpTiming; namespace Volo.Abp.SettingManagement.Web.Pages.SettingManagement.Components.TimeZoneSettingGroup; public class TimeZoneSettingGroupViewComponent : AbpViewComponent { - protected ITimeZoneSettingsAppService TimeZoneSettingsAppService { get; } - public TimeZoneSettingGroupViewComponent(ITimeZoneSettingsAppService timeZoneSettingsAppService) { ObjectMapperContext = typeof(AbpSettingManagementWebModule); - TimeZoneSettingsAppService = timeZoneSettingsAppService; } public virtual async Task InvokeAsync() { - var timezone = await TimeZoneSettingsAppService.GetAsync(); - var timezones = await TimeZoneSettingsAppService.GetTimezonesAsync(); - var model = new UpdateTimezoneSettingsViewModel() - { - Timezone = timezone, - TimeZoneItems = new List() - }; - model.TimeZoneItems.AddRange(timezones.Select(x => new SelectListItem(x.Name, x.Value)).ToList()); - return View("~/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.cshtml", model); - } - - public class UpdateTimezoneSettingsViewModel - { - public string Timezone { get; set; } - - public List TimeZoneItems { get; set; } + return View("~/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.cshtml"); } } diff --git a/npm/packs/core/src/abp.js b/npm/packs/core/src/abp.js index 05c9f3bbe55..9972039e2ba 100644 --- a/npm/packs/core/src/abp.js +++ b/npm/packs/core/src/abp.js @@ -748,9 +748,9 @@ var abp = abp || {}; abp.clock.kind = 'Unspecified'; - abp.clock.supportsMultipleTimezone = function () { - return abp.clock.kind === 'Utc'; - }; + abp.clock.supportsMultipleTimezone = abp.clock.kind === 'Utc'; + + abp.clock.timeZone = abp.setting.get('Abp.Timing.TimeZone'); // Normalize Date object or date string to standard string format that will be sent to server abp.clock.normalizeToString = function (date) { @@ -763,7 +763,7 @@ var abp = abp || {}; return date; } - if (abp.clock.kind === 'Utc') { + if (abp.clock.supportsMultipleTimezone) { return dateObj.toISOString(); } @@ -787,8 +787,11 @@ var abp = abp || {}; padMilliseconds(dateObj.getMilliseconds()); }; + // Default options for toLocaleString + abp.clock.toLocaleStringOptions = abp.clock.toLocaleStringOptions || {}; + // Normalize date string to locale date string that will be displayed to user - abp.clock.normalizeToLocaleString = function (dateString) { + abp.clock.normalizeToLocaleString = function (dateString, options) { if (!dateString) { return dateString; } @@ -797,8 +800,14 @@ var abp = abp || {}; if (isNaN(date)) { return dateString; } - - //TODO: Get timezone setting and pass it to toLocaleString + + if (abp.clock.supportsMultipleTimezone) { + var timezone = abp.clock.timeZone; + if (timezone) { + options = options || {}; + return date.toLocaleString(abp.localization.currentCulture.cultureName, Object.assign({}, abp.clock.toLocaleStringOptions, options, { timeZone: timezone })); + } + } return date.toLocaleString(); } From 228c6aff9a65a96d64a68f4f0aa52d518ee883f5 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 27 Feb 2025 10:18:53 +0800 Subject: [PATCH 02/15] Refactor abp.js to use functions for timezone support and retrieval --- npm/packs/core/src/abp.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/npm/packs/core/src/abp.js b/npm/packs/core/src/abp.js index 9972039e2ba..aca0196c15e 100644 --- a/npm/packs/core/src/abp.js +++ b/npm/packs/core/src/abp.js @@ -748,9 +748,13 @@ var abp = abp || {}; abp.clock.kind = 'Unspecified'; - abp.clock.supportsMultipleTimezone = abp.clock.kind === 'Utc'; + abp.clock.supportsMultipleTimezone = function () { + return abp.clock.kind === 'Utc'; + } - abp.clock.timeZone = abp.setting.get('Abp.Timing.TimeZone'); + abp.clock.timeZone = function () { + return abp.setting.get('Abp.Timing.TimeZone'); + } // Normalize Date object or date string to standard string format that will be sent to server abp.clock.normalizeToString = function (date) { @@ -763,7 +767,7 @@ var abp = abp || {}; return date; } - if (abp.clock.supportsMultipleTimezone) { + if (abp.clock.supportsMultipleTimezone()) { return dateObj.toISOString(); } @@ -801,8 +805,8 @@ var abp = abp || {}; return dateString; } - if (abp.clock.supportsMultipleTimezone) { - var timezone = abp.clock.timeZone; + if (abp.clock.supportsMultipleTimezone()) { + var timezone = abp.clock.timeZone(); if (timezone) { options = options || {}; return date.toLocaleString(abp.localization.currentCulture.cultureName, Object.assign({}, abp.clock.toLocaleStringOptions, options, { timeZone: timezone })); From c0dd379688be31c749c392c61ce75019901f7782 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 27 Feb 2025 10:38:43 +0800 Subject: [PATCH 03/15] Fix timezone settings form submission to use jQuery selector for value retrieval --- .../Components/TimeZoneSettingGroup/Default.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.js b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.js index 10dfc1a0d60..aebbe663525 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.js +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Pages/SettingManagement/Components/TimeZoneSettingGroup/Default.js @@ -22,7 +22,7 @@ $("#TimeZoneSettingsForm").on('submit', function (event) { event.preventDefault(); - volo.abp.settingManagement.timeZoneSettings.update(select2.find(':selected')[0].value).then(function (result) { + volo.abp.settingManagement.timeZoneSettings.update($("#timezone-select").val()).then(function (result) { $(document).trigger("AbpSettingSaved"); }); }); From ab1daa68ac3c537302543c308905c0f3ede373a5 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 27 Feb 2025 11:38:01 +0800 Subject: [PATCH 04/15] Remove `ISOStringToDateTimeLocaleString` function. --- .../datatables/datatables-extensions.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js index 6a59ec8a65a..c64d216ec0a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/datatables/datatables-extensions.js @@ -501,17 +501,6 @@ var abp = abp || {}; } }; - var ISOStringToDateTimeLocaleString = function (format) { - return function (data) { - var date = luxon - .DateTime - .fromISO(data, { - locale: abp.localization.currentCulture.name - }); - return format ? date.toLocaleString(format) : date.toLocaleString(); - }; - }; - datatables.defaultRenderers['date'] = function (value) { if (!value) { return value; From aa73510b3f648eb34a62797f4af00661c82fd976 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 27 Feb 2025 11:52:07 +0800 Subject: [PATCH 05/15] Rename comment section wrappers and update datepicker initialization for improved clarity --- .../Pages/CmsKit/Comments/Approve/Index.cshtml | 2 +- .../Pages/CmsKit/Comments/Approve/index.js | 7 ++++--- .../Pages/CmsKit/Comments/Details.cshtml | 2 +- .../Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js | 7 ++++--- .../Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js | 7 ++++--- .../Pages/Docs/Admin/Documents/index.js | 7 ++++--- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/Index.cshtml b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/Index.cshtml index d15396f33df..788ae3f6826 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/Index.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/Index.cshtml @@ -35,7 +35,7 @@ -
+
diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/index.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/index.js index f8aa2ba80dc..1871c379dbf 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/index.js +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/index.js @@ -14,7 +14,8 @@ return momentDate.isValid() ? momentDate.toISOString() : null; }; - $('.singledatepicker').daterangepicker({ + var singleDatePicker = $('#CmsKitCommentsApproveWrapper .singledatepicker'); + singleDatePicker.daterangepicker({ "singleDatePicker": true, "showDropdowns": true, "autoUpdateInput": false, @@ -23,9 +24,9 @@ "drops": "auto" }); - $('.singledatepicker').attr('autocomplete', 'off'); + singleDatePicker.attr('autocomplete', 'off'); - $('.singledatepicker').on('apply.daterangepicker', function (ev, picker) { + singleDatePicker.on('apply.daterangepicker', function (ev, picker) { $(this).val(picker.startDate.format('l')); }); diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Details.cshtml b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Details.cshtml index a73475bac2c..a9d064235fa 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Details.cshtml +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Details.cshtml @@ -33,7 +33,7 @@ } -
+
diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js index 2d007b09835..d0e83a0b9de 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js @@ -16,7 +16,8 @@ $(function (){ moment.localeData().preparse = (s)=>s; moment.localeData().postformat = (s)=>s; - $('.singledatepicker').daterangepicker({ + var singleDatePicker = $('#CmsKitCommentsDetailsWrapper .singledatepicker'); + singleDatePicker.daterangepicker({ "singleDatePicker": true, "showDropdowns": true, "autoUpdateInput": false, @@ -27,9 +28,9 @@ $(function (){ "maxYear": 2199, }); - $('.singledatepicker').attr('autocomplete', 'off'); + singleDatePicker.attr('autocomplete', 'off'); - $('.singledatepicker').on('apply.daterangepicker', function (ev, picker) { + singleDatePicker.on('apply.daterangepicker', function (ev, picker) { $(this).val(picker.startDate.format('l')); $(this).data('date', picker.startDate.locale('en').format('YYYY-MM-DD')); }); diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js index be652ce5522..a68f229f4e0 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js @@ -21,7 +21,8 @@ $(function () { return momentDate.isValid() ? momentDate.toISOString() : null; }; - $('.singledatepicker').daterangepicker({ + var singleDatePicker = $('#CmsKitCommentsWrapper .singledatepicker'); + singleDatePicker.daterangepicker({ "singleDatePicker": true, "showDropdowns": true, "autoUpdateInput": false, @@ -30,9 +31,9 @@ $(function () { "drops": "auto" }); - $('.singledatepicker').attr('autocomplete', 'off'); + singleDatePicker.attr('autocomplete', 'off'); - $('.singledatepicker').on('apply.daterangepicker', function (ev, picker) { + singleDatePicker.on('apply.daterangepicker', function (ev, picker) { $(this).val(picker.startDate.format('l')); }); diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js index 395ef8587ec..0d17f56a453 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js @@ -9,7 +9,8 @@ $(function () { moment.localeData().preparse = (s)=>s; moment.localeData().postformat = (s)=>s; - $('.singledatepicker').daterangepicker({ + var singleDatePicker = $('#DocumentsContainer .singledatepicker'); + singleDatePicker.daterangepicker({ "singleDatePicker": true, "showDropdowns": true, "autoUpdateInput": false, @@ -20,9 +21,9 @@ $(function () { "maxYear": 2199, }); - $('.singledatepicker').attr('autocomplete', 'off'); + singleDatePicker.attr('autocomplete', 'off'); - $('.singledatepicker').on('apply.daterangepicker', function (ev, picker) { + singleDatePicker.on('apply.daterangepicker', function (ev, picker) { $(this).val(picker.startDate.format('l')); $(this).data('date', picker.startDate.locale('en').format('YYYY-MM-DD')); }); From 0fb8606194b2fed3d612f8be7aec843c59d7eacb Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 27 Feb 2025 13:59:09 +0800 Subject: [PATCH 06/15] Add handleDatepicker function to manage datepicker inputs and hidden fields --- .../date-range-picker-extensions.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js index 01fe50dec3d..fe0ba57560e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js @@ -752,4 +752,37 @@ $(function () { abp.dom.initializers.initializeDateRangePickers($('body')); }); + + $.fn.handleDatepicker = function (datepickerSelector) { + var $this = $(this); + var datepickers = $this.find(datepickerSelector); + $this.find('input[class="new-datepicker"]').remove(); + + datepickers.each(function () { + var $this = $(this); + if($this.val() === ''){ + return; + } + + var name = $this.attr('name'); + $this.data('name', name).removeAttr('name'); + var datepicker = $this.data('daterangepicker'); + if (datepicker.singleDatePicker) { + var startDate = abp.clock.normalizeToString(datepicker.startDate.toDate()); + var startDateInput = $('').attr('type', 'hidden').attr('name', name).val(startDate).addClass('new-datepicker'); + $this.after(startDateInput); + } else { + var startDate = abp.clock.normalizeToString(datepicker.startDate.toDate()); + var startDateInput = $('').attr('type', 'hidden').attr('name', name).val(startDate).addClass('new-datepicker'); + $this.after(startDateInput); + + var endDate = abp.clock.normalizeToString(datepicker.endDate.toDate()); + var endDateInput = $('').attr('type', 'hidden').attr('name', name).val(endDate).addClass('new-datepicker'); + $this.after(endDateInput); + } + }); + + return this; + }; + })(jQuery); \ No newline at end of file From 099fb8ad640b5986a1c52af08ccc4855c62a56b1 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 27 Feb 2025 14:39:06 +0800 Subject: [PATCH 07/15] Update date range picker to use data attribute for name retrieval --- .../date-range-picker/date-range-picker-extensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js index fe0ba57560e..ae58704f379 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js @@ -764,7 +764,7 @@ return; } - var name = $this.attr('name'); + var name = $this.attr('name') || $this.data('name'); $this.data('name', name).removeAttr('name'); var datepicker = $this.data('daterangepicker'); if (datepicker.singleDatePicker) { From 1327b1148ea75b38f45a669765ffad4f637183f4 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 27 Feb 2025 15:03:27 +0800 Subject: [PATCH 08/15] Refactor date handling in comments section to utilize handleDatepicker function and remove redundant date formatting logic --- .../Pages/CmsKit/Comments/Approve/index.js | 22 +++------------- .../Pages/CmsKit/Comments/details.js | 16 +++--------- .../Pages/CmsKit/Comments/index.js | 20 +++----------- .../Pages/Docs/Admin/Documents/index.js | 26 ++++++++----------- 4 files changed, 21 insertions(+), 63 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/index.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/index.js index 1871c379dbf..0f619a2e4d2 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/index.js +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/index.js @@ -3,17 +3,6 @@ var commentsService = volo.cmsKit.admin.comments.commentAdmin; - moment()._locale.preparse = (string) => string; - moment()._locale.postformat = (string) => string; - - var getFormattedDate = function ($datePicker) { - if (!$datePicker.val()) { - return null; - } - var momentDate = moment($datePicker.val(), $datePicker.data('daterangepicker').locale.format); - return momentDate.isValid() ? momentDate.toISOString() : null; - }; - var singleDatePicker = $('#CmsKitCommentsApproveWrapper .singledatepicker'); singleDatePicker.daterangepicker({ "singleDatePicker": true, @@ -33,13 +22,10 @@ var filterForm = $('#CmsKitCommentsFilterForm'); var getFilter = function () { - var filterObj = filterForm.serializeFormToObject(); - - filterObj.creationStartDate = getFormattedDate($('#CreationStartDate')); - filterObj.creationEndDate = getFormattedDate($('#CreationEndDate')); - filterObj.commentApproveState = "Waiting"; - - return filterObj; + filterForm.handleDatepicker('.singledatepicker'); + var formObject = filterForm.serializeFormToObject(); + formObject.commentApproveState = "Waiting"; + return formObject; }; var _dataTable = $('#CommentsTable').DataTable(abp.libs.datatables.normalizeConfiguration({ diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js index d0e83a0b9de..59bce547de2 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js @@ -8,13 +8,6 @@ $(function (){ if (commentRequireApprovement) { $('#IsApprovedSelectInput').show(); } - - var getFormattedDate = function ($datePicker) { - return $datePicker.data('date'); - }; - - moment.localeData().preparse = (s)=>s; - moment.localeData().postformat = (s)=>s; var singleDatePicker = $('#CmsKitCommentsDetailsWrapper .singledatepicker'); singleDatePicker.daterangepicker({ @@ -38,12 +31,9 @@ $(function (){ var filterForm = $('#CmsKitCommentsFilterForm'); var getFilter = function () { - var filterObj = filterForm.serializeFormToObject(); - - filterObj.creationStartDate = getFormattedDate($('#creationStartDate')); - filterObj.creationEndDate = getFormattedDate($('#creationEndDate')); - - return filterObj; + filterForm.handleDatepicker('.singledatepicker'); + var formObject = filterForm.serializeFormToObject(); + return formObject; }; var _dataTable = $('#CommentsTable').DataTable(abp.libs.datatables.normalizeConfiguration({ diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js index a68f229f4e0..e1c2902a016 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js @@ -3,9 +3,6 @@ $(function () { var commentsService = volo.cmsKit.admin.comments.commentAdmin; - moment()._locale.preparse = (string) => string; - moment()._locale.postformat = (string) => string; - var commentRequireApprovement = abp.setting.getBoolean("CmsKit.Comments.RequireApprovement"); if (commentRequireApprovement) { @@ -13,14 +10,6 @@ $(function () { $('#IsApprovedSelectInput').show(); } - var getFormattedDate = function ($datePicker) { - if (!$datePicker.val()) { - return null; - } - var momentDate = moment($datePicker.val(), $datePicker.data('daterangepicker').locale.format); - return momentDate.isValid() ? momentDate.toISOString() : null; - }; - var singleDatePicker = $('#CmsKitCommentsWrapper .singledatepicker'); singleDatePicker.daterangepicker({ "singleDatePicker": true, @@ -40,12 +29,9 @@ $(function () { var filterForm = $('#CmsKitCommentsFilterForm'); var getFilter = function () { - var filterObj = filterForm.serializeFormToObject(); - - filterObj.creationStartDate = getFormattedDate($('#CreationStartDate')); - filterObj.creationEndDate = getFormattedDate($('#CreationEndDate')); - - return filterObj; + filterForm.handleDatepicker('.singledatepicker'); + var formObject = filterForm.serializeFormToObject(); + return formObject; }; var _dataTable = $('#CommentsTable').DataTable(abp.libs.datatables.normalizeConfiguration({ diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js index 0d17f56a453..fb513e5f0f2 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js @@ -2,13 +2,6 @@ $(function () { var l = abp.localization.getResource('Docs'); var service = window.volo.docs.admin.documentsAdmin; - var getFormattedDate = function ($datePicker) { - return $datePicker.data('date'); - }; - - moment.localeData().preparse = (s)=>s; - moment.localeData().postformat = (s)=>s; - var singleDatePicker = $('#DocumentsContainer .singledatepicker'); singleDatePicker.daterangepicker({ "singleDatePicker": true, @@ -131,23 +124,26 @@ $(function () { var getFilter = function () { + $('#DocumentsContainer').handleDatepicker('.singledatepicker'); + return { projectId: $('#ProjectId').val(), name: $('#Name').val(), version: $('#Version').val(), languageCode: $('#LanguageCode').val(), format: $('#Format').val(), - creationTimeMin: getFormattedDate($('#CreationTimeMin')), - creationTimeMax: getFormattedDate($('#CreationTimeMax')), - lastUpdatedTimeMin: getFormattedDate($('#LastUpdatedTimeMin')), - lastUpdatedTimeMax: getFormattedDate($('#LastUpdatedTimeMax')), - lastSignificantUpdateTimeMin: getFormattedDate($('#LastSignificantUpdateTimeMin')), - lastSignificantUpdateTimeMax: getFormattedDate($('#LastSignificantUpdateTimeMax')), - lastCachedTimeMin: getFormattedDate($('#LastCachedTimeMin')), - lastCachedTimeMax: getFormattedDate($('#LastCachedTimeMax')), + creationTimeMin: $('#DocumentsContainer').find('input[name="CreationTimeMin"'), + creationTimeMax: $('#DocumentsContainer').find('input[name="CreationTimeMax"'), + lastUpdatedTimeMin: $('#DocumentsContainer').find('input[name="LastUpdatedTimeMin"'), + lastUpdatedTimeMax: $('#DocumentsContainer').find('input[name="LastUpdatedTimeMax"'), + lastSignificantUpdateTimeMin: $('#DocumentsContainer').find('input[name="LastSignificantUpdateTimeMin"'), + lastSignificantUpdateTimeMax: $('#DocumentsContainer').find('input[name="LastSignificantUpdateTimeMax"'), + lastCachedTimeMin: $('#DocumentsContainer').find('input[name="LastCachedTimeMin"'), + lastCachedTimeMax: $('#DocumentsContainer').find('input[name="LastCachedTimeMax"'), }; }; + var parseDateToLocaleDateString = function (date) { var parsedDate = Date.parse(date); return new Date(parsedDate).toLocaleDateString( From cc2102f8d1de2e04fe5d2032d260e764587cc41c Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 27 Feb 2025 15:11:52 +0800 Subject: [PATCH 09/15] Do not remove `moment` code. --- .../Pages/CmsKit/Comments/Approve/index.js | 3 +++ .../src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js | 3 +++ .../src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js | 2 ++ .../Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js | 3 +++ 4 files changed, 11 insertions(+) diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/index.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/index.js index 0f619a2e4d2..587e7f77ec0 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/index.js +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/Approve/index.js @@ -3,6 +3,9 @@ var commentsService = volo.cmsKit.admin.comments.commentAdmin; + moment()._locale.preparse = (string) => string; + moment()._locale.postformat = (string) => string; + var singleDatePicker = $('#CmsKitCommentsApproveWrapper .singledatepicker'); singleDatePicker.daterangepicker({ "singleDatePicker": true, diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js index 59bce547de2..33f0b9211cb 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/details.js @@ -9,6 +9,9 @@ $(function (){ $('#IsApprovedSelectInput').show(); } + moment.localeData().preparse = (s)=>s; + moment.localeData().postformat = (s)=>s; + var singleDatePicker = $('#CmsKitCommentsDetailsWrapper .singledatepicker'); singleDatePicker.daterangepicker({ "singleDatePicker": true, diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js index e1c2902a016..ad0b8049f67 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Web/Pages/CmsKit/Comments/index.js @@ -2,6 +2,8 @@ $(function () { var l = abp.localization.getResource("CmsKit"); var commentsService = volo.cmsKit.admin.comments.commentAdmin; + moment()._locale.preparse = (string) => string; + moment()._locale.postformat = (string) => string; var commentRequireApprovement = abp.setting.getBoolean("CmsKit.Comments.RequireApprovement"); diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js index fb513e5f0f2..6dd0e1bde2d 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/index.js @@ -2,6 +2,9 @@ $(function () { var l = abp.localization.getResource('Docs'); var service = window.volo.docs.admin.documentsAdmin; + moment.localeData().preparse = (s)=>s; + moment.localeData().postformat = (s)=>s; + var singleDatePicker = $('#DocumentsContainer .singledatepicker'); singleDatePicker.daterangepicker({ "singleDatePicker": true, From d68bcdeb03b43788520928cbe2423c6a566301b6 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 28 Feb 2025 11:05:25 +0800 Subject: [PATCH 10/15] Add `ICurrentTimezoneProvider` and `AbpTimeZoneMiddleware`. --- ...zorCachedApplicationConfigurationClient.cs | 8 ++++- .../MauiBlazorCurrentTimezoneProvider.cs | 10 +++++++ ...blyCachedApplicationConfigurationClient.cs | 7 +++++ .../WebAssemblyCurrentTimezoneProvider.cs | 10 +++++++ .../AbpApplicationBuilderExtensions.cs | 11 +++++++ .../Timing/AbpTimeZoneMiddleware.cs | 30 +++++++++++++++++++ .../Volo.Abp.Timing/Volo/Abp/Timing/Clock.cs | 22 +++++++++++++- .../Abp/Timing/CurrentTimezoneProvider.cs | 20 +++++++++++++ .../CurrentTimezoneProviderExtensions.cs | 18 +++++++++++ .../Abp/Timing/IClientTimezoneProvider.cs | 6 ++++ .../Volo.Abp.Timing/Volo/Abp/Timing/IClock.cs | 7 +++++ 11 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCurrentTimezoneProvider.cs create mode 100644 framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCurrentTimezoneProvider.cs create mode 100644 framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Timing/AbpTimeZoneMiddleware.cs create mode 100644 framework/src/Volo.Abp.Timing/Volo/Abp/Timing/CurrentTimezoneProvider.cs create mode 100644 framework/src/Volo.Abp.Timing/Volo/Abp/Timing/CurrentTimezoneProviderExtensions.cs create mode 100644 framework/src/Volo.Abp.Timing/Volo/Abp/Timing/IClientTimezoneProvider.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCachedApplicationConfigurationClient.cs index c2fd8e21aaf..8eb2788c016 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCachedApplicationConfigurationClient.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCachedApplicationConfigurationClient.cs @@ -5,6 +5,7 @@ using Volo.Abp.AspNetCore.Mvc.Client; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; +using Volo.Abp.Timing; namespace Volo.Abp.AspNetCore.Components.MauiBlazor { @@ -18,16 +19,20 @@ public class MauiBlazorCachedApplicationConfigurationClient : ICachedApplication protected ICurrentTenantAccessor CurrentTenantAccessor { get; } + protected ICurrentTimezoneProvider CurrentTimezoneProvider { get; } + public MauiBlazorCachedApplicationConfigurationClient( AbpApplicationConfigurationClientProxy applicationConfigurationClientProxy, ApplicationConfigurationCache cache, ICurrentTenantAccessor currentTenantAccessor, + ICurrentTimezoneProvider currentTimezoneProvider, AuthenticationStateProvider authenticationStateProvider, AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy) { ApplicationConfigurationClientProxy = applicationConfigurationClientProxy; Cache = cache; CurrentTenantAccessor = currentTenantAccessor; + CurrentTimezoneProvider = currentTimezoneProvider; ApplicationLocalizationClientProxy = applicationLocalizationClientProxy; authenticationStateProvider.AuthenticationStateChanged += async _ => { await InitializeAsync(); }; @@ -51,6 +56,7 @@ public virtual async Task InitializeAsync() ); configurationDto.Localization.Resources = localizationDto.Resources; + CurrentTimezoneProvider.TimeZone = configurationDto.Timing.TimeZone.Iana.TimeZoneName; Cache.Set(configurationDto); @@ -81,4 +87,4 @@ private ApplicationConfigurationDto GetConfigurationByChecking() return configuration; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCurrentTimezoneProvider.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCurrentTimezoneProvider.cs new file mode 100644 index 00000000000..2a1a37da078 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor/Volo/Abp/AspNetCore/Components/MauiBlazor/MauiBlazorCurrentTimezoneProvider.cs @@ -0,0 +1,10 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.Timing; + +namespace Volo.Abp.AspNetCore.Components.MauiBlazor; + +[Dependency(ReplaceServices = true)] +public class MauiBlazorCurrentTimezoneProvider : ICurrentTimezoneProvider, ISingletonDependency +{ + public string? TimeZone { get; set; } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs index bebb2a78e14..cb69ea3120f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCachedApplicationConfigurationClient.cs @@ -6,6 +6,7 @@ using Volo.Abp.AspNetCore.Mvc.Client; using Volo.Abp.DependencyInjection; using Volo.Abp.MultiTenancy; +using Volo.Abp.Timing; namespace Volo.Abp.AspNetCore.Components.WebAssembly; @@ -19,6 +20,8 @@ public class WebAssemblyCachedApplicationConfigurationClient : ICachedApplicatio protected ICurrentTenantAccessor CurrentTenantAccessor { get; } + protected ICurrentTimezoneProvider CurrentTimezoneProvider { get; } + protected ApplicationConfigurationChangedService ApplicationConfigurationChangedService { get; } protected IJSRuntime JSRuntime { get; } @@ -27,6 +30,7 @@ public WebAssemblyCachedApplicationConfigurationClient( AbpApplicationConfigurationClientProxy applicationConfigurationClientProxy, ApplicationConfigurationCache cache, ICurrentTenantAccessor currentTenantAccessor, + ICurrentTimezoneProvider currentTimezoneProvider, AbpApplicationLocalizationClientProxy applicationLocalizationClientProxy, ApplicationConfigurationChangedService applicationConfigurationChangedService, IJSRuntime jsRuntime) @@ -34,6 +38,7 @@ public WebAssemblyCachedApplicationConfigurationClient( ApplicationConfigurationClientProxy = applicationConfigurationClientProxy; Cache = cache; CurrentTenantAccessor = currentTenantAccessor; + CurrentTimezoneProvider = currentTimezoneProvider; ApplicationLocalizationClientProxy = applicationLocalizationClientProxy; ApplicationConfigurationChangedService = applicationConfigurationChangedService; JSRuntime = jsRuntime; @@ -69,6 +74,8 @@ public virtual async Task InitializeAsync() configurationDto.CurrentTenant.Id, configurationDto.CurrentTenant.Name ); + + CurrentTimezoneProvider.TimeZone = configurationDto.Timing.TimeZone.Iana.TimeZoneName; } public virtual Task GetAsync() diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCurrentTimezoneProvider.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCurrentTimezoneProvider.cs new file mode 100644 index 00000000000..c387ec30ea2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/WebAssemblyCurrentTimezoneProvider.cs @@ -0,0 +1,10 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.Timing; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly; + +[Dependency(ReplaceServices = true)] +public class WebAssemblyCurrentTimezoneProvider : ICurrentTimezoneProvider, ISingletonDependency +{ + public string? TimeZone { get; set; } +} diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs index f0ca14657b4..e04018cf66a 100644 --- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Builder/AbpApplicationBuilderExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.RequestLocalization; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.StaticAssets; +using Microsoft.AspNetCore.Timing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; @@ -245,4 +246,14 @@ public static IApplicationBuilder UseVirtualStaticFiles(this IApplicationBuilder return app; } + + /// + /// Use this middleware after middleware. + /// + /// + /// + public static IApplicationBuilder UseAbpTimeZone(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } } diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Timing/AbpTimeZoneMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Timing/AbpTimeZoneMiddleware.cs new file mode 100644 index 00000000000..af0cb1c04ca --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/Timing/AbpTimeZoneMiddleware.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Middleware; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Settings; +using Volo.Abp.Timing; + +namespace Microsoft.AspNetCore.Timing; + +public class AbpTimeZoneMiddleware : AbpMiddlewareBase, ITransientDependency +{ + public async override Task InvokeAsync(HttpContext context, RequestDelegate next) + { + var settingProvider = context.RequestServices.GetRequiredService(); + var timezone = await settingProvider.GetOrNullAsync(TimingSettingNames.TimeZone); + if (timezone.IsNullOrEmpty()) + { + await next(context); + return; + } + + var currentTimezoneProvider = context.RequestServices.GetRequiredService(); + using (currentTimezoneProvider.Change(timezone)) + { + await next(context); + } + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Clock.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Clock.cs index 8788a2b5db3..9e012ed11c2 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Clock.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Clock.cs @@ -7,9 +7,16 @@ namespace Volo.Abp.Timing; public class Clock : IClock, ITransientDependency { protected AbpClockOptions Options { get; } + protected ICurrentTimezoneProvider CurrentTimezoneProvider { get; } + protected ITimezoneProvider TimezoneProvider { get; } - public Clock(IOptions options) + public Clock( + IOptions options, + ICurrentTimezoneProvider currentTimezoneProvider, + ITimezoneProvider timezoneProvider) { + CurrentTimezoneProvider = currentTimezoneProvider; + TimezoneProvider = timezoneProvider; Options = options.Value; } @@ -38,4 +45,17 @@ public virtual DateTime Normalize(DateTime dateTime) return DateTime.SpecifyKind(dateTime, Kind); } + + public virtual DateTime Convert(DateTime dateTime) + { + if (!SupportsMultipleTimezone || + dateTime.Kind != DateTimeKind.Utc || + CurrentTimezoneProvider.TimeZone.IsNullOrWhiteSpace()) + { + return dateTime; + } + + var timezoneInfo = TimezoneProvider.GetTimeZoneInfo(CurrentTimezoneProvider.TimeZone); + return TimeZoneInfo.ConvertTime(dateTime, timezoneInfo); + } } diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/CurrentTimezoneProvider.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/CurrentTimezoneProvider.cs new file mode 100644 index 00000000000..12781d43214 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/CurrentTimezoneProvider.cs @@ -0,0 +1,20 @@ +using System.Threading; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Timing; + +public class CurrentTimezoneProvider : ICurrentTimezoneProvider, ISingletonDependency +{ + public string? TimeZone + { + get => _currentScope.Value; + set => _currentScope.Value = value; + } + + private readonly AsyncLocal _currentScope; + + public CurrentTimezoneProvider() + { + _currentScope = new AsyncLocal(); + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/CurrentTimezoneProviderExtensions.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/CurrentTimezoneProviderExtensions.cs new file mode 100644 index 00000000000..2fc79289ad2 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/CurrentTimezoneProviderExtensions.cs @@ -0,0 +1,18 @@ +using System; + +namespace Volo.Abp.Timing; + +public static class CurrentTimezoneProviderExtensions +{ + public static IDisposable Change(this ICurrentTimezoneProvider currentTimezoneProvider, string? timeZone) + { + var parentScope = currentTimezoneProvider.TimeZone; + currentTimezoneProvider.TimeZone = timeZone; + + return new DisposeAction>(static (state) => + { + var (currentTimezoneProvider, parentScope) = state; + currentTimezoneProvider.TimeZone = parentScope; + }, (currentTimezoneProvider, parentScope)); + } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/IClientTimezoneProvider.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/IClientTimezoneProvider.cs new file mode 100644 index 00000000000..69c2835adc1 --- /dev/null +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/IClientTimezoneProvider.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.Timing; + +public interface ICurrentTimezoneProvider +{ + string? TimeZone { get; set; } +} diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/IClock.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/IClock.cs index 33db4b45a8a..29f16a749ae 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/IClock.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/IClock.cs @@ -25,4 +25,11 @@ public interface IClock /// DateTime to be normalized. /// Normalized DateTime DateTime Normalize(DateTime dateTime); + + /// + /// Converts given to user's time zone. + /// + /// DateTime to be normalized. + /// Converted DateTime + DateTime Convert(DateTime dateTime); } From 6dab00a98357c03c1c5507ec5320ae3d17797f12 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 28 Feb 2025 12:00:04 +0800 Subject: [PATCH 11/15] Filter IANA timezones to include only those with a '/' in their names --- .../Volo/Abp/Timing/TZConvertTimezoneProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs index 70d19090e28..e2ec99a6b00 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs @@ -15,7 +15,7 @@ public virtual List GetWindowsTimezones() public virtual List GetIanaTimezones() { - return TZConvert.KnownIanaTimeZoneNames.OrderBy(x => x).Select(x => new NameValue(x, x)).ToList(); + return TZConvert.KnownIanaTimeZoneNames.OrderBy(x => x).Where(x => x.Contains("/")).Select(x => new NameValue(x, x)).ToList(); } public virtual string WindowsToIana(string windowsTimeZoneId) From 185c3df2e6014ff127fbb53d0ce5f746eb6e377b Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 28 Feb 2025 12:04:42 +0800 Subject: [PATCH 12/15] Rename `new-datepicker` to `hidden-datepicker`. --- .../date-range-picker/date-range-picker-extensions.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js index ae58704f379..08f3664c23a 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js @@ -756,7 +756,7 @@ $.fn.handleDatepicker = function (datepickerSelector) { var $this = $(this); var datepickers = $this.find(datepickerSelector); - $this.find('input[class="new-datepicker"]').remove(); + $this.find('input[class="hidden-datepicker"]').remove(); datepickers.each(function () { var $this = $(this); @@ -769,15 +769,15 @@ var datepicker = $this.data('daterangepicker'); if (datepicker.singleDatePicker) { var startDate = abp.clock.normalizeToString(datepicker.startDate.toDate()); - var startDateInput = $('').attr('type', 'hidden').attr('name', name).val(startDate).addClass('new-datepicker'); + var startDateInput = $('').attr('type', 'hidden').attr('name', name).val(startDate).addClass('hidden-datepicker'); $this.after(startDateInput); } else { var startDate = abp.clock.normalizeToString(datepicker.startDate.toDate()); - var startDateInput = $('').attr('type', 'hidden').attr('name', name).val(startDate).addClass('new-datepicker'); + var startDateInput = $('').attr('type', 'hidden').attr('name', name).val(startDate).addClass('hidden-datepicker'); $this.after(startDateInput); var endDate = abp.clock.normalizeToString(datepicker.endDate.toDate()); - var endDateInput = $('').attr('type', 'hidden').attr('name', name).val(endDate).addClass('new-datepicker'); + var endDateInput = $('').attr('type', 'hidden').attr('name', name).val(endDate).addClass('hidden-datepicker'); $this.after(endDateInput); } }); @@ -785,4 +785,4 @@ return this; }; -})(jQuery); \ No newline at end of file +})(jQuery); From 5e3fff446e712425b7d0d236c8ff6102679a1ffe Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 1 Mar 2025 10:28:04 +0800 Subject: [PATCH 13/15] Add DateTimeOffset conversion method to IClock and only normalizing DateTime in MVC. --- .../AbpAspNetCoreMvcNewtonsoftModule.cs | 4 +- .../Mvc/Json/MvcCoreBuilderExtensions.cs | 8 +++ .../ClientProxyRequestPayloadBuilder.cs | 28 ++++++++-- .../ClientProxying/ClientProxyUrlBuilder.cs | 21 ++++++-- .../Json/Newtonsoft/AbpDateTimeConverter.cs | 22 ++++++-- .../Newtonsoft/AbpJsonNewtonsoftModule.cs | 3 +- .../Newtonsoft/AbpNewtonsoftJsonSerializer.cs | 3 +- .../AbpJsonSystemTextJsonModule.cs | 13 +++-- .../JsonConverters/AbpDateTimeConverter.cs | 22 ++++++-- .../AbpNullableDateTimeConverter.cs | 22 ++++++-- .../Volo.Abp.Timing/Volo/Abp/Timing/Clock.cs | 12 +++++ .../Volo.Abp.Timing/Volo/Abp/Timing/IClock.cs | 7 +++ .../Abp/Json/AbpJsonNewtonsoftTestModule.cs | 26 --------- ...TextJsonTestBase.cs => AbpJsonTestBase.cs} | 0 .../Volo/Abp/Json/AbpJsonTestModule.cs | 53 +++++++++++++++++++ 15 files changed, 186 insertions(+), 58 deletions(-) delete mode 100644 framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonNewtonsoftTestModule.cs rename framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/{AbpJsonSystemTextJsonTestBase.cs => AbpJsonTestBase.cs} (100%) create mode 100644 framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestModule.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.NewtonsoftJson/Volo/Abp/AspNetCore/Mvc/NewtonsoftJson/AbpAspNetCoreMvcNewtonsoftModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.NewtonsoftJson/Volo/Abp/AspNetCore/Mvc/NewtonsoftJson/AbpAspNetCoreMvcNewtonsoftModule.cs index d213c9f451f..95e07a06e01 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.NewtonsoftJson/Volo/Abp/AspNetCore/Mvc/NewtonsoftJson/AbpAspNetCoreMvcNewtonsoftModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.NewtonsoftJson/Volo/Abp/AspNetCore/Mvc/NewtonsoftJson/AbpAspNetCoreMvcNewtonsoftModule.cs @@ -16,7 +16,9 @@ public override void ConfigureServices(ServiceConfigurationContext context) context.Services.AddOptions() .Configure((options, rootServiceProvider) => { - options.SerializerSettings.ContractResolver = new AbpCamelCasePropertyNamesContractResolver(rootServiceProvider.GetRequiredService()); + options.SerializerSettings.ContractResolver = + new AbpCamelCasePropertyNamesContractResolver(rootServiceProvider + .GetRequiredService()); }); } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs index 8c6edd2eb08..f2bac813161 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Options; using Volo.Abp.Json.SystemTextJson; using Volo.Abp.Json.SystemTextJson.JsonConverters; +using Volo.Abp.Json.SystemTextJson.Modifiers; namespace Volo.Abp.AspNetCore.Mvc.Json; @@ -26,6 +27,13 @@ public static IMvcCoreBuilder AddAbpJson(this IMvcCoreBuilder builder) options.JsonSerializerOptions.TypeInfoResolver = new AbpDefaultJsonTypeInfoResolver(rootServiceProvider .GetRequiredService>()); + + var dateTimeConverter = rootServiceProvider.GetRequiredService(); + var nullableDateTimeConverter = rootServiceProvider.GetRequiredService(); + + options.JsonSerializerOptions.TypeInfoResolver.As().Modifiers.Add( + new AbpDateTimeConverterModifier(dateTimeConverter, nullableDateTimeConverter) + .CreateModifyAction()); }); return builder; diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs index c24404918e5..91fc6f33bf5 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs @@ -15,6 +15,7 @@ using Volo.Abp.Http.Modeling; using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Json; +using Volo.Abp.Timing; namespace Volo.Abp.Http.Client.ClientProxying; @@ -33,9 +34,15 @@ static ClientProxyRequestPayloadBuilder() protected AbpHttpClientProxyingOptions HttpClientProxyingOptions { get; } - public ClientProxyRequestPayloadBuilder(IServiceScopeFactory serviceScopeFactory, IOptions httpClientProxyingOptions) + protected IClock Clock { get; } + + public ClientProxyRequestPayloadBuilder( + IServiceScopeFactory serviceScopeFactory, + IOptions httpClientProxyingOptions, + IClock clock) { ServiceScopeFactory = serviceScopeFactory; + Clock = clock; HttpClientProxyingOptions = httpClientProxyingOptions.Value; } @@ -156,12 +163,12 @@ public ClientProxyRequestPayloadBuilder(IServiceScopeFactory serviceScopeFactory { foreach (var item in (IEnumerable) value) { - formData.Add(new StringContent(item.ToString()!, Encoding.UTF8), parameter.Name); + formData.Add(new StringContent(await ConvertValueToStringAsync(item), Encoding.UTF8), parameter.Name); } } else { - formData.Add(new StringContent(value.ToString()!, Encoding.UTF8), parameter.Name); + formData.Add(new StringContent(await ConvertValueToStringAsync(value), Encoding.UTF8), parameter.Name); } } @@ -172,4 +179,19 @@ protected virtual async Task>> ObjectToFo { return await converter.ConvertAsync(actionApiDescription, parameterApiDescription, value); } + + protected virtual Task ConvertValueToStringAsync(object value) + { + if (value is DateTime dateTimeValue) + { + if (Clock.SupportsMultipleTimezone || dateTimeValue.Kind == DateTimeKind.Utc) + { + return Task.FromResult(dateTimeValue.ToUniversalTime().ToString("O")); + } + + return Task.FromResult(dateTimeValue.ToString("yyyy-MM-ddTHH:mm:ss.fffffff").TrimEnd('0').TrimEnd('.')); + } + + return Task.FromResult(value.ToString()!); + } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs index 65703dc56bc..5be598af204 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs @@ -14,6 +14,7 @@ using Volo.Abp.Http.Modeling; using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Localization; +using Volo.Abp.Timing; namespace Volo.Abp.Http.Client.ClientProxying; @@ -36,11 +37,16 @@ static ClientProxyUrlBuilder() protected IServiceScopeFactory ServiceScopeFactory { get; } protected AbpHttpClientProxyingOptions HttpClientProxyingOptions { get; } + protected IClock Clock { get; } - public ClientProxyUrlBuilder(IServiceScopeFactory serviceScopeFactory, IOptions httpClientProxyingOptions) + public ClientProxyUrlBuilder( + IServiceScopeFactory serviceScopeFactory, + IOptions httpClientProxyingOptions, + IClock clock) { ServiceScopeFactory = serviceScopeFactory; HttpClientProxyingOptions = httpClientProxyingOptions.Value; + Clock = clock; } public async Task GenerateUrlWithParametersAsync(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) @@ -218,13 +224,18 @@ protected virtual async Task AddQueryStringParameterAsync( return true; } - protected virtual Task ConvertValueToStringAsync(object? value) + protected virtual Task ConvertValueToStringAsync(object value) { if (value is DateTime dateTimeValue) { - return Task.FromResult(dateTimeValue.ToUniversalTime().ToString("O"))!; + if (Clock.SupportsMultipleTimezone || dateTimeValue.Kind == DateTimeKind.Utc) + { + return Task.FromResult(dateTimeValue.ToUniversalTime().ToString("O")); + } + + return Task.FromResult(dateTimeValue.ToString("yyyy-MM-ddTHH:mm:ss.fffffff").TrimEnd('0').TrimEnd('.')); } - return Task.FromResult(value?.ToString()); + return Task.FromResult(value.ToString()!); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs index effda400572..c95660e4849 100644 --- a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpDateTimeConverter.cs @@ -19,6 +19,7 @@ public class AbpDateTimeConverter : DateTimeConverterBase, ITransientDependency private readonly CultureInfo _culture = CultureInfo.InvariantCulture; private readonly IClock _clock; private readonly AbpJsonOptions _options; + private bool _skipDateTimeNormalization; public AbpDateTimeConverter(IClock clock, IOptions options) { @@ -26,6 +27,12 @@ public AbpDateTimeConverter(IClock clock, IOptions options) _options = options.Value; } + public virtual AbpDateTimeConverter SkipDateTimeNormalization() + { + _skipDateTimeNormalization = true; + return this; + } + public override bool CanConvert(Type objectType) { return objectType == typeof(DateTime) || objectType == typeof(DateTime?); @@ -46,7 +53,7 @@ public override bool CanConvert(Type objectType) if (reader.TokenType == JsonToken.Date) { - return _clock.Normalize(reader.Value!.To()); + return Normalize(reader.Value!.To()); } if (reader.TokenType != JsonToken.String) @@ -67,20 +74,20 @@ public override bool CanConvert(Type objectType) { if (DateTime.TryParseExact(dateText, format, _culture, _dateTimeStyles, out var d1)) { - return _clock.Normalize(d1); + return Normalize(d1); } } } var date = DateTime.Parse(dateText!, _culture, _dateTimeStyles); - return _clock.Normalize(date); + return Normalize(date); } public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { if (value != null) { - value = _clock.Normalize(value.To()); + value = Normalize(value.To()); } if (value is DateTime dateTime) @@ -111,4 +118,11 @@ static internal bool ShouldNormalize(MemberInfo member, JsonProperty property) return ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(member) == null; } + + protected virtual DateTime Normalize(DateTime dateTime) + { + return _skipDateTimeNormalization + ? dateTime + : _clock.Normalize(dateTime); + } } diff --git a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpJsonNewtonsoftModule.cs b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpJsonNewtonsoftModule.cs index 6f93a6c54e3..ea35831d861 100644 --- a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpJsonNewtonsoftModule.cs +++ b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpJsonNewtonsoftModule.cs @@ -13,7 +13,8 @@ public override void ConfigureServices(ServiceConfigurationContext context) context.Services.AddOptions() .Configure((options, rootServiceProvider) => { - options.JsonSerializerSettings.ContractResolver = new AbpCamelCasePropertyNamesContractResolver(rootServiceProvider.GetRequiredService()); + options.JsonSerializerSettings.ContractResolver = new AbpCamelCasePropertyNamesContractResolver( + rootServiceProvider.GetRequiredService().SkipDateTimeNormalization()); }); } } diff --git a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializer.cs b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializer.cs index 0450064f2f4..8184ee6d45c 100644 --- a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializer.cs +++ b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpNewtonsoftJsonSerializer.cs @@ -86,7 +86,8 @@ protected virtual JsonSerializerSettings CreateJsonSerializerOptions(bool camelC if (!camelCase) { //Default contract resolver is AbpCamelCasePropertyNamesContractResolver} - settings.ContractResolver = new AbpDefaultContractResolver(RootServiceProvider.GetRequiredService()); + settings.ContractResolver = new AbpDefaultContractResolver(RootServiceProvider + .GetRequiredService().SkipDateTimeNormalization()); } if (indented) diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs index 498dedd8cd4..0a5066cec1c 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs @@ -29,14 +29,13 @@ public override void ConfigureServices(ServiceConfigurationContext context) options.JsonSerializerOptions.TypeInfoResolver = new AbpDefaultJsonTypeInfoResolver(rootServiceProvider .GetRequiredService>()); - }); - context.Services.AddOptions() - .Configure((options, rootServiceProvider) => - { - options.Modifiers.Add(new AbpDateTimeConverterModifier( - rootServiceProvider.GetRequiredService(), - rootServiceProvider.GetRequiredService()).CreateModifyAction()); + var dateTimeConverter = rootServiceProvider.GetRequiredService().SkipDateTimeNormalization(); + var nullableDateTimeConverter = rootServiceProvider.GetRequiredService().SkipDateTimeNormalization(); + + options.JsonSerializerOptions.TypeInfoResolver.As().Modifiers.Add( + new AbpDateTimeConverterModifier(dateTimeConverter, nullableDateTimeConverter) + .CreateModifyAction()); }); } } diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs index a8653bb54a9..3feea8d2996 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpDateTimeConverter.cs @@ -13,6 +13,7 @@ public class AbpDateTimeConverter : JsonConverter, ITransientDependenc { private readonly IClock _clock; private readonly AbpJsonOptions _options; + private bool _skipDateTimeNormalization; public AbpDateTimeConverter(IClock clock, IOptions abpJsonOptions) { @@ -20,6 +21,12 @@ public AbpDateTimeConverter(IClock clock, IOptions abpJsonOption _options = abpJsonOptions.Value; } + public virtual AbpDateTimeConverter SkipDateTimeNormalization() + { + _skipDateTimeNormalization = true; + return this; + } + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (_options.InputDateTimeFormats.Any()) @@ -31,7 +38,7 @@ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, Jso var s = reader.GetString(); if (DateTime.TryParseExact(s, format, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d1)) { - return _clock.Normalize(d1); + return Normalize(d1); } } } @@ -43,7 +50,7 @@ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, Jso if (reader.TryGetDateTime(out var d3)) { - return _clock.Normalize(d3); + return Normalize(d3); } var dateText = reader.GetString(); @@ -51,7 +58,7 @@ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, Jso { if (DateTime.TryParse(dateText, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d4)) { - return _clock.Normalize(d4); + return Normalize(d4); } } @@ -62,11 +69,16 @@ public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializer { if (_options.OutputDateTimeFormat.IsNullOrWhiteSpace()) { - writer.WriteStringValue(_clock.Normalize(value)); + writer.WriteStringValue(Normalize(value)); } else { - writer.WriteStringValue(_clock.Normalize(value).ToString(_options.OutputDateTimeFormat, CultureInfo.CurrentUICulture)); + writer.WriteStringValue(Normalize(value).ToString(_options.OutputDateTimeFormat, CultureInfo.CurrentUICulture)); } } + + protected virtual DateTime Normalize(DateTime dateTime) + { + return _skipDateTimeNormalization ? dateTime : _clock.Normalize(dateTime); + } } diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs index e8a94892dad..e73f39d0971 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/JsonConverters/AbpNullableDateTimeConverter.cs @@ -13,6 +13,7 @@ public class AbpNullableDateTimeConverter : JsonConverter, ITransient { private readonly IClock _clock; private readonly AbpJsonOptions _options; + private bool _skipDateTimeNormalization; public AbpNullableDateTimeConverter(IClock clock, IOptions abpJsonOptions) { @@ -20,6 +21,12 @@ public AbpNullableDateTimeConverter(IClock clock, IOptions abpJs _options = abpJsonOptions.Value; } + public virtual AbpNullableDateTimeConverter SkipDateTimeNormalization() + { + _skipDateTimeNormalization = true; + return this; + } + public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (_options.InputDateTimeFormats.Any()) @@ -31,7 +38,7 @@ public AbpNullableDateTimeConverter(IClock clock, IOptions abpJs var s = reader.GetString(); if (DateTime.TryParseExact(s, format, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d1)) { - return _clock.Normalize(d1); + return Normalize(d1); } } } @@ -43,7 +50,7 @@ public AbpNullableDateTimeConverter(IClock clock, IOptions abpJs if (reader.TryGetDateTime(out var d2)) { - return _clock.Normalize(d2); + return Normalize(d2); } var dateText = reader.GetString(); @@ -51,7 +58,7 @@ public AbpNullableDateTimeConverter(IClock clock, IOptions abpJs { if (DateTime.TryParse(dateText, CultureInfo.CurrentUICulture, DateTimeStyles.None, out var d3)) { - return _clock.Normalize(d3); + return Normalize(d3); } } @@ -68,12 +75,17 @@ public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerialize { if (_options.OutputDateTimeFormat.IsNullOrWhiteSpace()) { - writer.WriteStringValue(_clock.Normalize(value.Value)); + writer.WriteStringValue(Normalize(value.Value)); } else { - writer.WriteStringValue(_clock.Normalize(value.Value).ToString(_options.OutputDateTimeFormat, CultureInfo.CurrentUICulture)); + writer.WriteStringValue(Normalize(value.Value).ToString(_options.OutputDateTimeFormat, CultureInfo.CurrentUICulture)); } } } + + protected virtual DateTime Normalize(DateTime dateTime) + { + return _skipDateTimeNormalization ? dateTime : _clock.Normalize(dateTime); + } } diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Clock.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Clock.cs index 9e012ed11c2..1da94b73242 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Clock.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/Clock.cs @@ -58,4 +58,16 @@ public virtual DateTime Convert(DateTime dateTime) var timezoneInfo = TimezoneProvider.GetTimeZoneInfo(CurrentTimezoneProvider.TimeZone); return TimeZoneInfo.ConvertTime(dateTime, timezoneInfo); } + + public virtual DateTimeOffset Convert(DateTimeOffset dateTimeOffset) + { + if (!SupportsMultipleTimezone || + CurrentTimezoneProvider.TimeZone.IsNullOrWhiteSpace()) + { + return dateTimeOffset; + } + + var timezoneInfo = TimezoneProvider.GetTimeZoneInfo(CurrentTimezoneProvider.TimeZone); + return TimeZoneInfo.ConvertTime(dateTimeOffset, timezoneInfo); + } } diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/IClock.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/IClock.cs index 29f16a749ae..3a877266555 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/IClock.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/IClock.cs @@ -32,4 +32,11 @@ public interface IClock /// DateTime to be normalized. /// Converted DateTime DateTime Convert(DateTime dateTime); + + /// + /// Converts given to user's time zone. + /// + /// DateTimeOffset to be normalized. + /// Converted DateTimeOffset + DateTimeOffset Convert(DateTimeOffset dateTimeOffset); } diff --git a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonNewtonsoftTestModule.cs b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonNewtonsoftTestModule.cs deleted file mode 100644 index 42559397da7..00000000000 --- a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonNewtonsoftTestModule.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Volo.Abp.Autofac; -using Volo.Abp.Json.Newtonsoft; -using Volo.Abp.Json.SystemTextJson; -using Volo.Abp.Modularity; - -namespace Volo.Abp.Json; - -[DependsOn( - typeof(AbpAutofacModule), - typeof(AbpJsonSystemTextJsonModule), - typeof(AbpTestBaseModule) -)] -public class AbpJsonSystemTextJsonTestModule : AbpModule -{ - -} - -[DependsOn( - typeof(AbpAutofacModule), - typeof(AbpJsonNewtonsoftModule), - typeof(AbpTestBaseModule) -)] -public class AbpJsonNewtonsoftTestModule : AbpModule -{ - -} diff --git a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonSystemTextJsonTestBase.cs b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestBase.cs similarity index 100% rename from framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonSystemTextJsonTestBase.cs rename to framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestBase.cs diff --git a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestModule.cs b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestModule.cs new file mode 100644 index 00000000000..121ec62a854 --- /dev/null +++ b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestModule.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Autofac; +using Volo.Abp.Json.Newtonsoft; +using Volo.Abp.Json.SystemTextJson; +using Volo.Abp.Json.SystemTextJson.JsonConverters; +using Volo.Abp.Json.SystemTextJson.Modifiers; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Json; + +[DependsOn( + typeof(AbpAutofacModule), + typeof(AbpJsonSystemTextJsonModule), + typeof(AbpTestBaseModule) +)] +public class AbpJsonSystemTextJsonTestModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddOptions() + .Configure((options, rootServiceProvider) => + { + if (options.JsonSerializerOptions.TypeInfoResolver != null) + { + var modifiers = options.JsonSerializerOptions.TypeInfoResolver.As().Modifiers; + modifiers.RemoveAll(x => x.Target?.GetType() == typeof(AbpDateTimeConverterModifier)); + modifiers.Add(new AbpDateTimeConverterModifier( + rootServiceProvider.GetRequiredService(), + rootServiceProvider.GetRequiredService()).CreateModifyAction()); + } + }); + } +} + +[DependsOn( + typeof(AbpAutofacModule), + typeof(AbpJsonNewtonsoftModule), + typeof(AbpTestBaseModule) +)] +public class AbpJsonNewtonsoftTestModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddOptions() + .Configure((options, rootServiceProvider) => + { + options.JsonSerializerSettings.ContractResolver = new AbpCamelCasePropertyNamesContractResolver( + rootServiceProvider.GetRequiredService()); + }); + } +} From 90f7e064ce3b2154996ef37d73baddb88a950d51 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 2 Mar 2025 09:19:03 +0800 Subject: [PATCH 14/15] Filter IANA timezones to exclude 'Etc' entries while allowing 'UTC' --- .../Volo/Abp/Timing/TZConvertTimezoneProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs index e2ec99a6b00..565c04c6fac 100644 --- a/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs +++ b/framework/src/Volo.Abp.Timing/Volo/Abp/Timing/TZConvertTimezoneProvider.cs @@ -15,7 +15,7 @@ public virtual List GetWindowsTimezones() public virtual List GetIanaTimezones() { - return TZConvert.KnownIanaTimeZoneNames.OrderBy(x => x).Where(x => x.Contains("/")).Select(x => new NameValue(x, x)).ToList(); + return TZConvert.KnownIanaTimeZoneNames.OrderBy(x => x).Where(x => x.Contains("/") && !x.Contains("Etc") || x == "UTC").Select(x => new NameValue(x, x)).ToList(); } public virtual string WindowsToIana(string windowsTimeZoneId) From aa6e83076c79091589a77013990cb0d2584a07b1 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 3 Mar 2025 18:07:54 +0800 Subject: [PATCH 15/15] Enhance date picker functionality to support date range selection and timezone normalization --- .../AbpDatePickerBaseTagHelperService.cs | 111 +++++++++++------- .../AbpDatePickerTagHelperService.cs | 24 +++- .../AbpDateRangePickerTagHelperService.cs | 34 ++++-- .../bootstrap/modal-manager.js | 2 +- .../date-range-picker-extensions.js | 27 +++-- npm/packs/core/src/abp.js | 46 ++++---- 6 files changed, 152 insertions(+), 92 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs index bd3a4abe48c..786687696ab 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerBaseTagHelperService.cs @@ -16,63 +16,28 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Button; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.Json; +using Volo.Abp.Timing; namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; public abstract class AbpDatePickerBaseTagHelperService : AbpTagHelperService where TTagHelper : AbpDatePickerBaseTagHelper { - protected readonly Dictionary> SupportedInputTypes = new() - { - { - typeof(string), o => - { - if(o is string s && DateTime.TryParse(s, out var dt)) - { - return dt.ToString("O"); - } - - return string.Empty; - } - }, - { - typeof(DateTime), o => - { - if(o is DateTime dt && dt != default) - { - return dt.ToString("O"); - } - - return string.Empty; - } - }, - {typeof(DateTime?), o => ((DateTime?) o)?.ToString("O")!}, - { - typeof(DateTimeOffset), o => - { - if(o is DateTimeOffset dto && dto != default) - { - return dto.ToString("O"); - } - - return string.Empty; - } - }, - {typeof(DateTimeOffset?), o => ((DateTimeOffset?) o)?.ToString("O")!} - }; + protected readonly Dictionary> SupportedInputTypes; protected readonly IJsonSerializer JsonSerializer; protected readonly IHtmlGenerator Generator; protected readonly HtmlEncoder Encoder; protected readonly IServiceProvider ServiceProvider; protected readonly IAbpTagHelperLocalizer TagHelperLocalizer; + protected readonly IClock Clock; protected virtual string TagName { get; set; } = "abp-date-picker"; protected IStringLocalizer L { get; } protected abstract TagHelperOutput TagHelperOutput { get; set; } protected AbpDatePickerBaseTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator, HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer l, - IAbpTagHelperLocalizer tagHelperLocalizer) + IAbpTagHelperLocalizer tagHelperLocalizer, IClock clock) { JsonSerializer = jsonSerializer; Generator = generator; @@ -80,6 +45,65 @@ protected AbpDatePickerBaseTagHelperService(IJsonSerializer jsonSerializer, IHtm ServiceProvider = serviceProvider; L = l; TagHelperLocalizer = tagHelperLocalizer; + Clock = clock; + + SupportedInputTypes = new Dictionary> + { + { + typeof(string), x => + { + if(x is string s && DateTime.TryParse(s, out var dt)) + { + return Clock.Convert(dt).ToString("O"); + } + + return string.Empty; + } + }, + { + typeof(DateTime), x => + { + if(x is DateTime dt && dt != default) + { + return Clock.Convert(dt).ToString("O"); + } + + return string.Empty; + } + }, + { + typeof(DateTime?), x => + { + if(x is DateTime dt && dt != default) + { + return Clock.Convert(dt).ToString("O"); + } + return string.Empty; + } + }, + { + typeof(DateTimeOffset), x => + { + if(x is DateTimeOffset dto && dto != default) + { + return Clock.Convert(dto).ToString("O"); + } + + return string.Empty; + } + }, + { + typeof(DateTimeOffset?), x => + { + if(x is DateTimeOffset dto && dto != default) + { + return Clock.Convert(dto).ToString("O"); + } + + return string.Empty; + } + } + }; } protected virtual T? GetAttribute() where T : Attribute @@ -233,6 +257,7 @@ protected virtual void AddGroupToFormGroupContents(TagHelperContext context, str } protected abstract int GetOrder(); + protected abstract void AddBaseTagAttributes(TagHelperAttributeList attributes); protected virtual string GetExtraInputHtml(TagHelperContext context, TagHelperOutput output) @@ -375,7 +400,7 @@ protected TagHelperAttributeList ConvertDatePickerOptionsToAttributeList(IAbpDat { attrList.Add("data-visible-date-format", options.VisibleDateFormat); } - + if(!options.InputDateFormat.IsNullOrEmpty()) { attrList.Add("data-input-date-format", options.InputDateFormat); @@ -754,7 +779,7 @@ protected virtual Task GetValidationAsHtmlAsync(TagHelperContext context { return Task.FromResult(string.Empty); } - + return GetValidationAsHtmlByInputAsync(context, output, @for); } @@ -766,7 +791,7 @@ protected virtual async Task GetValidationAsHtmlByInputAsync(TagHelperCo new ValidationMessageTagHelper(Generator) { For = @for, ViewContext = TagHelper.ViewContext }; var attributeList = new TagHelperAttributeList { { "class", "text-danger" } }; - + if(!output.Attributes.TryGetAttribute("name", out var nameAttribute) || nameAttribute == null || nameAttribute.Value == null) { if (nameAttribute != null) @@ -776,7 +801,7 @@ protected virtual async Task GetValidationAsHtmlByInputAsync(TagHelperCo nameAttribute = new TagHelperAttribute("name", "date_" + Guid.NewGuid().ToString("N")); output.Attributes.Add(nameAttribute); } - + attributeList.Add("data-valmsg-for", nameAttribute.Value); return await validationMessageTagHelper.RenderAsync(attributeList, context, Encoder, "span", diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs index 3fc2861b59d..9781a69b055 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDatePickerTagHelperService.cs @@ -8,14 +8,22 @@ using Microsoft.Extensions.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.Json; +using Volo.Abp.Timing; namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; public class AbpDatePickerTagHelperService : AbpDatePickerBaseTagHelperService { - public AbpDatePickerTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator, HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer l, IAbpTagHelperLocalizer tagHelperLocalizer) : base(jsonSerializer, generator, encoder, serviceProvider, l, tagHelperLocalizer) + public AbpDatePickerTagHelperService( + IJsonSerializer jsonSerializer, + IHtmlGenerator generator, + HtmlEncoder encoder, + IServiceProvider serviceProvider, + IStringLocalizer l, + IAbpTagHelperLocalizer tagHelperLocalizer, + IClock clock) + : base(jsonSerializer, generator, encoder, serviceProvider, l, tagHelperLocalizer, clock) { - } protected override TagHelperOutput TagHelperOutput { get; set; } = default!; @@ -42,10 +50,18 @@ public async override Task ProcessAsync(TagHelperContext context, TagHelperOutpu { InputTypeName = "hidden", ViewContext = TagHelper.ViewContext, - For = TagHelper.AspFor, + For = TagHelper.AspFor }; var attributes = new TagHelperAttributeList { { "data-date", "true" }, { "type", "hidden" } }; + + if (Clock.SupportsMultipleTimezone && TagHelper.AspFor.Model is DateTime dateTime) + { + DateTagHelper.Format = "{0:O}"; + DateTagHelper.Value = Clock.Convert(dateTime).ToString("O"); + attributes.Add("value", DateTagHelper.Value); + } + DateTagHelperOutput = await DateTagHelper.ProcessAndGetOutputAsync(attributes, context, "input"); } @@ -80,4 +96,4 @@ protected override string GetExtraInputHtml(TagHelperContext context, TagHelperO { return DateTagHelperOutput?.Render(Encoder)!; } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelperService.cs index c8ba8b3a120..b51809b63cb 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/DatePicker/AbpDateRangePickerTagHelperService.cs @@ -9,16 +9,20 @@ using Microsoft.Extensions.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; using Volo.Abp.Json; +using Volo.Abp.Timing; namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form.DatePicker; public class AbpDateRangePickerTagHelperService : AbpDatePickerBaseTagHelperService { - public AbpDateRangePickerTagHelperService(IJsonSerializer jsonSerializer, IHtmlGenerator generator, - HtmlEncoder encoder, IServiceProvider serviceProvider, IStringLocalizer l, - IAbpTagHelperLocalizer tagHelperLocalizer) : - base(jsonSerializer, generator, encoder, serviceProvider, l, - tagHelperLocalizer) + public AbpDateRangePickerTagHelperService( + IJsonSerializer jsonSerializer, IHtmlGenerator generator, + HtmlEncoder encoder, + IServiceProvider serviceProvider, + IStringLocalizer l, + IAbpTagHelperLocalizer tagHelperLocalizer, + IClock clock) : + base(jsonSerializer, generator, encoder, serviceProvider, l, tagHelperLocalizer, clock) { } @@ -42,6 +46,13 @@ public async override Task ProcessAsync(TagHelperContext context, TagHelperOutpu InputTypeName = "hidden" }; + if (Clock.SupportsMultipleTimezone && TagHelper.AspForStart.Model is DateTime dateTime) + { + StartDateTagHelper.Format = "{0:O}"; + StartDateTagHelper.Value = Clock.Convert(dateTime).ToString("O"); + startDateAttributes.Add("value", StartDateTagHelper.Value); + } + StartDateTagHelperOutput = await StartDateTagHelper.ProcessAndGetOutputAsync(startDateAttributes, context, "input"); } @@ -55,6 +66,13 @@ public async override Task ProcessAsync(TagHelperContext context, TagHelperOutpu InputTypeName = "hidden" }; + if (Clock.SupportsMultipleTimezone && TagHelper.AspForEnd.Model is DateTime dateTime) + { + EndDateTagHelper.Format = "{0:O}"; + EndDateTagHelper.Value = Clock.Convert(dateTime).ToString("O"); + endDateAttributes.Add("value", EndDateTagHelper.Value); + } + EndDateTagHelperOutput = await EndDateTagHelper.ProcessAndGetOutputAsync(endDateAttributes, context, "input"); } @@ -78,7 +96,7 @@ protected override string GetPropertyName() protected override int GetOrder() { - return TagHelper.Order; + return TagHelper.AspForStart?.Metadata.Order ?? 0; } protected override void AddBaseTagAttributes(TagHelperAttributeList attributes) @@ -92,7 +110,7 @@ protected override void AddBaseTagAttributes(TagHelperAttributeList attributes) attributes.Add("data-start-date", convert); } } - + if (TagHelper.AspForEnd?.Model != null && SupportedInputTypes.TryGetValue(TagHelper.AspForEnd.Metadata.ModelType, out var convertFuncEnd)) { @@ -113,4 +131,4 @@ protected override string GetExtraInputHtml(TagHelperContext context, TagHelperO { return TagHelper.AspForStart ?? TagHelper.AspForEnd; } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js index 9bba9ee9a96..e11a8f4d994 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/bootstrap/modal-manager.js @@ -110,7 +110,7 @@ $.validator.defaults.ignore = ''; //TODO: Would be better if we can apply only f _onOpenCallbacks.triggerAll(_publicApi); - if ($firstVisibleInput.data("datepicker")) { + if ($firstVisibleInput.data("datepicker") || $firstVisibleInput.data("daterangepicker")) { return; //don't pop-up date pickers... } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js index 08f3664c23a..29facc3ac88 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/wwwroot/libs/abp/aspnetcore-mvc-ui-theme-shared/date-range-picker/date-range-picker-extensions.js @@ -754,16 +754,14 @@ }); $.fn.handleDatepicker = function (datepickerSelector) { - var $this = $(this); + var $this = $(this); var datepickers = $this.find(datepickerSelector); - $this.find('input[class="hidden-datepicker"]').remove(); - + $this.find('input[class~="hidden-datepicker"]').remove(); datepickers.each(function () { - var $this = $(this); - if($this.val() === ''){ + var $this = $(this); + if ($this.val() === '') { return; } - var name = $this.attr('name') || $this.data('name'); $this.data('name', name).removeAttr('name'); var datepicker = $this.data('daterangepicker'); @@ -772,13 +770,16 @@ var startDateInput = $('').attr('type', 'hidden').attr('name', name).val(startDate).addClass('hidden-datepicker'); $this.after(startDateInput); } else { - var startDate = abp.clock.normalizeToString(datepicker.startDate.toDate()); - var startDateInput = $('').attr('type', 'hidden').attr('name', name).val(startDate).addClass('hidden-datepicker'); - $this.after(startDateInput); - - var endDate = abp.clock.normalizeToString(datepicker.endDate.toDate()); - var endDateInput = $('').attr('type', 'hidden').attr('name', name).val(endDate).addClass('hidden-datepicker'); - $this.after(endDateInput); + if ($this.data('start-date')) { + var startDate = abp.clock.normalizeToString(datepicker.startDate.toDate()); + var startDateInput = $('').attr('type', 'hidden').attr('name', name).val(startDate).addClass('hidden-datepicker'); + $this.after(startDateInput); + } + if ($this.data('end-date')) { + var endDate = abp.clock.normalizeToString(datepicker.endDate.toDate()); + var endDateInput = $('').attr('type', 'hidden').attr('name', name).val(endDate).addClass('hidden-datepicker'); + $this.after(endDateInput); + } } }); diff --git a/npm/packs/core/src/abp.js b/npm/packs/core/src/abp.js index aca0196c15e..c06e58631c0 100644 --- a/npm/packs/core/src/abp.js +++ b/npm/packs/core/src/abp.js @@ -81,7 +81,7 @@ var abp = abp || {}; if (resource) { return resource; } - + var legacySource = abp.localization.values[resourceName]; if (legacySource) { return { @@ -89,11 +89,11 @@ var abp = abp || {}; baseResources: [] }; } - - abp.log.warn('Could not find localization source: ' + resourceName); + + abp.log.warn('Could not find localization source: ' + resourceName); return null; }; - + abp.localization.internal.localize = function (key, sourceName) { var resource = abp.localization.internal.getResource(sourceName); if (!resource){ @@ -104,7 +104,7 @@ var abp = abp || {}; } var value = resource.texts[key]; - if (value === undefined) { + if (value === undefined) { for (var i = 0; i < resource.baseResources.length; i++){ var basedArguments = Array.prototype.slice.call(arguments, 0); basedArguments[1] = resource.baseResources[i]; @@ -114,7 +114,7 @@ var abp = abp || {}; return result; } } - + return { value: key, found: false @@ -135,7 +135,7 @@ var abp = abp || {}; if (sourceName === '_') { //A convention to suppress the localization return key; } - + if (sourceName) { return abp.localization.internal.localize.apply(this, arguments).value; } @@ -767,28 +767,28 @@ var abp = abp || {}; return date; } - if (abp.clock.supportsMultipleTimezone()) { - return dateObj.toISOString(); - } - function padZero(num) { return num < 10 ? '0' + num : num; } - function padMilliseconds(num) { - if (num < 10) return '00' + num; - if (num < 100) return '0' + num; - return num; + var addZulu = false; + if (abp.clock.supportsMultipleTimezone()) { + var timeZone = abp.clock.timeZone(); + var now = new Date(); + var formattedDate = now.toLocaleString('en-US', { timeZone: timeZone, timeZoneName: 'short' }); + var match = formattedDate.match(/GMT([+-]\d+)/); + var targetOffsetHours = match ? parseInt(match[1], 10) : 0; + var dateObj = new Date(dateObj.getTime() - (targetOffsetHours * 60 * 60 * 1000)); + addZulu = true; } - - // yyyy-MM-ddTHH:mm:ss.SSS + + // yyyy-MM-DDTHH:mm:ss return dateObj.getFullYear() + '-' + - padZero(dateObj.getMonth() + 1) + '-' + - padZero(dateObj.getDate()) + 'T' + - padZero(dateObj.getHours()) + ':' + - padZero(dateObj.getMinutes()) + ':' + - padZero(dateObj.getSeconds()) + '.' + - padMilliseconds(dateObj.getMilliseconds()); + padZero(dateObj.getMonth() + 1) + '-' + + padZero(dateObj.getDate()) + 'T' + + padZero(dateObj.getHours()) + ':' + + padZero(dateObj.getMinutes()) + ':' + + padZero(dateObj.getSeconds()) + (addZulu ? 'Z' : ''); }; // Default options for toLocaleString