weekdays 0.1.1

Days of the week bit-mapped in a single byte
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
//! The Weekdays crate provides days of the week bit-mapped in a single byte.
//! 
//! The main type provided is the [`Weekdays`] struct.
//! 



//		Global configuration																							

//	Customisations of the standard linting configuration
#![allow(clippy::items_after_test_module, reason = "Not needed with separated tests")]

//	Lints specifically disabled for unit tests
#![cfg_attr(test, allow(
	non_snake_case,
	clippy::arithmetic_side_effects,
	clippy::cast_lossless,
	clippy::cast_precision_loss,
	clippy::cognitive_complexity,
	clippy::default_numeric_fallback,
	clippy::exhaustive_enums,
	clippy::exhaustive_structs,
	clippy::expect_used,
	clippy::indexing_slicing,
	clippy::let_underscore_must_use,
	clippy::let_underscore_untyped,
	clippy::missing_assert_message,
	clippy::missing_panics_doc,
	clippy::must_use_candidate,
	clippy::panic,
	clippy::print_stdout,
	clippy::too_many_lines,
	clippy::unwrap_in_result,
	clippy::unwrap_used,
	reason = "Not useful in unit tests"
))]



//		Modules																											

#[cfg(test)]
#[path = "tests/lib.rs"]
mod tests;



//		Packages																										

use core::{
	fmt::{Debug, Display, Formatter},
	fmt,
	ops::{Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Sub, SubAssign},
};

#[cfg(feature = "chrono")]
use chrono::Weekday;
#[cfg(feature = "postgres")]
use ::{
	bytes::BytesMut,
	core::error::Error,
	std::io::{Error as IoError, ErrorKind as IoErrorKind},
	tokio_postgres::types::{FromSql, IsNull, ToSql, Type, to_sql_checked},
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};



//		Structs																											

//		Weekdays																
/// A bit-mapped representation of the days of the week.
/// 
/// This struct uses a single byte, where each bit represents a day of the week.
/// The bits are ordered from most significant to least significant, starting
/// from Monday, with the least significant bit representing Sunday.
/// 
/// ```text
/// Monday
/// | Tuesday
/// | | Wednesday
/// | | | Thursday
/// | | | | Friday
/// | | | | | Saturday
/// | | | | | | Sunday
/// 1 1 1 1 1 1 1
/// ```
/// 
/// # Examples
/// 
/// ```
/// use weekdays::Weekdays;
/// 
/// let weekends = Weekdays::new(0b000_0011);
/// assert_eq!(weekends, Weekdays::WEEKENDS);
/// ```
/// 
#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Weekdays(u8);

//󰭅		Weekdays																
impl Weekdays {
	//		Public constants													
	/// Monday.
	pub const MONDAY:    Self = Self(0b100_0000);
	
	/// Tuesday.
	pub const TUESDAY:   Self = Self(0b010_0000);
	
	/// Wednesday.
	pub const WEDNESDAY: Self = Self(0b001_0000);
	
	/// Thursday.
	pub const THURSDAY:  Self = Self(0b000_1000);
	
	/// Friday.
	pub const FRIDAY:    Self = Self(0b000_0100);
	
	/// Saturday.
	pub const SATURDAY:  Self = Self(0b000_0010);
	
	/// Sunday.
	pub const SUNDAY:    Self = Self(0b000_0001);
	
	/// Weekdays (Monday to Friday).
	pub const WEEKDAYS:  Self = Self(0b111_1100);
	
	/// Weekends (Saturday and Sunday).
	pub const WEEKENDS:  Self = Self(0b000_0011);
	
	/// All days of the week.
	pub const ALL_DAYS:  Self = Self(0b111_1111);
	
	/// No days of the week (empty).
	pub const NONE:      Self = Self(0b000_0000);
	
	//		Private constants													
	/// The mask for all days of the week.
	const ALL_DAYS_MASK: u8 = 0b111_1111;
	
	//		Constructors														
	
	//		new																	
	/// Creates a new [`Weekdays`] struct from the given number of days.
	/// 
	/// The number of days is expected to be a 7-bit number, where each bit
	/// represents a day of the week. The bits are ordered from most significant
	/// to least significant as Monday to Sunday, with the least significant bit
	/// representing Sunday.
	/// 
	/// **IMPORTANT**: Note that if the incoming value is larger than 7 bits,
	/// the extra bit will be silently ignored.
	/// 
	/// # Parameters
	/// 
	/// * `days` - The days to represent, as a bit-mapped value.
	/// 
	/// # Examples
	/// 
	/// ```
	/// use weekdays::Weekdays;
	/// 
	/// let weekdays = Weekdays::new(0b111_1100);
	/// assert_eq!(weekdays, Weekdays::WEEKDAYS);
	/// ```
	/// 
	#[must_use]
	pub const fn new(days: u8) -> Self {
		//	Ensure only 7 bits are used
		Self(days & Self::ALL_DAYS_MASK)
	}
	
