From f8ec5dee38aba406ac4e57db0809833f5586e9d8 Mon Sep 17 00:00:00 2001 From: Colm Talbot Date: Sun, 29 Mar 2026 13:55:48 -0400 Subject: [PATCH 1/7] make leap seconds thread safe make default leaps seconds global constant and leap second array thread local --- src/dat.c | 51 +------------------------------- src/erfadatextra.c | 37 +++++++++++++++--------- src/erfadatextra.h | 72 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 65 deletions(-) diff --git a/src/dat.c b/src/dat.c index aabd952..0098863 100644 --- a/src/dat.c +++ b/src/dat.c @@ -146,60 +146,11 @@ int eraDat(int iy, int im, int id, double fd, double *deltat) /* Number of Delta(AT) expressions before leap seconds were introduced */ enum { NERA1 = (int) (sizeof drift / sizeof (double) / 2) }; -/* Dates and Delta(AT)s */ - static const eraLEAPSECOND _changes[] = { - { 1960, 1, 1.4178180 }, - { 1961, 1, 1.4228180 }, - { 1961, 8, 1.3728180 }, - { 1962, 1, 1.8458580 }, - { 1963, 11, 1.9458580 }, - { 1964, 1, 3.2401300 }, - { 1964, 4, 3.3401300 }, - { 1964, 9, 3.4401300 }, - { 1965, 1, 3.5401300 }, - { 1965, 3, 3.6401300 }, - { 1965, 7, 3.7401300 }, - { 1965, 9, 3.8401300 }, - { 1966, 1, 4.3131700 }, - { 1968, 2, 4.2131700 }, - { 1972, 1, 10.0 }, - { 1972, 7, 11.0 }, - { 1973, 1, 12.0 }, - { 1974, 1, 13.0 }, - { 1975, 1, 14.0 }, - { 1976, 1, 15.0 }, - { 1977, 1, 16.0 }, - { 1978, 1, 17.0 }, - { 1979, 1, 18.0 }, - { 1980, 1, 19.0 }, - { 1981, 7, 20.0 }, - { 1982, 7, 21.0 }, - { 1983, 7, 22.0 }, - { 1985, 7, 23.0 }, - { 1988, 1, 24.0 }, - { 1990, 1, 25.0 }, - { 1991, 1, 26.0 }, - { 1992, 7, 27.0 }, - { 1993, 7, 28.0 }, - { 1994, 7, 29.0 }, - { 1996, 1, 30.0 }, - { 1997, 7, 31.0 }, - { 1999, 1, 32.0 }, - { 2006, 1, 33.0 }, - { 2009, 1, 34.0 }, - { 2012, 7, 35.0 }, - { 2015, 7, 36.0 }, - { 2017, 1, 37.0 } - }; - -/* Number of Delta(AT) changes */ - enum { _NDAT = (int) (sizeof _changes / sizeof _changes[0]) }; - /* Get/initialise leap-second if needed */ int NDAT; eraLEAPSECOND *changes; - NDAT = eraDatini(_changes, _NDAT, &changes); + NDAT = eraDatini(&changes); /* Miscellaneous local variables */ int j, i, m; diff --git a/src/erfadatextra.c b/src/erfadatextra.c index f70400c..3b6f49d 100644 --- a/src/erfadatextra.c +++ b/src/erfadatextra.c @@ -17,9 +17,22 @@ */ #include "erfa.h" #include "erfaextra.h" +#include "erfadatextra.h" -static eraLEAPSECOND *changes; -static int NDAT = -1; +static ERFA_THREAD_LOCAL eraLEAPSECOND *changes = builtin_changes; +static ERFA_THREAD_LOCAL int NDAT = n_builtin_changes; + + +void _eraResetLeapSeconds() +/* +** Reset the leap second table to the built-in version. +** +** This function is for internal use only and should not be used elsewhere. +*/ +{ + changes = builtin_changes; + NDAT = n_builtin_changes; +} int eraGetLeapSeconds(eraLEAPSECOND **leapseconds) @@ -36,11 +49,7 @@ int eraGetLeapSeconds(eraLEAPSECOND **leapseconds) */ { if (NDAT <= 0) { - double delat; - int stat = eraDat(2000, 1, 1, 0., &delat); - if (stat != 0 || NDAT <= 0) { - return -1; - } + _eraResetLeapSeconds(); } *leapseconds = changes; return NDAT; @@ -60,12 +69,15 @@ void eraSetLeapSeconds(eraLEAPSECOND *leapseconds, int count) ** *No* sanity checks are performed. */ { - changes = leapseconds; - NDAT = count; + if (count <= 0) { + _eraResetLeapSeconds(); + } else { + changes = leapseconds; + NDAT = count; + } } -int eraDatini(const eraLEAPSECOND *builtin, int n_builtin, - eraLEAPSECOND **leapseconds) +int eraDatini(eraLEAPSECOND **leapseconds) /* ** Get the leap second table, initializing it to the built-in version ** if necessary. @@ -74,7 +86,6 @@ int eraDatini(const eraLEAPSECOND *builtin, int n_builtin, ** not be used elsewhere. ** ** Given: -** builtin eraLEAPSECOND Array of year, month, TAI minus UTC ** n_builtin int Number of entries of the table. ** ** Returned: @@ -86,7 +97,7 @@ int eraDatini(const eraLEAPSECOND *builtin, int n_builtin, */ { if (NDAT <= 0) { - eraSetLeapSeconds((eraLEAPSECOND *)builtin, n_builtin); + _eraResetLeapSeconds(); } *leapseconds = changes; return NDAT; diff --git a/src/erfadatextra.h b/src/erfadatextra.h index afea18b..a4a2528 100644 --- a/src/erfadatextra.h +++ b/src/erfadatextra.h @@ -20,7 +20,75 @@ ** This function is for internal use in dat.c only and should ** not be used elsewhere. */ -int eraDatini(const eraLEAPSECOND *builtin, int n_builtin, - eraLEAPSECOND **leapseconds); +int eraDatini(eraLEAPSECOND **leapseconds); + +/* Dates and Delta(AT)s */ +static const eraLEAPSECOND builtin_changes[] = { + { 1960, 1, 1.4178180 }, + { 1961, 1, 1.4228180 }, + { 1961, 8, 1.3728180 }, + { 1962, 1, 1.8458580 }, + { 1963, 11, 1.9458580 }, + { 1964, 1, 3.2401300 }, + { 1964, 4, 3.3401300 }, + { 1964, 9, 3.4401300 }, + { 1965, 1, 3.5401300 }, + { 1965, 3, 3.6401300 }, + { 1965, 7, 3.7401300 }, + { 1965, 9, 3.8401300 }, + { 1966, 1, 4.3131700 }, + { 1968, 2, 4.2131700 }, + { 1972, 1, 10.0 }, + { 1972, 7, 11.0 }, + { 1973, 1, 12.0 }, + { 1974, 1, 13.0 }, + { 1975, 1, 14.0 }, + { 1976, 1, 15.0 }, + { 1977, 1, 16.0 }, + { 1978, 1, 17.0 }, + { 1979, 1, 18.0 }, + { 1980, 1, 19.0 }, + { 1981, 7, 20.0 }, + { 1982, 7, 21.0 }, + { 1983, 7, 22.0 }, + { 1985, 7, 23.0 }, + { 1988, 1, 24.0 }, + { 1990, 1, 25.0 }, + { 1991, 1, 26.0 }, + { 1992, 7, 27.0 }, + { 1993, 7, 28.0 }, + { 1994, 7, 29.0 }, + { 1996, 1, 30.0 }, + { 1997, 7, 31.0 }, + { 1999, 1, 32.0 }, + { 2006, 1, 33.0 }, + { 2009, 1, 34.0 }, + { 2012, 7, 35.0 }, + { 2015, 7, 36.0 }, + { 2017, 1, 37.0 } +}; +static const int n_builtin_changes = 42; + +/* +** For thread safety we want a way to define thread-local variables. +** If no TLS is available, fall back to ordinary storage and disable +** thread safety for those variables. +*/ +#if defined(_MSC_VER) +# define ERFA_THREAD_LOCAL __declspec(thread) +# define ERFA_HAS_THREAD_LOCAL 1 +#elif defined(__cplusplus) && __cplusplus >= 201103L +# define ERFA_THREAD_LOCAL thread_local +# define ERFA_HAS_THREAD_LOCAL 1 +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +# define ERFA_THREAD_LOCAL _Thread_local +# define ERFA_HAS_THREAD_LOCAL 1 +#elif defined(__GNUC__) || defined(__clang__) +# define ERFA_THREAD_LOCAL __thread +# define ERFA_HAS_THREAD_LOCAL 1 +#else +# define ERFA_THREAD_LOCAL +# define ERFA_HAS_THREAD_LOCAL 0 +#endif #endif From c74dbc76282c0e974e7df27079f7fa64ff9ffaee Mon Sep 17 00:00:00 2001 From: Colm Talbot Date: Mon, 30 Mar 2026 07:10:43 -0400 Subject: [PATCH 2/7] leave leap seconds initialized as null --- src/erfadatextra.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/erfadatextra.c b/src/erfadatextra.c index 3b6f49d..0a99f0b 100644 --- a/src/erfadatextra.c +++ b/src/erfadatextra.c @@ -19,8 +19,10 @@ #include "erfaextra.h" #include "erfadatextra.h" -static ERFA_THREAD_LOCAL eraLEAPSECOND *changes = builtin_changes; -static ERFA_THREAD_LOCAL int NDAT = n_builtin_changes; +// static ERFA_THREAD_LOCAL eraLEAPSECOND *changes = builtin_changes; +// static ERFA_THREAD_LOCAL int NDAT = n_builtin_changes; +static ERFA_THREAD_LOCAL eraLEAPSECOND *changes; +static ERFA_THREAD_LOCAL int NDAT = -1; void _eraResetLeapSeconds() From 952d18074b10e3aba845f6bff1cfa6513ee4d627 Mon Sep 17 00:00:00 2001 From: Colm Talbot Date: Mon, 30 Mar 2026 13:38:39 -0400 Subject: [PATCH 3/7] revert moving leapseconds into header --- src/dat.c | 51 +++++++++++++++++++++++++++++++++++++++++++++- src/erfadatextra.c | 34 ++++++++++--------------------- src/erfadatextra.h | 50 ++------------------------------------------- 3 files changed, 63 insertions(+), 72 deletions(-) diff --git a/src/dat.c b/src/dat.c index 0098863..aabd952 100644 --- a/src/dat.c +++ b/src/dat.c @@ -146,11 +146,60 @@ int eraDat(int iy, int im, int id, double fd, double *deltat) /* Number of Delta(AT) expressions before leap seconds were introduced */ enum { NERA1 = (int) (sizeof drift / sizeof (double) / 2) }; +/* Dates and Delta(AT)s */ + static const eraLEAPSECOND _changes[] = { + { 1960, 1, 1.4178180 }, + { 1961, 1, 1.4228180 }, + { 1961, 8, 1.3728180 }, + { 1962, 1, 1.8458580 }, + { 1963, 11, 1.9458580 }, + { 1964, 1, 3.2401300 }, + { 1964, 4, 3.3401300 }, + { 1964, 9, 3.4401300 }, + { 1965, 1, 3.5401300 }, + { 1965, 3, 3.6401300 }, + { 1965, 7, 3.7401300 }, + { 1965, 9, 3.8401300 }, + { 1966, 1, 4.3131700 }, + { 1968, 2, 4.2131700 }, + { 1972, 1, 10.0 }, + { 1972, 7, 11.0 }, + { 1973, 1, 12.0 }, + { 1974, 1, 13.0 }, + { 1975, 1, 14.0 }, + { 1976, 1, 15.0 }, + { 1977, 1, 16.0 }, + { 1978, 1, 17.0 }, + { 1979, 1, 18.0 }, + { 1980, 1, 19.0 }, + { 1981, 7, 20.0 }, + { 1982, 7, 21.0 }, + { 1983, 7, 22.0 }, + { 1985, 7, 23.0 }, + { 1988, 1, 24.0 }, + { 1990, 1, 25.0 }, + { 1991, 1, 26.0 }, + { 1992, 7, 27.0 }, + { 1993, 7, 28.0 }, + { 1994, 7, 29.0 }, + { 1996, 1, 30.0 }, + { 1997, 7, 31.0 }, + { 1999, 1, 32.0 }, + { 2006, 1, 33.0 }, + { 2009, 1, 34.0 }, + { 2012, 7, 35.0 }, + { 2015, 7, 36.0 }, + { 2017, 1, 37.0 } + }; + +/* Number of Delta(AT) changes */ + enum { _NDAT = (int) (sizeof _changes / sizeof _changes[0]) }; + /* Get/initialise leap-second if needed */ int NDAT; eraLEAPSECOND *changes; - NDAT = eraDatini(&changes); + NDAT = eraDatini(_changes, _NDAT, &changes); /* Miscellaneous local variables */ int j, i, m; diff --git a/src/erfadatextra.c b/src/erfadatextra.c index 0a99f0b..997fbb4 100644 --- a/src/erfadatextra.c +++ b/src/erfadatextra.c @@ -19,24 +19,10 @@ #include "erfaextra.h" #include "erfadatextra.h" -// static ERFA_THREAD_LOCAL eraLEAPSECOND *changes = builtin_changes; -// static ERFA_THREAD_LOCAL int NDAT = n_builtin_changes; static ERFA_THREAD_LOCAL eraLEAPSECOND *changes; static ERFA_THREAD_LOCAL int NDAT = -1; -void _eraResetLeapSeconds() -/* -** Reset the leap second table to the built-in version. -** -** This function is for internal use only and should not be used elsewhere. -*/ -{ - changes = builtin_changes; - NDAT = n_builtin_changes; -} - - int eraGetLeapSeconds(eraLEAPSECOND **leapseconds) /* ** Get the current leap second table. @@ -51,7 +37,11 @@ int eraGetLeapSeconds(eraLEAPSECOND **leapseconds) */ { if (NDAT <= 0) { - _eraResetLeapSeconds(); + double delat; + int stat = eraDat(2000, 1, 1, 0., &delat); + if (stat != 0 || NDAT <= 0) { + return -1; + } } *leapseconds = changes; return NDAT; @@ -71,15 +61,12 @@ void eraSetLeapSeconds(eraLEAPSECOND *leapseconds, int count) ** *No* sanity checks are performed. */ { - if (count <= 0) { - _eraResetLeapSeconds(); - } else { - changes = leapseconds; - NDAT = count; - } + changes = leapseconds; + NDAT = count; } -int eraDatini(eraLEAPSECOND **leapseconds) +int eraDatini(const eraLEAPSECOND *builtin, int n_builtin, + eraLEAPSECOND **leapseconds) /* ** Get the leap second table, initializing it to the built-in version ** if necessary. @@ -88,6 +75,7 @@ int eraDatini(eraLEAPSECOND **leapseconds) ** not be used elsewhere. ** ** Given: +** builtin eraLEAPSECOND Array of year, month, TAI minus UTC ** n_builtin int Number of entries of the table. ** ** Returned: @@ -99,7 +87,7 @@ int eraDatini(eraLEAPSECOND **leapseconds) */ { if (NDAT <= 0) { - _eraResetLeapSeconds(); + eraSetLeapSeconds((eraLEAPSECOND *)builtin, n_builtin); } *leapseconds = changes; return NDAT; diff --git a/src/erfadatextra.h b/src/erfadatextra.h index a4a2528..c1a04eb 100644 --- a/src/erfadatextra.h +++ b/src/erfadatextra.h @@ -20,54 +20,8 @@ ** This function is for internal use in dat.c only and should ** not be used elsewhere. */ -int eraDatini(eraLEAPSECOND **leapseconds); - -/* Dates and Delta(AT)s */ -static const eraLEAPSECOND builtin_changes[] = { - { 1960, 1, 1.4178180 }, - { 1961, 1, 1.4228180 }, - { 1961, 8, 1.3728180 }, - { 1962, 1, 1.8458580 }, - { 1963, 11, 1.9458580 }, - { 1964, 1, 3.2401300 }, - { 1964, 4, 3.3401300 }, - { 1964, 9, 3.4401300 }, - { 1965, 1, 3.5401300 }, - { 1965, 3, 3.6401300 }, - { 1965, 7, 3.7401300 }, - { 1965, 9, 3.8401300 }, - { 1966, 1, 4.3131700 }, - { 1968, 2, 4.2131700 }, - { 1972, 1, 10.0 }, - { 1972, 7, 11.0 }, - { 1973, 1, 12.0 }, - { 1974, 1, 13.0 }, - { 1975, 1, 14.0 }, - { 1976, 1, 15.0 }, - { 1977, 1, 16.0 }, - { 1978, 1, 17.0 }, - { 1979, 1, 18.0 }, - { 1980, 1, 19.0 }, - { 1981, 7, 20.0 }, - { 1982, 7, 21.0 }, - { 1983, 7, 22.0 }, - { 1985, 7, 23.0 }, - { 1988, 1, 24.0 }, - { 1990, 1, 25.0 }, - { 1991, 1, 26.0 }, - { 1992, 7, 27.0 }, - { 1993, 7, 28.0 }, - { 1994, 7, 29.0 }, - { 1996, 1, 30.0 }, - { 1997, 7, 31.0 }, - { 1999, 1, 32.0 }, - { 2006, 1, 33.0 }, - { 2009, 1, 34.0 }, - { 2012, 7, 35.0 }, - { 2015, 7, 36.0 }, - { 2017, 1, 37.0 } -}; -static const int n_builtin_changes = 42; +int eraDatini(const eraLEAPSECOND *builtin, int n_builtin, + eraLEAPSECOND **leapseconds); /* ** For thread safety we want a way to define thread-local variables. From a599c86f5c6653bb1259400eea0d53e03b489bb9 Mon Sep 17 00:00:00 2001 From: Colm Talbot Date: Mon, 30 Mar 2026 13:39:33 -0400 Subject: [PATCH 4/7] documentation fix --- src/erfadatextra.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/erfadatextra.c b/src/erfadatextra.c index 997fbb4..c58f7ed 100644 --- a/src/erfadatextra.c +++ b/src/erfadatextra.c @@ -75,7 +75,7 @@ int eraDatini(const eraLEAPSECOND *builtin, int n_builtin, ** not be used elsewhere. ** ** Given: -** builtin eraLEAPSECOND Array of year, month, TAI minus UTC +** builtin eraLEAPSECOND Array of year, month, TAI minus UTC ** n_builtin int Number of entries of the table. ** ** Returned: From 618946ecce4299b5b5b68006b0ab05877de921ac Mon Sep 17 00:00:00 2001 From: Colm Talbot Date: Mon, 30 Mar 2026 13:59:50 -0400 Subject: [PATCH 5/7] remove unused variable --- src/erfadatextra.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/erfadatextra.h b/src/erfadatextra.h index c1a04eb..d584547 100644 --- a/src/erfadatextra.h +++ b/src/erfadatextra.h @@ -30,19 +30,14 @@ int eraDatini(const eraLEAPSECOND *builtin, int n_builtin, */ #if defined(_MSC_VER) # define ERFA_THREAD_LOCAL __declspec(thread) -# define ERFA_HAS_THREAD_LOCAL 1 #elif defined(__cplusplus) && __cplusplus >= 201103L # define ERFA_THREAD_LOCAL thread_local -# define ERFA_HAS_THREAD_LOCAL 1 #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L # define ERFA_THREAD_LOCAL _Thread_local -# define ERFA_HAS_THREAD_LOCAL 1 #elif defined(__GNUC__) || defined(__clang__) # define ERFA_THREAD_LOCAL __thread -# define ERFA_HAS_THREAD_LOCAL 1 #else # define ERFA_THREAD_LOCAL -# define ERFA_HAS_THREAD_LOCAL 0 #endif #endif From 0960e47b198e2e2430a82fb03b78b9896c262729 Mon Sep 17 00:00:00 2001 From: Colm Talbot Date: Mon, 6 Apr 2026 11:40:52 -0400 Subject: [PATCH 6/7] Add some more comments about thread local methods --- src/erfadatextra.h | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/erfadatextra.h b/src/erfadatextra.h index d584547..4a6c570 100644 --- a/src/erfadatextra.h +++ b/src/erfadatextra.h @@ -26,16 +26,23 @@ int eraDatini(const eraLEAPSECOND *builtin, int n_builtin, /* ** For thread safety we want a way to define thread-local variables. ** If no TLS is available, fall back to ordinary storage and disable -** thread safety for those variables. +** thread safety for those variables. This mostly follows the numpy +** specification (as of 8203da6e4cdad722664fa740c7a9514104483f07) +** +** The order is: +** C++ >= 11 and GCC C >= 23 uses thread_local +** GCC: 11 <= C < 23 uses _Thread_local +** Clang uses __thread +** MSVC uses _declspec(thread) */ -#if defined(_MSC_VER) -# define ERFA_THREAD_LOCAL __declspec(thread) -#elif defined(__cplusplus) && __cplusplus >= 201103L +#if defined(__cplusplus) && __cplusplus >= 201103L # define ERFA_THREAD_LOCAL thread_local #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L # define ERFA_THREAD_LOCAL _Thread_local #elif defined(__GNUC__) || defined(__clang__) # define ERFA_THREAD_LOCAL __thread +#elif defined(_MSC_VER) +# define ERFA_THREAD_LOCAL __declspec(thread) #else # define ERFA_THREAD_LOCAL #endif From 468d4c954612441246d914c2beb57a0a95338c3e Mon Sep 17 00:00:00 2001 From: Colm Talbot Date: Mon, 6 Apr 2026 11:42:35 -0400 Subject: [PATCH 7/7] Update TLS for C23 --- src/erfadatextra.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/erfadatextra.h b/src/erfadatextra.h index 4a6c570..7bf4626 100644 --- a/src/erfadatextra.h +++ b/src/erfadatextra.h @@ -37,6 +37,8 @@ int eraDatini(const eraLEAPSECOND *builtin, int n_builtin, */ #if defined(__cplusplus) && __cplusplus >= 201103L # define ERFA_THREAD_LOCAL thread_local +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L +# define ERFA_THREAD_LOCAL thread_local #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L # define ERFA_THREAD_LOCAL _Thread_local #elif defined(__GNUC__) || defined(__clang__)