Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/eckit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ list( APPEND eckit_utils_srcs
utils/EnumBitmask.h
utils/Hash.cc
utils/Hash.h
utils/HashCombine.h
utils/HyperCube.cc
utils/HyperCube.h
utils/Literals.h
Expand Down
9 changes: 9 additions & 0 deletions src/eckit/types/Date.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#ifndef eckit_Date_h
#define eckit_Date_h

#include <functional>

#include "eckit/persist/Bless.h"

namespace eckit {
Expand Down Expand Up @@ -167,4 +169,11 @@ class Date {

} // namespace eckit

template <>
struct std::hash<eckit::Date> {
std::size_t operator()(const eckit::Date& d) const noexcept {
return std::hash<long>{}(d.julian());
}
};

#endif
10 changes: 10 additions & 0 deletions src/eckit/types/DateTime.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include "eckit/types/Date.h"
#include "eckit/types/Time.h"
#include "eckit/utils/HashCombine.h"


namespace eckit {
Expand Down Expand Up @@ -98,4 +99,13 @@ class DateTime {

} // namespace eckit

template <>
struct std::hash<eckit::DateTime> {
std::size_t operator()(const eckit::DateTime& dt) const noexcept {
std::size_t seed = std::hash<eckit::Date>{}(dt.date());
eckit::hash_combine(seed, std::hash<eckit::Time>{}(dt.time()));
return seed;
}
};

#endif
8 changes: 8 additions & 0 deletions src/eckit/types/Time.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#define eckit_Time_h

#include <cstdint>
#include <functional>

#include "eckit/exception/Exceptions.h"
#include "eckit/persist/Bless.h"
Expand Down Expand Up @@ -125,4 +126,11 @@ class BadTime : public BadValue {

} // namespace eckit

template <>
struct std::hash<eckit::Time> {
std::size_t operator()(const eckit::Time& t) const noexcept {
return std::hash<long>{}(t.hhmmss());
}
};

#endif
34 changes: 34 additions & 0 deletions src/eckit/utils/HashCombine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* (C) Copyright 1996- ECMWF.
*
* This software is licensed under the terms of the Apache Licence Version 2.0
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
* In applying this licence, ECMWF does not waive the privileges and immunities
* granted to it by virtue of its status as an intergovernmental organisation nor
* does it submit to any jurisdiction.
*/

#pragma once

#include <cstddef>
#include <functional>

namespace eckit {

/// Combine a hash value into a seed (boost::hash_combine equivalent).
///
/// Uses the 64-bit golden-ratio constant to mix bits and reduce
/// collisions when combining multiple hash values for use with
/// std::hash and unordered containers.
inline void hash_combine(std::size_t& seed, std::size_t value) noexcept {
seed ^= value + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2);
}

/// Variadic convenience: combine multiple hash values into a seed.
template <typename T, typename... Rest>
inline void hash_combine(std::size_t& seed, const T& v, const Rest&... rest) {
hash_combine(seed, std::hash<T>{}(v));
(hash_combine(seed, std::hash<Rest>{}(rest)), ...);
}

} // namespace eckit
47 changes: 47 additions & 0 deletions tests/types/test_time.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@


#include <iomanip>
#include <unordered_set>
#include "eckit/testing/Test.h"
#include "eckit/types/Date.h"
#include "eckit/types/DateTime.h"
#include "eckit/types/Time.h"

using namespace std;
Expand Down Expand Up @@ -167,6 +170,50 @@ CASE("Time with unit (__h__m__s)") {
EXPECT(Time("2D3h", true) == Time("51h", true));
}

CASE("std::hash<Date>") {
std::unordered_set<Date> dates;
dates.insert(Date(2024, 3, 15));
dates.insert(Date(2024, 3, 15)); // duplicate
dates.insert(Date(2024, 3, 16));
EXPECT(dates.size() == 2);

EXPECT(std::hash<Date>{}(Date(2024, 3, 15))
== std::hash<Date>{}(Date(2024, 3, 15)));

EXPECT(std::hash<Date>{}(Date(2024, 3, 15))
!= std::hash<Date>{}(Date(2024, 3, 16)));
}

CASE("std::hash<Time>") {
std::unordered_set<Time> times;
times.insert(Time(12, 30, 0));
times.insert(Time(12, 30, 0)); // duplicate
times.insert(Time(12, 30, 1));
EXPECT(times.size() == 2);

EXPECT(std::hash<Time>{}(Time(12, 30, 0))
== std::hash<Time>{}(Time(12, 30, 0)));

EXPECT(std::hash<Time>{}(Time(12, 30, 0))
!= std::hash<Time>{}(Time(12, 30, 1)));
}

CASE("std::hash<DateTime>") {
std::unordered_set<DateTime> dts;
dts.insert(DateTime(Date(2024, 3, 15), Time(12, 0, 0)));
dts.insert(DateTime(Date(2024, 3, 15), Time(12, 0, 0))); // duplicate
dts.insert(DateTime(Date(2024, 3, 15), Time(12, 0, 1)));
EXPECT(dts.size() == 2);

// Same date, different time
EXPECT(std::hash<DateTime>{}(DateTime(Date(2024, 3, 15), Time(12, 0, 0)))
!= std::hash<DateTime>{}(DateTime(Date(2024, 3, 15), Time(12, 0, 1))));

// Different date, same time
EXPECT(std::hash<DateTime>{}(DateTime(Date(2024, 3, 15), Time(12, 0, 0)))
!= std::hash<DateTime>{}(DateTime(Date(2024, 3, 16), Time(12, 0, 0))));
}

//----------------------------------------------------------------------------------------------------------------------

} // namespace eckit::test
Expand Down
Loading