1 package org.djunits.quantity;
2
3 import org.djunits.quantity.def.AbsoluteQuantity;
4 import org.djunits.quantity.def.AbstractReference;
5 import org.djunits.quantity.def.Quantity;
6 import org.djunits.unit.AbstractUnit;
7 import org.djunits.unit.UnitRuntimeException;
8 import org.djunits.unit.Units;
9 import org.djunits.unit.scale.LinearScale;
10 import org.djunits.unit.scale.Scale;
11 import org.djunits.unit.si.SIUnit;
12 import org.djunits.unit.system.UnitSystem;
13
14 /**
15 * Temperature is the absolute equivalent of Temperature, and represents a true temperature rather than a temperature
16 * difference.
17 * <p>
18 * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
19 * for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
20 * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
21 * @author Alexander Verbraeck
22 */
23 public class Temperature extends AbsoluteQuantity<Temperature, TemperatureDifference, Temperature.Unit, Temperature.Reference>
24 {
25 /** */
26 private static final long serialVersionUID = 600L;
27
28 /**
29 * Instantiate a Temperature quantity with a unit and a reference point.
30 * @param value the temperature value, expressed in a temperature unit
31 * @param unit the temperature unit in which the value is expressed, relative to the reference point
32 * @param reference the reference point of this absolute temperature
33 */
34 public Temperature(final double value, final Temperature.Unit unit, final Reference reference)
35 {
36 super(new TemperatureDifference(value, unit), reference);
37 }
38
39 /**
40 * Instantiate a Temperature quantity with a unit and the KELVIN reference point.
41 * @param value the temperature value, expressed in a temperature unit
42 * @param unit the temperature unit in which the value is expressed, relative to the reference point
43 */
44 public Temperature(final double value, final Temperature.Unit unit)
45 {
46 this(value, unit, unit.getReference());
47 }
48
49 /**
50 * Instantiate a Temperature quantity with a unit, expressed as a String, and a reference point.
51 * @param value the temperature value, expressed in the unit, relative to the reference point
52 * @param abbreviation the String abbreviation of the unit in which the value is expressed
53 * @param reference the reference point of this absolute temperature
54 */
55 public Temperature(final double value, final String abbreviation, final Reference reference)
56 {
57 this(value, Units.resolve(Temperature.Unit.class, abbreviation), reference);
58 }
59
60 /**
61 * Instantiate a Temperature quantity with a unit, expressed as a String, and the reference point belonging to the unit.
62 * @param value the temperature value, expressed in the unit, relative to the reference point
63 * @param abbreviation the String abbreviation of the unit in which the value is expressed
64 */
65 public Temperature(final double value, final String abbreviation)
66 {
67 this(value, Units.resolve(Temperature.Unit.class, abbreviation),
68 Units.resolve(Temperature.Unit.class, abbreviation).getReference());
69 }
70
71 /**
72 * Instantiate a Temperature instance based on an temperature and a reference point.
73 * @param temperature the temperature, relative to the reference point
74 * @param reference the reference point of this absolute temperature
75 */
76 public Temperature(final TemperatureDifference temperature, final Reference reference)
77 {
78 super(temperature, reference);
79 }
80
81 /**
82 * Instantiate a Temperature instance based on an temperature and the KELVIN reference point.
83 * @param temperature the temperature, relative to the reference point
84 */
85 public Temperature(final TemperatureDifference temperature)
86 {
87 this(temperature, Reference.KELVIN);
88 }
89
90 /**
91 * Return a Temperature instance based on an SI value and a reference point.
92 * @param si the temperature si value, relative to the reference point
93 * @param reference the reference point of this absolute temperature
94 * @return the Temperature instance based on an SI value
95 */
96 public static Temperature ofSi(final double si, final Reference reference)
97 {
98 return new Temperature(si, Temperature.Unit.SI, reference);
99 }
100
101 /**
102 * Return a Temperature instance based on an SI value and the KELVIN reference point.
103 * @param si the temperature si value, relative to the reference point
104 * @return the Temperature instance based on an SI value
105 */
106 public static Temperature ofSi(final double si)
107 {
108 return new Temperature(si, Temperature.Unit.SI, Reference.KELVIN);
109 }
110
111 @Override
112 public Temperature instantiate(final TemperatureDifference temperature, final Reference reference)
113 {
114 return new Temperature(temperature, reference);
115 }
116
117 @Override
118 public SIUnit siUnit()
119 {
120 return Temperature.Unit.SI_UNIT;
121 }
122
123 /**
124 * Returns a Temperature representation of a textual representation of a value with a unit. The String representation that
125 * can be parsed is the double value in the unit, followed by a localized or English abbreviation of the unit. Spaces are
126 * allowed, but not required, between the value and the unit.
127 * @param text the textual representation to parse into a Temperature
128 * @param reference the reference point of this absolute temperature
129 * @return the Scalar representation of the value in its unit
130 * @throws IllegalArgumentException when the text cannot be parsed
131 * @throws NullPointerException when the text argument is null
132 */
133 public static Temperature valueOf(final String text, final Reference reference)
134 {
135 return new Temperature(Quantity.valueOf(text, TemperatureDifference.ZERO), reference);
136 }
137
138 /**
139 * Returns a Temperature representation of a textual representation of a value with a unit, and the KELVIN reference. The
140 * String representation that can be parsed is the double value in the unit, followed by a localized or English abbreviation
141 * of the unit. Spaces are allowed, but not required, between the value and the unit.
142 * @param text the textual representation to parse into a Temperature
143 * @return the Scalar representation of the value in its unit
144 * @throws IllegalArgumentException when the text cannot be parsed
145 * @throws NullPointerException when the text argument is null
146 */
147 public static Temperature valueOf(final String text)
148 {
149 return new Temperature(Quantity.valueOf(text, TemperatureDifference.ZERO), Reference.KELVIN);
150 }
151
152 /**
153 * Returns a Temperature based on a value and the textual representation of the unit, which can be localized.
154 * @param value the value to use
155 * @param unitString the textual representation of the unit
156 * @param reference the reference point of this absolute temperature
157 * @return the Scalar representation of the value in its unit
158 * @throws IllegalArgumentException when the unit cannot be parsed or is incorrect
159 * @throws NullPointerException when the unitString argument is null
160 */
161 public static Temperature of(final double value, final String unitString, final Reference reference)
162 {
163 return new Temperature(Quantity.of(value, unitString, TemperatureDifference.ZERO), reference);
164 }
165
166 /**
167 * Returns a Temperature based on a value and the textual representation of the unit, which can be localized. Use the KELVIN
168 * reference.
169 * @param value the value to use
170 * @param unitString the textual representation of the unit
171 * @return the Scalar representation of the value in its unit
172 * @throws IllegalArgumentException when the unit cannot be parsed or is incorrect
173 * @throws NullPointerException when the unitString argument is null
174 */
175 public static Temperature of(final double value, final String unitString)
176 {
177 return new Temperature(Quantity.of(value, unitString, TemperatureDifference.ZERO), Reference.KELVIN);
178 }
179
180 @Override
181 public TemperatureDifference subtract(final Temperature other)
182 {
183 var otherRef = other.relativeTo(getReference());
184 return TemperatureDifference.ofSi(si() - otherRef.si()).setDisplayUnit(getDisplayUnit());
185 }
186
187 @Override
188 public Temperature add(final TemperatureDifference other)
189 {
190 return new Temperature(TemperatureDifference.ofSi(si() + other.si()).setDisplayUnit(getDisplayUnit()), getReference());
191 }
192
193 @Override
194 public Temperature subtract(final TemperatureDifference other)
195 {
196 return new Temperature(TemperatureDifference.ofSi(si() - other.si()).setDisplayUnit(getDisplayUnit()), getReference());
197 }
198
199 /**
200 * The reference class to define a reference point for the absolute temperature.
201 */
202 public static final class Reference extends AbstractReference<Reference, TemperatureDifference>
203 {
204 /** Kelvin. */
205 public static final Reference KELVIN =
206 new Reference("KELVIN", "Kelvin scale temperature", TemperatureDifference.ZERO, null);
207
208 /** Celsius. */
209 public static final Reference CELSIUS =
210 new Reference("CELSIUS", "Celsius scale temperature", TemperatureDifference.ofSi(273.15), KELVIN);
211
212 /** Fahrenheit. */
213 public static final Reference FAHRENHEIT = new Reference("FAHRENHEIT", "Fahrenheit scale temperature",
214 TemperatureDifference.ofSi(273.15 - 32 / 1.8), KELVIN);
215
216 /**
217 * Define a new reference point for an absolute temperature.
218 * @param id the id
219 * @param name the name or explanation
220 * @param offset the offset w.r.t. offsetReference
221 * @param offsetReference the reference to which the offset is relative
222 */
223 public Reference(final String id, final String name, final TemperatureDifference offset,
224 final Reference offsetReference)
225 {
226 super(id, name, offset, offsetReference);
227 }
228
229 /**
230 * Define a new reference point for the absolute temperature, with an offset to 0 kelvin.
231 * @param id the id
232 * @param name the name or explanation
233 * @param offset the offset w.r.t. offsetReference
234 */
235 public Reference(final String id, final String name, final TemperatureDifference offset)
236 {
237 super(id, name, offset, KELVIN);
238 }
239
240 /**
241 * Define a new reference point for the absolute temperature, with an offset to 0 kelvin.
242 * @param id the id
243 * @param name the name or explanation
244 * @param offset the offset of this scale relative to 0 kelvin
245 */
246 public static void add(final String id, final String name, final TemperatureDifference offset)
247 {
248 new Reference(id, name, offset);
249 }
250
251 /**
252 * Get a reference point for the absolute temperature, based on its id. Return null when the id could not be found.
253 * @param id the id
254 * @return the Temperature.Reference object
255 */
256 public static Reference get(final String id)
257 {
258 return AbstractReference.get(Temperature.Reference.class, id);
259 }
260 }
261
262 /******************************************************************************************************/
263 /********************************************** UNIT CLASS ********************************************/
264 /******************************************************************************************************/
265
266 /**
267 * Temperature.Unit encodes the units of relative and absolute temperature. Note that he reference is not initialized
268 * immediately for the units, since Reference needs working units to initialize itself. Therefore, references will be
269 * allocated when an reference is retrieved for the first time.
270 * <p>
271 * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
272 * See for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
273 * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
274 * @author Alexander Verbraeck
275 */
276 @SuppressWarnings("checkstyle:constantname")
277 public static class Unit extends AbstractUnit<Temperature.Unit, TemperatureDifference>
278 {
279 /** The dimensions of temperature: K. */
280 public static final SIUnit SI_UNIT = SIUnit.of("K");
281
282 /** Kelvin. */
283 public static final Temperature.Unit K = new Temperature.Unit("K", "kelvin", 1.0, UnitSystem.SI_BASE);
284
285 /** The SI or BASE unit. */
286 public static final Temperature.Unit SI = K.generateSiPrefixes(false, false);
287
288 /** Degree Celsius. */
289 public static final Temperature.Unit degC =
290 new Temperature.Unit("degC", "\u00B0C", "degree Celsius", new LinearScale(1.0), UnitSystem.SI_DERIVED);
291
292 /** Degree Fahrenheit. */
293 public static final Temperature.Unit degF =
294 new Temperature.Unit("degF", "\u00B0F", "degree Fahrenheit", new LinearScale(5.0 / 9.0), UnitSystem.OTHER);
295
296 /** Degree Rankine. */
297 public static final Temperature.Unit degR =
298 new Temperature.Unit("degR", "\u00B0R", "degree Rankine", new LinearScale(5.0 / 9.0), UnitSystem.OTHER);
299
300 /** Degree Reaumur. */
301 public static final Temperature.Unit degRe =
302 new Temperature.Unit("degRe", "\u00B0R\u00E9", "degree Reaumur", new LinearScale(4.0 / 5.0), UnitSystem.OTHER);
303
304 /** the default reference for this unit (used for absolute quantities). */
305 private Reference reference;
306
307 /** Guard to assign default references lazily, safely, exactly once. */
308 private static volatile boolean defaultReferencesAssigned = false;
309
310 /**
311 * Create a new Temperature unit.
312 * @param id the id or main abbreviation of the unit
313 * @param name the full name of the unit
314 * @param scaleFactorToBaseUnit the scale factor of the unit to convert it TO the base (SI) unit
315 * @param unitSystem the unit system such as SI or IMPERIAL
316 */
317 public Unit(final String id, final String name, final double scaleFactorToBaseUnit, final UnitSystem unitSystem)
318 {
319 super(id, name, new LinearScale(scaleFactorToBaseUnit), unitSystem);
320 }
321
322 /**
323 * Return a derived unit for this unit, with textual abbreviation(s) and a display abbreviation.
324 * @param textualAbbreviation the textual abbreviation of the unit, which doubles as the id
325 * @param displayAbbreviation the display abbreviation of the unit
326 * @param name the full name of the unit
327 * @param scale the scale to use to convert between this unit and the standard (e.g., SI, BASE) unit
328 * @param unitSystem unit system, e.g. SI or Imperial
329 */
330 public Unit(final String textualAbbreviation, final String displayAbbreviation, final String name, final Scale scale,
331 final UnitSystem unitSystem)
332 {
333 super(textualAbbreviation, displayAbbreviation, name, scale, unitSystem);
334 }
335
336 /**
337 * Ensure default references for all Temperature units are assigned once and only when needed. This avoids circular
338 * initialization between Temperature.Unit, Temperature.Reference, and TemperatureDifference. The method is deliberately
339 * minimal: it only assigns references to existing unit singletons and does not create new objects or resolve units.
340 */
341 private static void ensureDefaultReferencesAssigned()
342 {
343 if (defaultReferencesAssigned)
344 {
345 return;
346 }
347 synchronized (Unit.class)
348 {
349 if (!defaultReferencesAssigned)
350 {
351 // IMPORTANT:
352 // - Assign only to already-constructed constants.
353 // - Do NOT create new objects here.
354 // - Do NOT call Units.resolve(...) or valueOf(...) here.
355 // - Do NOT refer to TemperatureDifference.ZERO, etc., from here.
356
357 K.reference = Reference.KELVIN;
358 degC.reference = Reference.CELSIUS;
359 degF.reference = Reference.FAHRENHEIT;
360 degR.reference = Reference.KELVIN; // Rankine uses Kelvin as default reference
361 degRe.reference = Reference.CELSIUS; // Réaumur uses Celsius as default reference
362
363 defaultReferencesAssigned = true;
364 }
365 }
366 }
367
368 /**
369 * Return the default reference for this unit. References are assigned lazily and safely (once) to avoid circular class
370 * initialization issues.
371 * @return the reference for an absolute temperature
372 */
373 protected Reference getReference()
374 {
375 if (this.reference == null)
376 {
377 // assign for all units exactly once, then return for this instance
378 ensureDefaultReferencesAssigned();
379 }
380 return this.reference;
381 }
382
383 @Override
384 public SIUnit siUnit()
385 {
386 return SI_UNIT;
387 }
388
389 @Override
390 public Unit getBaseUnit()
391 {
392 return SI;
393 }
394
395 @Override
396 public TemperatureDifference ofSi(final double si)
397 {
398 return TemperatureDifference.ofSi(si);
399 }
400
401 @Override
402 public Unit deriveUnit(final String textualAbbreviation, final String displayAbbreviation, final String name,
403 final double scaleFactor, final UnitSystem unitSystem)
404 {
405 if (getScale() instanceof LinearScale ls)
406 {
407 return new Temperature.Unit(textualAbbreviation, displayAbbreviation, name,
408 new LinearScale(ls.getScaleFactorToBaseUnit() * scaleFactor), unitSystem);
409 }
410 throw new UnitRuntimeException("Only possible to derive a unit from a unit with a linear scale");
411 }
412
413 }
414
415 }