1//
2// Copyright (c) 2009-2011 Artyom Beilis (Tonkikh)
3// Copyright (c) 2021-2022 Alexander Grund
4//
5// Distributed under the Boost Software License, Version 1.0.
6// https://www.boost.org/LICENSE_1_0.txt
7
8#include <boost/locale/date_time.hpp>
9#include <boost/locale/date_time_facet.hpp>
10#include <boost/locale/formatting.hpp>
11#include <boost/locale/hold_ptr.hpp>
12#include "boost/locale/icu/all_generator.hpp"
13#include "boost/locale/icu/cdata.hpp"
14#include "boost/locale/icu/icu_util.hpp"
15#include "boost/locale/icu/time_zone.hpp"
16#include "boost/locale/icu/uconv.hpp"
17#include <boost/thread.hpp>
18#include <cmath>
19#include <memory>
20#include <unicode/calendar.h>
21#include <unicode/gregocal.h>
22#include <unicode/utypes.h>
23
24namespace boost { namespace locale { namespace impl_icu {
25
26 static void check_and_throw_dt(UErrorCode& e)
27 {
28 if(U_FAILURE(code: e))
29 throw date_time_error(u_errorName(code: e));
30 }
31 using period::marks::period_mark;
32
33 static UCalendarDateFields to_icu(period::marks::period_mark f)
34 {
35 using namespace period::marks;
36
37 switch(f) {
38 case era: return UCAL_ERA;
39 case year: return UCAL_YEAR;
40 case extended_year: return UCAL_EXTENDED_YEAR;
41 case month: return UCAL_MONTH;
42 case day: return UCAL_DATE;
43 case day_of_year: return UCAL_DAY_OF_YEAR;
44 case day_of_week: return UCAL_DAY_OF_WEEK;
45 case day_of_week_in_month: return UCAL_DAY_OF_WEEK_IN_MONTH;
46 case day_of_week_local: return UCAL_DOW_LOCAL;
47 case hour: return UCAL_HOUR_OF_DAY;
48 case hour_12: return UCAL_HOUR;
49 case am_pm: return UCAL_AM_PM;
50 case minute: return UCAL_MINUTE;
51 case second: return UCAL_SECOND;
52 case week_of_year: return UCAL_WEEK_OF_YEAR;
53 case week_of_month: return UCAL_WEEK_OF_MONTH;
54 case first_day_of_week:
55 case invalid: break;
56 }
57 throw std::invalid_argument("Invalid date_time period type"); // LCOV_EXCL_LINE
58 }
59
60 class calendar_impl : public abstract_calendar {
61 public:
62 calendar_impl(const cdata& dat)
63 {
64 UErrorCode err = U_ZERO_ERROR;
65 calendar_.reset(p: icu::Calendar::createInstance(aLocale: dat.locale(), success&: err));
66 // Use accuracy of seconds, see #221
67 const double rounded_time = std::floor(x: calendar_->getTime(status&: err) / U_MILLIS_PER_SECOND) * U_MILLIS_PER_SECOND;
68 calendar_->setTime(date: rounded_time, status&: err);
69 check_and_throw_dt(e&: err);
70#if BOOST_LOCALE_ICU_VERSION < 402
71 // workaround old/invalid data, it should be 4 in general
72 calendar_->setMinimalDaysInFirstWeek(4);
73#endif
74 encoding_ = dat.encoding();
75 }
76 calendar_impl(const calendar_impl& other)
77 {
78 calendar_.reset(p: other.calendar_->clone());
79 encoding_ = other.encoding_;
80 }
81
82 calendar_impl* clone() const override
83 {
84 return new calendar_impl(*this);
85 }
86
87 void set_value(period::marks::period_mark p, int value) override
88 {
89 calendar_->set(field: to_icu(f: p), value: int32_t(value));
90 }
91
92 int get_value(period::marks::period_mark p, value_type type) const override
93 {
94 UErrorCode err = U_ZERO_ERROR;
95 int v = 0;
96 if(p == period::marks::first_day_of_week) {
97 guard l(lock_);
98 v = calendar_->getFirstDayOfWeek(status&: err);
99 } else {
100 UCalendarDateFields field = to_icu(f: p);
101 guard l(lock_);
102 switch(type) {
103 case absolute_minimum: v = calendar_->getMinimum(field); break;
104 case actual_minimum: v = calendar_->getActualMinimum(field, status&: err); break;
105 case greatest_minimum: v = calendar_->getGreatestMinimum(field); break;
106 case current: v = calendar_->get(field, status&: err); break;
107 case least_maximum: v = calendar_->getLeastMaximum(field); break;
108 case actual_maximum: v = calendar_->getActualMaximum(field, status&: err); break;
109 case absolute_maximum: v = calendar_->getMaximum(field); break;
110 }
111 }
112 check_and_throw_dt(e&: err);
113 return v;
114 }
115
116 void normalize() override
117 {
118 // Can't call complete() explicitly (protected)
119 // calling get which calls complete
120 UErrorCode code = U_ZERO_ERROR;
121 calendar_->get(field: UCAL_YEAR, status&: code);
122 check_and_throw_dt(e&: code);
123 }
124
125 void set_time(const posix_time& p) override
126 {
127 // Ignore `p.nanoseconds / 1e6` for simplicity of users as there is no
128 // easy way to set the sub-seconds via `date_time`.
129 // Matches behavior of other backends that only have seconds resolution
130 const double utime = p.seconds * 1e3;
131 UErrorCode code = U_ZERO_ERROR;
132 calendar_->setTime(date: utime, status&: code);
133 check_and_throw_dt(e&: code);
134 }
135 posix_time get_time() const override
136 {
137 const double timeMs = get_time_ms();
138 posix_time res;
139 res.seconds = static_cast<int64_t>(std::floor(x: timeMs / 1e3));
140 const double remainTimeMs = std::fmod(x: timeMs, y: 1e3); // = timeMs - seconds * 1000
141 constexpr uint32_t ns_in_s = static_cast<uint32_t>(1000) * 1000 * 1000;
142 res.nanoseconds = std::min(a: static_cast<uint32_t>(remainTimeMs * 1e6), b: ns_in_s - 1u);
143 return res;
144 }
145 double get_time_ms() const override
146 {
147 UErrorCode code = U_ZERO_ERROR;
148 double result;
149 {
150 guard l(lock_);
151 result = calendar_->getTime(status&: code);
152 }
153 check_and_throw_dt(e&: code);
154 return result;
155 }
156
157 void set_option(calendar_option_type opt, int /*v*/) override
158 {
159 switch(opt) {
160 case is_gregorian: throw date_time_error("is_gregorian is not settable options for calendar");
161 case is_dst: throw date_time_error("is_dst is not settable options for calendar");
162 }
163 throw std::invalid_argument("Invalid option type"); // LCOV_EXCL_LINE
164 }
165 int get_option(calendar_option_type opt) const override
166 {
167 switch(opt) {
168 case is_gregorian: return icu_cast<const icu::GregorianCalendar>(p: calendar_.get()) != nullptr;
169 case is_dst: {
170 guard l(lock_);
171 UErrorCode err = U_ZERO_ERROR;
172 bool res = (calendar_->inDaylightTime(status&: err) != 0);
173 check_and_throw_dt(e&: err);
174 return res;
175 }
176 }
177 throw std::invalid_argument("Invalid option type"); // LCOV_EXCL_LINE
178 }
179 void adjust_value(period::marks::period_mark p, update_type u, int difference) override
180 {
181 UErrorCode err = U_ZERO_ERROR;
182 switch(u) {
183 case move: calendar_->add(field: to_icu(f: p), amount: difference, status&: err); break;
184 case roll: calendar_->roll(field: to_icu(f: p), amount: difference, status&: err); break;
185 }
186 check_and_throw_dt(e&: err);
187 }
188 int difference(const abstract_calendar& other, period::marks::period_mark m) const override
189 {
190 // era can't be queried via fieldDifference
191 if(BOOST_UNLIKELY(m == period::marks::era))
192 return get_value(p: m, type: value_type::current) - other.get_value(m, v: value_type::current);
193
194 const double other_time_ms = other.get_time_ms();
195
196 // fieldDifference has side effect of moving calendar (WTF?)
197 // So we clone it for performing this operation
198 hold_ptr<icu::Calendar> self(calendar_->clone());
199
200 UErrorCode err = U_ZERO_ERROR;
201 const int diff = self->fieldDifference(when: other_time_ms, field: to_icu(f: m), status&: err);
202 check_and_throw_dt(e&: err);
203 return diff;
204 }
205 void set_timezone(const std::string& tz) override
206 {
207 calendar_->adoptTimeZone(value: get_time_zone(time_zone: tz));
208 }
209 std::string get_timezone() const override
210 {
211 icu::UnicodeString tz;
212 calendar_->getTimeZone().getID(ID&: tz);
213 icu_std_converter<char> cvt(encoding_);
214 return cvt.std(str: tz);
215 }
216 bool same(const abstract_calendar* other) const override
217 {
218 const calendar_impl* oc = dynamic_cast<const calendar_impl*>(other);
219 if(!oc)
220 return false;
221 return calendar_->isEquivalentTo(other: *oc->calendar_) != 0;
222 }
223
224 private:
225 typedef boost::unique_lock<boost::mutex> guard;
226 mutable boost::mutex lock_;
227 std::string encoding_;
228 hold_ptr<icu::Calendar> calendar_;
229 };
230
231 class icu_calendar_facet : public calendar_facet {
232 public:
233 icu_calendar_facet(const cdata& d, size_t refs = 0) : calendar_facet(refs), data_(d) {}
234 abstract_calendar* create_calendar() const override { return new calendar_impl(data_); }
235
236 private:
237 cdata data_;
238 };
239
240 std::locale create_calendar(const std::locale& in, const cdata& d)
241 {
242 return std::locale(in, new icu_calendar_facet(d));
243 }
244
245}}} // namespace boost::locale::impl_icu
246

source code of boost/libs/locale/src/boost/locale/icu/date_time.cpp