Closed tommck closed 5 years ago
Now, I'm aware of this: https://github.com/driftyco/ionic/issues/8009
But the problem is that the datetime value returned is actually WRONG. This isn't just an "it's hard to do this in a cross platform way" kind of issue. If you can't do it in a cross platform way, that's fine, but at least don't return incorrect data.
Have different issue with ion-datetime picker in ionic 3.1.0, I described it on ionic forum. Feels like these changes should be better documented
Hello, all! We are looking into this. Unfortunately, it looks like this is a breaking change that slipped through.
Hey, @hitpopdimestop would you mind posting the code you're using?
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
@jgw96 - You're saying my issue is an accidental breaking change? I think my issue's been hijacked
@tommck - I thought that collecting all the issues on the same component was a good idea. By the way, has 3.1.1 addressed your issue? I will check mine after we finish deploy in few days.
I confirm, with 3.1.1 the datetime component works well (with ISO String)
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
@hitpopdimestop each issue should separate. reporting more than 1 problem in one issue just gets confusing.
@jgw96 please read the original issue and not the comments, since they're not about my issue
@jgw96 I can confirm @tommck's issue. The behavior is absolutely incorrect. In order to reproduce it:
<ion-datetime>
's value to a date/time string in the format YYYY-MM-DDTHH:mm:ss
.Z
. This is interpreted as local time.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.
I'm having the same problem as @tommck. Any update on this?
same error
When component update val will invoke _inputNormalize I see parseDate (in datetime-util.ts/) set tzOffset is 0, but i want it's a local value.
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.
Any update on this issue?
We have also encountered this issue. Any ionic solution in the horizon?
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.
use moment as ionic recomends on its documentation https://ionicframework.com/docs/api/components/datetime/DateTime/#advanced-datetime-validation-and-manipulation
npm install moment --S
.
import moment from 'moment';
.this.myDate = moment().format()
.@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.
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.
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.
SOLVED. Finally, I initializated correctly the time variables and everything suddenly works :)
Instead of:
startTimeForm = moment("2018-01-01T00:00").format("YYYY-MM-DDTHH:mm");
I use:
this.startTimeForm = moment().format();
The entire example with some extra functionalities is:
import { Component } from "@angular/core";
import moment from 'moment';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
//View variables
startDateForm: string;
startTimeForm: string;
endTimeForm: string;
//Calculate total time
totalTime;
//Final dates to upload to firebase
start: string;
end: string;
constructor() {
//Show the current date
this.startDateForm = moment().format("YYYY-MM-DDTHH:mm");
//Show the current time, but with 00 minutes
this.startTimeForm = moment().minutes(0).format();
this.endTimeForm = moment().minutes(0).format();
}
startDateChanged(startDateForm) {
this.calculateAppointmentDate();
}
startTimeChanged(startTimeForm) {
this.calculateAppointmentDate();
this.calculateTotalTime();
}
endTimeChanged(endTimeForm) {
this.calculateAppointmentDate();
this.calculateTotalTime();
}
//Configure start and end dates before storing in DDBB with a "YYYY-MM-DDTHH:mm" format
calculateAppointmentDate() {
//Configure start = start date + start time
this.start = moment(this.startDateForm).format("YYYY-MM-DD") + "T" + moment(this.startTimeForm).format("HH:mm");
var begin = moment(this.startTimeForm);
var finish = moment(this.endTimeForm);
//If finish is before than begin, the time belogn to the next day
if (finish.isBefore(begin)) {
//Calculate the day after
var nextDay = moment(this.startDateForm).add(1, 'days');
//Generate the complete date and time for uploading to server
this.end = nextDay.format("YYYY-MM-DD") + "T" + moment(this.endTimeForm).format("HH:mm");
} else {
//Create the format date
this.end = moment(this.startDateForm).format("YYYY-MM-DD") + "T" + moment(this.endTimeForm).format("HH:mm");
}
}
//Total time for the date in hours and minutes
calculateTotalTime() {
const begin = moment(this.start);
const finish = moment(this.end);
const diffDuration = moment.duration(finish.diff(begin));
var hours = diffDuration.hours();
var minutes = diffDuration.minutes();
if (minutes != 0) {
this.totalTime = hours + " horas " + minutes + " minutos"; //Showing hours and minutes
} else {
this.totalTime = hours + " horas"; //No minutes
}
}
}
<ion-content >
<ion-grid>
<ion-row text-left>
<ion-col>
<ion-datetime (ionChange)="startDateChanged(startDateForm)" [(ngModel)]="startDateForm" displayFormat="DDD DD/MM/YYYY" pickerFormat="DD/MMM/YYYY"></ion-datetime>
<ion-label>Raw: {{startDateForm}}</ion-label>
</ion-col>
<ion-col>
<ion-datetime (ionChange)="startTimeChanged(startTimeForm)" displayFormat="HH:mm" [(ngModel)]="startTimeForm">
</ion-datetime>
<ion-label>Raw: {{startTimeForm}}</ion-label>
</ion-col>
<ion-col>
<ion-datetime (ionChange)="endTimeChanged(endTimeForm)" displayFormat="HH:mm" [(ngModel)]="endTimeForm">
</ion-datetime>
<ion-label>Raw: {{endTimeForm}}</ion-label>
</ion-col>
</ion-row>
</ion-grid>
<h2>Start string for DDBB: {{start}}</h2>
<h2>End string for DDBB: {{end}}</h2>
<h2>After downloading from DDBB: {{end}}</h2>
<ion-item no-lines>
<h2 *ngIf="start">
{{start | date: 'EEEE d MMMM yyyy'}}
</h2>
</ion-item>
<ion-item no-lines *ngIf="start&&end">
<ion-label>
{{start | date: 'HH:mm'}} a {{end | date: 'HH:mm'}}
</ion-label>
</ion-item>
<!-- Total Time -->
<ion-item *ngIf="totalTime" no-lines>
<ion-label item-start>Duración Total:</ion-label>
<!--TODO-->
<ion-badge item-end>{{totalTime}}</ion-badge>
</ion-item>
</ion-content>
I just wanted to add confirmation that formatting the incoming date correctly seems to resolve the issue.
Specifically, using moment().format()
vs moment().format("YYYY-MM-DDTHH:mm")
In my case, I was transitioning from ionic AlertController with input fields to Modals with FormGroups. AlertController datetime input required the specific format, so my copy/paste code was messing with my timezones.
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.
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.
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.
This issue has been automatically identified as an Ionic 3 issue. We recently moved Ionic 3 to its own repository. I am moving this issue to the repository for Ionic 3. Please track this issue over there.
If I've made a mistake, and if this issue is still relevant to Ionic 4, please let the Ionic Framework team know!
Thank you for using Ionic!
Issue moved to: https://github.com/ionic-team/ionic-v3/issues/215
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