evolution-3.6.4/e-util/e-datetime-format.c

No issues found

  1 /*
  2  *
  3  * This program is free software; you can redistribute it and/or
  4  * modify it under the terms of the GNU Lesser General Public
  5  * License as published by the Free Software Foundation; either
  6  * version 2 of the License, or (at your option) version 3.
  7  *
  8  * This program is distributed in the hope that it will be useful,
  9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 11  * Lesser General Public License for more details.
 12  *
 13  * You should have received a copy of the GNU Lesser General Public
 14  * License along with the program; if not, see <http://www.gnu.org/licenses/>
 15  *
 16  *
 17  * Copyright (C) 1999-2009 Novell, Inc. (www.novell.com)
 18  *
 19  */
 20 
 21 #ifdef HAVE_CONFIG_H
 22 #include <config.h>
 23 #endif
 24 
 25 #include <glib/gi18n.h>
 26 #include <gtk/gtk.h>
 27 
 28 #include "e-datetime-format.h"
 29 #include "e-util.h"
 30 
 31 #define KEYS_FILENAME "datetime-formats.ini"
 32 #define KEYS_GROUPNAME "formats"
 33 
 34 #ifdef G_OS_WIN32
 35 #ifdef localtime_r
 36 #undef localtime_r
 37 #endif
 38 /* The localtime() in Microsoft's C library *is* thread-safe */
 39 #define localtime_r(timep, result)  (localtime (timep) ? memcpy ((result), localtime (timep), sizeof (*(result))) : 0)
 40 #endif
 41 
 42 static GHashTable *key2fmt = NULL;
 43 
 44 static GKeyFile *setup_keyfile = NULL; /* used on the combo */
 45 static gint setup_keyfile_instances = 0;
 46 
 47 static void
 48 save_keyfile (GKeyFile *keyfile)
 49 {
 50 	gchar *contents;
 51 	gchar *filename;
 52 	gsize length;
 53 	GError *error = NULL;
 54 
 55 	g_return_if_fail (keyfile != NULL);
 56 
 57 	filename = g_build_filename (e_get_user_data_dir (), KEYS_FILENAME, NULL);
 58 	contents = g_key_file_to_data (keyfile, &length, NULL);
 59 
 60 	g_file_set_contents (filename, contents, length, &error);
 61 
 62 	if (error != NULL) {
 63 		g_warning ("%s", error->message);
 64 		g_error_free (error);
 65 	}
 66 
 67 	g_free (contents);
 68 	g_free (filename);
 69 }
 70 
 71 static void
 72 ensure_loaded (void)
 73 {
 74 	GKeyFile *keyfile;
 75 	gchar *str, **keys;
 76 	gint i;
 77 
 78 	if (key2fmt)
 79 		return;
 80 
 81 	key2fmt = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 82 	keyfile = g_key_file_new ();
 83 
 84 	str = g_build_filename (e_get_user_data_dir (), KEYS_FILENAME, NULL);
 85 	g_key_file_load_from_file (keyfile, str, G_KEY_FILE_NONE, NULL);
 86 	g_free (str);
 87 
 88 	keys = g_key_file_get_keys (keyfile, KEYS_GROUPNAME, NULL, NULL);
 89 
 90 	if (keys) {
 91 		for (i = 0;  keys[i]; i++) {
 92 			str = g_key_file_get_string (keyfile, KEYS_GROUPNAME, keys[i], NULL);
 93 			if (str)
 94 				g_hash_table_insert (key2fmt, g_strdup (keys[i]), str);
 95 		}
 96 
 97 		g_strfreev (keys);
 98 	}
 99 
100 	g_key_file_free (keyfile);
101 }
102 
103 static const gchar *
104 get_default_format (DTFormatKind kind,
105                     const gchar *key)
106 {
107 	const gchar *res = NULL;
108 
109 	ensure_loaded ();
110 
111 	switch (kind) {
112 	case DTFormatKindDate:
113 		res = g_hash_table_lookup (key2fmt, "Default-Date");
114 		if (!res)
115 			res = "%x";
116 		break;
117 	case DTFormatKindTime:
118 		res = g_hash_table_lookup (key2fmt, "Default-Time");
119 		if (!res)
120 			res = "%X";
121 		break;
122 	case DTFormatKindDateTime:
123 		res = g_hash_table_lookup (key2fmt, "Default-DateTime");
124 		if (!res && key && g_str_has_prefix (key, "mail-table"))
125 			res = "%ad %H:%M";
126 		if (!res)
127 			res = "%x %X"; /* %c is also possible, but it doesn't play well with time zone identifiers */
128 		break;
129 	case DTFormatKindShortDate:
130 		res = g_hash_table_lookup (key2fmt, "Default-ShortDate");
131 		if (!res)
132 			res = "%A, %B %d";
133 		break;
134 	}
135 
136 	if (!res)
137 		res = "%x %X";
138 
139 	return res;
140 }
141 
142 static const gchar *
143 get_format_internal (const gchar *key,
144                      DTFormatKind kind)
145 {
146 	const gchar *res;
147 
148 	ensure_loaded ();
149 
150 	g_return_val_if_fail (key != NULL, NULL);
151 	g_return_val_if_fail (key2fmt != NULL, NULL);
152 
153 	res = g_hash_table_lookup (key2fmt, key);
154 	if (!res)
155 		res = get_default_format (kind, key);
156 
157 	return res;
158 }
159 
160 static void
161 set_format_internal (const gchar *key,
162                      const gchar *fmt,
163                      GKeyFile *keyfile)
164 {
165 	ensure_loaded ();
166 
167 	g_return_if_fail (key != NULL);
168 	g_return_if_fail (key2fmt != NULL);
169 	g_return_if_fail (keyfile != NULL);
170 
171 	if (!fmt || !*fmt) {
172 		g_hash_table_remove (key2fmt, key);
173 		g_key_file_remove_key (keyfile, KEYS_GROUPNAME, key, NULL);
174 	} else {
175 		g_hash_table_insert (key2fmt, g_strdup (key), g_strdup (fmt));
176 		g_key_file_set_string (keyfile, KEYS_GROUPNAME, key, fmt);
177 	}
178 }
179 
180 static gchar *
181 format_relative_date (time_t tvalue,
182                       time_t ttoday,
183                       const struct tm *value,
184                       const struct tm *today)
185 {
186 	gchar *res = g_strdup (get_default_format (DTFormatKindDate, NULL));
187 	GDate now, val;
188 	gint diff;
189 
190 	g_return_val_if_fail (value != NULL, res);
191 	g_return_val_if_fail (today != NULL, res);
192 
193 	g_date_set_time_t (&now, ttoday);
194 	g_date_set_time_t (&val, tvalue);
195 
196 	diff = g_date_get_julian (&now) - g_date_get_julian (&val);
197 	/* if it's more than a week, use the default date format */
198 	if (ABS (diff) > 7)
199 		return res;
200 
201 	g_free (res);
202 
203 	if (value->tm_year == today->tm_year &&
204 	    value->tm_mon == today->tm_mon &&
205 	    value->tm_mday == today->tm_mday) {
206 		res = g_strdup (_("Today"));
207 	} else {
208 		gboolean future = FALSE;
209 
210 		if (diff < 0)
211 			future = TRUE;
212 
213 		diff = ABS (diff);
214 
215 		if (diff <= 1) {
216 			if (future)
217 				res = g_strdup (_("Tomorrow"));
218 			else
219 				res = g_strdup (_("Yesterday"));
220 		} else {
221 			if (future) {
222 				switch (g_date_get_weekday (&val)) {
223 				case 1:
224 					/* Translators: This is used for abbreviated days in the future.
225 				 *         You can use strftime modifiers here too, like "Next %a", to avoid
226 					 * repeated translation of the abbreviated day name. */
227 					res = g_strdup (C_ ("DateFmt", "Next Mon"));
228 					break;
229 				case 2:
230 					/* Translators: This is used for abbreviated days in the future.
231 				 *         You can use strftime modifiers here too, like "Next %a", to avoid
232 					 * repeated translation of the abbreviated day name. */
233 					res = g_strdup (C_ ("DateFmt", "Next Tue"));
234 					break;
235 				case 3:
236 					/* Translators: This is used for abbreviated days in the future.
237 				 *         You can use strftime modifiers here too, like "Next %a", to avoid
238 					 * repeated translation of the abbreviated day name. */
239 					res = g_strdup (C_ ("DateFmt", "Next Wed"));
240 					break;
241 				case 4:
242 					/* Translators: This is used for abbreviated days in the future.
243 				 *         You can use strftime modifiers here too, like "Next %a", to avoid
244 					 * repeated translation of the abbreviated day name. */
245 					res = g_strdup (C_ ("DateFmt", "Next Thu"));
246 					break;
247 				case 5:
248 					/* Translators: This is used for abbreviated days in the future.
249 				 *         You can use strftime modifiers here too, like "Next %a", to avoid
250 					 * repeated translation of the abbreviated day name. */
251 					res = g_strdup (C_ ("DateFmt", "Next Fri"));
252 					break;
253 				case 6:
254 					/* Translators: This is used for abbreviated days in the future.
255 				 *         You can use strftime modifiers here too, like "Next %a", to avoid
256 					 * repeated translation of the abbreviated day name. */
257 					res = g_strdup (C_ ("DateFmt", "Next Sat"));
258 					break;
259 				case 7:
260 					/* Translators: This is used for abbreviated days in the future.
261 				 *         You can use strftime modifiers here too, like "Next %a", to avoid
262 					 * repeated translation of the abbreviated day name. */
263 					res = g_strdup (C_ ("DateFmt", "Next Sun"));
264 					break;
265 				default:
266 					g_return_val_if_reached (NULL);
267 					break;
268 				}
269 			} else {
270 				res = g_strdup ("%a");
271 			}
272 		}
273 	}
274 
275 	return res;
276 }
277 
278 static gchar *
279 format_internal (const gchar *key,
280                  DTFormatKind kind,
281                  time_t tvalue,
282                  struct tm *tm_value)
283 {
284 	const gchar *fmt;
285 	gchar buff[129];
286 	GString *use_fmt = NULL;
287 	gint i, last = 0;
288 	struct tm today, value;
289 	time_t ttoday;
290 
291 	tzset ();
292 	if (!tm_value) {
293 		localtime_r (&tvalue, &value);
294 		tm_value = &value;
295 	} else {
296 		/* recalculate tvalue to local (system) timezone */
297 		tvalue = mktime (tm_value);
298 		localtime_r (&tvalue, &value);
299 	}
300 
301 	fmt = get_format_internal (key, kind);
302 	for (i = 0; fmt[i]; i++) {
303 		if (fmt[i] == '%') {
304 			if (fmt[i + 1] == '%') {
305 				i++;
306 			} else if (fmt[i + 1] == 'a' && fmt[i + 2] == 'd' && (fmt[i + 3] == 0 || !g_ascii_isalpha (fmt[i + 3]))) {
307 				gchar *ad;
308 
309 				/* "%ad" for abbreviated date */
310 				if (!use_fmt) {
311 					use_fmt = g_string_new ("");
312 
313 					ttoday = time (NULL);
314 					localtime_r (&ttoday, &today);
315 				}
316 
317 				g_string_append_len (use_fmt, fmt + last, i - last);
318 				last = i + 3;
319 				i += 2;
320 
321 				ad = format_relative_date (tvalue, ttoday, &value, &today);
322 				if (ad)
323 					g_string_append (use_fmt, ad);
324 				else if (g_ascii_isspace (fmt[i + 3]))
325 					i++;
326 
327 				g_free (ad);
328 			}
329 		}
330 	}
331 
332 	if (use_fmt && last < i) {
333 		g_string_append_len (use_fmt, fmt + last, i - last);
334 	}
335 
336 	e_utf8_strftime_fix_am_pm (buff, sizeof (buff) - 1, use_fmt ? use_fmt->str : fmt, tm_value);
337 
338 	if (use_fmt)
339 		g_string_free (use_fmt, TRUE);
340 
341 	return g_strstrip (g_strdup (buff));
342 }
343 
344 static void
345 fill_combo_formats (GtkWidget *combo,
346                     const gchar *key,
347                     DTFormatKind kind)
348 {
349 	const gchar *date_items[] = {
350 		N_ ("Use locale default"),
351 		"%m/%d/%y",	/* American style */
352 		"%m/%d/%Y",	/* American style, full year */
353 		"%d.%m.%y",	/* non-American style */
354 		"%d.%m.%Y",	/* non-American style, full year */
355 		"%ad",		/* abbreviated date, like "Today" */
356 		NULL
357 	};
358 
359 	const gchar *time_items[] = {
360 		N_ ("Use locale default"),
361 		"%I:%M:%S %p",	/* 12hours style */
362 		"%I:%M %p",	/* 12hours style, without seconds */
363 		"%H:%M:%S",	/* 24hours style */
364 		"%H:%M",	/* 24hours style, without seconds */
365 		NULL
366 	};
367 
368 	const gchar *datetime_items[] = {
369 		N_ ("Use locale default"),
370 		"%m/%d/%y %I:%M:%S %p",	/* American style */
371 		"%m/%d/%Y %I:%M:%S %p",	/* American style, full year */
372 		"%m/%d/%y %I:%M %p",	/* American style, without seconds */
373 		"%m/%d/%Y %I:%M %p",	/* American style, without seconds, full year */
374 		"%ad %I:%M:%S %p",	/* %ad is an abbreviated date, like "Today" */
375 		"%ad %I:%M %p",		/* %ad is an abbreviated date, like "Today", without seconds */
376 		"%d.%m.%y %H:%M:%S",	/* non-American style */
377 		"%d.%m.%Y %H:%M:%S",	/* non-American style, full year */
378 		"%d.%m.%y %H:%M",	/* non-American style, without seconds */
379 		"%d.%m.%Y %H:%M",	/* non-American style, without seconds, full year */
380 		"%ad %H:%M:%S",
381 		"%ad %H:%M",		/* without seconds */
382 		NULL
383 	};
384 
385 	const gchar *shortdate_items[] = {
386 		"%A, %B %d",
387 		"%A, %d %B",
388 		"%a, %b %d",
389 		"%a, %d %b",
390 		NULL
391 	};
392 
393 	const gchar **items = NULL;
394 	gint i, idx = 0;
395 	const gchar *fmt;
396 
397 	g_return_if_fail (GTK_IS_COMBO_BOX (combo));
398 
399 	switch (kind) {
400 	case DTFormatKindDate:
401 		items = date_items;
402 		break;
403 	case DTFormatKindTime:
404 		items = time_items;
405 		break;
406 	case DTFormatKindDateTime:
407 		items = datetime_items;
408 		break;
409 	case DTFormatKindShortDate:
410 		items = shortdate_items;
411 		break;
412 	}
413 
414 	g_return_if_fail (items != NULL);
415 
416 	fmt = get_format_internal (key, kind);
417 
418 	for (i = 0; items[i]; i++) {
419 		if (i == 0) {
420 			gtk_combo_box_text_append_text (
421 				GTK_COMBO_BOX_TEXT (combo), _(items[i]));
422 		} else {
423 			gtk_combo_box_text_append_text (
424 				GTK_COMBO_BOX_TEXT (combo), items[i]);
425 			if (!idx && fmt && g_str_equal (fmt, items[i]))
426 				idx = i;
427 		}
428 	}
429 
430 	if (idx == 0 && fmt && !g_str_equal (fmt, get_default_format (kind, key))) {
431 		gtk_combo_box_text_append_text (
432 			GTK_COMBO_BOX_TEXT (combo), fmt);
433 		idx = i;
434 	}
435 
436 	gtk_combo_box_set_active ((GtkComboBox *) combo, idx);
437 }
438 
439 static void
440 update_preview_widget (GtkWidget *combo)
441 {
442 	GtkWidget *preview;
443 	const gchar *key;
444 	gchar *value;
445 	time_t now;
446 
447 	g_return_if_fail (GTK_IS_COMBO_BOX (combo));
448 
449 	preview = g_object_get_data (G_OBJECT (combo), "preview-label");
450 	g_return_if_fail (preview != NULL);
451 	g_return_if_fail (GTK_IS_LABEL (preview));
452 
453 	key = g_object_get_data (G_OBJECT (combo), "format-key");
454 	g_return_if_fail (key != NULL);
455 
456 	time (&now);
457 
458 	value = format_internal (key, GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "format-kind")), now, NULL);
459 	gtk_label_set_text (GTK_LABEL (preview), value ? value : "");
460 	g_free (value);
461 }
462 
463 static void
464 format_combo_changed_cb (GtkWidget *combo,
465                          gpointer user_data)
466 {
467 	const gchar *key;
468 	DTFormatKind kind;
469 	GKeyFile *keyfile;
470 
471 	g_return_if_fail (GTK_IS_COMBO_BOX (combo));
472 
473 	key = g_object_get_data (G_OBJECT (combo), "format-key");
474 	g_return_if_fail (key != NULL);
475 
476 	kind = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (combo), "format-kind"));
477 	keyfile = g_object_get_data (G_OBJECT (combo), "setup-key-file");
478 
479 	if (kind != DTFormatKindShortDate && gtk_combo_box_get_active (GTK_COMBO_BOX (combo)) == 0) {
480 		/* use locale default */
481 		set_format_internal (key, NULL, keyfile);
482 	} else {
483 		gchar *text;
484 
485 		text = gtk_combo_box_text_get_active_text (
486 			GTK_COMBO_BOX_TEXT (combo));
487 		set_format_internal (key, text, keyfile);
488 		g_free (text);
489 	}
490 
491 	update_preview_widget (combo);
492 
493 	/* save on every change only because 'unref_setup_keyfile' is never called :(
494 	 * how about in kill - bonobo? */
495 	save_keyfile (keyfile);
496 }
497 
498 static gchar *
499 gen_key (const gchar *component,
500          const gchar *part,
501          DTFormatKind kind)
502 {
503 	const gchar *kind_str = NULL;
504 
505 	g_return_val_if_fail (component != NULL, NULL);
506 	g_return_val_if_fail (*component != 0, NULL);
507 
508 	switch (kind) {
509 	case DTFormatKindDate:
510 		kind_str = "Date";
511 		break;
512 	case DTFormatKindTime:
513 		kind_str = "Time";
514 		break;
515 	case DTFormatKindDateTime:
516 		kind_str = "DateTime";
517 		break;
518 	case DTFormatKindShortDate:
519 		kind_str = "ShortDate";
520 		break;
521 	}
522 
523 	return g_strconcat (component, (part && *part) ? "-" : "", part && *part ? part : "", "-", kind_str, NULL);
524 }
525 
526 static void
527 unref_setup_keyfile (gpointer ptr)
528 {
529 	g_return_if_fail (ptr == setup_keyfile);
530 	g_return_if_fail (setup_keyfile != NULL);
531 	g_return_if_fail (setup_keyfile_instances > 0);
532 
533 	/* this is never called */
534 	setup_keyfile_instances--;
535 	if (setup_keyfile_instances == 0) {
536 		save_keyfile (setup_keyfile);
537 		g_key_file_free (setup_keyfile);
538 		setup_keyfile = NULL;
539 	}
540 }
541 
542 /**
543  * e_datetime_format_add_setup_widget:
544  * @table: Where to attach widgets. Requires 3 columns.
545  * @row: On which row to attach.
546  * @component: Component identifier for the format. Cannot be empty nor NULL.
547  * @part: Part in the component, can be NULL or empty string.
548  * @kind: Kind of the format for the component/part.
549  * @caption: Caption for the widget, can be NULL, then the "Format:" is used.
550  *
551  * Adds a setup widget for a component and part. The table should have 3 columns.
552  * All the work related to loading and saving the value is done automatically,
553  * on user's changes.
554  **/
555 void
556 e_datetime_format_add_setup_widget (GtkWidget *table,
557                                     gint row,
558                                     const gchar *component,
559                                     const gchar *part,
560                                     DTFormatKind kind,
561                                     const gchar *caption)
562 {
563 	GtkListStore *store;
564 	GtkWidget *label, *combo, *preview, *align;
565 	gchar *key;
566 
567 	g_return_if_fail (table != NULL);
568 	g_return_if_fail (row >= 0);
569 	g_return_if_fail (component != NULL);
570 	g_return_if_fail (*component != 0);
571 
572 	key = gen_key (component, part, kind);
573 
574 	label = gtk_label_new_with_mnemonic (caption ? caption : _("Format:"));
575 
576 	store = gtk_list_store_new (1, G_TYPE_STRING);
577 	combo = g_object_new (
578 		GTK_TYPE_COMBO_BOX_TEXT,
579 		"model", store,
580 		"has-entry", TRUE,
581 		"entry-text-column", 0,
582 		NULL);
583 	g_object_unref (store);
584 
585 	fill_combo_formats (combo, key, kind);
586 	gtk_label_set_mnemonic_widget ((GtkLabel *) label, combo);
587 
588 	align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0);
589 	gtk_container_add (GTK_CONTAINER (align), combo);
590 
591 	gtk_table_attach ((GtkTable *) table, label, 0, 1, row, row + 1, 0, 0, 2, 0);
592 	gtk_table_attach ((GtkTable *) table, align, 1, 2, row, row + 1, 0, 0, 2, 0);
593 
594 	preview = gtk_label_new ("");
595 	gtk_misc_set_alignment (GTK_MISC (preview), 0.0, 0.5);
596 	gtk_label_set_ellipsize (GTK_LABEL (preview), PANGO_ELLIPSIZE_END);
597 	gtk_table_attach ((GtkTable *) table, preview, 2, 3, row, row + 1, GTK_EXPAND | GTK_FILL, 0, 2, 0);
598 
599 	if (!setup_keyfile) {
600 		gchar *filename;
601 
602 		filename = g_build_filename (e_get_user_data_dir (), KEYS_FILENAME, NULL);
603 		setup_keyfile = g_key_file_new ();
604 		g_key_file_load_from_file (setup_keyfile, filename, G_KEY_FILE_NONE, NULL);
605 		g_free (filename);
606 
607 		setup_keyfile_instances = 1;
608 	} else {
609 		setup_keyfile_instances++;
610 	}
611 
612 	g_object_set_data (G_OBJECT (combo), "preview-label", preview);
613 	g_object_set_data (G_OBJECT (combo), "format-kind", GINT_TO_POINTER (kind));
614 	g_object_set_data_full (G_OBJECT (combo), "format-key", key, g_free);
615 	g_object_set_data_full (G_OBJECT (combo), "setup-key-file", setup_keyfile, unref_setup_keyfile);
616 	g_signal_connect (
617 		combo, "changed",
618 		G_CALLBACK (format_combo_changed_cb), NULL);
619 
620 	update_preview_widget (combo);
621 
622 	gtk_widget_show_all (table);
623 }
624 
625 gchar *
626 e_datetime_format_format (const gchar *component,
627                           const gchar *part,
628                           DTFormatKind kind,
629                           time_t value)
630 {
631 	gchar *key, *res;
632 
633 	g_return_val_if_fail (component != NULL, NULL);
634 	g_return_val_if_fail (*component != 0, NULL);
635 
636 	key = gen_key (component, part, kind);
637 	g_return_val_if_fail (key != NULL, NULL);
638 
639 	res = format_internal (key, kind, value, NULL);
640 
641 	g_free (key);
642 
643 	return res;
644 }
645 
646 gchar *
647 e_datetime_format_format_tm (const gchar *component,
648                              const gchar *part,
649                              DTFormatKind kind,
650                              struct tm *tm_time)
651 {
652 	gchar *key, *res;
653 
654 	g_return_val_if_fail (component != NULL, NULL);
655 	g_return_val_if_fail (*component != 0, NULL);
656 	g_return_val_if_fail (tm_time != NULL, NULL);
657 
658 	key = gen_key (component, part, kind);
659 	g_return_val_if_fail (key != NULL, NULL);
660 
661 	res = format_internal (key, kind, 0, tm_time);
662 
663 	g_free (key);
664 
665 	return res;
666 }
667 
668 gboolean
669 e_datetime_format_includes_day_name (const gchar *component,
670                                      const gchar *part,
671                                      DTFormatKind kind)
672 {
673 	gchar *key;
674 	const gchar *fmt;
675 	gboolean res;
676 
677 	g_return_val_if_fail (component != NULL, FALSE);
678 	g_return_val_if_fail (*component != 0, FALSE);
679 
680 	key = gen_key (component, part, kind);
681 	g_return_val_if_fail (key != NULL, FALSE);
682 
683 	fmt = get_format_internal (key, kind);
684 
685 	res = fmt && (strstr (fmt, "%a") != NULL || strstr (fmt, "%A") != NULL);
686 
687 	g_free (key);
688 
689 	return res;
690 }