	//		Public methods														
	
	//		contains															
	/// Checks if the given day (or days) is contained within the set of days.
	/// 
	/// This method can be used to check for the presence of a single day, or
	/// multiple days such as a weekend or other combination.
	/// 
	/// It will return `true` if all the bits of the given day(s) are set in the
	/// current set of days.
	/// 
	/// # Parameters
	/// 
	/// * `day` - The day to check for.
	/// 
	/// # Examples
	/// 
	/// ```
	/// use weekdays::Weekdays;
	/// 
	/// let weekdays = Weekdays::WEEKDAYS;
	/// assert!( weekdays.contains(Weekdays::MONDAY));
	/// assert!( weekdays.contains(Weekdays::FRIDAY));
	/// assert!(!weekdays.contains(Weekdays::SATURDAY));
	/// ```
	/// 
	#[must_use]
	pub const fn contains(&self, day: Self) -> bool {
		self.0 & day.0 == day.0
	}
	
	//		days																
	/// Returns the number of days set.
	/// 
	/// This method will count the number of bits set in the bit-mapped value,
	/// and return the count as the number of days.
	/// 
	/// # Examples
	/// 
	/// ```
	/// use weekdays::Weekdays;
	/// 
	/// let weekdays = Weekdays::WEEKDAYS;
	/// assert_eq!(weekdays.days(), 5);
	/// ```
	/// 
	#[expect(clippy::cast_possible_truncation, reason = "Value is guaranteed to be <= 7")]
	#[must_use]
	pub const fn days(&self) -> u8 {
		self.0.count_ones() as u8
	}
	
	//		is_empty															
	/// Checks if the set of days is empty.
	/// 
	/// This method will return `true` if no days are set in the bit-mapped
	/// value.
	/// 
	/// # Examples
	/// 
	/// ```
	/// use weekdays::Weekdays;
	/// 
	/// let weekdays = Weekdays::NONE;
	/// assert!(weekdays.is_empty());
	/// ```
	/// 
	#[must_use]
	pub const fn is_empty(&self) -> bool {
		self.0 == 0
	}
	
	//		is_weekday															
	/// Checks if the set of days represents a weekday.
	/// 
	/// This method will return `true` if the set of days contains only weekdays
	/// (Monday to Friday).
	/// 
	/// # Examples
	/// 
	/// ```
	/// use weekdays::Weekdays;
	/// 
	/// let weekdays = Weekdays::THURSDAY;
	/// assert!(weekdays.is_weekday());
	/// ```
	/// 
	#[must_use]
	pub const fn is_weekday(&self) -> bool {
		self.0 & Self::WEEKENDS.0 == 0
	}
	
	//		is_weekend															
	/// Checks if the set of days represents a weekend.
	/// 
	/// This method will return `true` if the set of days contains only weekends
	/// (Saturday and Sunday).
	/// 
	/// # Examples
	/// 
	/// ```
	/// use weekdays::Weekdays;
	/// 
	/// let weekdays = Weekdays::SATURDAY;
	/// assert!(weekdays.is_weekend());
	/// ```
	/// 
	#[must_use]
	pub const fn is_weekend(&self) -> bool {
		self.0 & Self::WEEKDAYS.0 == 0
	}
	
	//		iter																
	/// Returns an iterator over the days of the week.
	/// 
	/// This method will return an iterator that will yield each day of the week
	/// that is set in the bit-mapped value.
	/// 
	/// # Examples
	/// 
	/// ```
	/// use weekdays::Weekdays;
	/// 
	/// let weekdays = Weekdays::WEEKDAYS;
	/// let mut iter = weekdays.iter();
	/// 
	/// assert_eq!(iter.next(), Some(Weekdays::MONDAY));
	/// assert_eq!(iter.next(), Some(Weekdays::TUESDAY));
	/// assert_eq!(iter.next(), Some(Weekdays::WEDNESDAY));
	/// assert_eq!(iter.next(), Some(Weekdays::THURSDAY));
	/// assert_eq!(iter.next(), Some(Weekdays::FRIDAY));
	/// assert_eq!(iter.next(), None);
	/// ```
	/// 
	/// # See also
	/// 
	/// * [`Iterator`]
	/// 
	#[must_use]
	pub const fn iter(&self) -> WeekdaysIter {
		WeekdaysIter {
			remaining: self.0,
			position:  0,
		}
	}
	
