Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DatePicker timezone problem #3609

Open
callawey opened this issue Jan 31, 2018 · 48 comments · Fixed by #5364 or #5526
Open

DatePicker timezone problem #3609

callawey opened this issue Jan 31, 2018 · 48 comments · Fixed by #5364 or #5526

Comments

@callawey
Copy link

[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
Copy link

+1

@beppe1992
Copy link

Help us!!!!

@bhavesh-neo
Copy link

Same problem!

@shyamal890
Copy link

+1

@beppe1992
Copy link

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
Copy link

mixtou commented Mar 16, 2018

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

@beppe1992
Copy link

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
Copy link

Would #3440 solve this issue?

@valorkin
Copy link
Member

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
Copy link

simonv3 commented Sep 20, 2018

@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.

@Domainv Domainv closed this as completed Jan 11, 2019
@roscoedeemarutam
Copy link

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

@viktor-mezhenskyi
Copy link

viktor-mezhenskyi commented May 9, 2019

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
Copy link

waldof123 commented May 9, 2019

@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 ?

@Domainv Domainv reopened this May 13, 2019
@imben1109
Copy link

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

@imben1109
Copy link

imben1109 commented Jun 30, 2019

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
Copy link

gp187 commented Jul 20, 2019

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

@Hojekunle
Copy link

Hojekunle commented Jul 24, 2019

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
Copy link
Contributor

Domainv commented Aug 21, 2019

@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
Copy link

imben1109 commented Aug 26, 2019

@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
Copy link
Contributor

Domainv commented Sep 4, 2019

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

@Domainv Domainv removed this from the 5.2.0 milestone Sep 5, 2019
@Domainv
Copy link
Contributor

Domainv commented Oct 4, 2019

@isalox Provide more info, please.
Some reproduction.

@vaztimur
Copy link

same problem, datepicker return one day behind

@isalox
Copy link

isalox commented Oct 23, 2019

@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
Copy link
Contributor

Domainv commented Oct 23, 2019

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

@imben1109
Copy link

imben1109 commented Oct 23, 2019

@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
Copy link
Contributor

Domainv commented Oct 24, 2019

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

@isalox
Copy link

isalox commented Oct 24, 2019

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
Copy link

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
Copy link

imben1109 commented Oct 24, 2019

@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
Copy link
Contributor

Domainv commented Oct 25, 2019

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

@Domainv Domainv reopened this Oct 25, 2019
@Domainv Domainv added this to the 5.4.0 milestone Oct 28, 2019
@onlytafkas
Copy link

onlytafkas commented Nov 19, 2019

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
Copy link

isalox commented Nov 21, 2019

@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
Copy link

Any idea when a fix will be available?

@Omi231
Copy link

Omi231 commented Feb 11, 2020

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
Copy link
Contributor

@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
Copy link

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

@blazekv
Copy link

blazekv commented May 7, 2020

@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
Copy link

funny parts of being a developer :)

@dinhanhthi
Copy link

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

@blazekv
Copy link

blazekv commented Jun 12, 2021

@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
Copy link

@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
Copy link

@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
Copy link

blazekv commented Jun 18, 2021

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

@harry-kalligeros
Copy link

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

@Aravindhsiva
Copy link

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
Copy link

manjushakarpe1982 commented Apr 5, 2022

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
Copy link

dp1127 commented Oct 11, 2023

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.
- updated the date : 2023-10-20T10:30:00Z

2.) Second Time open DatePicker and update the same date.
- updated the date : 2023-10-20T05:00:00Z

3.) Third Time open DatePicker and update the same date.
- updated the date : 2023-10-19T23:30:00Z

4.) Forth Time when I open the DakePicker It changed the date.
- updated the date : 2023-10-19T18:00:00Z

Solution :

  • I found out date was being updating proper but while fetching the record, Date format was "2023-10-19T18:00:00" which was different from inserted date "2023-10-19T18:00:00Z" and different was just for "Z" UTC time zone.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment