Development for Sailfish OS: Features of working with dates and time zones

  • Tutorial
Hello! This article is the continuation of a series of articles on application development for the Sailfish OS mobile platform. This time we will talk about the features of working with dates and time zones in QML. We start the article with a description of the problem itself, and then move on to methods for solving it.

Description of the problem


When developing Sailfish OS applications, quite often in one form or another you will have to work with dates and times (as, however, when developing for any other platform). Sailfish OS applications use components such as DatePickerDialog and TimePickerDialog to specify the date and time . Internally, they use the Date QML object inherited from the standard JavaScript Date object , which does not support the ability to create a date and time with a time zone other than UTC or local to control the date and time . The Date object simply does not have a constructor and methods for this.

new Date(); 
new Date(value); 
new Date(dateString); 
new Date(year, month[, day[, hour[, minute[, second[, millisecond]]]]]);

It would seem that the third constructor from the list should help here if you pass him a string with a date, time and offset relative to UTC, but no. The time zone of the object will still be local, and not the one indicated in the offset.

new Date('Jan 30 2017 10:00:00 GMT+0700') // Jan 30 2017 06:00:00 GMT+0300

You may ask: “Why use time zones at all? Why can’t you do with time in UTC? ”And I will answer you: yes, sometimes time zones do not make sense. It is enough to use only the date and time. For example, if your working day starts at 9:00, you hardly expect your colleague from Kamchatka to start working at 18:00. However, in the case of regular events occurring at the same time in different time zones, a time zone is still needed. For example, a daily discussion of ongoing work on a project begins at 10:00 for you and at 19:00 for your colleagues in Kamchatka.

One of the solutions to the problem of creating the date and time with setting the time zone was to use one of the third-party libraries: timezone-js and moment.js. But they turned out to be inappropriate, because DatePickerDialog and TimePickerDialog do not know anything about these libraries, but inside they actively use the standard Date , which is incompatible with objects created using timezone-js and moment.js . As a result, two other solutions were developed.

Solution No. 1


The first decision that occurred to us was to create our own JavaScript object to control the date and time. Such an object should allow storing date, time and time zone information, and most importantly - change the date and time using Sailfish OS components DatePickerDialog and TimePickerDialog , without affecting the time zone.

To create your own JavaScript object, you must define a constructor function in a separate JavaScript file.

// CustomDateTime.js
function CustomDateTime(dateTimeString) {
    this.dateTime = Date.fromLocaleString(Qt.locale(), 
                                    dateTimeString.substring(0, dateTimeString.length - 6), 
                                    "yyyy-MM-ddTHH:mm:ss");
    this.utcOffset = dateTimeString.substring(dateTimeString.length - 6);
}

The constructor function takes a string of the form “yyyy-MM-ddTHH: mm: ssZ”, where Z is the offset relative to UTC of the form “[+ -] HH: mm”, standard ISO 8601. A Date object is created from the part of the string and assigned to the dateTime property . This property will contain information about the date and time excluding the time zone. The rest of the line, containing the offset relative to UTC, is stored in a separate utcOffset property . Now we can create an object that will contain information about the date, time and time zone.

var myDateTime = new CustomDateTime("2016-12-22T13:40:00+05:00");
print(myDateTime.dateTime); // Dec 22 2016 13:40:00 GMT+03:00
print(myDateTime.utcOffset); // "+05:00"
myDateTime.dateTime = new Date(2016, 11, 23, 13, 00, 00);
print(myDateTime.dateTime); // Dec 23 2016 13:00:00 GMT+03:00
print(myDateTime.utcOffset); // "+05:00"

Add a method to our object that returns the date and time in the same format "yyyy-MM-ddTHH: mm: ssZ".

// CustomDateTime.js
CustomDateTime.prototype.toISO8601String = function() {
   return this.dateTime.toLocaleString(Qt.locale(), "yyyy-MM-ddTHH:mm:ss").concat(this.utcOffset);
}

Often in applications that work with date and time, you need to display the corresponding values. We, as developers, must ensure that all users date and time will be displayed correctly in accordance with the current locale. To do this, add methods to our JavaScript object that return strings with a language-dependent representation of date and time.

// CustomDateTime.js
CustomDateTime.prototype.toLocaleDateString = function() {
    return Qt.formatDate(this.dateTime, Qt.SystemLocaleShortDate);
}
CustomDateTime.prototype.toLocaleTimeString = function() {
    return Qt.formatTime(this.dateTime, "HH:mm");
}
CustomDateTime.prototype.toLocaleDateTimeString = function() {
    return this.toLocaleDateString() + " " + this.toLocaleTimeString();
}

Thus, we have an object that stores and allows you to edit information about the date, time and time zone, is created using a string in a specific format, can return a string in the same format, as well as formatted strings in the current locale. Such an object will easily allow us to operate on date and time in the required time zone.

Consider an example of using the CustomDateTime object .

