Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
datetime.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2022 Aldric Renaudin.
4 Copyright (C) 2022 HansBull.
5 Copyright (C) 2022 Martin Bařinka.
6 Copyright (C) 2022 Philippe Weyland.
7 Copyright (C) 2024, 2026 Guillaume Stutin.
8
9 darktable is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 darktable 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 General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with darktable. If not, see <http://www.gnu.org/licenses/>.
21*/
22
23#include "common/datetime.h"
24
25#define DT_DATETIME_ORIGIN "0001-01-01 00:00:00.000"
26#define DT_DATETIME_EPOCH "1970-01-01 00:00:00.000"
27#define DT_DATETIME_EXIF_FORMAT "%Y:%m:%d %H:%M:%S"
28
29/* Owned here rather than in the application struct: datetime.c is the only file that
30 * ever touches them, and every reader already goes through the two accessors below. */
31static GTimeZone *_utc_tz = NULL;
32static GDateTime *_origin_gdt = NULL;
33
35{
36 _utc_tz = g_time_zone_new_utc();
37 _origin_gdt = g_date_time_new_from_iso8601(DT_DATETIME_ORIGIN, _utc_tz);
38}
39
40GTimeZone *dt_datetime_utc_tz(void)
41{
42 return _utc_tz;
43}
44
45GDateTime *dt_datetime_origin(void)
46{
47 return _origin_gdt;
48}
49
50gboolean _datetime_gdatetime_to_numbers(dt_datetime_t *dt, GDateTime *gdt)
51{
52 if(gdt)
53 {
54 dt->year = g_date_time_get_year(gdt);
55 dt->month = g_date_time_get_month(gdt);
56 dt->day = g_date_time_get_day_of_month(gdt);
57 dt->hour = g_date_time_get_hour(gdt);
58 dt->minute = g_date_time_get_minute(gdt);
59 dt->second = g_date_time_get_second(gdt);
60 dt->msec = g_date_time_get_microsecond(gdt) * 0.001;
61 return TRUE;
62 }
63 return FALSE;
64}
65
66static char *_datetime_append_msec(char *exif, GDateTime *gdt)
67{
68 return g_strdup_printf("%s%s%03d", exif, ".", (int)(g_date_time_get_microsecond(gdt) * 0.001));
69 }
70
71static GTimeSpan _gdatetime_to_gtimespan(GDateTime *gdt)
72{
73 if(gdt)
74 {
75 GTimeSpan gts = g_date_time_difference(gdt, dt_datetime_origin());
76 g_date_time_unref(gdt);
77 return gts;
78 }
79 return 0;
80}
81
82gboolean dt_datetime_exif_to_numbers(dt_datetime_t *dt, const char *exif)
83{
84 if(IS_NULL_PTR(exif) || !*exif || IS_NULL_PTR(dt)) return FALSE;
85
86 // fast-path for ISO-8601 (handles timezone offsets and 'T' separator)
87 GDateTime *gdt = g_date_time_new_from_iso8601(exif, dt_datetime_utc_tz());
88 if(gdt)
89 {
90 const gboolean res = _datetime_gdatetime_to_numbers(dt, gdt);
91 g_date_time_unref(gdt);
92 return res;
93 }
94
95 // fallback for EXIF-like formats (YYYY:MM:DD HH:MM:SS[.sss])
96 // convert date separators to ISO form before parsing. Partial inputs (e.g. a bare
97 // "YYYY:MM:DD" with no time part) are padded from the origin template so the missing
98 // components default to the start of the period — g_date_time_new_from_iso8601 rejects
99 // a date without a time. We memcpy (not g_strlcpy) so the copy does NOT drop a null
100 // terminator over the template tail, which would defeat the padding. See #924 and the
101 // matching logic in dt_string_to_datetime().
103 size_t len = strlen(exif);
104 if(len > sizeof(sdt) - 1) len = sizeof(sdt) - 1;
105 memcpy(sdt, exif, len);
106 sdt[4] = sdt[7] = '-';
107 gdt = g_date_time_new_from_iso8601(sdt, dt_datetime_utc_tz());
108 if(gdt)
109 {
110 const gboolean res = _datetime_gdatetime_to_numbers(dt, gdt);
111 g_date_time_unref(gdt);
112 return res;
113 }
114
115 return FALSE;
116}
117
118gboolean dt_datetime_exif_to_numbers_raw(dt_datetime_t *dt, const char *exif)
119{
120 if(exif && *exif && dt)
121 {
122 GMatchInfo *match_info;
123 // we capture each date componenent
124 GRegex *regex = g_regex_new(
125 "^\\s*(\\d{4})?(?::(\\d{2}))?(?::(\\d{2}))?(?: (\\d{2}))?(?::(\\d{2}))?(?::(\\d{2}))?\\s*$", 0, 0, NULL);
126 g_regex_match_full(regex, exif, -1, 0, 0, &match_info, NULL);
127 int match_count = g_match_info_get_match_count(match_info);
128 if(match_count == 7)
129 {
130 dt->year = atoi(g_match_info_fetch(match_info, 1));
131 dt->month = atoi(g_match_info_fetch(match_info, 2));
132 dt->day = atoi(g_match_info_fetch(match_info, 3));
133 dt->hour = atoi(g_match_info_fetch(match_info, 4));
134 dt->minute = atoi(g_match_info_fetch(match_info, 5));
135 dt->second = atoi(g_match_info_fetch(match_info, 6));
136 g_match_info_free(match_info);
137 g_regex_unref(regex);
138 return TRUE;
139 }
140 g_match_info_free(match_info);
141 g_regex_unref(regex);
142 }
143 return FALSE;
144}
145
146gboolean dt_datetime_gdatetime_to_local(char *local, const size_t local_size,
147 GDateTime *gdt, const gboolean msec, const gboolean tz)
148{
149 if(IS_NULL_PTR(local) || !local_size || IS_NULL_PTR(gdt)) return FALSE;
150 local[0] = '\0';
151 if(gdt)
152 {
153 char *sdt;
154 if(tz)
155 {
156 GDateTime *lgdt = g_date_time_to_local(gdt);
157 sdt = g_date_time_format(lgdt, "%a %x %X");
158 g_date_time_unref(lgdt);
159 }
160 else
161 sdt = g_date_time_format(gdt, "%a %x %X");
162 if(sdt)
163 {
164 if(msec)
165 { // add milliseconds
166 char *sdt2 = _datetime_append_msec(sdt, gdt);
167 dt_free(sdt);
168 sdt = sdt2;
169 }
170 g_strlcpy(local, sdt, local_size);
171 dt_free(sdt);
172 return TRUE;
173 }
174 }
175 return FALSE;
176}
177
178gboolean dt_datetime_gtimespan_to_local(char *local, const size_t local_size,
179 const GTimeSpan gts, const gboolean msec, const gboolean tz)
180{
181 gboolean res = FALSE;
182 if(IS_NULL_PTR(local) || !local_size) return FALSE;
183 local[0] = '\0';
184 GDateTime *gdt = g_date_time_add(dt_datetime_origin(), gts);
185 if(gdt)
186 {
187 res = dt_datetime_gdatetime_to_local(local, local_size, gdt, msec, tz);
188 g_date_time_unref(gdt);
189 }
190 return res;
191}
192
193gboolean dt_datetime_img_to_local(char *local, const size_t local_size,
194 const dt_image_t *img, const gboolean msec)
195{
196 return dt_datetime_gtimespan_to_local(local, local_size, img->exif_datetime_taken, msec, FALSE);
197}
198
199gboolean dt_datetime_unix_to_img(dt_image_t *img, const time_t *unix)
200{
201 GDateTime *gdt = g_date_time_new_from_unix_local(*unix);
202 if(gdt)
203 {
204 img->exif_datetime_taken = g_date_time_difference(gdt, dt_datetime_origin());
205 g_date_time_unref(gdt);
206 return TRUE;
207 }
208 img->exif_datetime_taken = 0;
209 return FALSE;
210}
211
212gboolean dt_datetime_unix_to_exif(char *exif, const size_t exif_size, const time_t *unix)
213{
214 GDateTime *gdt = g_date_time_new_from_unix_local(*unix);
215 if(gdt)
216 {
217 const gboolean res = dt_datetime_gdatetime_to_exif(exif, exif_size, gdt);
218 g_date_time_unref(gdt);
219 return res;
220 }
221 return FALSE;
222}
223
224
226{
227 if(IS_NULL_PTR(exif)) return;
228 exif[0] = '\0';
229 GDateTime *gdt = g_date_time_new_now_local();
230 if(gdt)
231 {
233 g_date_time_unref(gdt);
234 }
235}
236
238{
239 GDateTime *gdt = g_date_time_new_now_local();
240 return _gdatetime_to_gtimespan(gdt);
241}
242
243void dt_datetime_exif_to_img(dt_image_t *img, const char *exif)
244{
245 if(IS_NULL_PTR(exif)) return;
246 GDateTime *gdt = dt_datetime_exif_to_gdatetime(exif, dt_datetime_utc_tz());
247 if(gdt)
248 {
249 img->exif_datetime_taken = g_date_time_difference(gdt, dt_datetime_origin());
250 g_date_time_unref(gdt);
251 }
252 else img->exif_datetime_taken = 0;
253}
254
255gboolean dt_datetime_img_to_exif(char *exif, const size_t exif_size, const dt_image_t *img)
256{
257 return dt_datetime_gtimespan_to_exif(exif, exif_size, img->exif_datetime_taken);
258}
259
260GDateTime *dt_datetime_exif_to_gdatetime(const char *exif, const GTimeZone *tz)
261{
262 dt_datetime_t dt;
263 if(dt_datetime_exif_to_numbers(&dt, exif))
264 {
265 GDateTime *gdt = g_date_time_new((GTimeZone *)tz, dt.year, dt.month, dt.day,
266 dt.hour, dt.minute, dt.second);
267 if(gdt)
268 {
269 if(dt.msec)
270 {
271 GDateTime *gdt2 = g_date_time_add(gdt, dt.msec * 1000);
272 g_date_time_unref(gdt);
273 return gdt2;
274 }
275 }
276 return gdt;
277 }
278 return NULL;
279}
280
281gboolean dt_datetime_gdatetime_to_exif(char *exif, const size_t exif_size, GDateTime *gdt)
282{
283 if(IS_NULL_PTR(exif) || !exif_size || IS_NULL_PTR(gdt)) return FALSE;
284 exif[0] = '\0';
285 char *sdt = g_date_time_format(gdt, DT_DATETIME_EXIF_FORMAT);
286 if(sdt)
287 {
288 if(exif_size == DT_DATETIME_LENGTH)
289 {
290 // the format %f seems not to be available in glib2.0 before version 2.70
291 char *sdt2 = _datetime_append_msec(sdt, gdt);
292 dt_free(sdt);
293 sdt = sdt2;
294 }
295 g_strlcpy(exif, sdt, exif_size);
296 dt_free(sdt);
297 return TRUE;
298 }
299 return FALSE;
300}
301
302GDateTime *dt_datetime_img_to_gdatetime(const dt_image_t *img, const GTimeZone *tz)
303{
304 // GTimeSpan is UTC based. Therefore we have to cheat a little bit to get image datetime
305 if(IS_NULL_PTR(tz)) return NULL;
306 GDateTime *gdt = g_date_time_add(dt_datetime_origin(), img->exif_datetime_taken);
307 if(gdt)
308 {
309 dt_datetime_t dt;
311 {
312 g_date_time_unref(gdt);
313 gdt = g_date_time_new((GTimeZone *)tz, dt.year, dt.month, dt.day,
314 dt.hour, dt.minute, (double)dt.second);
315 return gdt;
316 }
317 }
318 return NULL;
319}
320
321GDateTime *dt_string_to_datetime(const char *string)
322{
323 if(g_utf8_strlen(string, -1) > DT_DATETIME_LENGTH - 1)
324 return NULL;
325
326 char idt[DT_DATETIME_LENGTH];
327 g_strlcpy(idt, DT_DATETIME_ORIGIN, sizeof(idt));
328 memcpy(idt, string, g_utf8_strlen(string, -1));
329 idt[4] = idt[7] = '-';
330 return g_date_time_new_from_iso8601(idt, dt_datetime_utc_tz());
331}
332
333gboolean dt_datetime_entry_to_exif(char *exif, const size_t exif_size, const char *entry)
334{
335 if(IS_NULL_PTR(exif) || !exif_size) return FALSE;
336 exif[0] = '\0';
337 GDateTime *gdt = dt_string_to_datetime(entry);
338
339 if(gdt)
340 {
341 const gboolean res = dt_datetime_gdatetime_to_exif(exif, exif_size, gdt);
342 g_date_time_unref(gdt);
343 return res;
344 }
345 return FALSE;
346}
347
348gboolean dt_datetime_entry_to_exif_upper_bound(char *exif, const size_t exif_size, const char *entry)
349{
350 if(IS_NULL_PTR(exif) || !exif_size) return FALSE;
351 exif[0] = '\0';
352
353 const int len = strlen(entry);
354 if(len > DT_DATETIME_LENGTH - 1)
355 return FALSE;
356 char idt[DT_DATETIME_LENGTH];
357 g_strlcpy(idt, DT_DATETIME_ORIGIN, sizeof(idt));
358 memcpy(idt, entry, strlen(entry));
359 idt[4] = idt[7] = '-';
360 GDateTime *gdt = g_date_time_new_from_iso8601(idt, dt_datetime_utc_tz());
361 if(gdt)
362 {
363 GDateTime *gdt2 = NULL;
364 if(len < 7)
365 gdt2 = g_date_time_add_years(gdt, 1);
366 else if(len < 10)
367 gdt2 = g_date_time_add_months(gdt, 1);
368 else if(len < 13)
369 gdt2 = g_date_time_add_days(gdt, 1);
370 else if(len < 16)
371 gdt2 = g_date_time_add_hours(gdt, 1);
372 else if(len < 19)
373 gdt2 = g_date_time_add_minutes(gdt, 1);
374 else if(len < 23)
375 gdt2 = g_date_time_add_seconds(gdt, 1);
376 else
377 gdt2 = g_date_time_add(gdt, 2);
378 g_date_time_unref(gdt);
379 if(gdt2)
380 {
381 GDateTime *gdt3 = g_date_time_add(gdt2, -1);
382 g_date_time_unref(gdt2);
383 gdt = gdt3;
384 if(gdt)
385 {
386 const gboolean res = dt_datetime_gdatetime_to_exif(exif, exif_size, gdt);
387 g_date_time_unref(gdt);
388 return res;
389 }
390 }
391 }
392 return FALSE;
393}
394
395void dt_datetime_add_subsec_to_exif(char *exif, const size_t exif_size, const char*subsec)
396{
397 if(IS_NULL_PTR(exif) || exif_size < DT_DATETIME_EXIF_LENGTH + 1) return;
398
399 g_strlcpy(&exif[DT_DATETIME_EXIF_LENGTH - 1], ".000000", exif_size - DT_DATETIME_EXIF_LENGTH + 1);
400 for(int i = 0; i < 6 && subsec[i] != '\0' && (DT_DATETIME_EXIF_LENGTH + i < exif_size - 1); i++)
401 exif[DT_DATETIME_EXIF_LENGTH + i] = subsec[i];
402 exif[exif_size - 1] = '\0';
403}
404
405gboolean dt_datetime_gtimespan_to_exif(char *sdt, const size_t sdt_size, const GTimeSpan gts)
406{
407 if(IS_NULL_PTR(sdt) || !sdt_size) return FALSE;
408 sdt[0] = '\0';
409 if(!gts) return FALSE;
410 GDateTime *gdt = g_date_time_add(dt_datetime_origin(), gts);
411 if(gdt)
412 {
413 const gboolean res = dt_datetime_gdatetime_to_exif(sdt, sdt_size, gdt);
414 g_date_time_unref(gdt);
415 return res;
416 }
417 return FALSE;
418}
419
420GTimeSpan dt_datetime_exif_to_gtimespan(const char *sdt)
421{
422 GTimeSpan gts = 0;
423 if(IS_NULL_PTR(sdt)) return gts;
424 GDateTime *gdt = dt_datetime_exif_to_gdatetime(sdt, dt_datetime_utc_tz());
425 if(gdt)
426 {
427 gts = g_date_time_difference(gdt, dt_datetime_origin());
428 g_date_time_unref(gdt);
429 }
430 return gts;
431}
432
433gboolean dt_datetime_gtimespan_to_numbers(dt_datetime_t *dt, const GTimeSpan gts)
434{
435 GDateTime *gdt = g_date_time_add(dt_datetime_origin(), gts);
436 if(gdt)
437 {
438 const gboolean res = _datetime_gdatetime_to_numbers(dt, gdt);
439 g_date_time_unref(gdt);
440 return res;
441 }
442 return FALSE;
443}
444
445GDateTime *dt_datetime_gtimespan_to_gdatetime(const GTimeSpan gts)
446{
447 return g_date_time_add(dt_datetime_origin(), gts);
448}
449
451{
452 if(IS_NULL_PTR(dt)) return 0;
453 GDateTime *gdt = g_date_time_new(dt_datetime_utc_tz(),
454 dt->year, dt->month, dt->day,
455 dt->hour, dt->minute, (double)dt->second);
456 return _gdatetime_to_gtimespan(gdt);
457}
458
459GTimeSpan dt_datetime_gdatetime_to_gtimespan(GDateTime *gdt)
460{
461 if(gdt)
462 return g_date_time_difference(gdt, dt_datetime_origin());
463 else
464 return 0;
465}
466
467GDateTime *dt_datetime_gdatetime_add_numbers(GDateTime *dte, const dt_datetime_t numbers, const gboolean add)
468{
469 const int s = add ? 1 : -1;
470
471 GDateTime *dt2 = g_date_time_add_years(dte, s * numbers.year);
472 GDateTime *dt = g_date_time_add_months(dt2, s * numbers.month);
473 g_date_time_unref(dt2);
474 dt2 = g_date_time_add_days(dt, s * numbers.day);
475 g_date_time_unref(dt);
476 dt = g_date_time_add_hours(dt2, s * numbers.hour);
477 g_date_time_unref(dt2);
478 dt2 = g_date_time_add_minutes(dt, s * numbers.minute);
479 g_date_time_unref(dt);
480 dt = g_date_time_add_seconds(dt2, s * numbers.second);
481 g_date_time_unref(dt2);
482 return dt;
483}
484
485GTimeSpan dt_datetime_gtimespan_add_numbers(const GTimeSpan dt, const dt_datetime_t numbers, const gboolean add)
486{
487 GDateTime *dte = dt_datetime_gtimespan_to_gdatetime(dt);
488 GDateTime *dt2 = dt_datetime_gdatetime_add_numbers(dte, numbers, add);
489 GTimeSpan ret = dt_datetime_gdatetime_to_gtimespan(dt2);
490 g_date_time_unref(dte);
491 g_date_time_unref(dt2);
492 return ret;
493}
494
495gboolean dt_datetime_exif_add_numbers(const gchar *exif, const dt_datetime_t numbers, const gboolean add,
496 gchar **result)
497{
498 GDateTime *dte = dt_datetime_exif_to_gdatetime(exif, dt_datetime_utc_tz());
499 if(IS_NULL_PTR(dte)) return FALSE;
500 GDateTime *dt2 = dt_datetime_gdatetime_add_numbers(dte, numbers, add);
501 char txt[DT_DATETIME_EXIF_LENGTH];
503 g_date_time_unref(dte);
504 g_date_time_unref(dt2);
505 *result = g_strdup(txt);
506 return TRUE;
507}
508
509// clang-format off
510// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
511// vim: shiftwidth=2 expandtab tabstop=2 cindent
512// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
513// clang-format on
514
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
GTimeSpan dt_datetime_exif_to_gtimespan(const char *sdt)
Definition datetime.c:420
gboolean dt_datetime_img_to_exif(char *exif, const size_t exif_size, const dt_image_t *img)
Definition datetime.c:255
gboolean dt_datetime_gdatetime_to_exif(char *exif, const size_t exif_size, GDateTime *gdt)
Definition datetime.c:281
gboolean dt_datetime_gtimespan_to_local(char *local, const size_t local_size, const GTimeSpan gts, const gboolean msec, const gboolean tz)
Definition datetime.c:178
void dt_datetime_init()
Definition datetime.c:34
void dt_datetime_now_to_exif(char *exif)
Definition datetime.c:225
void dt_datetime_add_subsec_to_exif(char *exif, const size_t exif_size, const char *subsec)
Definition datetime.c:395
GDateTime * dt_datetime_exif_to_gdatetime(const char *exif, const GTimeZone *tz)
Definition datetime.c:260
GTimeZone * dt_datetime_utc_tz(void)
Definition datetime.c:40
static char * _datetime_append_msec(char *exif, GDateTime *gdt)
Definition datetime.c:66
GDateTime * dt_datetime_origin(void)
Definition datetime.c:45
GTimeSpan dt_datetime_numbers_to_gtimespan(const dt_datetime_t *dt)
Definition datetime.c:450
gboolean dt_datetime_unix_to_img(dt_image_t *img, const time_t *unix)
Definition datetime.c:199
GDateTime * dt_datetime_gdatetime_add_numbers(GDateTime *dte, const dt_datetime_t numbers, const gboolean add)
Definition datetime.c:467
static GTimeZone * _utc_tz
Definition datetime.c:31
GDateTime * dt_string_to_datetime(const char *string)
Definition datetime.c:321
gboolean dt_datetime_img_to_local(char *local, const size_t local_size, const dt_image_t *img, const gboolean msec)
Definition datetime.c:193
gboolean dt_datetime_exif_add_numbers(const gchar *exif, const dt_datetime_t numbers, const gboolean add, gchar **result)
Definition datetime.c:495
#define DT_DATETIME_ORIGIN
Definition datetime.c:25
GDateTime * dt_datetime_gtimespan_to_gdatetime(const GTimeSpan gts)
Definition datetime.c:445
gboolean dt_datetime_gtimespan_to_exif(char *sdt, const size_t sdt_size, const GTimeSpan gts)
Definition datetime.c:405
GTimeSpan dt_datetime_now_to_gtimespan()
Definition datetime.c:237
GTimeSpan dt_datetime_gdatetime_to_gtimespan(GDateTime *gdt)
Definition datetime.c:459
gboolean dt_datetime_gtimespan_to_numbers(dt_datetime_t *dt, const GTimeSpan gts)
Definition datetime.c:433
static GDateTime * _origin_gdt
Definition datetime.c:32
gboolean dt_datetime_exif_to_numbers(dt_datetime_t *dt, const char *exif)
Definition datetime.c:82
GTimeSpan dt_datetime_gtimespan_add_numbers(const GTimeSpan dt, const dt_datetime_t numbers, const gboolean add)
Definition datetime.c:485
gboolean dt_datetime_gdatetime_to_local(char *local, const size_t local_size, GDateTime *gdt, const gboolean msec, const gboolean tz)
Definition datetime.c:146
static GTimeSpan _gdatetime_to_gtimespan(GDateTime *gdt)
Definition datetime.c:71
gboolean dt_datetime_exif_to_numbers_raw(dt_datetime_t *dt, const char *exif)
Definition datetime.c:118
#define DT_DATETIME_EXIF_FORMAT
Definition datetime.c:27
void dt_datetime_exif_to_img(dt_image_t *img, const char *exif)
Definition datetime.c:243
gboolean dt_datetime_entry_to_exif_upper_bound(char *exif, const size_t exif_size, const char *entry)
Definition datetime.c:348
gboolean dt_datetime_unix_to_exif(char *exif, const size_t exif_size, const time_t *unix)
Definition datetime.c:212
gboolean dt_datetime_entry_to_exif(char *exif, const size_t exif_size, const char *entry)
Definition datetime.c:333
gboolean _datetime_gdatetime_to_numbers(dt_datetime_t *dt, GDateTime *gdt)
Definition datetime.c:50
GDateTime * dt_datetime_img_to_gdatetime(const dt_image_t *img, const GTimeZone *tz)
Definition datetime.c:302
#define DT_DATETIME_LENGTH
Definition datetime.h:38
#define DT_DATETIME_EXIF_LENGTH
Definition datetime.h:39
const int res
Definition dtpthread.h:351
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
GTimeSpan exif_datetime_taken
Definition image.h:377