	//		to_chrono_vec														
	/// Converts the set of days to a [`Vec`] of Chrono [`Weekday`]s.
	/// 
	/// This method will return a [`Vec`] containing each day of the week that
	/// is set in the bit-mapped value, converted to a Chrono [`Weekday`].
	/// 
	/// # Examples
	/// 
	/// ```
	/// use chrono::Weekday;
	/// use weekdays::Weekdays;
	///
	/// let weekdays = Weekdays::WEEKDAYS;
	/// let days     = weekdays.to_chrono_vec();
	///
	/// assert_eq!(days, vec![
	///     Weekday::Mon,
	///     Weekday::Tue,
	///     Weekday::Wed,
	///     Weekday::Thu,
	///     Weekday::Fri,
	/// ]);
	///
	/// assert_eq!(Weekdays::NONE.to_chrono_vec(), vec![]);
	/// ```
	/// 
	#[cfg(feature = "chrono")]
	#[must_use]
	pub fn to_chrono_vec(&self) -> Vec<Weekday> {
		[
			Weekday::Mon,
			Weekday::Tue,
			Weekday::Wed,
			Weekday::Thu,
			Weekday::Fri,
			Weekday::Sat,
			Weekday::Sun,
		]
			.into_iter()
			.filter(|&day| self.contains(Self::from(day)))
			.collect()
	}
	
	//		to_vec																
	/// Converts the set of days to a [`Vec`] of days.
	/// 
	/// This method will return a [`Vec`] containing each day of the week that
	/// is set in the bit-mapped value.
	/// 
	/// # Examples
	/// 
	/// ```
	/// use weekdays::Weekdays;
	/// 
	/// let weekdays = Weekdays::WEEKDAYS;
	/// let days     = weekdays.to_vec();
	/// 
	/// assert_eq!(days, vec![
	///     Weekdays::MONDAY,
	///     Weekdays::TUESDAY,
	///     Weekdays::WEDNESDAY,
	///     Weekdays::THURSDAY,
	///     Weekdays::FRIDAY,
	/// ]);
	/// 
	/// assert_eq!(Weekdays::NONE.to_vec(), vec![]);
	/// ```
	/// 
	#[must_use]
	pub fn to_vec(&self) -> Vec<Self> {
		self.iter().collect()
	}
}

//󰭅		Add																		
impl Add for Weekdays {
	type Output = Self;
	
	//		add																	
	#[expect(clippy::suspicious_arithmetic_impl, reason = "Bitwise OR is the correct operation")]
	fn add(self, rhs: Self) -> Self::Output {
		Self(self.0 | rhs.0)
	}
}

//󰭅		AddAssign																
impl AddAssign for Weekdays {
	//		add_assign															
	#[expect(clippy::suspicious_op_assign_impl, reason = "Bitwise OR is the correct operation")]
	fn add_assign(&mut self, rhs: Self) {
		self.0 |= rhs.0;
	}
}

//󰭅		BitAnd																	
impl BitAnd for Weekdays {
	type Output = Self;
	
	//		bitand																
	fn bitand(self, rhs: Self) -> Self::Output {
		Self(self.0 & rhs.0)
	}
}

//󰭅		BitAndAssign															
impl BitAndAssign for Weekdays {
	//		bitand_assign														
	fn bitand_assign(&mut self, rhs: Self) {
		self.0 &= rhs.0;
	}
}

//󰭅		BitOr																	
impl BitOr for Weekdays {
	type Output = Self;
	
	//		bitor																
	fn bitor(self, rhs: Self) -> Self::Output {
		Self(self.0 | rhs.0)
	}
}

//󰭅		BitOrAssign																
impl BitOrAssign for Weekdays {
	//		bitor_assign														
	fn bitor_assign(&mut self, rhs: Self) {
		self.0 |= rhs.0;
	}
}

//󰭅		BitXor																	
impl BitXor for Weekdays {
	type Output = Self;
	
	//		bitxor																
	fn bitxor(self, rhs: Self) -> Self::Output {
		Self(self.0 ^ rhs.0)
	}
}

//󰭅		BitXorAssign															
impl BitXorAssign for Weekdays {
	//		bitxor_assign														
	fn bitxor_assign(&mut self, rhs: Self) {
		self.0 ^= rhs.0;
	}
}

//󰭅		Debug																	
impl Debug for Weekdays {
	//		fmt																	
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		write!(f, "Weekdays({:03b}_{:04b})", self.0 >> 4, self.0 & 0b1111)
	}
}

