Base solution for your next web application
Open Closed

TimeZone Issue #4946


User avatar
0
cangunaydin created

Hello, I am using the latest asp.net core and angular .net framework v4.6 version. I have a weird problem when i try to use itimezonconverter. I am getting an exception from the application that says.

System.ArgumentException: The conversion could not be completed because the supplied DateTime did not have the Kind property set correctly. For example, when the Kind property is DateTimeKind.Local, the source time zone must be TimeZoneInfo.Local.

Do you know what can cause this problem? It is also doing the same thing in auditlogservice. I have set the clockprovider to utc. and my timezone settings is utc +1 stockholm/berlin


15 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    @cangunaydin,

    Can you share the related code ? I couldn't think of a case when the kind property is not set for a datetime.

    Seeing your code can help us.

  • User Avatar
    0
    cangunaydin created

    Of course, Sorry my bad i didn't give detailed information about it. I am facing this issue in my custom class but also i have the same problem when you want the export audit logs to excel file. In the AuditLogAppService,

    Task<FileDto> GetAuditLogsToExcel(GetAuditLogsInput input)
    

    within this method you are calling

    FileDto ExportToFile(List<AuditLogListDto> auditLogListDtos)
    

    and inside that method you use

    AddObjects(
                            sheet, 2, auditLogListDtos,
                            _ => _timeZoneConverter.Convert(_.ExecutionTime, _abpSession.TenantId, _abpSession.GetUserId()),
                            _ => _.UserName,
                            _ => _.ServiceName,
                            _ => _.MethodName,
                            _ => _.Parameters,
                            _ => _.ExecutionDuration,
                            _ => _.ClientIpAddress,
                            _ => _.ClientName,
                            _ => _.BrowserInfo,
                            _ => _.Exception.IsNullOrEmpty() ? L("Success") : _.Exception
                            );
    

    here _timeZoneConverter.Convert method has a problem and giving the same exception.

    My code is sth like this, very similar to above code.

    var beginDate = _offer.OfferItems.Min(o => o.Dates.Min(od => od.BeginDate));
                var endDate = _offer.OfferItems.Max(o => o.Dates.Max(od => od.EndDate));
                var newDate=_timeZoneConverter.Convert(beginDate, _abpSession.TenantId, _abpSession.UserId.Value);
    

    the values are coming from db and their kind property is set to UTC when i debug it. Probably some setting issue because when i download the aspnetzero template from the scratch it doesn't do the same thing. This thing happens when the user setting have changed to different timezone in my application rather than utc. I was thinking maybe same thing happen to someone else so it can be related with some kind of settings or sth i forgot.

  • User Avatar
    0
    cangunaydin created

    Hello @ismcagdas, i think i have figured it out, i think there is a problem with timezoneconverter nuget package. when i update it from 2.3.1 to 2.3.2 the problem has disappeared at least it doesn't give me any error. If somebody face the same situtation it can help.

  • User Avatar
    0
    cangunaydin created

    By the way another question that is related with this. Let me give an example i set the server to use utc settings so we are assuming everything that the server holds is in utc format. For ex if if choose utc +1 for the user settings. When i choose the user settings as utc+1 and send a date like 208-04-05 00:00:00 i am expecting the server should store it like 2018-04-04 23:00:00 but instead of that it stores as 2018-04-04 22:00:00 it doesn't effect anything for me now cause what i want is have the same values on the client so what i have seen in client is the same as the first value. But what is the cause of this do you have any idea?

  • User Avatar
    0
    ismcagdas created
    Support Team

    @cangunaydin,

    We also updated timezoneconverter to 2.3.2 on ABP. v2.3.1 has a bug for UTC timezone. It will in the next release of ABP and AspNet Zero.

    When you send a date like "2018-04-05 00:00:00" to server, ASP.NET considers it as the server's local timezone, so it is converted to UTC according to server's timezone.

    When using UtcClockProvider, you need to send the date string including the current user's timezone. moment-timezone library can handle this for you use it like below;

    moment(date).format()
    

    The datepickers used in AspNet Zero works with moment-timezone and moment libaries and they return the date values with current user's timezone.

    If you can share how do you get the values of dates from user, we can take a look.

  • User Avatar
    0
    cangunaydin created

    Hello @ismcagdas thanks for the help.

    then maybe there is no problem, my server settings are utc+1 stockholm and user setttings are the same utc+1 stockholm. When i try to send a date it sends it like this. "2018-04-09T22:00:00.000Z"

    when i select utc+1 in user settings is it also calculating the server local settings and giving the timezone utc+2 to moment-timezone? so when it is converted in the server side it goes directly to db as utc value 22:00:00 according to server?

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    I think it should be "2018-04-09T23:00:00.000Z". Can you write below code to your browser console when you are browsing your AspNet Zero project and share it's result with us ?

    moment().format()
    
  • User Avatar
    0
    cangunaydin created

    Hello @ismcagdas it gives me 2018-04-10T08:07:10+02:00

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    I think your problem is on the client side. The problem is where you get the value "2018-04-09T22:00:00.000Z". It seems like it is calculated according to user's browser timezone, not the one you select on my settings page.

    Can you share how do you get the value of the date from user ? Are you using one of the DatePickers in AspNet Zero ? It would be great if you can share both html and javascript codes.

  • User Avatar
    0
    cangunaydin created

    Hello @ismcagdas i have modified the daterangepicker little bit. Maybe that's why i am having an issue with timezones. Maybe you can look at it and say what can be the problem over here. Here i am pasting the code for date-range-picker.component.ts

    import { Component, AfterViewInit, ElementRef, ViewChild, Injector, Input, Output, EventEmitter } from '@angular/core';
    import { AppComponentBase } from '@shared/common/app-component-base';
    
    import * as moment from 'moment';
    
    @Component({
        selector: 'date-range-picker',
        template:
            `&lt;input #DateRangePicker type=&quot;text&quot; class=&quot;form-control&quot; [disabled]=&quot;isDisabled&quot;/&gt;`
    })
    export class DateRangePickerComponent extends AppComponentBase implements AfterViewInit {
    
        @ViewChild('DateRangePicker') dateRangePickerElement: ElementRef;
    
        _startDate: moment.Moment = moment().startOf('day');
        _endDate: moment.Moment = moment().startOf('day');
    
        @Input() isDisabled = false;
        @Input() allowFutureDate = false;
        @Input() isSingleDatePicker = false;
         
        @Input() dateRangePickerOptions: any = undefined;
        @Input() createDefaultRanges: boolean = true;
    
    
        @Output() startDateChange = new EventEmitter();
        @Output() endDateChange = new EventEmitter();
    
        @Output() applyButtonClicked = new EventEmitter();
        @Output() closed = new EventEmitter();
        @Output() pickerShown = new EventEmitter();
    
        @Input()
        get startDate() {
            return this._startDate;
        }
    
        set startDate(val) {
            this._startDate = val;
            var $dateRangePicker = $(this.dateRangePickerElement.nativeElement).data('daterangepicker');
            if ($dateRangePicker) {
                $dateRangePicker.setStartDate(this._startDate);
            }
            this.startDateChange.emit(this._startDate);
        }
    
        @Input()
        get endDate() {
            return this._endDate;
        }
    
        set endDate(val) {
            this._endDate = val;
            var $dateRangePicker = $(this.dateRangePickerElement.nativeElement).data('daterangepicker');
            if ($dateRangePicker) {
                $dateRangePicker.setEndDate(this._endDate);
            }
            this.endDateChange.emit(this._endDate);
        }
    
        constructor(
            injector: Injector,
            private _element: ElementRef
        ) {
            super(injector);
        }
    
        ngAfterViewInit(): void {
            var self = this;
            const $element = $(this.dateRangePickerElement.nativeElement);
    
            const _selectedDateRange = {
                startDate: this._startDate,
                endDate: this._endDate
            };
    
            if (!this.dateRangePickerOptions) {
                this.dateRangePickerOptions = {
                    singleDatePicker: this.isSingleDatePicker
                };
            }
           
            $element.daterangepicker(
                $.extend(true, this.createDateRangePickerOptions(), this.dateRangePickerOptions, _selectedDateRange), (start, end, label) => {
                    this.startDate = start;
                    this.endDate = end;
                });
                $element.on('show.daterangepicker',function fn(showCalEvent,picker){
                    
                    var event = {
                        ev: showCalEvent,
                        datePicker: picker
                    };
                    self.pickerShown.emit(event);
                });
                $element.on('showCalendar.daterangepicker',function (showCalEvent,picker){
                    //when 2 dates are selected
                });
                $element.on('apply.daterangepicker', function (ev, picker) {
                    var strValue=$("div.calendar.left .daterangepicker_input input[name='daterangepicker_start']").val();
                    var event = {
                        startDate: self._startDate,
                        endDate: self._endDate
                    };
                    self.applyButtonClicked.emit(event);
                });
                $element.on('hide.daterangepicker', function (ev, picker) {
                    var event = {
                        startDate: self._startDate,
                        endDate: self._endDate
                    };
                    self.closed.emit(event);
                });
        }
    
        createDateRangePickerOptions(): any {
            const self = this;
            const options: any = {
                locale: {
                    format: 'L',
                    applyLabel: self.l('Apply'),
                    cancelLabel: self.l('Cancel'),
                    customRangeLabel: self.l('CustomRange')
                },
                min: moment('2015/05/01'),
                minDate: moment('2015/05/01'),
                max: moment(),
                maxDate: moment(),
                ranges: {}
            };
            if (this.createDefaultRanges) {
                if (!this.isSingleDatePicker) {
                    if (!this.allowFutureDate) {
                        options.max = moment();
                        options.maxDate = moment();
                    }
    
                    options.ranges[self.l('Today')] = [moment().startOf('day'), moment().endOf('day')];
                    options.ranges[self.l('Yesterday')] = [moment().subtract(1, 'days').startOf('day'), moment().subtract(1, 'days').endOf('day')];
                    options.ranges[self.l('Last7Days')] = [moment().subtract(6, 'days').startOf('day'), moment().endOf('day')];
                    options.ranges[self.l('Last30Days')] = [moment().subtract(29, 'days').startOf('day'), moment().endOf('day')];
                    options.ranges[self.l('ThisMonth')] = [moment().startOf('month'), moment().endOf('month')];
                    options.ranges[self.l('LastMonth')] = [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')];
                }
            }
            return options;
        }
    }
    

    i have added options and some new events to catch and to handle them. I am using it in my page like this.

    <div class="row m--margin-bottom-10" [hidden]="!advancedFiltersAreShown">
                                <div class="col-md-6">
                                    <div class="form-group">
                                        <label>{{l("DateRange")}}</label>
                                        <date-range-picker [(startDate)]="startDate" [(endDate)]="endDate" [dateRangePickerOptions]="getDateRangePickerOptions()"></date-range-picker>
                                    </div>
                                </div>
                                <div class="col-md-6">
                                    <div class="form-group">
                                        <label class="control-label">{{l("Status")}}</label>
                                        <select name="OfferStatus" class="form-control" [(ngModel)]="status">
                                            <option value="0">{{l("All")}}</option>
                                            <option value="1">{{l("OfferStatusNew")}}</option>
                                            <option value="2">{{l("OfferStatusCustomerApproved")}}</option>
                                        </select>
                                    </div>
                                </div>
                            </div>
    

    like in the base template. Thank you for the assistance.

  • User Avatar
    0
    ismcagdas created
    Support Team

    @cangunaydin, sorry for my late response. Your changes seems fine.

    Can you check the value of "startDate" or "endDate" properties just before making your request on the client side ? It should be a moment object instead of a string value I guess.

    If you cannot figure it out, please send your project to us via email and let us check it for you.

  • User Avatar
    0
    cangunaydin created

    Hello @ismcagdas, I can of course send you the project, but i think this is not related with my code. what i get from the server is europe/berlin iana timezone which is set to my moment-timezone library. If i debug the code that you provided in abp template it does the same thing when you send the moment object, it sends with utc+2. Even abp.clock.now() gives as utc+02 and what is interesting is when i look at the list of timezones in wikipedia

    <a class="postlink" href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">https://en.wikipedia.org/wiki/List_of_t ... time_zones</a>

    it shows europe/berlin in "utc dst offset " category as utc+2, can it be related with that? maybe it gets that value somehow?

    and my code for startDate and endDate is very simple

    getOffers(event?: LazyLoadEvent) {
            var self = this;
            if (self.primengDatatableHelper.shouldResetPaging(event)) {
                self.offersPaginator.changePage(0);
    
                return;
            }
    
            self.primengDatatableHelper.showLoadingIndicator();
            
            self._offerService.getOffers(
                self.filterText,
                self.advancedFiltersAreShown ? self.startDate : undefined,
                self.advancedFiltersAreShown ? self.endDate : undefined,
                self.status != 0 ? self.status : undefined,
                self.primengDatatableHelper.getSorting(self.offers),
                self.primengDatatableHelper.getMaxResultCount(self.offersPaginator, event),
                self.primengDatatableHelper.getSkipCount(self.offersPaginator, event)
            ).subscribe(result => {
                self.primengDatatableHelper.totalRecordsCount = result.totalCount;
                self.primengDatatableHelper.records = result.items;
                self.primengDatatableHelper.hideLoadingIndicator();
            });
        }
    

    i do not change anything over there just passing the values to server like in audit logs of the template. And it is moment object. Also when i got the scratch template and run it, it gets utc+2. Do you still want me to send the project to you? I can do that if you want. Both angular and asp.net core you want i guess?

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    You are right. It seems irrelevant. As far as I checked on the web, both Berlin and Stockholm are currently in UTC+2. So, the values you have shared seems correct according to this information, see,

    <a class="postlink" href="https://www.timeanddate.com/worldclock/converted.html?p1=0&p2=239">https://www.timeanddate.com/worldclock/ ... 1=0&p2=239</a> <a class="postlink" href="https://www.timeanddate.com/worldclock/converted.html?p1=0&p2=37">https://www.timeanddate.com/worldclock/ ... p1=0&p2=37</a>

    Moment timezone library handles UTC offset changes during the year as well.

    Both Berlin and Stockholm are considered as same since they are in the same timezone. The related timezone mapping is defined in ABP here <a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate/blob/master/src/Abp/Timing/Timezone/Mapping/WindowsZones.xml">https://github.com/aspnetboilerplate/as ... sZones.xml</a>

    Let me know of your thoughts.

  • User Avatar
    0
    cangunaydin created

    @ismcagdas thanks for the link it was helpful. Last question that is relevant with timezone. you use _timeZoneConverter.Convert method to change the values while you are exporting to excel from utc to user timezone. Does abp have the opposite method, like from user's timezone back again to universal time.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Currently not but we can add it for the next versions. For now, you can use TimeZoneHelper for that <a class="postlink" href="https://github.com/aspnetboilerplate/aspnetboilerplate/blob/dev/src/Abp/Timing/Timezone/TimezoneHelper.cs">https://github.com/aspnetboilerplate/as ... eHelper.cs</a>