valor-software / ngx-bootstrap

Fast and reliable Bootstrap widgets in Angular (supports Ivy engine)
https://valor-software.com/ngx-bootstrap
MIT License
5.52k stars 1.69k forks source link

DatePicker timezone problem #3609

Open callawey opened 6 years ago

callawey commented 6 years ago

[bsConfig]="{ dateInputFormat: 'YYYY-MM-DD' }"

when this format is used, i may expect the same entry come back regardless of client. because we are selecting a date not datetime. But its considering it as datetime and puts timezone info to it.

So, for example, 2018-01-30 00:00:00 becomes 2018-01-29 if your timezone is +

There is no config option to set timezone or any thing that allows to retreive the input directly.

If there is a way, i am not aware but per the source code, i dont see any option that lets this.

sasastojkovicgfi commented 6 years ago

+1

beppe1992 commented 6 years ago

Help us!!!!

bhavesh-neo commented 6 years ago

Same problem!

shyamal890 commented 6 years ago

+1

beppe1992 commented 6 years ago

The problem shows up when the Date is converted to JSON. I solved the problem overriding Date.prototype.toJSON method returning Date in string format yyyy-MM-dd'T'hh:mm:ss.

This work for me.

mixtou commented 6 years ago

Same Problem Here. Can you post some example code? How many years this issue exists?? Isn't this a bit unacceptable??

beppe1992 commented 6 years ago

This is my code

`Date.prototype.toJSON = function(){ // this method transform a Date in a string (format "yyyy-MM-dd'T'HH:mm:ss") return DateService.formatDate(this); };

Server side I accept date in "yyyy-MM-dd'T'HH:mm:ss" format

`

fralewsmi commented 6 years ago

Would https://github.com/valor-software/ngx-bootstrap/issues/3440 solve this issue?

valorkin commented 6 years ago

This is actually expected date behaviour in js. And yes js Dates is a pain. Just call .toUTCstring() and you should see the same date

simonv3 commented 5 years ago

@valorkin where would you call .toUTCstring()? the date itself gets sent appropriately, but someone at +9 timezone sees a different date in the front-end than what we're storing. Maybe there's a way for the datepicker to just care about the date?

Edit: fwiw, we're working around this by just making sure that the date before getting shown in the datepicker doesn't have any timezone info on it (so that Date renders it in the client's timezone) and then chopping off the time information before sending it back to the server.

roscoedeemarutam commented 5 years ago

why is this closed... still a problem and no solution!?

viktor-mezhenskyi commented 5 years ago

I had the same problem. Just look at this examples, maybe it will be useful for someone. Timezon local maching is GMT+3. It is the oportunity to pase time to your timezone without changing time (dateTime2 in example). it is force timezone converting)

var dateStr = '2018-01-30 04:00:00';
var dateTime1 = moment(dateStr).toDate();
var dateTime2 = moment.tz(dateStr, 'YYYY-MM-DDTHH:mm:ss[Z]', true, "ETC/UTC").toDate();
console.log(dateTime1);
//Tue Jan 30 2018 04:00:00 GMT+0300 (GMT+03:00)
console.log(dateTime2);
//Tue Jan 30 2018 07:00:00 GMT+0300 (GMT+03:00)

U need to include moment.js and moment-timezon.js

waldof123 commented 5 years ago

@valorkin where would you call .toUTCstring()? the date itself gets sent appropriately, but someone at +9 timezone sees a different date in the front-end than what we're storing. Maybe there's a way for the datepicker to just care about the date?

Edit: fwiw, we're working around this by just making sure that the date before getting shown in the datepicker doesn't have any timezone info on it (so that Date renders it in the client's timezone) and then chopping off the time information before sending it back to the server.

Was that workaround then found ?

imben1109 commented 5 years ago

Is there workaround for timezone issue? I want to have datepicker for UTC Date selection.

imben1109 commented 5 years ago

https://medium.com/self-learning/ngx-datepicker-utc-datepicker-design-77e33789e9d7

I have tried a UTC Datepicker which is a wrapper to original datepicker. The wrapper try to convert the input with a timezone offset procession.

image

import { Component, OnInit, forwardRef, ChangeDetectorRef } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

@Component({
  selector: 'app-utc-datepicker',
  templateUrl: './utc-datepicker.component.html',
  styleUrls: ['./utc-datepicker.component.css'],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => UtcDatepickerComponent),
    multi: true
  }]
})
export class UtcDatepickerComponent implements OnInit, ControlValueAccessor{