//...
import "../model/CustomDateTime.js" as CustomDateTime
Page {
    property var сustomDateTime: new CustomDateTime.CustomDateTime("2017-01-15T13:45:00+05:00")
    SilicaFlickable {
        anchors.fill: parent
        contentHeight: column.height
        Column {
            id: column
            //...
            ValueButton {
                 label: qsTr("Date").concat(":")
                 value: сustomDateTime.toLocaleDateString()
                 //...
            }
            ValueButton {
                width: parent.width
                label: qsTr("Time").concat(":")
                value: сustomDateTime.toLocaleTimeString()
                onClicked: {
                    var dialog = pageStack.push("Sailfish.Silica.TimePickerDialog",
                                                { hour: сustomDateTime.dateTime.getHours(),
                                                  minute: сustomDateTime.dateTime.getMinutes()});
                    dialog.accepted.connect(function() {
                        сustomDateTime.dateTime = new Date(сustomDateTime.dateTime.getFullYear(),
                                                           сustomDateTime.dateTime.getMonth(),
                                                           сustomDateTime t.dateTime.getDate(),
                                                           dialog.hour, dialog.minute);
                    });
                }
            }
        }
    }
}

The example contains ValueButton components for editing date and time. By clicking on one component, DatePickerDialog opens , by clicking on the second - TimePickerDialog . The ValueButton component for time editing is described in more detail . The CustomDateTime object is created as a property of the Page component and is used to display the date and time in the ValueButton using the value property , and also to pass values ​​to DatePickerDialog and TimePickerDialog , as described in the onClicked event handler . Obtaining data fromDatePickerDialog and TimePickerDialog and updating the dateTime property of the CustomDateTime object .

So, we created a JavaScript CustomDateTime object that allows you to store information about the date, time and time zone, and also allows you to edit the date and time using DatePickerDialog and TimePickerDialog .

The downside of this solution is that the JavaScript object does not support property bindings. In the example, after changing the date or time (changing the dateTime property of the CustomDateTime object ), the value property of the ValueButton object will not be updated, i.e. visually, no changes will occur on the screen, despite the fact that the CustomDateTime object has actually changed. This is because the dateTime property of the CustomDateTime object cannot be associated with the value property of the ValueButton object .

In cases where the binding of properties does not matter, you can use the solution described above, but in other cases, you must refer to solution No. 2.

Decision number 2


The second solution is to create your own QML component, in particular a component of the QtObject type . QtObject is the most “lightweight” standard QML type, has no visual component and can be useful when creating a model object. Most importantly, QML components support property binding. Rewrite the JavaScript object defined above on the QML component.

// CustomDateTime.qml
import QtQuick 2.0
QtObject {
    property string dateTimeStringToSet
    property date dateTime: Date.fromLocaleString(Qt.locale(), 
                                 dateTimeStringToSet.substring(0, dateTimeStringToSet.length - 6), 
                                 "yyyy-MM-ddTHH:mm:ss")
    property string utcOffset: dateTimeStringToSet.substring(dateTimeStringToSet.length - 6)
    property string localeDateString: Qt.formatDate(dateTime, Qt.SystemLocaleShortDate)
    property string localeTimeString: Qt.formatTime(dateTime, "HH:mm")
    property string localeDateTimeString: localeDateString.concat(" ").concat(localeTimeString)
    property string iso8601String: dateTime.toLocaleString(Qt.locale(), "yyyy-MM-ddTHH:mm:ss")
                                           .concat(utcOffset)
}

The code has become more concise, the constructor function and methods of the JavaScript object have been replaced with properties inside QtObject . Now, to create a new object, we need to use the standard QML syntax and define only one property dateTimeStringToSet , all other properties will be calculated automatically, because property binding will work.

CustomDateTime {
    dateTimeStringToSet: "2017-01-15T13:45:00+05:00"
}

We rewrite the example that was above using the CustomMLateTime QML object .

//...
Page {
    CustomDateTime {
        id: customDateTime
        dateTimeStringToSet: "2017-01-15T13:45:00+05:00"
    }
    SilicaFlickable {
        anchors.fill: parent
        contentHeight: column.height
        Column {
            id: column
            //...
            ValueButton {
                label: qsTr("Date").concat(":")
                value: customDateTime.localeDateString
                //...
            }
            ValueButton {
                width: parent.width
                label: qsTr("Time").concat(":")
                value: customDateTime.localeTimeString
                onClicked: {
                    var dialog = pageStack.push("Sailfish.Silica.TimePickerDialog",
                                                { hour: customDateTime.dateTime.getHours(),
                                                  minute: customDateTime.dateTime.getMinutes()});
                    dialog.accepted.connect(function() {
                        customDateTime.dateTime = new Date(customDateTime.dateTime.getFullYear(),
                                                           customDateTime.dateTime.getMonth(),
                                                           customDateTime.dateTime.getDate(),
                                                           dialog.hour, dialog.minute);
                    });
                }
            }
        }
    }
}

It is easy to see that there are not many changes at all. Ad ad properties replaced by QML-component CustomDateTime , and instead functions toLocaleDateString () and toLocaleTimeString () uses properties localeDateString and localeTimeString . In all other respects, the code has not changed at all, but now property binding works. Changing the dateTime property of a CustomDateTime object will update all object properties and the localeTimeString property in particular, which will update the appearance of the ValueButton object .

Conclusion


As a result, a solution for managing date, time and time zone information was developed, supported by components for editing date and time in Sailfish OS. The solution is to create your own QML component and use it as a model. Such an object allows you to store the date, time and time zone, and also supports a property-binding mechanism and can be used inside Sailfish OS of the DatePickerDialog and TimePickerDailog components for editing. The source code for this example is available on GitHub .

Author: Ivan Shchitov

Also popular now: