1 package org.djunits.quantity;
2
3 import org.djunits.quantity.def.AbsQuantity;
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 AbsQuantity<Temperature, TemperatureDifference, 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 valueInUnit 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 valueInUnit, final Temperature.Unit unit, final Reference reference)
35 {
36 super(new TemperatureDifference(valueInUnit, unit), reference);
37 }
38
39 /**
40 * Instantiate a Temperature quantity with a unit and the KELVIN reference point.
41 * @param valueInUnit 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 valueInUnit, final Temperature.Unit unit)
45 {
46 this(valueInUnit, unit, unit.getReference());
47 }
48
49 /**
50 * Instantiate a Temperature quantity with a unit, expressed as a String, and a reference point.
51 * @param valueInUnit 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 valueInUnit, final String abbreviation, final Reference reference)
56 {
57 this(valueInUnit, 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 valueInUnit 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 valueInUnit, final String abbreviation)
66 {
67 this(valueInUnit, 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 /**
118 * Returns a Temperature representation of a textual representation of a value with a unit. The String representation that
119 * can be parsed is the double value in the unit, followed by a localized or English abbreviation of the unit. Spaces are
120 * allowed, but not required, between the value and the unit.
121 * @param text the textual representation to parse into a Temperature
122 * @param reference the reference point of this absolute temperature
123 * @return the Scalar representation of the value in its unit
124 * @throws IllegalArgumentException when the text cannot be parsed
125 * @throws NullPointerException when the text argument is null
126 */
127 public static Temperature valueOf(final String text, final Reference reference)
128 {
129 return new Temperature(Quantity.valueOf(text, TemperatureDifference.ZERO), reference);
130 }
131
132 /**
133 * Returns a Temperature representation of a textual representation of a value with a unit, and the KELVIN reference. The
134 * String representation that can be parsed is the double value in the unit, followed by a localized or English abbreviation
135 * of the unit. Spaces are allowed, but not required, between the value and the unit.
136 * @param text the textual representation to parse into a Temperature
137 * @return the Scalar representation of the value in its unit
138 * @throws IllegalArgumentException when the text cannot be parsed
139 * @throws NullPointerException when the text argument is null
140 */
141 public static Temperature valueOf(final String text)
142 {
143 return new Temperature(Quantity.valueOf(text, TemperatureDifference.ZERO), Reference.KELVIN);
144 }
145
146 /**
147 * Returns a Temperature based on a value and the textual representation of the unit, which can be localized.
148 * @param valueInUnit the value, expressed in the unit as given by unitString
149 * @param unitString the textual representation of the unit
150 * @param reference the reference point of this absolute temperature
151 * @return the Scalar representation of the value in its unit
152 * @throws IllegalArgumentException when the unit cannot be parsed or is incorrect
153 * @throws NullPointerException when the unitString argument is null
154 */
155 public static Temperature of(final double valueInUnit, final String unitString, final Reference reference)
156 {
157 return new Temperature(Quantity.of(valueInUnit, unitString, TemperatureDifference.ZERO), reference);
158 }
159
160 /**
161 * Returns a Temperature based on a value and the textual representation of the unit, which can be localized. Use the KELVIN
162 * reference.
163 * @param valueInUnit the value, expressed in the unit as given by unitString
164 * @param unitString the textual representation of the unit
165 * @return the Scalar representation of the value in its unit
166 * @throws IllegalArgumentException when the unit cannot be parsed or is incorrect
167 * @throws NullPointerException when the unitString argument is null
168 */
169 public static Temperature of(final double valueInUnit, final String unitString)
170 {
171 return new Temperature(Quantity.of(valueInUnit, unitString, TemperatureDifference.ZERO), Reference.KELVIN);
172 }
173
174 @Override
175 public TemperatureDifference subtract(final Temperature other)
176 {
177 var otherRef = other.relativeTo(getReference());
178 return TemperatureDifference.ofSi(si() - otherRef.si()).setDisplayUnit(getDisplayUnit());
179 }
180
181 @Override
182 public Temperature add(final TemperatureDifference other)
183 {
184 return new Temperature(TemperatureDifference.ofSi(si() + other.si()).setDisplayUnit(getDisplayUnit()), getReference());
185 }
186
187 @Override
188 public Temperature subtract(final TemperatureDifference other)
189 {
190 return new Temperature(TemperatureDifference.ofSi(si() - other.si()).setDisplayUnit(getDisplayUnit()), getReference());
191 }
192
193 /**
194 * The reference class to define a reference point for the absolute temperature.
195 */
196 public static final class Reference extends AbstractReference<Reference, Temperature, TemperatureDifference>
197 {
198 /** Kelvin. */
199 public static final Reference KELVIN =
200 new Reference("KELVIN", "Kelvin scale temperature", TemperatureDifference.ZERO, null);
201
202 /** Celsius. */
203 public static final Reference CELSIUS =
204 new Reference("CELSIUS", "Celsius scale temperature", TemperatureDifference.ofSi(273.15), KELVIN);
205
206 /** Fahrenheit. */
207 public static final Reference FAHRENHEIT = new Reference("FAHRENHEIT", "Fahrenheit scale temperature",
208 TemperatureDifference.ofSi(273.15 - 32 / 1.8), KELVIN);
209
210 /**
211 * Define a new reference point for an absolute temperature.
212 * @param id the id
213 * @param name the name or explanation
214 * @param offset the offset w.r.t. offsetReference
215 * @param offsetReference the reference to which the offset is relative
216 */
217 public Reference(final String id, final String name, final TemperatureDifference offset,
218 final Reference offsetReference)
219 {
220 super(id, name, offset, offsetReference);
221 }
222
223 /**
224 * Define a new reference point for the absolute temperature, with an offset to 0 kelvin.
225 * @param id the id
226 * @param name the name or explanation
227 * @param offset the offset w.r.t. offsetReference
228 */
229 public Reference(final String id, final String name, final TemperatureDifference offset)
230 {
231 super(id, name, offset, KELVIN);
232 }
233
234 /**
235 * Define a new reference point for the absolute temperature, with an offset to 0 kelvin.
236 * @param id the id
237 * @param name the name or explanation
238 * @param offset the offset of this scale relative to 0 kelvin
239 */
240 public static void add(final String id, final String name, final TemperatureDifference offset)
241 {
242 new Reference(id, name, offset);
243 }
244
245 /**
246 * Get a reference point for the absolute temperature, based on its id. Return null when the id could not be found.
247 * @param id the id
248 * @return the Temperature.Reference object
249 */
250 public static Reference get(final String id)
251 {
252 return AbstractReference.get(Temperature.Reference.class, id);
253 }
254
255 @Override
256 public Temperature instantiate(final TemperatureDifference temperatureDifference)
257 {
258 return new Temperature(temperatureDifference, this);
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 }