1/*
2 This file is part of the kholidays library.
3
4 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (c) 2004 Allen Winter <winter@kde.org>
6 Copyright (c) 2008 David Jarvie <djarvie@kde.org>
7 Copyright 2010 John Layt <john@layt.net>
8
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public
11 License as published by the Free Software Foundation; either
12 version 2 of the License, or (at your option) any later version.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU Library General Public License for more details.
18
19 You should have received a copy of the GNU Library General Public License
20 along with this library; see the file COPYING.LIB. If not, write to the
21 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 Boston, MA 02110-1301, USA.
23*/
24
25#include "holidayregion.h"
26
27#include <QtCore/QDateTime>
28#include <QtCore/QFile>
29#include <QtCore/QSharedData>
30#include <QtCore/QFileInfo>
31
32#include <KStandardDirs>
33#include <KGlobal>
34#include <KLocale>
35#include <KLocalizedString>
36#include <KDebug>
37
38#include "holiday_p.h"
39#include "parsers/plan2/holidayparserdriverplan_p.h"
40
41using namespace KHolidays;
42
43class HolidayRegion::Private
44{
45 public:
46 Private( const QString &regionCode ) : mDriver( 0 ),
47 mRegionCode( regionCode )
48 {
49 if ( !mRegionCode.isEmpty() ) {
50
51 if ( mRegionCode.length() == 2 ) { //Backwards compatible mode for old location code
52 mLocation = mRegionCode;
53 QStringList locationFiles = KGlobal::dirs()->findAllResources( "data",
54 QLatin1String("libkholidays/plan2/holiday_") + mLocation + QLatin1Char('*'),
55 KStandardDirs::NoDuplicates );
56 if ( locationFiles.count() > 0 ) {
57 mRegionCode = locationFiles.at( 0 ).
58 mid( locationFiles.at( 0 ).lastIndexOf( QLatin1String("holiday_") ) + 8 );
59 }
60 }
61
62 mHolidayFile.setFile(
63 KStandardDirs::locate( "data", QLatin1String("libkholidays/plan2/holiday_") + mRegionCode ) );
64 }
65
66 init();
67 }
68
69 Private( const QFileInfo &regionFile ) : mDriver( 0 ),
70 mHolidayFile( regionFile )
71 {
72 init();
73 }
74
75 ~Private()
76 {
77 delete mDriver;
78 }
79
80 void init()
81 {
82 if ( mHolidayFile.exists() ) {
83 mDriver = new HolidayParserDriverPlan( mHolidayFile.absoluteFilePath() );
84 if ( mDriver ) {
85
86 if ( mLocation.isEmpty() ) {
87 mLocation = mDriver->fileCountryCode().left( 2 );
88 }
89
90 if ( mRegionCode.isEmpty() ) {
91 if ( mHolidayFile.fileName().startsWith( QLatin1String( "holiday_" ) ) ) {
92 mRegionCode = mHolidayFile.fileName().mid( 8 );
93 } else {
94 mRegionCode = mHolidayFile.fileName();
95 }
96 }
97
98 } else {
99 mRegionCode.clear();
100 mLocation.clear();
101 }
102 } else {
103 mRegionCode.clear();
104 mLocation.clear();
105 }
106 }
107
108 HolidayParserDriver *mDriver; // The parser driver for the holiday file
109 QString mRegionCode; // region code of holiday region
110 QString mLocation; // old location code, use now deprecated
111 QFileInfo mHolidayFile; // file containing holiday data, or null
112};
113
114HolidayRegion::HolidayRegion( const QString &regionCode )
115 : d( new Private( regionCode ) )
116{
117}
118
119HolidayRegion::HolidayRegion( const QFileInfo &regionFile )
120 : d( new Private( regionFile ) )
121{
122}
123
124HolidayRegion::~HolidayRegion()
125{
126 delete d;
127}
128
129QStringList HolidayRegion::locations()
130{
131 const QStringList files =
132 KGlobal::dirs()->findAllResources( "data", QLatin1String("libkholidays/plan2/holiday_*"),
133 KStandardDirs::NoDuplicates );
134
135 QStringList locations;
136 foreach ( const QString &filename, files ) {
137 locations.append( filename.mid( filename.lastIndexOf( QLatin1String("holiday_") ) + 8, 2 ) );
138 }
139
140 locations.removeDuplicates();
141 qSort( locations );
142 return locations;
143}
144
145QString HolidayRegion::location() const
146{
147 return d->mLocation;
148}
149
150QStringList HolidayRegion::regionCodes()
151{
152 const QStringList files =
153 KGlobal::dirs()->findAllResources( "data", QLatin1String("libkholidays/plan2/holiday_*"),
154 KStandardDirs::NoDuplicates );
155
156 QStringList regionCodesList;
157 foreach ( const QString &filename, files ) {
158 regionCodesList.append( filename.mid( filename.lastIndexOf( QLatin1String("holiday_") ) + 8 ) );
159 }
160
161 qSort( regionCodesList );
162 return regionCodesList;
163}
164
165QString HolidayRegion::regionCode() const
166{
167 return d->mRegionCode;
168}
169
170QString HolidayRegion::countryCode() const
171{
172 return d->mDriver->fileCountryCode();
173}
174
175QString HolidayRegion::countryCode( const QString &regionCode )
176{
177 HolidayRegion temp = HolidayRegion( regionCode );
178 if ( temp.isValid() ) {
179 return temp.countryCode();
180 } else {
181 return QString();
182 }
183}
184
185QString HolidayRegion::languageCode() const
186{
187 return d->mDriver->fileLanguageCode();
188}
189
190QString HolidayRegion::languageCode( const QString &regionCode )
191{
192 HolidayRegion temp = HolidayRegion( regionCode );
193 if ( temp.isValid() ) {
194 return temp.languageCode();
195 } else {
196 return QString();
197 }
198}
199
200QString HolidayRegion::name() const
201{
202 QString tempName = d->mDriver->fileName();
203
204 if ( tempName.isEmpty() ) {
205 QStringList countryParts = countryCode().toLower().split( QLatin1Char('-') );
206 QString country = countryParts.at( 0 );
207 QString regionName, typeName;
208
209 if ( country != QLatin1String("xx") ) {
210 if ( countryParts.count() == 2 ) {
211 // Temporary measure to get regions translated, only those files that already exist
212 // In 4.6 hope to have isocodes project integration for translations via KLocale
213 QString subdivision = countryParts.at( 1 );
214 if ( country == QLatin1String("ca") && subdivision == QLatin1String("qc") ) {
215 regionName = i18nc( "Canadian region", "Quebec" );
216 } else if ( country == QLatin1String("de") && subdivision == QLatin1String("by") ) {
217 regionName = i18nc( "German region", "Bavaria" );
218 } else if ( country == QLatin1String("es") && subdivision == QLatin1String("ct") ) {
219 regionName = i18nc( "Spanish region", "Catalonia" );
220 } else if ( country == QLatin1String("gb") && subdivision == QLatin1String("eaw") ) {
221 regionName = i18nc( "UK Region", "England and Wales" );
222 } else if ( country == QLatin1String("gb") && subdivision == QLatin1String("eng") ) {
223 regionName = i18nc( "UK Region", "England" );
224 } else if ( country == QLatin1String("gb") && subdivision == QLatin1String("wls") ) {
225 regionName = i18nc( "UK Region", "Wales" );
226 } else if ( country == QLatin1String("gb") && subdivision == QLatin1String("sct") ) {
227 regionName = i18nc( "UK Region", "Scotland" );
228 } else if ( country == QLatin1String("gb") && subdivision == QLatin1String("nir") ) {
229 regionName = i18nc( "UK Region", "Northern Ireland" );
230 } else if ( country == QLatin1String("it") && subdivision == QLatin1String("bz") ) {
231 regionName = i18nc( "Italian Region", "South Tyrol" );
232 } else if ( country == QLatin1String("au") && subdivision == QLatin1String("nsw") ) {
233 regionName = i18nc( "Australian Region", "New South Wales" );
234 } else if ( country == QLatin1String("au") && subdivision == QLatin1String("qld") ) {
235 regionName = i18nc( "Australian Region", "Queensland" );
236 } else if ( country == QLatin1String("au") && subdivision == QLatin1String("vic") ) {
237 regionName = i18nc( "Australian Region", "Victoria" );
238 } else if ( country == QLatin1String("au") && subdivision == QLatin1String("sa") ) {
239 regionName = i18nc( "Australian Region", "South Australia" );
240 } else if ( country == QLatin1String("au") && subdivision == QLatin1String("nt") ) {
241 regionName = i18nc( "Australian Region", "Northern Territory" );
242 } else if ( country == QLatin1String("au") && subdivision == QLatin1String("act") ) {
243 regionName = i18nc( "Australian Region", "Australian Capital Territory" );
244 } else if ( country == QLatin1String("au") && subdivision == QLatin1String("wa") ) {
245 regionName = i18nc( "Australian Region", "Western Australia" );
246 } else if ( country == QLatin1String("au") && subdivision == QLatin1String("tas") ) {
247 regionName = i18nc( "Australian Region", "Tasmania" );
248 } else if ( country == QLatin1String("ba") && subdivision == QLatin1String("srp") ) {
249 regionName = i18nc( "Bosnian and Herzegovinian Region", "Republic of Srpska" );
250 } else {
251 regionName = KGlobal::locale()->countryCodeToName( country );
252 }
253 } else {
254 regionName = KGlobal::locale()->countryCodeToName( country );
255 }
256 }
257
258 //Cheat on type for now,take direct from region code until API is introduced in SC 4.6
259 QStringList regionParts = regionCode().toLower().split( QLatin1Char('_') );
260 if ( regionParts.count() == 3 ) {
261 QString type = regionParts.at( 2 );
262 // Will create lots more in 4.6
263 // Religious types, just simple for now
264 if ( type == QLatin1String("public") ) {
265 typeName = i18nc( "Holiday type", "Public" );
266 } else if ( type == QLatin1String("civil") ) {
267 typeName = i18nc( "Holiday type", "Civil" );
268 } else if ( type == QLatin1String("religious") ) {
269 typeName = i18nc( "Holiday type", "Religious" );
270 } else if ( type == QLatin1String("government") ) {
271 typeName = i18nc( "Holiday type", "Government" );
272 } else if ( type == QLatin1String("financial") ) {
273 typeName = i18nc( "Holiday type", "Financial" );
274 } else if ( type == QLatin1String("cultural") ) {
275 typeName = i18nc( "Holiday type", "Cultural" );
276 } else if ( type == QLatin1String("commemorative") ) {
277 typeName = i18nc( "Holiday type", "Commemorative" );
278 } else if ( type == QLatin1String("historical") ) {
279 typeName = i18nc( "Holiday type", "Historical" );
280 } else if ( type == QLatin1String("school") ) {
281 typeName = i18nc( "Holiday type", "School" );
282 } else if ( type == QLatin1String("seasonal") ) {
283 typeName = i18nc( "Holiday type", "Seasonal" );
284 } else if ( type == QLatin1String("nameday") ) {
285 typeName = i18nc( "Holiday type", "Name Days" );
286 } else if ( type == QLatin1String("personal") ) {
287 typeName = i18nc( "Holiday type", "Personal" );
288 } else if ( type == QLatin1String("christian") ) {
289 typeName = i18nc( "Holiday type", "Christian" );
290 } else if ( type == QLatin1String("anglican") ) {
291 typeName = i18nc( "Holiday type", "Anglican" );
292 } else if ( type == QLatin1String("catholic") ) {
293 typeName = i18nc( "Holiday type", "Catholic" );
294 } else if ( type == QLatin1String("protestant") ) {
295 typeName = i18nc( "Holiday type", "Protestant" );
296 } else if ( type == QLatin1String("orthodox") ) {
297 typeName = i18nc( "Holiday type", "Orthodox" );
298 } else if ( type == QLatin1String("jewish") ) {
299 typeName = i18nc( "Holiday type", "Jewish" );
300 } else if ( type == QLatin1String("jewish-orthodox") ) {
301 typeName = i18nc( "Holiday type", "Jewish Orthodox" );
302 } else if ( type == QLatin1String("jewish-conservative") ) {
303 typeName = i18nc( "Holiday type", "Jewish Conservative" );
304 } else if ( type == QLatin1String("jewish-reform") ) {
305 typeName = i18nc( "Holiday type", "Jewish Reform" );
306 } else if ( type == QLatin1String("islamic") ) {
307 typeName = i18nc( "Holiday type", "Islamic" );
308 } else if ( type == QLatin1String("islamic-sunni") ) {
309 typeName = i18nc( "Holiday type", "Islamic Sunni" );
310 } else if ( type == QLatin1String("islamic-shia") ) {
311 typeName = i18nc( "Holiday type", "Islamic Shia" );
312 } else if ( type == QLatin1String("islamic-sufi") ) {
313 typeName = i18nc( "Holiday type", "Islamic Sufi" );
314 }
315 }
316
317 if ( !regionName.isEmpty() ) {
318 if ( !typeName.isEmpty() ) {
319 tempName = i18nc( "Holiday file display name, %1 = region name, %2 = holiday type", "%1 - %2" ).arg( regionName ).arg( typeName );
320 } else {
321 tempName = regionName;
322 }
323 } else if ( !typeName.isEmpty() ) {
324 tempName = typeName;
325 } else {
326 tempName = i18nc( "Unknown holiday region", "Unknown" );
327 }
328 }
329 return tempName;
330}
331
332QString HolidayRegion::name( const QString &regionCode )
333{
334 HolidayRegion temp = HolidayRegion( regionCode );
335 if ( temp.isValid() ) {
336 return temp.name();
337 } else {
338 return QString();
339 }
340}
341
342QString HolidayRegion::description() const
343{
344 return d->mDriver->fileDescription();
345}
346
347QString HolidayRegion::description( const QString &regionCode )
348{
349 HolidayRegion temp = HolidayRegion( regionCode );
350 if ( temp.isValid() ) {
351 return temp.description();
352 } else {
353 return QString();
354 }
355}
356
357bool HolidayRegion::isValid() const
358{
359 return d->mHolidayFile.exists() && d->mDriver;
360}
361
362bool HolidayRegion::isValid( const QString &regionCode )
363{
364 HolidayRegion temp = HolidayRegion( regionCode );
365 return temp.isValid();
366}
367
368Holiday::List HolidayRegion::holidays( const QDate &startDate, const QDate &endDate ) const
369{
370 return holidays( startDate, endDate, Holiday::MultidayHolidaysAsMultipleEvents );
371}
372
373Holiday::List HolidayRegion::holidays( const QDate &startDate, const QDate &endDate,
374 Holiday::MultidayMode multidayMode ) const
375{
376 if ( isValid() ) {
377 return d->mDriver->parseHolidays( startDate, endDate, multidayMode );
378 } else {
379 return Holiday::List();
380 }
381}
382
383Holiday::List HolidayRegion::holidays( const QDate &date ) const
384{
385 return holidays( date, Holiday::MultidayHolidaysAsMultipleEvents );
386}
387
388Holiday::List HolidayRegion::holidays( const QDate &date, Holiday::MultidayMode multidayMode ) const
389{
390 if ( isValid() ) {
391 return d->mDriver->parseHolidays( date, multidayMode );
392 } else {
393 return Holiday::List();
394 }
395}
396
397Holiday::List HolidayRegion::holidays( int calendarYear, const QString &calendarType ) const
398{
399 return holidays( calendarYear, calendarType, Holiday::MultidayHolidaysAsMultipleEvents );
400}
401
402Holiday::List HolidayRegion::holidays( int calendarYear, const QString &calendarType,
403 Holiday::MultidayMode multidayMode ) const
404{
405 if ( isValid() ) {
406 return d->mDriver->parseHolidays( calendarYear, calendarType, multidayMode );
407 } else {
408 return Holiday::List();
409 }
410}
411
412bool HolidayRegion::isHoliday( const QDate &date ) const
413{
414 Holiday::List holidayList = holidays( date, Holiday::MultidayHolidaysAsMultipleEvents );
415 if ( holidayList.count() > 0 ) {
416 foreach ( const KHolidays::Holiday &holiday, holidayList ) {
417 if ( holiday.dayType() == Holiday::NonWorkday ) {
418 return true;
419 }
420 }
421 }
422 return false;
423}
424
425QString HolidayRegion::defaultRegionCode( const QString &country, const QString &language )
426{
427 // Try to match against the users country and language, or failing that the language country.
428 // Scan through all the regions finding the first match for each possible default
429 // Holiday Region Country Code can be a country subdivision or the country itself,
430 // e.g. US or US-CA for California, so we can try match on both but an exact match has priority
431 // The Holiday Region file is in one language only, so give priority to any file in the
432 // users language, e.g. bilingual countries with a separate file for each language
433 // Locale language can have a country code embedded in it e.g. en_GB, which we can try use if
434 // no country set, but a lot of countries use en_GB so it's a lower priority option
435
436 QString localeCountry, localeLanguage, localeLanguageCountry;
437
438 if ( country.isEmpty() ) {
439 localeCountry = KGlobal::locale()->country().toLower();
440 } else {
441 localeCountry = country.toLower();
442 }
443
444 if ( language.isEmpty() ) {
445 localeLanguage = KGlobal::locale()->language().toLower();
446 } else {
447 localeLanguage = language.toLower();
448 }
449
450 if ( localeLanguage.split( QLatin1Char('_') ).count() > 1 ) {
451 localeLanguageCountry = localeLanguage.split( QLatin1Char('_') ).at( 1 );
452 }
453
454 QStringList regionList = KHolidays::HolidayRegion::regionCodes();
455
456 QString countryAndLanguageMatch, countryOnlyMatch, subdivisionAndLanguageMatch,
457 subdivisionOnlyMatch, languageCountryAndLanguageMatch, languageCountryOnlyMatch,
458 languageSubdivisionAndLanguageMatch, languageSubdivisionOnlyMatch;
459
460 foreach ( const QString &regionCode, regionList ) {
461 QString regionCountry = KHolidays::HolidayRegion::countryCode( regionCode ).toLower();
462 QString regionSubdivisionCountry;
463 if ( regionCountry.split( QLatin1Char('-') ).count() > 1 ) {
464 regionSubdivisionCountry = regionCountry.split( QLatin1Char('-') ).at( 0 );
465 }
466 QString regionLanguage = KHolidays::HolidayRegion::languageCode( regionCode ).toLower();
467
468 if ( regionCountry == localeCountry && regionLanguage == localeLanguage ) {
469 countryAndLanguageMatch = regionCode;
470 break; // exact match so don't look further
471 } else if ( regionCountry == localeCountry ) {
472 if ( countryOnlyMatch.isEmpty() ) {
473 countryOnlyMatch = regionCode;
474 }
475 } else if ( !regionSubdivisionCountry.isEmpty() &&
476 regionSubdivisionCountry == localeCountry &&
477 regionLanguage == localeLanguage ) {
478 if ( subdivisionAndLanguageMatch.isEmpty() ) {
479 subdivisionAndLanguageMatch = regionCode;
480 }
481 } else if ( !regionSubdivisionCountry.isEmpty() && regionSubdivisionCountry == localeCountry ) {
482 if ( subdivisionOnlyMatch.isEmpty() ) {
483 subdivisionOnlyMatch = regionCode;
484 }
485 } else if ( !localeLanguageCountry.isEmpty() &&
486 regionCountry == localeLanguageCountry &&
487 regionLanguage == localeLanguage ) {
488 if ( languageCountryAndLanguageMatch.isEmpty() ) {
489 languageCountryAndLanguageMatch = regionCode;
490 }
491 } else if ( !localeLanguageCountry.isEmpty() && regionCountry == localeLanguageCountry ) {
492 if ( languageCountryOnlyMatch.isEmpty() ) {
493 languageCountryOnlyMatch = regionCode;
494 }
495 } else if ( !regionSubdivisionCountry.isEmpty() &&
496 !localeLanguageCountry.isEmpty() &&
497 regionSubdivisionCountry == localeLanguageCountry &&
498 regionLanguage == localeLanguage ) {
499 if ( languageSubdivisionAndLanguageMatch.isEmpty() ) {
500 languageSubdivisionAndLanguageMatch = regionCode;
501 }
502 } else if ( !regionSubdivisionCountry.isEmpty() &&
503 !localeLanguageCountry.isEmpty() &&
504 regionSubdivisionCountry == localeLanguageCountry ) {
505 if ( languageSubdivisionOnlyMatch.isEmpty() ) {
506 languageSubdivisionOnlyMatch = regionCode;
507 }
508 }
509 }
510
511 QString defaultRegionCode;
512
513 if ( !countryAndLanguageMatch.isEmpty() ) {
514 defaultRegionCode = countryAndLanguageMatch;
515 } else if ( !countryOnlyMatch.isEmpty() ) {
516 defaultRegionCode = countryOnlyMatch;
517 } else if ( !subdivisionAndLanguageMatch.isEmpty() ) {
518 defaultRegionCode = subdivisionAndLanguageMatch;
519 } else if ( !subdivisionOnlyMatch.isEmpty() ) {
520 defaultRegionCode = subdivisionOnlyMatch;
521 } else if ( !languageCountryAndLanguageMatch.isEmpty() ) {
522 defaultRegionCode = languageCountryAndLanguageMatch;
523 } else if ( !languageCountryOnlyMatch.isEmpty() ) {
524 defaultRegionCode = languageCountryOnlyMatch;
525 } else if ( !languageSubdivisionAndLanguageMatch.isEmpty() ) {
526 defaultRegionCode = languageSubdivisionAndLanguageMatch;
527 } else if ( !languageSubdivisionOnlyMatch.isEmpty() ) {
528 defaultRegionCode = languageSubdivisionOnlyMatch;
529 }
530
531 return defaultRegionCode;
532}
533