1 /* 2 * hunt-time: A time library for D programming language. 3 * 4 * Copyright (C) 2015-2018 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.time.DayOfWeek; 13 14 import hunt.time.temporal.ChronoField; 15 import hunt.time.temporal.ChronoUnit; 16 import hunt.time.format.DateTimeFormatterBuilder; 17 import hunt.time.format.TextStyle; 18 import hunt.time.temporal.ChronoField; 19 import hunt.time.temporal.Temporal; 20 import hunt.time.temporal.TemporalAccessor; 21 import hunt.time.temporal.TemporalAdjuster; 22 import hunt.time.temporal.TemporalField; 23 import hunt.time.temporal.TemporalQueries; 24 import hunt.time.temporal.TemporalQuery; 25 import hunt.time.temporal.ValueRange; 26 import hunt.time.temporal.WeekFields; 27 import hunt.time.Exceptions; 28 import hunt.time.util.Common; 29 30 import hunt.Enum; 31 import hunt.util.Locale; 32 33 import std.concurrency : initOnce; 34 import std.conv : to; 35 36 /** 37 * A day-of-week, such as 'Tuesday'. 38 * !(p) 39 * {@code DayOfWeek} is an enum representing the 7 days of the week - 40 * Monday, Tuesday, Wednesday, Thursday, Friday, Saturday and Sunday. 41 * !(p) 42 * In addition to the textual enum name, each day-of-week has an {@code int} value. 43 * The {@code int} value follows the ISO-8601 standard, from 1 (Monday) to 7 (Sunday). 44 * It is recommended that applications use the enum rather than the {@code int} value 45 * to ensure code clarity. 46 * !(p) 47 * This enum provides access to the localized textual form of the day-of-week. 48 * Some locales also assign different numeric values to the days, declaring 49 * Sunday to have the value 1, however this class provides no support for this. 50 * See {@link WeekFields} for localized week-numbering. 51 * !(p) 52 * !(b)Do not use {@code ordinal()} to obtain the numeric representation of {@code DayOfWeek}. 53 * Use {@code getValue()} instead.</b> 54 * !(p) 55 * This enum represents a common concept that is found _in many calendar systems. 56 * As such, this enum may be used by any calendar system that has the day-of-week 57 * concept defined exactly equivalent to the ISO calendar system. 58 * 59 * @implSpec 60 * This is an immutable and thread-safe enum. 61 * 62 * @since 1.8 63 */ 64 final class DayOfWeek : AbstractEnum!DayOfWeek, TemporalAccessor, TemporalAdjuster { 65 66 /** 67 * The singleton instance for the day-of-week of Monday. 68 * This has the numeric value of {@code 1}. 69 */ 70 static DayOfWeek MONDAY() { 71 __gshared DayOfWeek d; 72 return initOnce!d(new DayOfWeek("MONDAY", 0)); 73 } 74 /** 75 * The singleton instance for the day-of-week of Tuesday. 76 * This has the numeric value of {@code 2}. 77 */ 78 static DayOfWeek TUESDAY() { 79 __gshared DayOfWeek d; 80 return initOnce!d(new DayOfWeek("TUESDAY", 1)); 81 } 82 /** 83 * The singleton instance for the day-of-week of Wednesday. 84 * This has the numeric value of {@code 3}. 85 */ 86 static DayOfWeek WEDNESDAY() { 87 __gshared DayOfWeek d; 88 return initOnce!d(new DayOfWeek("WEDNESDAY", 2)); 89 } 90 /** 91 * The singleton instance for the day-of-week of Thursday. 92 * This has the numeric value of {@code 4}. 93 */ 94 static DayOfWeek THURSDAY() { 95 __gshared DayOfWeek d; 96 return initOnce!d(new DayOfWeek("THURSDAY", 3)); 97 } 98 /** 99 * The singleton instance for the day-of-week of Friday. 100 * This has the numeric value of {@code 5}. 101 */ 102 static DayOfWeek FRIDAY() { 103 __gshared DayOfWeek d; 104 return initOnce!d(new DayOfWeek("FRIDAY", 4)); 105 } 106 /** 107 * The singleton instance for the day-of-week of Saturday. 108 * This has the numeric value of {@code 6}. 109 */ 110 static DayOfWeek SATURDAY() { 111 __gshared DayOfWeek d; 112 return initOnce!d(new DayOfWeek("SATURDAY", 5)); 113 } 114 /** 115 * The singleton instance for the day-of-week of Sunday. 116 * This has the numeric value of {@code 7}. 117 */ 118 static DayOfWeek SUNDAY() { 119 __gshared DayOfWeek d; 120 return initOnce!d(new DayOfWeek("SUNDAY", 6)); 121 } 122 123 static DayOfWeek valueOf(string name) { 124 return hunt.Enum.valueOf!(DayOfWeek)(name); 125 } 126 127 static DayOfWeek[] values() { 128 __gshared DayOfWeek[] d; 129 return initOnce!d({ 130 DayOfWeek[] arr; 131 arr ~= DayOfWeek.MONDAY; 132 arr ~= DayOfWeek.TUESDAY; 133 134 arr ~= DayOfWeek.WEDNESDAY; 135 arr ~= DayOfWeek.THURSDAY;/* */ 136 arr ~= DayOfWeek.FRIDAY; 137 arr ~= DayOfWeek.SATURDAY; 138 arr ~= DayOfWeek.SUNDAY; 139 return arr; 140 }()); 141 } 142 143 144 private this(string name, int ordinal) { 145 super(name, ordinal); 146 } 147 148 //----------------------------------------------------------------------- 149 /** 150 * Obtains an instance of {@code DayOfWeek} from an {@code int} value. 151 * !(p) 152 * {@code DayOfWeek} is an enum representing the 7 days of the week. 153 * This factory allows the enum to be obtained from the {@code int} value. 154 * The {@code int} value follows the ISO-8601 standard, from 1 (Monday) to 7 (Sunday). 155 * 156 * @param dayOfWeek the day-of-week to represent, from 1 (Monday) to 7 (Sunday) 157 * @return the day-of-week singleton, not null 158 * @throws DateTimeException if the day-of-week is invalid 159 */ 160 static DayOfWeek of(int dayOfWeek) { 161 if (dayOfWeek < 1 || dayOfWeek > 7) { 162 throw new DateTimeException("Invalid value for DayOfWeek: " ~ dayOfWeek.to!string); 163 } 164 return values[dayOfWeek - 1]; 165 } 166 167 //----------------------------------------------------------------------- 168 /** 169 * Obtains an instance of {@code DayOfWeek} from a temporal object. 170 * !(p) 171 * This obtains a day-of-week based on the specified temporal. 172 * A {@code TemporalAccessor} represents an arbitrary set of date and time information, 173 * which this factory converts to an instance of {@code DayOfWeek}. 174 * !(p) 175 * The conversion extracts the {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} field. 176 * !(p) 177 * This method matches the signature of the functional interface {@link TemporalQuery} 178 * allowing it to be used as a query via method reference, {@code DayOfWeek::from}. 179 * 180 * @param temporal the temporal object to convert, not null 181 * @return the day-of-week, not null 182 * @throws DateTimeException if unable to convert to a {@code DayOfWeek} 183 */ 184 static DayOfWeek from(TemporalAccessor temporal) { 185 if (cast(DayOfWeek)(temporal) !is null) { 186 return cast(DayOfWeek) temporal; 187 } 188 try { 189 return of(temporal.get(ChronoField.DAY_OF_WEEK)); 190 } catch (DateTimeException ex) { 191 throw new DateTimeException("Unable to obtain DayOfWeek from TemporalAccessor: " ~ 192 typeid(temporal).stringof ~ " of type " ~ typeid(temporal).stringof, ex); 193 } 194 } 195 196 //----------------------------------------------------------------------- 197 /** 198 * Gets the day-of-week {@code int} value. 199 * !(p) 200 * The values are numbered following the ISO-8601 standard, from 1 (Monday) to 7 (Sunday). 201 * See {@link hunt.time.temporal.WeekFields#dayOfWeek()} for localized week-numbering. 202 * 203 * @return the day-of-week, from 1 (Monday) to 7 (Sunday) 204 */ 205 int getValue() { 206 return _ordinal + 1; 207 } 208 209 //----------------------------------------------------------------------- 210 /** 211 * Gets the textual representation, such as 'Mon' or 'Friday'. 212 * !(p) 213 * This returns the textual name used to identify the day-of-week, 214 * suitable for presentation to the user. 215 * The parameters control the style of the returned text and the locale. 216 * !(p) 217 * If no textual mapping is found then the {@link #getValue() numeric value} is returned. 218 * 219 * @param style the length of the text required, not null 220 * @param locale the locale to use, not null 221 * @return the text value of the day-of-week, not null 222 */ 223 string getDisplayName(TextStyle style, Locale locale) { 224 return new DateTimeFormatterBuilder().appendText(ChronoField.DAY_OF_WEEK, style).toFormatter(locale).format(this); 225 } 226 227 //----------------------------------------------------------------------- 228 /** 229 * Checks if the specified field is supported. 230 * !(p) 231 * This checks if this day-of-week can be queried for the specified field. 232 * If false, then calling the {@link #range(TemporalField) range} and 233 * {@link #get(TemporalField) get} methods will throw an exception. 234 * !(p) 235 * If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then 236 * this method returns true. 237 * All other {@code ChronoField} instances will return false. 238 * !(p) 239 * If the field is not a {@code ChronoField}, then the result of this method 240 * is obtained by invoking {@code TemporalField.isSupportedBy(TemporalAccessor)} 241 * passing {@code this} as the argument. 242 * Whether the field is supported is determined by the field. 243 * 244 * @param field the field to check, null returns false 245 * @return true if the field is supported on this day-of-week, false if not 246 */ 247 override 248 bool isSupported(TemporalField field) { 249 if (cast(ChronoField)(field) !is null) { 250 return field == ChronoField.DAY_OF_WEEK; 251 } 252 return field !is null && field.isSupportedBy(this); 253 } 254 255 /** 256 * Gets the range of valid values for the specified field. 257 * !(p) 258 * The range object expresses the minimum and maximum valid values for a field. 259 * This day-of-week is used to enhance the accuracy of the returned range. 260 * If it is not possible to return the range, because the field is not supported 261 * or for some other reason, an exception is thrown. 262 * !(p) 263 * If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then the 264 * range of the day-of-week, from 1 to 7, will be returned. 265 * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. 266 * !(p) 267 * If the field is not a {@code ChronoField}, then the result of this method 268 * is obtained by invoking {@code TemporalField.rangeRefinedBy(TemporalAccessor)} 269 * passing {@code this} as the argument. 270 * Whether the range can be obtained is determined by the field. 271 * 272 * @param field the field to query the range for, not null 273 * @return the range of valid values for the field, not null 274 * @throws DateTimeException if the range for the field cannot be obtained 275 * @throws UnsupportedTemporalTypeException if the field is not supported 276 */ 277 override 278 ValueRange range(TemporalField field) { 279 if (field == ChronoField.DAY_OF_WEEK) { 280 return field.range(); 281 } 282 return /* TemporalAccessor. super.*/range_super(field); 283 } 284 285 ValueRange range_super(TemporalField field) { 286 if (cast(ChronoField)(field) !is null) { 287 if (isSupported(field)) { 288 return field.range(); 289 } 290 throw new UnsupportedTemporalTypeException("Unsupported field: " ~ typeof(field).stringof); 291 } 292 assert(field, "field"); 293 return field.rangeRefinedBy(this); 294 } 295 296 /** 297 * Gets the value of the specified field from this day-of-week as an {@code int}. 298 * !(p) 299 * This queries this day-of-week for the value of the specified field. 300 * The returned value will always be within the valid range of values for the field. 301 * If it is not possible to return the value, because the field is not supported 302 * or for some other reason, an exception is thrown. 303 * !(p) 304 * If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then the 305 * value of the day-of-week, from 1 to 7, will be returned. 306 * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. 307 * !(p) 308 * If the field is not a {@code ChronoField}, then the result of this method 309 * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} 310 * passing {@code this} as the argument. Whether the value can be obtained, 311 * and what the value represents, is determined by the field. 312 * 313 * @param field the field to get, not null 314 * @return the value for the field, within the valid range of values 315 * @throws DateTimeException if a value for the field cannot be obtained or 316 * the value is outside the range of valid values for the field 317 * @throws UnsupportedTemporalTypeException if the field is not supported or 318 * the range of values exceeds an {@code int} 319 * @throws ArithmeticException if numeric overflow occurs 320 */ 321 override 322 int get(TemporalField field) { 323 if (field == ChronoField.DAY_OF_WEEK) { 324 return getValue(); 325 } 326 return /* TemporalAccessor. super.*/super_get(field); 327 } 328 329 int super_get(TemporalField field) { 330 ValueRange range = range(field); 331 if (range.isIntValue() == false) { 332 throw new UnsupportedTemporalTypeException("Invalid field " ~ typeof(field).stringof ~ " for get() method, use getLong() instead"); 333 } 334 long value = getLong(field); 335 if (range.isValidValue(value) == false) { 336 throw new DateTimeException("Invalid value for " ~ typeof(field).stringof ~ " (valid values " ~ range.toString ~ "): " ~ value.to!string); 337 } 338 return cast(int) value; 339 } 340 341 /** 342 * Gets the value of the specified field from this day-of-week as a {@code long}. 343 * !(p) 344 * This queries this day-of-week for the value of the specified field. 345 * If it is not possible to return the value, because the field is not supported 346 * or for some other reason, an exception is thrown. 347 * !(p) 348 * If the field is {@link ChronoField#DAY_OF_WEEK DAY_OF_WEEK} then the 349 * value of the day-of-week, from 1 to 7, will be returned. 350 * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. 351 * !(p) 352 * If the field is not a {@code ChronoField}, then the result of this method 353 * is obtained by invoking {@code TemporalField.getFrom(TemporalAccessor)} 354 * passing {@code this} as the argument. Whether the value can be obtained, 355 * and what the value represents, is determined by the field. 356 * 357 * @param field the field to get, not null 358 * @return the value for the field 359 * @throws DateTimeException if a value for the field cannot be obtained 360 * @throws UnsupportedTemporalTypeException if the field is not supported 361 * @throws ArithmeticException if numeric overflow occurs 362 */ 363 override 364 long getLong(TemporalField field) { 365 if (field == ChronoField.DAY_OF_WEEK) { 366 return getValue(); 367 } else if (cast(ChronoField)(field) !is null) { 368 throw new UnsupportedTemporalTypeException("Unsupported field: " ~ typeid(field).stringof); 369 } 370 return field.getFrom(this); 371 } 372 373 //----------------------------------------------------------------------- 374 /** 375 * Returns the day-of-week that is the specified number of days after this one. 376 * !(p) 377 * The calculation rolls around the end of the week from Sunday to Monday. 378 * The specified period may be negative. 379 * !(p) 380 * This instance is immutable and unaffected by this method call. 381 * 382 * @param days the days to add, positive or negative 383 * @return the resulting day-of-week, not null 384 */ 385 DayOfWeek plus(long days) { 386 int amount = cast(int) (days % 7); 387 return values[(_ordinal + (amount + 7)) % 7]; 388 } 389 390 /** 391 * Returns the day-of-week that is the specified number of days before this one. 392 * !(p) 393 * The calculation rolls around the start of the year from Monday to Sunday. 394 * The specified period may be negative. 395 * !(p) 396 * This instance is immutable and unaffected by this method call. 397 * 398 * @param days the days to subtract, positive or negative 399 * @return the resulting day-of-week, not null 400 */ 401 DayOfWeek minus(long days) { 402 return plus(-(days % 7)); 403 } 404 405 //----------------------------------------------------------------------- 406 /** 407 * Queries this day-of-week using the specified query. 408 * !(p) 409 * This queries this day-of-week using the specified query strategy object. 410 * The {@code TemporalQuery} object defines the logic to be used to 411 * obtain the result. Read the documentation of the query to understand 412 * what the result of this method will be. 413 * !(p) 414 * The result of this method is obtained by invoking the 415 * {@link TemporalQuery#queryFrom(TemporalAccessor)} method on the 416 * specified query passing {@code this} as the argument. 417 * 418 * @param !(R) the type of the result 419 * @param query the query to invoke, not null 420 * @return the query result, null may be returned (defined by the query) 421 * @throws DateTimeException if unable to query (defined by the query) 422 * @throws ArithmeticException if numeric overflow occurs (defined by the query) 423 */ 424 /*@SuppressWarnings("unchecked")*/ 425 // override 426 R query(R)(TemporalQuery!(R) query) { 427 if (query == TemporalQueries.precision()) { 428 return cast(R) (ChronoUnit.DAYS); 429 } 430 return /* TemporalAccessor. */super_query(query); 431 } 432 R super_query(R)(TemporalQuery!(R) query) { 433 if (query == TemporalQueries.zoneId() 434 || query == TemporalQueries.chronology() 435 || query == TemporalQueries.precision()) { 436 return null; 437 } 438 return query.queryFrom(this); 439 } 440 /** 441 * Adjusts the specified temporal object to have this day-of-week. 442 * !(p) 443 * This returns a temporal object of the same observable type as the input 444 * with the day-of-week changed to be the same as this. 445 * !(p) 446 * The adjustment is equivalent to using {@link Temporal#_with(TemporalField, long)} 447 * passing {@link ChronoField#DAY_OF_WEEK} as the field. 448 * Note that this adjusts forwards or backwards within a Monday to Sunday week. 449 * See {@link hunt.time.temporal.WeekFields#dayOfWeek()} for localized week start days. 450 * See {@code TemporalAdjuster} for other adjusters with more control, 451 * such as {@code next(MONDAY)}. 452 * !(p) 453 * In most cases, it is clearer to reverse the calling pattern by using 454 * {@link Temporal#_with(TemporalAdjuster)}: 455 * !(pre) 456 * // these two lines are equivalent, but the second approach is recommended 457 * temporal = thisDayOfWeek.adjustInto(temporal); 458 * temporal = temporal._with(thisDayOfWeek); 459 * </pre> 460 * !(p) 461 * For example, given a date that is a Wednesday, the following are output: 462 * !(pre) 463 * dateOnWed._with(MONDAY); // two days earlier 464 * dateOnWed._with(TUESDAY); // one day earlier 465 * dateOnWed._with(WEDNESDAY); // same date 466 * dateOnWed._with(THURSDAY); // one day later 467 * dateOnWed._with(FRIDAY); // two days later 468 * dateOnWed._with(SATURDAY); // three days later 469 * dateOnWed._with(SUNDAY); // four days later 470 * </pre> 471 * !(p) 472 * This instance is immutable and unaffected by this method call. 473 * 474 * @param temporal the target object to be adjusted, not null 475 * @return the adjusted object, not null 476 * @throws DateTimeException if unable to make the adjustment 477 * @throws ArithmeticException if numeric overflow occurs 478 */ 479 override 480 Temporal adjustInto(Temporal temporal) { 481 return temporal._with(ChronoField.DAY_OF_WEEK, getValue()); 482 } 483 484 override string toString() { 485 return super.toString(); 486 } 487 }