Changeset View
Changeset View
Standalone View
Standalone View
src/widget/utils/kexidatetimeformatter.cpp
Show All 15 Lines | 1 | /* This file is part of the KDE project | |||
---|---|---|---|---|---|
16 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 16 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
17 | * Boston, MA 02110-1301, USA. | 17 | * Boston, MA 02110-1301, USA. | ||
18 | */ | 18 | */ | ||
19 | 19 | | |||
20 | #include "kexidatetimeformatter.h" | 20 | #include "kexidatetimeformatter.h" | ||
21 | 21 | | |||
22 | #include <KLocalizedString> | 22 | #include <KLocalizedString> | ||
23 | 23 | | |||
24 | #include <QDebug> | ||||
24 | #include <QLineEdit> | 25 | #include <QLineEdit> | ||
25 | #include <QLocale> | 26 | #include <QLocale> | ||
26 | #include <QRegularExpression> | 27 | | ||
28 | namespace { | ||||
29 | const QString INPUT_MASK_BLANKS_FORMAT(QLatin1String(";_")); | ||||
30 | | ||||
31 | //! Like replace(const QString &before, QLatin1String after) but also returns | ||||
32 | //! @c true if replacement has been made. | ||||
33 | bool tryReplace(QString *str, const char *from, const char *to) { | ||||
34 | Q_ASSERT(str); | ||||
35 | if (str->contains(QLatin1String(from))) { | ||||
36 | str->replace(QLatin1String(from), QLatin1String(to)); | ||||
37 | return true; | ||||
38 | } | ||||
39 | return false; | ||||
40 | } | ||||
41 | } | ||||
27 | 42 | | |||
28 | class KexiDateFormatter::Private | 43 | class KexiDateFormatter::Private | ||
29 | { | 44 | { | ||
30 | public: | 45 | public: | ||
31 | Private() {} | 46 | Private() | ||
47 | // use "short date" format system settings | ||||
48 | //! @todo allow to override the format using column property and/or global app settings | ||||
49 | : inputFormat(QLocale().dateFormat(QLocale::ShortFormat)) | ||||
50 | { | ||||
51 | outputFormat = inputFormat; | ||||
52 | emptyFormat = inputFormat; | ||||
53 | inputMask = inputFormat; | ||||
54 | computeDaysFormatAndMask(); | ||||
55 | computeMonthsFormatAndMask(); | ||||
56 | computeYearsFormatAndMask(); | ||||
57 | inputMask += INPUT_MASK_BLANKS_FORMAT; | ||||
58 | } | ||||
32 | 59 | | |||
33 | //! Input mask generated using the formatter settings. Can be used in QLineEdit::setInputMask(). | 60 | //! Input mask generated using the formatter settings. Can be used in QLineEdit::setInputMask(). | ||
34 | QString inputMask; | 61 | QString inputMask; | ||
35 | 62 | | |||
36 | //! Order of date sections | 63 | //! Date format used by fromString() and stringToVariant() | ||
37 | Order order; | 64 | QString inputFormat; | ||
38 | | ||||
39 | //! 4 or 2 digits | | |||
40 | bool longYear; | | |||
41 | 65 | | |||
42 | bool monthWithLeadingZero, dayWithLeadingZero; | 66 | //! Date format used by toString() | ||
43 | 67 | QString outputFormat; | |||
44 | //! Date format used in toString() | | |||
45 | QString qtFormat; | | |||
46 | 68 | | |||
47 | //! Used in fromString(const QString&) to convert string back to QDate | 69 | //! Date format used by isEmpty() | ||
48 | int yearpos, monthpos, daypos; | 70 | QString emptyFormat; | ||
49 | 71 | | |||
50 | QString separator; | 72 | private: | ||
73 | void computeDaysFormatAndMask() { | ||||
74 | // day (note, order or lookup is important): | ||||
75 | // - dddd - named days not supported, fall back to "d": | ||||
76 | if (tryReplace(&inputMask, "dddd", "90")) { | ||||
77 | // also replace the input format | ||||
78 | inputFormat.replace(QLatin1String("dddd"), QLatin1String("d")); | ||||
79 | emptyFormat.remove(QLatin1String("dddd")); | ||||
80 | return; | ||||
81 | } | ||||
82 | // - ddd - named days not supported, fall back to "d": | ||||
83 | if (tryReplace(&inputMask, "ddd", "90")) { | ||||
84 | // also replace the input format | ||||
85 | inputFormat.replace(QLatin1String("ddd"), QLatin1String("d")); | ||||
86 | emptyFormat.remove(QLatin1String("ddd")); | ||||
87 | return; | ||||
88 | } | ||||
89 | // - dd - The day as a number with a leading zero (01 to 31) | ||||
90 | // second character is optional, e.g. 1_ is OK | ||||
91 | if (tryReplace(&inputMask, "dd", "90")) { | ||||
92 | // also replace the input format | ||||
93 | inputFormat.replace(QLatin1String("dd"), QLatin1String("d")); | ||||
94 | emptyFormat.remove(QLatin1String("dd")); | ||||
95 | return; | ||||
96 | } | ||||
97 | // - d - The day as a number without a leading zero (1 to 31); | ||||
98 | // second character is optional, e.g. 1_ is OK | ||||
99 | if (tryReplace(&inputMask, "d", "90")) { | ||||
100 | emptyFormat.remove(QLatin1String("d")); | ||||
101 | return; | ||||
102 | } | ||||
103 | qWarning() << "Not found 'days' part in format" << inputFormat; | ||||
104 | } | ||||
105 | void computeMonthsFormatAndMask() { | ||||
106 | // month (note, order or lookup is important): | ||||
107 | // - MMMM - named months not supported, fall back to "M" | ||||
108 | if (tryReplace(&inputMask, "MMMM", "90")) { | ||||
109 | // also replace the input format | ||||
110 | inputFormat.replace(QLatin1String("MMMM"), QLatin1String("M")); | ||||
111 | emptyFormat.remove(QLatin1String("MMMM")); | ||||
112 | return; | ||||
113 | } | ||||
114 | // - MMM - named months not supported, fall back to "M" | ||||
115 | if (tryReplace(&inputMask, "MMM", "90")) { | ||||
116 | // also replace the input format | ||||
117 | inputFormat.replace(QLatin1String("MMM"), QLatin1String("M")); | ||||
118 | emptyFormat.remove(QLatin1String("MMM")); | ||||
119 | return; | ||||
120 | } | ||||
121 | // - MM - The month as a number with a leading zero (01 to 12) | ||||
122 | // second character is optional, e.g. 1_ is OK | ||||
123 | if (tryReplace(&inputMask, "MM", "90")) { | ||||
124 | // also replace the input format | ||||
125 | inputFormat.replace(QLatin1String("MM"), QLatin1String("M")); | ||||
126 | emptyFormat.remove(QLatin1String("MM")); | ||||
127 | return; | ||||
128 | } | ||||
129 | // - M - The month as a number without a leading zero (1 to 12); | ||||
130 | // second character is optional, e.g. 1_ is OK | ||||
131 | if (tryReplace(&inputMask, "M", "90")) { | ||||
132 | emptyFormat.remove(QLatin1String("M")); | ||||
133 | return; | ||||
134 | } | ||||
135 | qWarning() << "Not found 'months' part in format" << inputFormat; | ||||
136 | } | ||||
137 | void computeYearsFormatAndMask() { | ||||
138 | // - yyyy - The year as four digit number. | ||||
139 | if (tryReplace(&inputMask, "yyyy", "9999")) { | ||||
140 | emptyFormat.remove(QLatin1String("yyyy")); | ||||
141 | return; | ||||
142 | } | ||||
143 | // - yy - The year as four digit number. | ||||
144 | if (tryReplace(&inputMask, "yy", "99")) { | ||||
145 | emptyFormat.remove(QLatin1String("yy")); | ||||
146 | return; | ||||
147 | } | ||||
148 | qWarning() << "Not found 'years' part in format" << inputFormat; | ||||
149 | } | ||||
51 | }; | 150 | }; | ||
52 | 151 | | |||
53 | class KexiTimeFormatter::Private | 152 | class KexiTimeFormatter::Private | ||
54 | { | 153 | { | ||
55 | public: | 154 | public: | ||
56 | Private() | 155 | Private() | ||
57 | : hmsRegExp( | 156 | // use "short date" format system settings | ||
58 | QLatin1String("(\\d*):(\\d*):(\\d*).*( am| pm){,1}"), QRegularExpression::CaseInsensitiveOption) | 157 | //! @todo allow to override the format using column property and/or global app settings | ||
59 | , hmRegExp( | 158 | : inputFormat(QLocale().timeFormat(QLocale::ShortFormat)) | ||
60 | QLatin1String("(\\d*):(\\d*).*( am| pm){,1}"), QRegularExpression::CaseInsensitiveOption) | | |||
61 | { | 159 | { | ||
160 | outputFormat = inputFormat; | ||||
161 | emptyFormat = inputFormat; | ||||
162 | inputMask = inputFormat; | ||||
163 | computeHoursFormatAndMask(); | ||||
164 | computeMinutesFormatAndMask(); | ||||
165 | computeSecondsFormatAndMask(); | ||||
166 | computeMillisecondsFormatAndMask(); | ||||
167 | computeAmPmFormatAndMask(); | ||||
168 | inputMask += INPUT_MASK_BLANKS_FORMAT; | ||||
62 | } | 169 | } | ||
63 | 170 | | |||
64 | ~Private() | 171 | ~Private() { | ||
65 | { | | |||
66 | } | 172 | } | ||
67 | 173 | | |||
68 | //! Input mask generated using the formatter settings. Can be used in QLineEdit::setInputMask(). | 174 | //! Input mask generated using the formatter settings. Can be used in QLineEdit::setInputMask(). | ||
69 | QString inputMask; | 175 | QString inputMask; | ||
70 | 176 | | |||
71 | //! 12 or 12h | 177 | //! Time format used by fromString() and stringToVariant() | ||
72 | bool is24h; | 178 | QString inputFormat; | ||
73 | 179 | | |||
74 | bool hoursWithLeadingZero; | 180 | //! Time format used by toString() | ||
75 | | ||||
76 | //! Time format used in toString(). | | |||
77 | //! @todo KEXI3 port this to QLocale: Notation from KLocale::setTimeFormat() is used. | | |||
78 | //! @todo KEXI3 Qt5's QTime/QDate::fromString() differs from KLocale::setTimeFormat() | | |||
79 | QString outputFormat; | 181 | QString outputFormat; | ||
80 | 182 | | |||
81 | //! Used in fromString(const QString&) to convert string back to QTime | 183 | //! Date format used by isEmpty() | ||
82 | int hourpos, minpos, secpos, ampmpos; | 184 | QString emptyFormat; | ||
83 | 185 | | |||
84 | QRegularExpression hmsRegExp, hmRegExp; | 186 | private: | ||
187 | void computeHoursFormatAndMask() { | ||||
188 | // - hh - the hour with a leading zero (00 to 23 or 01 to 12 if AM/PM display). | ||||
189 | // second character is optional, e.g. 1_ is OK | ||||
190 | if (tryReplace(&inputMask, "hh", "90")) { | ||||
191 | // also replace the input format | ||||
192 | inputFormat.replace(QLatin1String("hh"), QLatin1String("h")); | ||||
193 | emptyFormat.remove(QLatin1String("hh")); | ||||
194 | return; | ||||
195 | } | ||||
196 | // the same for HH | ||||
197 | if (tryReplace(&inputMask, "HH", "90")) { | ||||
198 | // also replace the input format | ||||
199 | inputFormat.replace(QLatin1String("HH"), QLatin1String("h")); | ||||
200 | emptyFormat.remove(QLatin1String("HH")); | ||||
201 | return; | ||||
202 | } | ||||
203 | // - h - the hour without a leading zero (0 to 23 or 1 to 12 if AM/PM display). | ||||
204 | // second character is optional, e.g. 1_ is OK | ||||
205 | if (tryReplace(&inputMask, "h", "90")) { | ||||
206 | emptyFormat.remove(QLatin1String("h")); | ||||
207 | return; | ||||
208 | } | ||||
209 | // the same for H | ||||
210 | if (tryReplace(&inputMask, "H", "90")) { | ||||
211 | emptyFormat.remove(QLatin1String("H")); | ||||
212 | return; | ||||
213 | } | ||||
214 | qWarning() << "Not found 'hours' part in format" << inputFormat; | ||||
215 | } | ||||
216 | void computeMinutesFormatAndMask() { | ||||
217 | // - mm - the minute with a leading zero (00 to 59). | ||||
218 | if (tryReplace(&inputMask, "mm", "90")) { | ||||
219 | // also replace the input format | ||||
220 | inputFormat.replace(QLatin1String("mm"), QLatin1String("m")); | ||||
221 | emptyFormat.remove(QLatin1String("mm")); | ||||
222 | return; | ||||
223 | } | ||||
224 | // - m - the minute without a leading zero (0 to 59). | ||||
225 | // second character is optional, e.g. 1_ is OK | ||||
226 | if (tryReplace(&inputMask, "m", "90")) { | ||||
227 | emptyFormat.remove(QLatin1String("m")); | ||||
228 | return; | ||||
229 | } | ||||
230 | qWarning() << "Not found 'minutes' part in format" << inputFormat; | ||||
231 | } | ||||
232 | void computeSecondsFormatAndMask() { | ||||
233 | // - ss - the second with a leading zero (00 to 59). | ||||
234 | // second character is optional, e.g. 1_ is OK | ||||
235 | if (tryReplace(&inputMask, "ss", "90")) { | ||||
236 | // also replace the input format | ||||
237 | inputFormat.replace(QLatin1String("ss"), QLatin1String("s")); | ||||
238 | emptyFormat.remove(QLatin1String("ss")); | ||||
239 | return; | ||||
240 | } | ||||
241 | // - s - the second without a leading zero (0 to 59). | ||||
242 | // second character is optional, e.g. 1_ is OK | ||||
243 | if (tryReplace(&inputMask, "s", "90")) { | ||||
244 | emptyFormat.remove(QLatin1String("s")); | ||||
245 | return; | ||||
246 | } | ||||
247 | //qDebug() << "Not found 'seconds' part in format" << inputFormat; | ||||
248 | } | ||||
249 | void computeMillisecondsFormatAndMask() { | ||||
250 | // - zzz - the milliseconds with leading zeroes (000 to 999). | ||||
251 | // last two characters are optional, e.g. 1_ is OK | ||||
252 | if (tryReplace(&inputMask, "zzz", "900")) { | ||||
253 | // also replace the input format | ||||
254 | inputFormat.replace(QLatin1String("zzz"), QLatin1String("z")); | ||||
255 | emptyFormat.remove(QLatin1String("zzz")); | ||||
256 | return; | ||||
257 | } | ||||
258 | // - m - the milliseconds without leading zeroes (0 to 999). | ||||
259 | // last two characters are optional, e.g. 1_ is OK | ||||
260 | if (tryReplace(&inputMask, "z", "900")) { | ||||
261 | emptyFormat.remove(QLatin1String("z")); | ||||
262 | return; | ||||
263 | } | ||||
264 | //qDebug() << "Not found 'milliseconds' part in format" << inputFormat; | ||||
265 | } | ||||
266 | void computeAmPmFormatAndMask() { | ||||
267 | // - AP - interpret as an AM/PM time. AP must be either "AM" or "PM". | ||||
268 | //! @note not a 100% accurate approach, we're assuming that "AP" substring is only | ||||
269 | //! used to indicate AM/PM | ||||
270 | if (tryReplace(&inputMask, "AP", ">AA!")) { // we're also converting to upper case | ||||
271 | emptyFormat.remove(QLatin1String("AP")); | ||||
272 | return; | ||||
273 | } | ||||
274 | // - ap - interpret as an AM/PM time. ap must be either "am" or "pm". | ||||
275 | //! @note see above | ||||
276 | if (tryReplace(&inputMask, "ap", "<AA!")) { // we're also converting to upper case | ||||
277 | emptyFormat.remove(QLatin1String("ap")); | ||||
278 | return; | ||||
279 | } | ||||
280 | //qDebug() << "Not found 'AM/PM' part in format" << inputFormat; | ||||
281 | } | ||||
85 | }; | 282 | }; | ||
86 | 283 | | |||
87 | KexiDateFormatter::KexiDateFormatter() | 284 | KexiDateFormatter::KexiDateFormatter() | ||
88 | : d(new Private) | 285 | : d(new Private) | ||
89 | { | 286 | { | ||
90 | // use "short date" format system settings | | |||
91 | //! @todo allow to override the format using column property and/or global app settings | | |||
92 | QLocale locale; | | |||
93 | QString df(locale.dateFormat(QLocale::ShortFormat)); | | |||
94 | if (df.length() > 2) | | |||
95 | d->separator = df.mid(2, 1); | | |||
96 | else | | |||
97 | d->separator = "-"; | | |||
98 | const int separatorLen = d->separator.length(); | | |||
99 | QString yearMask("9999"); | | |||
100 | QString yearDateFormat("yyyy"); | | |||
101 | QString monthDateFormat("MM"); | | |||
102 | QString dayDateFormat("dd"); //for setting up d->dateFormat | | |||
103 | bool ok = df.length() >= 8; | | |||
104 | int yearpos, monthpos, daypos; //result of df.find() | | |||
105 | if (ok) {//look at % variables | | |||
106 | //! @todo more variables are possible here, see QDate::toString() docs | | |||
107 | yearpos = df.indexOf("%y", 0, Qt::CaseInsensitive); //&y or %y | | |||
108 | d->longYear = !(yearpos >= 0 && df.mid(yearpos + 1, 1) == "y"); | | |||
109 | if (!d->longYear) { | | |||
110 | yearMask = "99"; | | |||
111 | yearDateFormat = "yy"; | | |||
112 | } | | |||
113 | monthpos = df.indexOf("%m", 0, Qt::CaseSensitive); //%m or %n | | |||
114 | d->monthWithLeadingZero = true; | | |||
115 | if (monthpos < 0) { | | |||
116 | monthpos = df.indexOf("%n", 0, Qt::CaseInsensitive); | | |||
117 | d->monthWithLeadingZero = false; | | |||
118 | monthDateFormat = "M"; | | |||
119 | } | | |||
120 | daypos = df.indexOf("%d", 0, Qt::CaseSensitive);//%d or %e | | |||
121 | d->dayWithLeadingZero = true; | | |||
122 | if (daypos < 0) { | | |||
123 | daypos = df.indexOf("%e", 0, Qt::CaseInsensitive); | | |||
124 | d->dayWithLeadingZero = false; | | |||
125 | dayDateFormat = "d"; | | |||
126 | } | | |||
127 | ok = (yearpos >= 0 && monthpos >= 0 && daypos >= 0); | | |||
128 | } | | |||
129 | d->order = YMD; //default | | |||
130 | if (ok) { | | |||
131 | if (yearpos < monthpos && monthpos < daypos) { | | |||
132 | //will be set in "default: YMD" | | |||
133 | } else if (yearpos < daypos && daypos < monthpos) { | | |||
134 | d->order = YDM; | | |||
135 | //! @todo use QRegExp (to replace %Y by %1, etc.) instead of hardcoded "%1%299%399" | | |||
136 | //! because df may contain also other characters | | |||
137 | d->inputMask = yearMask + d->separator + QLatin1String("99") + d->separator + QLatin1String("99"); | | |||
138 | d->qtFormat = yearDateFormat + d->separator + dayDateFormat + d->separator + monthDateFormat; | | |||
139 | d->yearpos = 0; | | |||
140 | d->daypos = yearMask.length() + separatorLen; | | |||
141 | d->monthpos = d->daypos + 2 + separatorLen; | | |||
142 | } else if (daypos < monthpos && monthpos < yearpos) { | | |||
143 | d->order = DMY; | | |||
144 | d->inputMask = QLatin1String("99") + d->separator + QLatin1String("99") + d->separator + yearMask; | | |||
145 | d->qtFormat = dayDateFormat + d->separator + monthDateFormat + d->separator + yearDateFormat; | | |||
146 | d->daypos = 0; | | |||
147 | d->monthpos = 2 + separatorLen; | | |||
148 | d->yearpos = d->monthpos + 2 + separatorLen; | | |||
149 | } else if (monthpos < daypos && daypos < yearpos) { | | |||
150 | d->order = MDY; | | |||
151 | d->inputMask = QLatin1String("99") + d->separator + QLatin1String("99") + d->separator + yearMask; | | |||
152 | d->qtFormat = monthDateFormat + d->separator + dayDateFormat + d->separator + yearDateFormat; | | |||
153 | d->monthpos = 0; | | |||
154 | d->daypos = 2 + separatorLen; | | |||
155 | d->yearpos = d->daypos + 2 + separatorLen; | | |||
156 | } else | | |||
157 | ok = false; | | |||
158 | } | | |||
159 | if (!ok || d->order == YMD) {//default: YMD | | |||
160 | d->inputMask = yearMask + d->separator + QLatin1String("99") + d->separator + QLatin1String("99"); | | |||
161 | d->qtFormat = yearDateFormat + d->separator + monthDateFormat + d->separator + dayDateFormat; | | |||
162 | d->yearpos = 0; | | |||
163 | d->monthpos = yearMask.length() + separatorLen; | | |||
164 | d->daypos = d->monthpos + 2 + separatorLen; | | |||
165 | } | | |||
166 | d->inputMask += ";_"; | | |||
167 | } | 287 | } | ||
168 | 288 | | |||
169 | KexiDateFormatter::~KexiDateFormatter() | 289 | KexiDateFormatter::~KexiDateFormatter() | ||
170 | { | 290 | { | ||
171 | delete d; | 291 | delete d; | ||
172 | } | 292 | } | ||
173 | 293 | | |||
174 | QDate KexiDateFormatter::fromString(const QString& str, bool *ok) const | 294 | QDate KexiDateFormatter::fromString(const QString& str) const | ||
175 | { | 295 | { | ||
176 | bool thisOk = true; | 296 | return QDate::fromString(str, d->inputFormat); | ||
177 | if (!ok) { | | |||
178 | ok = &thisOk; | | |||
179 | } | | |||
180 | int year = str.mid(d->yearpos, d->longYear ? 4 : 2).toInt(ok); | | |||
181 | if (!*ok) { | | |||
182 | return QDate(); | | |||
183 | } | | |||
184 | if (year < 30) {//2000..2029 | | |||
185 | year = 2000 + year; | | |||
186 | } else if (year < 100) {//1930..1999 | | |||
187 | year = 1900 + year; | | |||
188 | } | | |||
189 | | ||||
190 | const int month = str.mid(d->monthpos, 2).toInt(ok); | | |||
191 | if (!*ok) { | | |||
192 | return QDate(); | | |||
193 | } | 297 | } | ||
194 | 298 | | |||
195 | const int day = str.mid(d->daypos, 2).toInt(ok); | 299 | QVariant KexiDateFormatter::stringToVariant(const QString& str) const | ||
196 | if (!*ok) { | | |||
197 | return QDate(); | | |||
198 | } | | |||
199 | | ||||
200 | const QDate date(year, month, day); | | |||
201 | if (!date.isValid()) { | | |||
202 | *ok = false; | | |||
203 | return QDate(); | | |||
204 | } | | |||
205 | *ok = true; | | |||
206 | return date; | | |||
207 | } | | |||
208 | | ||||
209 | QVariant KexiDateFormatter::stringToVariant(const QString& str, bool *ok) const | | |||
210 | { | 300 | { | ||
211 | if (isEmpty(str)) { | 301 | const QDate date(fromString(str)); | ||
212 | if (ok) { | 302 | return date.isValid() ? date : QVariant(); | ||
213 | *ok = true; | | |||
214 | } | | |||
215 | return QVariant(); | | |||
216 | } | | |||
217 | const QDate date(fromString(str, ok)); | | |||
218 | if (date.isValid()) { | | |||
219 | return date; | | |||
220 | } | | |||
221 | return QVariant(); | | |||
222 | } | 303 | } | ||
223 | 304 | | |||
224 | bool KexiDateFormatter::isEmpty(const QString& str) const | 305 | bool KexiDateFormatter::isEmpty(const QString& str) const | ||
225 | { | 306 | { | ||
226 | if (str.isEmpty()) { | 307 | const QString t(str.trimmed()); | ||
227 | return true; | 308 | return t.isEmpty() || t == d->emptyFormat; | ||
228 | } | | |||
229 | QString s(str); | | |||
230 | return s.remove(d->separator).trimmed().isEmpty(); | | |||
231 | } | 309 | } | ||
232 | 310 | | |||
233 | QString KexiDateFormatter::inputMask() const | 311 | QString KexiDateFormatter::inputMask() const | ||
234 | { | 312 | { | ||
235 | return d->inputMask; | 313 | return d->inputMask; | ||
236 | } | 314 | } | ||
237 | 315 | | |||
238 | QString KexiDateFormatter::separator() const | | |||
239 | { | | |||
240 | return d->separator; | | |||
241 | } | | |||
242 | | ||||
243 | QString KexiDateFormatter::toString(const QDate& date) const | 316 | QString KexiDateFormatter::toString(const QDate& date) const | ||
244 | { | 317 | { | ||
245 | return date.toString(d->qtFormat); | 318 | return date.toString(d->outputFormat); | ||
246 | } | 319 | } | ||
247 | 320 | | |||
248 | //------------------------------------------------ | 321 | //------------------------------------------------ | ||
249 | 322 | | |||
250 | KexiTimeFormatter::KexiTimeFormatter() | 323 | KexiTimeFormatter::KexiTimeFormatter() | ||
251 | : d(new Private) | 324 | : d(new Private) | ||
252 | { | 325 | { | ||
253 | QLocale locale; | | |||
254 | QString tf(locale.timeFormat(QLocale::ShortFormat)); | | |||
255 | //d->hourpos, d->minpos, d->secpos; are result of tf.indexOf() | | |||
256 | QString hourVariable, minVariable, secVariable; | | |||
257 | | ||||
258 | //detect position of HOUR section: find %H or %k or %I or %l | | |||
259 | d->is24h = true; | | |||
260 | d->hoursWithLeadingZero = true; | | |||
261 | d->hourpos = tf.indexOf("%H", 0, Qt::CaseSensitive); | | |||
262 | if (d->hourpos >= 0) { | | |||
263 | d->is24h = true; | | |||
264 | d->hoursWithLeadingZero = true; | | |||
265 | } else { | | |||
266 | d->hourpos = tf.indexOf("%k", 0, Qt::CaseSensitive); | | |||
267 | if (d->hourpos >= 0) { | | |||
268 | d->is24h = true; | | |||
269 | d->hoursWithLeadingZero = false; | | |||
270 | } else { | | |||
271 | d->hourpos = tf.indexOf("%I", 0, Qt::CaseSensitive); | | |||
272 | if (d->hourpos >= 0) { | | |||
273 | d->is24h = false; | | |||
274 | d->hoursWithLeadingZero = true; | | |||
275 | } else { | | |||
276 | d->hourpos = tf.indexOf("%l", 0, Qt::CaseSensitive); | | |||
277 | if (d->hourpos >= 0) { | | |||
278 | d->is24h = false; | | |||
279 | d->hoursWithLeadingZero = false; | | |||
280 | } | | |||
281 | } | | |||
282 | } | | |||
283 | } | | |||
284 | d->minpos = tf.indexOf("%M", 0, Qt::CaseSensitive); | | |||
285 | d->secpos = tf.indexOf("%S", 0, Qt::CaseSensitive); //can be -1 | | |||
286 | d->ampmpos = tf.indexOf("%p", 0, Qt::CaseSensitive); //can be -1 | | |||
287 | | ||||
288 | if (d->hourpos < 0 || d->minpos < 0) { | | |||
289 | //set default: hr and min are needed, sec are optional | | |||
290 | tf = "%H:%M:%S"; | | |||
291 | d->is24h = true; | | |||
292 | d->hoursWithLeadingZero = false; | | |||
293 | d->hourpos = 0; | | |||
294 | d->minpos = 3; | | |||
295 | d->secpos = d->minpos + 3; | | |||
296 | d->ampmpos = -1; | | |||
297 | } | | |||
298 | hourVariable = tf.mid(d->hourpos, 2); | | |||
299 | | ||||
300 | d->inputMask = tf; | | |||
301 | d->inputMask.replace(hourVariable, "99"); | | |||
302 | d->inputMask.replace("%M", "99"); | | |||
303 | d->inputMask.replace("%S", "00"); //optional | | |||
304 | d->inputMask.replace("%p", "AA"); //am or pm | | |||
305 | d->inputMask += ";_"; | | |||
306 | | ||||
307 | d->outputFormat = tf; | | |||
308 | } | 326 | } | ||
309 | 327 | | |||
310 | KexiTimeFormatter::~KexiTimeFormatter() | 328 | KexiTimeFormatter::~KexiTimeFormatter() | ||
311 | { | 329 | { | ||
312 | delete d; | 330 | delete d; | ||
313 | } | 331 | } | ||
314 | 332 | | |||
315 | QTime KexiTimeFormatter::fromString(const QString& str) const | 333 | QTime KexiTimeFormatter::fromString(const QString& str) const | ||
316 | { | 334 | { | ||
317 | QTime time; | 335 | return QTime::fromString(str, d->inputFormat); | ||
318 | int hour, min, sec; | | |||
319 | bool pm = false; | | |||
320 | QRegularExpressionMatch matchHms = d->hmsRegExp.match(str); | | |||
321 | QRegularExpressionMatch matchHm = d->hmRegExp.match(str); | | |||
322 | bool tryWithoutSeconds = true; | | |||
323 | | ||||
324 | if (d->secpos >= 0) { | | |||
325 | if (-1 != matchHms.capturedStart()) { | | |||
326 | hour = matchHms.captured(1).toInt(); | | |||
327 | min = matchHms.captured(2).toInt(); | | |||
328 | sec = matchHms.captured(3).toInt(); | | |||
329 | if (d->ampmpos >= 0 && d->hmsRegExp.captureCount() > 3) | | |||
330 | pm = matchHms.captured(4).trimmed().toLower() == "pm"; | | |||
331 | tryWithoutSeconds = false; | | |||
332 | } | | |||
333 | } | | |||
334 | if (tryWithoutSeconds) { | | |||
335 | if (-1 == matchHm.capturedStart()) | | |||
336 | return QTime(99, 0, 0); | | |||
337 | hour = matchHm.captured(1).toInt(); | | |||
338 | min = matchHm.captured(2).toInt(); | | |||
339 | sec = 0; | | |||
340 | if (d->ampmpos >= 0 && d->hmRegExp.captureCount() > 2) | | |||
341 | pm = matchHm.captured(4).toLower() == "pm"; | | |||
342 | } | | |||
343 | | ||||
344 | if (pm && hour < 12) | | |||
345 | hour += 12; //PM | | |||
346 | time = QTime(hour, min, sec); | | |||
347 | return time; | | |||
348 | } | | |||
349 | | ||||
350 | QVariant KexiTimeFormatter::stringToVariant(const QString& str, bool *ok) | | |||
351 | { | | |||
352 | if (isEmpty(str)) | | |||
353 | return QVariant(); | | |||
354 | const QTime time(fromString(str)); | | |||
355 | if (time.isValid()) { | | |||
356 | if (ok) { | | |||
357 | *ok = true; | | |||
358 | } | 336 | } | ||
359 | return time; | 337 | | ||
360 | } | 338 | QVariant KexiTimeFormatter::stringToVariant(const QString& str) | ||
361 | if (ok) { | 339 | { | ||
362 | *ok = false; | 340 | const QTime result(fromString(str)); | ||
363 | } | 341 | return result.isValid() ? result : QVariant(); | ||
364 | return QVariant(); | | |||
365 | } | 342 | } | ||
366 | 343 | | |||
367 | bool KexiTimeFormatter::isEmpty(const QString& str) const | 344 | bool KexiTimeFormatter::isEmpty(const QString& str) const | ||
368 | { | 345 | { | ||
369 | QString s(str); | 346 | const QString t(str.trimmed()); | ||
370 | return s.remove(':').trimmed().isEmpty(); | 347 | return t.isEmpty() || t == d->emptyFormat; | ||
371 | } | 348 | } | ||
372 | 349 | | |||
373 | QString KexiTimeFormatter::toString(const QTime& time) const | 350 | QString KexiTimeFormatter::toString(const QTime& time) const | ||
374 | { | 351 | { | ||
375 | if (!time.isValid()) | 352 | return time.toString(d->outputFormat); | ||
376 | return QString(); | | |||
377 | | ||||
378 | QString s(d->outputFormat); | | |||
379 | if (d->is24h) { | | |||
380 | if (d->hoursWithLeadingZero) | | |||
381 | s.replace("%H", QString::fromLatin1(time.hour() < 10 ? "0" : "") + QString::number(time.hour())); | | |||
382 | else | | |||
383 | s.replace("%k", QString::number(time.hour())); | | |||
384 | } else { | | |||
385 | int time12 = (time.hour() > 12) ? (time.hour() - 12) : time.hour(); | | |||
386 | if (d->hoursWithLeadingZero) | | |||
387 | s.replace("%I", QString::fromLatin1(time12 < 10 ? "0" : "") + QString::number(time12)); | | |||
388 | else | | |||
389 | s.replace("%l", QString::number(time12)); | | |||
390 | } | | |||
391 | s.replace("%M", QString::fromLatin1(time.minute() < 10 ? "0" : "") + QString::number(time.minute())); | | |||
392 | if (d->secpos >= 0) | | |||
393 | s.replace("%S", QString::fromLatin1(time.second() < 10 ? "0" : "") + QString::number(time.second())); | | |||
394 | if (d->ampmpos >= 0) | | |||
395 | s.replace("%p", time.hour() >= 12 ? xi18nc("afternoon", "pm") : xi18nc("before noon", "am")); | | |||
396 | return s; | | |||
397 | } | 353 | } | ||
398 | 354 | | |||
399 | QString KexiTimeFormatter::inputMask() const | 355 | QString KexiTimeFormatter::inputMask() const | ||
400 | { | 356 | { | ||
401 | return d->inputMask; | 357 | return d->inputMask; | ||
402 | } | 358 | } | ||
403 | 359 | | |||
404 | //------------------------------------------------ | 360 | //------------------------------------------------ | ||
405 | 361 | | |||
406 | QString KexiDateTimeFormatter::inputMask(const KexiDateFormatter& dateFormatter, | 362 | QString KexiDateTimeFormatter::inputMask(const KexiDateFormatter& dateFormatter, | ||
407 | const KexiTimeFormatter& timeFormatter) | 363 | const KexiTimeFormatter& timeFormatter) | ||
408 | { | 364 | { | ||
409 | QString mask(dateFormatter.inputMask()); | 365 | QString mask(dateFormatter.inputMask()); | ||
410 | mask.truncate(dateFormatter.inputMask().length() - 2); | 366 | mask.chop(INPUT_MASK_BLANKS_FORMAT.length()); | ||
411 | return mask + " " + timeFormatter.inputMask(); | 367 | return mask + " " + timeFormatter.inputMask(); | ||
412 | } | 368 | } | ||
413 | 369 | | |||
414 | QDateTime KexiDateTimeFormatter::fromString( | 370 | QDateTime KexiDateTimeFormatter::fromString( | ||
415 | const KexiDateFormatter& dateFormatter, | 371 | const KexiDateFormatter& dateFormatter, | ||
416 | const KexiTimeFormatter& timeFormatter, const QString& str) | 372 | const KexiTimeFormatter& timeFormatter, const QString& str) | ||
417 | { | 373 | { | ||
418 | QString s(str.trimmed()); | 374 | QString s(str.trimmed()); | ||
▲ Show 20 Lines • Show All 50 Lines • Show Last 20 Lines |