ionic-framework: ion-datetime returns incorrect UTC datetime

Ionic version: (check one with “x”) [ ] 1.x [x] 2.x [x] 3.x

I’m submitting a … (check one with “x”) [x] bug report [ ] feature request [ ] support request => Please do not submit support requests here, use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/

Current behavior: The ion-datetime returns an ISO 8601 string including the Time Zone, (e.g. “2017-01-01T13:00:00Z” when I enter 1:00pm in the Eastern time zone)

Expected behavior: I would expect it to return “2017-01-01T13:00:00” with no time zone or “2017-01-01T13:00:00-05:00”, which is the correct time offset for my location.

The value from the ion-datetime should either be local timezone aware, or not add timezone information into the date string. As it is now, it is just wrong. Since it has “Z” on the end, it is forcing the result to be UTC/Zulu time. Thus, even using a library like momentjs won’t allow you to easily convert the value.

Steps to reproduce: just enter a date into an ion-date and look at the output value.

Ionic info: (run ionic info from a terminal/cmd prompt and paste output below): Your system information:

ordova CLI: 6.5.0 Ionic Framework Version: 3.0.1 Ionic CLI Version: 2.2.2 Ionic App Lib Version: 2.2.1 Ionic App Scripts Version: 1.3.4 ios-deploy version: Not installed ios-sim version: Not installed OS: Windows 10 Node Version: v6.10.0 Xcode version: Not installed

Here’s a plunkr for kicks: http://plnkr.co/edit/eNxoq89PlhUxWEiX6uen?p=preview

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 13
  • Comments: 28 (3 by maintainers)

Most upvoted comments

@jgw96 I can confirm @tommck’s issue. The behavior is absolutely incorrect. In order to reproduce it:

  1. Set an <ion-datetime>'s value to a date/time string in the format YYYY-MM-DDTHH:mm:ss.
  2. Observe that the component’s value does not end with a Z. This is interpreted as local time.
  3. Change the component’s value using the UI.
  4. Observe that the component’s value is the time displayed on the control, only with a Z appended. This is almost certainly not the same as your local time.

The relevant issue is that the component is taking a timezone-naive value and making it timezone-aware without adjusting the time to compensate for the change. Timezones are already really, really hard. The component is just making them even harder.

Demo Plunkr: http://plnkr.co/edit/4OHGcOK2XIjHgWdHP54i?p=preview

To trigger the bug, just open the datetime picker and then click “Done”. You don’t even need to change the date.

Looked at the issue, and can confirm the findings from @tommck. After doing some research, it seems I’ve found the cause, at L210 the let tzOffset = 0; is initialized to naive. It should be let tzOffset = isPresent(parse[8]) ? 0 : null;, then, subsequently the check at L364 can be more specific. The snippet below shows the whole fix, as far as I was able to test it.

// datetime-util.ts
// @line 210
let tzOffset = isPresent(parse[8]) ? 0 : null

// @line 364
if (data.tzOffset === 0) {
  // YYYY-MM-DDTHH:mm:SSZ
  rtn += "Z";
} else if (!isBlank(data.tzOffset)) {
  // YYYY-MM-DDTHH:mm:SS+/-HH:mm
  rtn +=
    (data.tzOffset > 0 ? "+" : "-") +
    twoDigit(Math.floor(data.tzOffset / 60)) +
    ":" +
    twoDigit(data.tzOffset % 60);
}

Tested with the following input values, and modified e.g. the minutes, "2018-04-03T10:32:57", "2017-01-01T10:32:57-05:00" and "2017-01-01T10:32:57Z" where the output its timezone are preserved.

As a workaround, you can use moment.js to convert your ISO8601 strings 2017-10-10T13:00:00Z to include the timezone offset 2017-10-10T08:00:00-05:00 before using in the ion-datetime: moment(utcDateString).format(); // 2017-10-10T13:00:00Z -> 2017-10-10T08:00:00-05:00 and then convert it back before storing the value (if you want to store as UTC): moment(localDateString).toISOString(); // 2017-10-10T08:00:00-05:00 -> 2017-10-10T13:00:00Z