  value: any;

  constructor() { }

  onChange: (value: any) => void;

  ngOnInit() {
  }

  bsValueChange(val){
    setTimeout(()=>{
      this.value = val;
      if (val instanceof Date){
        this.onChange(new Date(val.getTime() - val.getTimezoneOffset() * 60 * 1000));  
      }else {
        this.onChange(val);
      }
    });
  }

  writeValue(val: any): void {
    if (val){
      if (val instanceof Date){
        this.value = new Date(val.getTime() + val.getTimezoneOffset() * 60 * 1000);
      }else {
        this.value = val;
      }
    }
  }

  registerOnChange(fn: (value: any) => void): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
  }

}

Remark for this solution All time input should be converted to a time with a specific processing of timezone offset such as min day, max day handling.

gp187 commented 5 years ago

Really?!? We need to a special custom function to handle such a simple problem!? This is so stupid!

Hojekunle commented 5 years ago

Try the below function OnInit. Below function processes the timezone offset upon loading the record from db and you need to also use same when saving the record (or on date change event). Effect is saving and displaying the "Date chosen" by the user and not "Date chosen minus 1 day".

const dd = new Date(this.userEditForm.controls['dateOfDeployment'].value); dd.setHours(dd.getHours() - dd.getTimezoneOffset() / 60); this.userEditForm.patchValue({ dateOfDeployment: dd }); 2 1

Domainv commented 5 years ago

@imben1109 @Hojekunle @waldof123 @viktor-mezhenskyi @roscoedeemarutam

Please, try this version npm i ngx-bootstrap-ci@d94457108f5fd2527b7cab501e1cc0271a087a08 then rename ngx-bootstrap-ci => ngx-bootstrap inside node_modules

imben1109 commented 5 years ago

@Domainv I took a look on ngx-bootstrap-ci@d94457108f5fd2527b7cab501e1cc0271a087a08. It should be my expected result.

I want to try on this version by npm install ngx-bootstrap-ci@d94457108f5fd2527b7cab501e1cc0271a087a08

npm ERR! code ETARGET
npm ERR! notarget No matching version found for ngx-bootstrap-ci@d94457108f5fd2527b7cab501e1cc0271a087a08
npm ERR! notarget In most cases you or one of your dependencies are requesting
npm ERR! notarget a package version that doesn't exist.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\user\AppData\Roaming\npm-cache\_logs\2019-08-26T16_11_53_312Z-debug.log

I have another issue for custom timezone which I want to select a timezone which is different from the timezone browser timezone offset.

e.g. Browser Timezone: +7 Custom Timezone: +8 If I select a date of 27/08/2019. The expected UTC time is 26/08/2019 16:00

The below is my workaround solution. Could you take a look?

import { CustomTimezoneDatepickerService } from './custom-timezone-datepicker.service';
import { Component, OnInit, forwardRef, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil, tap } from 'rxjs/operators';

@Component({
  selector: 'app-custom-timezone-datepicker',
  templateUrl: './custom-timezone-datepicker.component.html',
  styleUrls: ['./custom-timezone-datepicker.component.css'],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CustomTimezoneDatepickerComponent),
    multi: true
  }],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CustomTimezoneDatepickerComponent implements OnInit, ControlValueAccessor{

  destory$ : Subject<boolean> = new Subject<boolean>();

  timezone: number = 0;
  value: Date;

  onChange: (value: any) => void;

  constructor(private _changeDetectorRef: ChangeDetectorRef, 
              private _localTimeZoneDatepickerService: CustomTimezoneDatepickerService) {
    this.timezone = this._localTimeZoneDatepickerService.getTimeZone();
  }

  ngOnInit() {
    this._localTimeZoneDatepickerService.getTimeZoneUpdate().pipe(
      takeUntil(this.destory$),
      tap(val => {
        this.timezone = val;
        if (this.value instanceof Date){
          let tmpDate = new Date(this.value.getTime());
          tmpDate.setHours(0);
          tmpDate.setMinutes(0);
          tmpDate.setSeconds(0);
          tmpDate.setMilliseconds(0);
          tmpDate = new Date(tmpDate.getTime() - (this.value.getTimezoneOffset() + this.timezone * 60) * 60 * 1000)
          this.onChange(tmpDate);  
        }
      })
    ).subscribe()
  }

  bsValueChange(val){
    setTimeout(()=>{
      this.value = val;
      if (val instanceof Date){
        let tmpDate = new Date(val.getTime());
        tmpDate.setHours(0);
        tmpDate.setMinutes(0);
        tmpDate.setSeconds(0);
        tmpDate.setMilliseconds(0);
        tmpDate = new Date(tmpDate.getTime() - (val.getTimezoneOffset() + this.timezone * 60) * 60 * 1000)
        this.onChange(tmpDate);  
      }else {
        this.onChange(val);
      }
    });
  }

  writeValue(val: any): void {
    if (val){
      if (val instanceof Date){
        this.value = new Date(val.getTime() + (val.getTimezoneOffset() + + this.timezone * 60) * 60 * 1000);
      }else {
        this.value = val;
      }
      this._changeDetectorRef.detectChanges();
    }
  }

  registerOnChange(fn: (value: any) => void): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
  }

}
Domainv commented 5 years ago

@imben1109 Please, retry this version npm i ngx-bootstrap-ci@430758f867a9d2b385f14d9bc60a3a5b15cf0bf4 then rename ngx-bootstrap-ci => ngx-bootstrap inside node_modules

isalox commented 4 years ago

it doesn't work in 5.2.0 version

Domainv commented 4 years ago

@isalox Provide more info, please. Some reproduction.

vaztimur commented 4 years ago

same problem, datepicker return one day behind

isalox commented 4 years ago

@isalox Provide more info, please. Some reproduction.

take a look here: https://stackblitz.com/edit/angular-vbku3b

just click Set Date and you will see in control date 1 day back

Domainv commented 4 years ago

Thanks, guys, I'll fix it as soon as possible.

imben1109 commented 4 years ago

@isalox @Domainv Sorry, i have some question about the date. As JavaScript Date actually is timezone dependent, new Date(2019, 11, 23) would be different in different timezone. In my timezone that is UTC+8, the date would be 2018/11/22 16:00. but in UTC +0, the date should be correct. i.e. 2018/11/23 00:00

Therefore, i think UTC Time should be used. https://stackblitz.com/edit/angular-mb7szh

new Date(Date.UTC(2019, 10, 23))

instead of

new Date(2019, 10, 23)

And that is why i need a wrapper for my workaround solution. As my situation, i need a datepicker for UTC+0 situation (by my workaround solution). Actually, I need a wrapper to handle problem of UTC + 0, UTC + Custom Timezone and also browser timezone.

Domainv commented 4 years ago

@imben1109 If I understand right, for you datepicker works correctly now?

isalox commented 4 years ago

And that is why i need a wrapper for my workaround solution. As my situation, i need a datepicker for UTC+0 situation. Actually, I need a wrapper to handle problem of UTC + 0, UTC + Custom Timezone and also browser timezone.

it is just for example. Actually my problem is when I am getting data from Rest API. In data base, i store only dates without time. That data is send to UI and deserializes and when I want to edit I am getting dates one day back.

Parison125 commented 4 years ago

I had the same issue, for some reasons, bsDatepicker always picked the day before the date you choose. Here is a solution I found which uses datepipe from angular/common.

