When RAD is used to generate fields for DateTime the client side code generated something like below assuming that date fields are called EffectiveFrom and EffectiveTo:
In angular create-edit component class:
effectiveFrom: Date; effectiveTo: Date;
in the save method:
if (this.effectiveTo) { if (!this.product.effectiveTo) { this.product.effectiveTo = moment(this.effectiveTo).startOf('day'); } else { this.product.effectiveTo = moment(this.effectiveTo); } } else { this.product.effectiveTo = null; } ... //similar for effective from
The problem is that when we are in a different time zone (GMT) then and we want to auto populate 'Effective To' once user selects a date for Effecitve From as below:
effectiveFromChanged(newValue): void {
this.effectiveFrom = newValue;
this.effectiveTo = moment(this.effectiveFrom).add(12, 'M').toDate();
}
}
This sets the effective to 1 day before the actual expected date. For example if the user selects effective from 01/04/2020 then effective to should be set to 01/04/2021 instead of 31/03/2021.
I guess this is happening due the fact that conversation takes the timezone into account. But we do not want that! How can we over come this sort of date issues? Any option that would disable this behaviour? Maybe the date pickers should return moment object instead of a date?
Note: Clock.Provider = ClockProviders.Utc;
is set in the core module.
24 Answer(s)
-
0
We are solving this problem. If there is a result, I will reply to you.
-
0
Hello @adamphones It is because of you use
moment.toDate();
.It just creates a JavaScript date object without handling almost anything. Here is how it worksThat methods give you javascript date object. By default, almost every date method in JavaScript gives you a date/time in local time. You only get UTC if you specify UTC. But as you see it does not specify it, if you use toDate();
So you can use moment instead of javascript date. Or you can use toISOString to get UTC value from javascript date
Edit: Here is more explained document. https://css-tricks.com/everything-you-need-to-know-about-date-in-javascript/
-
0
Hi Musa,
Not sure if this answers my question... I am aware of the issue actually but I was looking for a solution. The RAD tool defines those date fields as Date object. And to be able to assign value to them I use toDate() to convert moment to date. If I use toISOString() then I will get a string representation of the date but actually I would need the date unless if I replace the Date types.
Are you planning to replace those Date type completely in the RAD tool in the future? As you can see you cannot rely on them for date operations.
This is my solution for now to overcome the problem:
let userTimezoneOffset = this.effectiveFrom.getTimezoneOffset() * 60000; let selectedRealDate = this.effectiveFrom.getTime() - userTimezoneOffset; this.effectiveTo = moment(selectedRealDate).add(this.contractProductLine.duration, 'months').toDate();
Considering above how would you fix the issue with toISOString()?
I also tried to replace data type from Date to Moment but in this case the date shows in the field as 'Invalid Date' at first load.
-
0
Sory about that. I missed to read RadTool part.
Can you please share your entities json file if it is not problem for you. It is located in
aspnet-core\AspNetZeroRadTool
folder. The file pattern is[YourNameSpace].[YourEntityName].json
. -
0
{ "IsRegenerate": false, "MenuPosition": "main", "RelativeNamespace": "Contracts", "EntityName": "Product", "EntityNamePlural": "Products", "TableName": "Products", "PrimaryKeyType": "int", "BaseClass": "AuditedEntity", "EntityHistory": true, "AutoMigration": false, "UpdateDatabase": false, "CreateUserInterface": true, "CreateViewOnly": true, "CreateExcelExport": true, "PagePermission": { "Host": false, "Tenant": false }, "Properties": [ { "Name": "EffectiveFrom", "Type": "DateTime", "MaxLength": 0, "MinLength": 0, "Range": { "IsRangeSet": false, "MinimumValue": 0, "MaximumValue": 0 }, "Required": false, "Nullable": true, "Regex": "", "UserInterface": { "AdvancedFilter": true, "List": true, "CreateOrUpdate": true } }, { "Name": "EffectiveTo", "Type": "DateTime", "MaxLength": 0, "MinLength": 0, "Range": { "IsRangeSet": false, "MinimumValue": 0, "MaximumValue": 0 }, "Required": false, "Nullable": true, "Regex": "", "UserInterface": { "AdvancedFilter": true, "List": true, "CreateOrUpdate": true } } ], "NavigationProperties": [ { "Namespace": "AdamP.Contracts", "ForeignEntityName": "ContractProduct", "IdType": "int", "IsNullable": false, "PropertyName": "ContractProductId", "DisplayPropertyName": "SupplierRef", "DuplicationNumber": 0, "RelationType": "single" }, { "Namespace": "AdamP.Contracts", "ForeignEntityName": "ContractStatusHistory", "IdType": "int", "IsNullable": true, "PropertyName": "ContractStatusHistoryId", "DisplayPropertyName": "Description", "DuplicationNumber": 0, "RelationType": "single" }, { "Namespace": "AdamP.ProductLines", "ForeignEntityName": "ProductLine", "IdType": "int", "IsNullable": false, "PropertyName": "ProductLineId", "DisplayPropertyName": "Description", "DuplicationNumber": 0, "RelationType": "single" } ], "EnumDefinitions": [] }
Here is one of the entities config.
On the client side (angular) we get angular components auto generated.
If the DateTime is set as nullable in the Rad tool the following gets generated:
//.ts effectiveTo: Date;
save(): void { this.saving = true; if (this.effectiveTo) { if (!this.test.effectiveTo) { this.test.effectiveTo = moment(this.effectiveTo).startOf('day'); } else { this.test.effectiveTo = moment(this.effectiveTo); } } else { this.test.effectiveTo = null; }
//.html
<div class="form-group"> <label for="Test_EffectiveTo">{{l("EffectiveTo")}}</label> <input class="form-control m-input" type="datetime" bsDatepicker [(ngModel)]="effectiveTo" id="Test_EffectiveTo" name="Test_EffectiveTo"> </div>
As our dates need to be nulllable how can I achive the above hack without a hack? It seems very complicated to handle nullable dates as Rad Tool generates a Date local variable first and do operation on that variable instead of using Moment. I guess there is a reason behind that decision that I wonder...
-
0
Hi @adamphones,
Sorry for our late reply, we are working on your problem and will inform you tomorrow.
-
0
It is fixed and merged to Power Tools. That fix will be included in next release. Until release, you can change it manually. Remove date properties, use the data you use on save method. Use datepicker just like that: https://github.com/aspnetzero/aspnet-zero-core/blob/844f192e2fdeca9127da0a60b2a3d0ae2847c219/angular/src/app/admin/demo-ui-components/demo-ui-date-time.component.html#L15-L23
Here is the current page for your entity:
create-or-edit-product-modal.component.ts
import { Component, ViewChild, Injector, Output, EventEmitter} from '@angular/core'; import { ModalDirective } from 'ngx-bootstrap'; import { finalize } from 'rxjs/operators'; import { ProductsServiceProxy, CreateOrEditProductDto } from '@shared/service-proxies/service-proxies'; import { AppComponentBase } from '@shared/common/app-component-base'; import * as moment from 'moment'; @Component({ selector: 'createOrEditProductModal', templateUrl: './create-or-edit-product-modal.component.html' }) export class CreateOrEditProductModalComponent extends AppComponentBase { @ViewChild('createOrEditModal', { static: true }) modal: ModalDirective; @Output() modalSave: EventEmitter<any> = new EventEmitter<any>(); active = false; saving = false; product: CreateOrEditProductDto = new CreateOrEditProductDto(); constructor( injector: Injector, private _productsServiceProxy: ProductsServiceProxy ) { super(injector); } show(productId?: number): void { if (!productId) { this.product = new CreateOrEditProductDto(); this.product.id = productId; this.product.effectiveFrom = moment().startOf('day'); this.product.effectiveTo = moment().startOf('day'); this.active = true; this.modal.show(); } else { this._productsServiceProxy.getProductForEdit(productId).subscribe(result => { this.product = result.product; this.active = true; this.modal.show(); }); } } save(): void { this.saving = true; this._productsServiceProxy.createOrEdit(this.product) .pipe(finalize(() => { this.saving = false;})) .subscribe(() => { this.notify.info(this.l('SavedSuccessfully')); this.close(); this.modalSave.emit(null); }); } close(): void { this.active = false; this.modal.hide(); } }
create-or-edit-product-modal.component.html
<div bsModal #createOrEditModal="bs-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="createOrEditModal" aria-hidden="true" [config]="{backdrop: 'static'}"> <div class="modal-dialog modal-lg"> <div class="modal-content"> <form *ngIf="active" #productForm="ngForm" novalidate (ngSubmit)="save()" autocomplete="off"> <div class="modal-header"> <h4 class="modal-title"> <span *ngIf="product.id">{{l("EditProduct")}}</span> <span *ngIf="!product.id">{{l("CreateNewProduct")}}</span> </h4> <button type="button" class="close" (click)="close()" aria-label="Close" [disabled]="saving"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <div class="form-group"> <label for="Product_EffectiveFrom">{{l("EffectiveFrom")}}</label> <input class="form-control m-input" type="datetime" bsDatepicker datePickerMomentModifier [(date)]="product.effectiveFrom" id="Product_EffectiveFrom" name="Product_EffectiveFrom"> </div> <div class="form-group"> <label for="Product_EffectiveTo">{{l("EffectiveTo")}}</label> <input class="form-control m-input" type="datetime" bsDatepicker datePickerMomentModifier [(date)]="product.effectiveTo" id="Product_EffectiveTo" name="Product_EffectiveTo"> </div> </div> <div class="modal-footer"> <button [disabled]="saving" type="button" class="btn btn-default" (click)="close()">{{l("Cancel")}}</button> <button type="submit" class="btn btn-primary blue" [disabled]="!productForm.form.valid" [buttonBusy]="saving" [busyText]="l('SavingWithThreeDot')"><i class="fa fa-save"></i> <span>{{l("Save")}}</span></button> </div> </form> </div> </div> </div>
-
0
It seems very complicated to handle nullable dates as Rad Tool generates a Date local variable first and do operation on that variable instead of using Moment. I guess there is a reason behind that decision that I wonder
Datepicker does not work with moment. We fixed in 7.2.0 with
datePickerMomentModifier
but missed to change that part of Power Tools. Sorry about that. -
0
Nope! <div class="form-group"> <label for="Product_DateTo">{{l("DateTo")}} *</label> <input required class="form-control m-input" type="datetime" [bsConfig]="{dateInputFormat: 'MM/YYYY', adaptivePosition: true}" (onShown)="onOpenCalendar($event)" bsDatepicker datePickerMomentModifier [(date)]="product.dateTo" id="Product_DateTo" name="Product_DateTo"> </div>
This a month selection field. When 05/2020 is selected that date (2020-05-01) should be sent back as payload. Not taking into account of user location time zone and changing it to utc with toIsoString(). As a result the date ends up in the server as "2020-04-30".
I understand that this could be an issue when the user selects times. But what is the intention here is to allow user to select a month or even a specific date. Ignore the time completely. So what ever the user selects that is the date or month should be sent back to the server by ignoring the timezone of the user.
-
0
Hi @adamphones
Sorry I didn't saw you have mentioned using
Clock.Provider = ClockProviders.Utc;
. In that case, all dates sent from client to server will be converted to UTC. If you send the date to server in2020-05-01T00:00:000Z
format, it will not be modified. So, you must specify that the date you send is already in UTC format.You can do it like this using moment;
moment("2020-05").format(moment.defaultFormatUtc)
-
0
Hi Ismail,
Thank you coming back.
AFAIK that conversion is happening in the service-proxies.ts and I don't think I should touch that at all... Can you please be more specific as this causing us big issue.
html:
<div class="form-group"> <label for="DateTo">{{l("DateTo")}} *</label> <input required class="form-control m-input" type="datetime" [bsConfig]="{format: 'YYYY-MM-DD', dateInputFormat: 'YYYY-MM', adaptivePosition: true}" (onShown)="onOpenCalendar($event)" bsDatepicker datePickerMomentModifier [(date)]="product.dateTo" id="DateTo" name="DateTo"> </div> ts:
`save(): void { this.saving = true;
this._productServiceProxy.createOrEdit(this.product) .pipe(finalize(() => { this.saving = false;})) .subscribe(() => { this.notify.info(this.l('SavedSuccessfully')); this.close(); this.modalSave.emit(null); });}
In service-proxies.ts file (which generated by nswag) I can see the following:toJSON(data?: any) { data = typeof data === 'object' ? data : {}; data["description"] = this.description; data["dateTo"] = this.dateTo ? this.dateTo.toISOString() : <any>undefined; data["postedDate"] = this.postedDate ? this.postedDate.toISOString() : <any>undefined; data["id"] = this.id; return data; }
When DateTo field selected as "2020-05" and save button is hit. the payload becomes as below due the fact that client time zone is GMT+1 and toISOString() in the service-proxies.ts converts that to utc time.
Request Payload:So what do we change to make sure the date goes back as selected date instead of taking into account time zone?
-
0
Hi,
I can thin of two options;
- set the related date field right before calling
this._productServiceProxy.createOrEdit(this.product)
in your page.
this.product.dateTo = moment(moment("2020-05").format(moment.defaultFormatUtc)); this._productServiceProxy.createOrEdit(this.product)
- You can stop using
ClockProviders.Utc
if you don't want AspNet Zero to modify your dates between server-client communication.
- set the related date field right before calling
-
0
I tried option 2 already and i didn't see any change that dates still gets converted to utc. Is there any process that I should be running to prevent nswag to generate toISOString() after commenting out ClockProviders.Utc ?
-
0
Neither of the solution works.
this.product.dateTo type is
moment.Moment
. I cannot have operation without wrapping it in a moment. when I call this.product.dateTo.utc() I get excecption as "utc is not a function".Then when I wrap it in a moment as below:
moment(this.product.dateTo)
As soon as I wrap it in moment then the date becomes previous day.
I tried to remove ClockProviders.Utc as well with no success.
console.log(this.product.dateTo); console.log(moment(this.product.dateTo).utc(true).format()); console.log(moment(this.product.dateTo).utcOffset(0, false).format()); console.log(moment(this.product.dateTo).utc(true).format(moment.defaultFormatUtc)); console.log(moment(this.product.dateTo).format(moment.defaultFormatUtc));
here is the output
Wed Apr 01 2020 00:00:00 GMT+0100 (British Summer Time) 2020-03-31T23:00:00Z 2020-03-31T23:00:00Z 2020-03-31T23:00:00Z 2020-03-31T23:00:00Z
We really need a fix for this...
-
0
Hi,
We override toIsoString here https://github.com/aspnetzero/aspnet-zero-core/blob/dev/angular/src/AppPreBootstrap.ts#L201. Maybe you can modify this part to achieve what you want. But, this code is not used if you don't use UtcClockProvider.
Can you share your project with [email protected] if you can't solve this problem ? If you can, please explain steps to reproduce the problem for us to test.
Thanks,
-
0
Hi Ismail,
The poject is quite big to send by email. However, it is very simple to reproduce the issue on your side as well. If you use RAD tool to add an entity (product) with a property DateTo (DateTime not null) then change your timezone on your machine to : "British Summer Time". Then try to add a product with datetime selected "01/05/2020". Once you hit the save then the date gets saved in the database as "2020-04-30 23:00:00.0000000". I don't want that. The date should be saved in the database as it is selected by not taking into account user timezone. it should just ignore client time zone and it should treat the selected date as UTC time and save it as it is.
I tried to add "Clock.Provider = ClockProviders.Utc;" in the core project but with no success.
Note that the screenshot are taken from freshly downloaded demo project from aspzero and only added Product Entity by using the Rad Tool.
-
0
Hi @adamphones
Thank you for the explanation. We are working on this and inform you soon.
-
0
-
0
-
0
I could not reproduce it. Can you please share recurring project to [email protected]
-
0
Hi @demirmusa,
I sent the project to the email. Can you please have a look at this issue? It is causing us a lot of trouble...
Regards,
-
0
Hi,
Could you change configureMoment in AppPreBootstrap.ts like below;
private static configureMoment() { moment.locale(new LocaleMappingService().map('moment', abp.localization.currentLanguage.name)); (window as any).moment.locale(new LocaleMappingService().map('moment', abp.localization.currentLanguage.name)); if (abp.clock.provider.supportsMultipleTimezone) { moment.tz.setDefault(abp.timing.timeZoneInfo.iana.timeZoneId); (window as any).moment.tz.setDefault(abp.timing.timeZoneInfo.iana.timeZoneId); } else { moment.fn.toJSON = function () { return this.locale('en').format(); }; moment.fn.toISOString = function () { return this.locale('en').format(); }; Date.prototype.toISOString = function () { return moment(this).locale('en').format(); }; } }
-
0
Hi Ismail,
I am not sure what I am missing here and also wondering whether no one else expereineced this issue...
Sure you can agree with me that time is crucial for systems.
I will put it in this way: Here are what we are trying to achive:
Rule 1 : Capture any datetime (for audit purposes creation time modification time etc) in UTC time regardless of users timezone. (I believe this is achieved by setting the
Clock.Provider = ClockProviders.Utc;
in core module. Rule 2 : If user selects a Date ( no time just the date or even a month selection) that date should be sent back to the server as it is without converting the date to utc. If user selects a date as 2020-05-01 and the user in British Summer Time zone that date must NOT be sent back to the server as 2020-04-30T11:00. This is wrong!What we tried:
As we want the capture the times in UTC we anable
Clock.Provider = ClockProviders.Utc;
. Then you have a selection appears under Settings. What that option should be set to? default is UTC. But do you think we should set that to Servers time zone? I tried both GMT and UTC. Both gives me the same result. What is the purpose of this option and how it should be used?I tried your code in AppPreBootstrap.ts and nothing has changed.
The only way I could make the code work as I wanted is as below (I have to say that I think there must be a better way for step 3)
1- Set:
Clock.Provider = ClockProviders.Utc;
in core mode 2- Change default time zone under "Settings" to GMT time zone 3- Change client side code before saving asthis.product.dateTo = moment(moment(this.product.dateTo).format(moment.defaultFormatUtc));
(Not sure why I have to wrap this.product.dateTo in a 'moment' even though the type of it is moment.Moment. Without wrapping you can't call format..).I just don't want to move on because I found a solution that works for me. I'd better understand why it needs the step 3? and how can we overcome that part.
-
0
Hi @adamphones
I think this is a braeking change with the latest version of moment or nswag, I couldn't find which one yet. Below code block in your project was responsible for formatting date values;
moment.fn.toISOString = function () { return this.locale('en').format(); };
But, this is not working anymore. Instead, below line worked for me;
Date.prototype.toISOString = function () { return moment(this).locale('en').format(); };