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