let dateFromBsdatepicker:Date = reactiveForm['formControlName']; let correctDate = this.datepipe.transform(dateFromBsdatepicker,'yyyy-MM-dd');

imben1109 commented 4 years ago

@isalox you could try my workaround solution that i post before. a utc date wrapper for ngx-bootstrap for version before 5.2.0 @Domainv No. Still have some problem in https://stackblitz.com/edit/angular-mb7szh. It work fine when setDate button is pressed. But when the date is selected by datepicker, it do not work.

image

image

image

Domainv commented 4 years ago

Released ngx-bootstrap@5.3.2

behavior was back as in 5.1.2 version

Needs more info and time to find the correct solution for timezone issue

onlytafkas commented 4 years ago

after an afternoon and an evening plenty of frustration, I switched to a simple input control type=date and the date is saved without time, what I needed for my case

isalox commented 4 years ago

@isalox you could try my workaround solution that i post before. a utc date wrapper for ngx-bootstrap for version before 5.2.0 @Domainv No. Still have some problem in https://stackblitz.com/edit/angular-mb7szh. It work fine when setDate button is pressed. But when the date is selected by datepicker, it do not work.

image

image

image

I don't need any workarounds. It should work properly without any hacks. It is basic functionality for datepicker. If it can't render date correctly why do anyone need it. Right now it is useless. Hope it will be fixed in next release.

garthmountain commented 4 years ago

Any idea when a fix will be available?

Omi231 commented 4 years ago

Any idea when a fix will be available?

I came to check this issue after 1 year and mods still need info about the issue. Maybe in year 3020 they will find root cause and in year 4020 it is expected to be solved.

daniloff200 commented 4 years ago

@Omi231 very funny joke, thanks I wish, that such people, like you, who can only post some silly jokes, also, post some code snippets with help, or, even PRs with fixes / features

com-xuonghuynh commented 4 years ago

its 2 years from first post and this issues is not fix ?

blazekv commented 4 years ago

@Domainv Maybe you should consider to extend date picker config. It will be great if anyone could decide how output should look like.

If I got it right - only problem is, that you are sending date object to onChange function here. Something like this with default value should solve this problem.

this._onChange((this._picker._config.transform(this._value));

with default value

this._picker._config.transform = (value) => value;

When configured like @imben1109 do that

this._picker._config.transform = (value) => new Date(value.getTime() - value.getTimezoneOffset() * 60 * 1000);

It should return correct date and also could help to return to those who wants same output as dateInput format.

This will also help because it did not drop information about timezone -

this._onChange(this._value.toString());

But it will create breaking change... So I dont think it is solution.

okansarica commented 3 years ago

funny parts of being a developer :)

dinhanhthi commented 3 years ago

I've exactly the same problem here. Any idea or fixes?

blazekv commented 3 years ago

@dinhanhthi I made wrapper around this component and use it instead. If you do not need all original Inputs and Outputs it should be easy and small component.

export function getDateWithoutOffset(value: string): Date {
  const date = moment(value).utcOffset(value);
  return moment({ year: date.year(), month: date.month(), date: date.date() }).toDate();
}

utc-datepicker.component.ts

import { Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { BsDatepickerConfig } from 'ngx-bootstrap/datepicker/bs-datepicker.config';

@Component({
  selector: 'app-utc-datepicker',
  templateUrl: './utc-datepicker.component.html',
  styleUrls: ['./utc-datepicker.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => UtcDatepickerComponent),
      multi: true,
    },
  ],
})
export class UtcDatepickerComponent implements ControlValueAccessor {
  @Input() showDatePickerOnFieldClick = false;
  @Input() disabled = false;
  @Input() showToggleButton = true;
  @Input() set bsConfig(config: Partial<BsDatepickerConfig>) {
    this._bsConfig = {
      showWeekNumbers: false,
      containerClass: 'theme-dark-blue',
      ...config,
    } as BsDatepickerConfig;
  }
  get bsConfig() {
    return this._bsConfig;
  }

  value: any;
  private _bsConfig: BsDatepickerConfig;

  onChange: (value: any) => void = () => {
    /**
     * Do nothing
     */
  };
  onTouched: (value: any) => void = () => {
    /**
     * Do nothing
     */
  };

  bsValueChange(val) {
    setTimeout(() => {
      this.value = val;
      if (val) {
        this.onTouched(val);
      }
      if (val instanceof Date) {
        this.onChange(
          new Date(val.getTime() - val.getTimezoneOffset() * 60 * 1000)
        );
      } else if (val !== undefined) {
        // This condition is because if undefined is default value it will trigger dirty check and show error message
        this.onChange(val);
      }
    });
  }

  writeValue(val: any): void {
    if (val) {
      if (val instanceof Date) {
        this.value = new Date(
          val.getTime() + val.getTimezoneOffset() * 60 * 1000
        );
      } else if (typeof val === 'string') {
        this.value = getDateWithoutOffset(val);
      } else {
        console.warn('Expected Date or string - unexpected value', val);
      }
    }
  }

  registerOnChange(fn: (value: any) => void): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }
}

utc-datepicker.component.html

<div
  class="input-group mb-3"
  (click)="showDatePickerOnFieldClick ? birthDatePicker.show() : {}"
>
  <input
    type="text"
    [ngClass]="{ 'form-control': true, 'input-clickable': showDatePickerOnFieldClick }"
    bsDatepicker
    #birthDatePicker="bsDatepicker"
    [disabled]="disabled"
    [bsConfig]="bsConfig"
    [bsValue]="value"
    (bsValueChange)="bsValueChange($event)"
  />
  <div class="input-group-append" *ngIf="showToggleButton">
    <span
      class="input-group-text"
      id="inputGroupPrepend"
      (click)="birthDatePicker.show()"
    >
      <i class="fa fa-calendar"></i>
    </span>
  </div>
</div>

Usage example:

          <app-utc-datepicker
            [bsConfig]="{ maxDate: maximumDate, minDate: minimumDate }"
            [disabled]="true"
            [showDatePickerOnFieldClick]="true"
            formControlName="date"
          >
          </app-utc-datepicker>
dinhanhthi commented 3 years ago

@blazekv Thanks for a very detailed answer. For a moment, I've found a simpler solution rather than using your suggestion. It suits only for my case, not the problem in this topic. I've noted your way and may use in the future. Thanks.

zakarea-Tfaili commented 3 years ago

@blazekv Great solution you saved my day, just add the .html of the component

Hope the team fixes this issue or at least provides a good reason why it is there.

Best regards

blazekv commented 3 years ago

@zakarea-Tfaili I have updated previous post and added template.

harry-kalligeros commented 3 years ago

@blazekv Great solution you saved my day too!!!! Great solution

Aravindhsiva commented 2 years ago

A quick workaround in my case is to avoid moment in formatting the date before using it in this datepicker.... Like, i had moment(date).format("MM/DD/YYYY") which i changed to new Date(date) Yes, simply this helped me to fix this, also my API response supported this change..... Now it works fine....

manjushakarpe1982 commented 2 years ago

this is solved the issue <input id="input-dateOfBirth" class="form-control" type="text" bsDatepicker name="dateOfBirth" placeholder="Birth Date" [(ngModel)]="dateOfBirth" ng-model-options="{timezone: 'local'}" />

here is example: https://stackblitz.com/edit/angular-usdq2z

dp1127 commented 11 months ago

I had used Formate "DD/MM/YYYY".

I was getting the issue, when update the date then it was updating correct date up to three time and when I was trying for forth time, Date was changed by datepicker hence it was updating the wrong date to database.

I Got the solution and It works properly but It is the proper way or not I don't know

Scenario :

1.) First Time open DatePicker and update the same date.

2.) Second Time open DatePicker and update the same date.

3.) Third Time open DatePicker and update the same date.

4.) Forth Time when I open the DakePicker It changed the date.

Solution :

So I made change while fetching the record "moment(new Date(date.concat("Z")).toLocaleDateString()).format("DD/MM/YYYY");"