//󰭅		Deserialize																
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Weekdays {
	//		deserialize															
	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
		where D: Deserializer<'de>,
	{
		u8::deserialize(deserializer).map(Self::new)
	}
}

//󰭅		Display																	
impl Display for Weekdays {
	//		fmt																	
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		write!(f, "{:05b}_{:02b}", self.0 >> 2, self.0 & 0b11)
	}
}

//󰭅		From: Weekday -> Weekdays												
#[cfg(feature = "chrono")]
impl From<Weekday> for Weekdays {
	//		from																
	fn from(day: Weekday) -> Self {
		match day {
			Weekday::Mon => Self::MONDAY,
			Weekday::Tue => Self::TUESDAY,
			Weekday::Wed => Self::WEDNESDAY,
			Weekday::Thu => Self::THURSDAY,
			Weekday::Fri => Self::FRIDAY,
			Weekday::Sat => Self::SATURDAY,
			Weekday::Sun => Self::SUNDAY,
		}
	}
}

//󰭅		FromSql																	
#[cfg(feature = "postgres")]
impl FromSql<'_> for Weekdays {
	//		from_sql															
	fn from_sql(ty: &Type, raw: &[u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
		match ty {
			&Type::BIT => Ok(
				//	PostgreSQL gives us a byte that represents the 7 bits
				match raw.first() {
					Some(&byte) => Self(byte & Self::ALL_DAYS_MASK),
					None        => Self(0)
				}
			),
			unknown    => Err(Box::new(IoError::new(
				IoErrorKind::InvalidData,
				format!("Invalid type for Weekdays: {unknown}"),
			))),
		}
	}
	
	//		accepts																
	fn accepts(ty: &Type) -> bool {
		ty.name() == "bit"
	}
}

//󰭅		IntoIterator															
impl IntoIterator for Weekdays {
	type Item     = Self;
	type IntoIter = WeekdaysIter;
	
	//		into_iter															
	fn into_iter(self) -> Self::IntoIter {
		WeekdaysIter {
			remaining: self.0,
			position:  0,
		}
	}
}

//󰭅		IntoIterator															
impl IntoIterator for &Weekdays {
	type Item     = Weekdays;
	type IntoIter = WeekdaysIter;
	
	//		into_iter															
	fn into_iter(self) -> Self::IntoIter {
		WeekdaysIter {
			remaining: self.0,
			position:  0,
		}
	}
}

//󰭅		Not																		
impl Not for Weekdays {
	type Output = Self;
	
	//		not																	
	fn not(self) -> Self::Output {
		Self(!self.0 & Self::ALL_DAYS_MASK)
	}
}

//󰭅		Serialize																
#[cfg(feature = "serde")]
impl Serialize for Weekdays {
	//		serialize															
	fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
		serializer.serialize_u8(self.0)
	}
}

//󰭅		Sub																		
impl Sub for Weekdays {
	type Output = Self;
	
	//		sub																	
	fn sub(self, rhs: Self) -> Self::Output {
		Self(self.0 & !rhs.0)
	}
}

//󰭅		SubAssign																
impl SubAssign for Weekdays {
	//		sub_assign															
	fn sub_assign(&mut self, rhs: Self) {
		self.0 &= !rhs.0;
	}
}

//󰭅		ToSql																	
#[cfg(feature = "postgres")]
impl ToSql for Weekdays {
	//		to_sql																
	fn to_sql(&self, ty: &Type, out: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
		match ty {
			&Type::BIT => {
				//	PostgreSQL expects 7 bits in the same format
				out.extend_from_slice(&[self.0]);
				Ok(IsNull::No)
			}
			unknown    => Err(Box::new(IoError::new(
				IoErrorKind::InvalidData,
				format!("Invalid type for Weekdays: {unknown}"),
			))),
		}
	}
	
	//		accepts																
	fn accepts(ty: &Type) -> bool {
		ty.name() == "bit"
	}
	
	to_sql_checked!();
}

//		WeekdaysIter															
/// An iterator over the days of the week.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct WeekdaysIter {
	/// The remaining days to iterate over.
	remaining: u8,
	
	/// The current position in the iteration.
	position:  u8,
}

//󰭅		Iterator																
impl Iterator for WeekdaysIter {
	type Item = Weekdays;
	
	//		next																
	fn next(&mut self) -> Option<Self::Item> {
		while self.position < 7 {
			let current      = 0b100_0000 >> self.position;
			#[expect(clippy::arithmetic_side_effects, reason = "This is checked by the while loop")]
			{ self.position += 1; }
			
			if self.remaining & current != 0 {
				return Some(Weekdays(current));
			}
		}
		None
	}
}