ion-datetime currently only uses the first 10 characters of the string and ignores any “Z” or timezone offset, but when the value is modified, it will replace the “Z” or the timezone offset, but if neither were there originally (representing a local time), then a “Z” is incorrectly added.

Same here. Is it going to be fixed soon?

Having to use other libraries to solve the issue with the “official” datepicker does not seem right.

@dedd1993 Simply using moment will not save you. Even if you use moment, ion-datetime will still screw up your time zones unless you take precautions such as those described by @Simpler1.

I fixed it with this

moment(ion_datetime_value).local().format("YYYY-MM-DD[T]HH:mm:ss.000") + 'Z' 

the datatype I used for it is string.

use moment as ionic recomends on its documentation https://ionicframework.com/docs/api/components/datetime/DateTime/#advanced-datetime-validation-and-manipulation

  1. Open console at root proyect and install moment: npm install moment --S.
  2. Import moment in component file: import moment from ‘moment’;.
  3. Set value of model variable: this.myDate = moment().format().

Hi,

I’m dealing with the same problem. I work and store dates in a “YYYY-MM-DDTHH:mm” format. When I use ion-datetime, a end Z is added, and when I try to parse or manipulate with Date() or moment(), hours are +1 hour than selected. I’m trying everything without success.

Previously, I initializate the date variable as indicated in the documentation, but then, the string is converted with a Z.

image

startTimeForm: string;
startTimeForm = moment("2018-01-01T00:00").format("YYYY-MM-DDTHH:mm");
//startTimeForm = "2018-01-01T00:00"
//From view, If I select a new time like 15:00, the string is converted to "2018-01-01T15:00Z" 
//Now, If I try to convert  "2018-01-01T15:00Z" to get the HH o mm values, with moment() or Date(), time is 16:00, not 15:00

Please, help, which approach would be better for have a consistency between selected Time and String in that format (without Z or timezone changes). I have spent many hours with this.

Thanks in advance.

Here are some helper methods to get past this shortcoming of the <ion-datetime> element.

  /* Convert a real ISO 8601 UTC date stringto a BOGUS ISO 8601 local date string (with a Z).
   * utcDateString should be of format:  YYYY-MM-DDTHH-mm-ss.sssZ
   */
  convertISO8601UTCtoLocalwZ(utcDateString: string): string {
    const ISO_8601_UTC_REGEXP = /^(\d{4})(-\d{2})(-\d{2})T(\d{2})(\:\d{2}(\:\d{2}(\.\d{3})?)?)?Z$/;
    try {
      if (utcDateString.match(ISO_8601_UTC_REGEXP)) {
        let localDateString: string;
        let utcDate: Date = new Date(utcDateString);
        let tzOffset: number = new Date().getTimezoneOffset() * 60 * 1000;
        let newTime: number = utcDate.getTime() - tzOffset;
        let localDate: Date = new Date(newTime);
        localDateString = localDate.toJSON()
        return localDateString;
      } else {
        throw 'Incorrect UTC ISO8601 date string';
      }
    }
    catch(err) {
      alert('Date string is formatted incorrectly: \n' + err);
    }
  }

  /* Convert a BOGUS ISO 8601 Local date string (with a Z) to a real ISO 8601 UTC date string.
   * localDateString should be of format:  YYYY-MM-DDTHH-mm-ss.sssZ
   */
  convertISO8601LocalwZtoUTC(localDateString: string): string {
    const ISO_8601_UTC_REGEXP = /^(\d{4})(-\d{2})(-\d{2})T(\d{2})(\:\d{2}(\:\d{2}(\.\d{3})?)?)?Z$/;
    try {
      if (localDateString.match(ISO_8601_UTC_REGEXP)) {
        let utcDateString: string;
        let localDate: Date = new Date(localDateString);
        let tzOffset: number = new Date().getTimezoneOffset() * 60 * 1000;
        let newTime: number = localDate.getTime() + tzOffset;
        let utcDate: Date = new Date(newTime);
        utcDateString = utcDate.toJSON()
        return utcDateString;
      } else {
        throw 'Incorrect BOGUS local ISO8601 date string';
      }
    }
    catch(err) {
      alert('Date string is formatted incorrectly: \n' + err);
    }
  }

  /* Convert an ISO 8601 Local date string (no Z) to a BOGUS ISO 8601 UTC date string.
   * localDateString should be of format:  YYYY-MM-DDTHH-mm-ss.sss
   */
  appendZ(localDateString: string): string {
    const ISO_8601_LOCAL_REGEXP = /^(\d{4})(-\d{2})(-\d{2})T(\d{2})(\:\d{2}(\:\d{2}(\.\d{3})?)?)?$/;
    try {
      if (localDateString.match(ISO_8601_LOCAL_REGEXP)) {
        return localDateString + "Z";
      } else {
        throw 'Incorrect local ISO8601 date string';
      }
    }
    catch(err) {
      alert('Date string is formatted incorrectly: \n' + err);
    }
  }

  /* Convert a BOGUS ISO 8601 Local date string (with a Z) to a real ISO 8601 local date string.
   * bogusLocalDateString should be of format:  YYYY-MM-DDTHH-mm-ss.sssZ
   */
  removeZ(bogusLocalDateString: string): string {
    const ISO_8601_UTC_REGEXP = /^(\d{4})(-\d{2})(-\d{2})T(\d{2})(\:\d{2}(\:\d{2}(\.\d{3})?)?)?Z$/;
    try {
      if (bogusLocalDateString.match(ISO_8601_UTC_REGEXP)) {
        return bogusLocalDateString.slice(0,-1);
      } else {
        throw 'Incorrect BOGUS local ISO8601 date string';
      }
    }
    catch(err) {
      alert('Date string is formatted incorrectly: \n' + err);
    }
  }

The first two are for when you’re storing your dates as valid ISO 8601 UTC date strings and need to convert to a bogus string in order to display as local time in your form.

The second two are for when you’re storing you dates as a valid ISO 8601 local date strings and need to convert to a bogus string in order to display as local time in your form.

I’m having the same problem as @tommck. Any update on this?

With the latest of everything, the control is still returning a full ISO string “2017-01-01T13:00:00Z”

It’s assuming UTC when it’s not timezone aware… so this is wrong. Same issue still exists

Sorry for late response, had to reproduce it once again in a separate branch. I cannot post the entire code, but this is what you’re interested about. Part of the template:

<ion-datetime displayFormat="D MMM"
                        pickerFormat="DD MMMM YYYY"
                        min="{{today}}"
                        max="2020"
                        [(ngModel)]="checkInDate"
                        (ionChange)="changeCheckOutDate()"
                        #checkInInput>
</ion-datetime>
<ion-datetime displayFormat="D MMM"
                        pickerFormat="DD MMMM YYYY"
                        [min]="minCheckOutDate()"
                        max="2020"
                        [(ngModel)]="checkOutDate"
                        #checkOutInput>
</ion-datetime>

And part of my TS:

  @ViewChild('checkInInput') checkInInput;
  @ViewChild('checkOutInput') checkOutInput;
  checkInDate: any;
  checkOutDate: any;
  today: string;

  constructor(...) {
    this.today = moment().format('YYYY-MM-DD');
    this.checkInDate  = moment().format('YYYY-MM-DD');
    this.checkOutDate = moment().add(1, 'days').format('YYYY-MM-DD');
}

  changeCheckOutDate() {
    if(moment(this.checkInDate).isSameOrAfter(this.checkOutDate)) {
      this.checkOutDate = moment(this.checkInDate).add(1, 'day').format('YYYY-MM-DD');
    }
  }

  minCheckOutDate() {
    return moment(this.checkInDate).add(1, 'day').format('YYYY-MM-DD');
  }

Here is what I get after submitting the form if I don’t change anything in the input:

CheckInDate 2017-04-27 CheckOutDate 2017-04-28

And if I change only CheckIn for April, 30th:

CheckInDate {“year”:2017,“month”:4,“day”:30,“hour”:null,“minute”:null,“second”:null,“millisecond”:null,“tzOffset”:0} CheckOutDate 2017-05-31

If you need any additional info, then just ask me. And thank you for paying attention. Dmytro