1use std::fmt;
20
21use crate::{
22 defaults::IntegerType,
23 error::{Error, Result},
24};
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub enum Certainty {
31 Approximate,
33 Uncertain,
35 Priority,
39}
40
41impl Certainty {
42 #[must_use]
44 pub fn name(self) -> &'static str {
45 match self {
46 Self::Approximate => "approximate",
47 Self::Uncertain => "uncertain",
48 Self::Priority => "priority",
49 }
50 }
51
52 #[must_use]
55 pub fn symbol(self) -> Option<char> {
56 match self {
57 Self::Approximate => Some('~'),
58 Self::Uncertain => Some('?'),
59 Self::Priority => None,
60 }
61 }
62
63 #[must_use]
66 pub fn named(name: &str) -> Option<Self> {
67 match name.to_lowercase().as_str() {
68 "approximate" | "~" | "x" => Some(Self::Approximate),
69 "uncertain" | "?" | "z" => Some(Self::Uncertain),
70 "priority" | "<" | "{" | ">" | "}" => Some(Self::Priority),
71 _ => None,
72 }
73 }
74
75 fn of_symbol(symbol: char) -> Option<Self> {
78 match symbol {
79 '~' | 'x' => Some(Self::Approximate),
80 '?' | 'z' => Some(Self::Uncertain),
81 '<' | '{' | '>' | '}' => Some(Self::Priority),
82 _ => None,
83 }
84 }
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
89#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
90pub enum DatePart {
91 Year,
93 Month,
95 Day,
97 Hour,
99 Minute,
101 Second,
103}
104
105impl DatePart {
106 pub const ALL: [DatePart; 6] = [
108 Self::Year,
109 Self::Month,
110 Self::Day,
111 Self::Hour,
112 Self::Minute,
113 Self::Second,
114 ];
115
116 fn at(self) -> usize {
118 match self {
119 Self::Year => 0,
120 Self::Month => 1,
121 Self::Day => 2,
122 Self::Hour => 3,
123 Self::Minute => 4,
124 Self::Second => 5,
125 }
126 }
127
128 #[must_use]
130 pub fn name(self) -> &'static str {
131 match self {
132 Self::Year => "year",
133 Self::Month => "month",
134 Self::Day => "day",
135 Self::Hour => "hour",
136 Self::Minute => "minute",
137 Self::Second => "second",
138 }
139 }
140}
141
142#[derive(Clone, Debug, Default, Eq)]
155#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
156#[must_use]
157pub struct Date {
158 values: [Option<IntegerType>; 6],
159 certainties: [Option<Certainty>; 6],
160}
161
162impl PartialEq for Date {
165 fn eq(&self, other: &Self) -> bool {
166 self.to_string() == other.to_string()
167 }
168}
169
170impl std::hash::Hash for Date {
171 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
172 self.to_string().hash(state);
173 }
174}
175
176impl Date {
177 pub fn new() -> Self {
179 Self::default()
180 }
181
182 pub fn value(&self, part: DatePart) -> Option<IntegerType> {
184 self.values[part.at()]
185 }
186
187 pub fn certainty(&self, part: DatePart) -> Option<Certainty> {
189 self.certainties[part.at()]
190 }
191
192 pub fn year(&self) -> Option<IntegerType> {
194 self.value(DatePart::Year)
195 }
196
197 pub fn month(&self) -> Option<IntegerType> {
199 self.value(DatePart::Month)
200 }
201
202 pub fn day(&self) -> Option<IntegerType> {
204 self.value(DatePart::Day)
205 }
206
207 pub fn set(
217 &mut self,
218 part: DatePart,
219 value: Option<IntegerType>,
220 certainty: Option<Certainty>,
221 ) -> Result<()> {
222 self.set_unchecked(part, value, certainty);
223 self.check()
224 }
225
226 pub fn set_unchecked(
231 &mut self,
232 part: DatePart,
233 value: Option<IntegerType>,
234 certainty: Option<Certainty>,
235 ) {
236 self.values[part.at()] = value;
237 if certainty.is_some() {
238 self.certainties[part.at()] = certainty;
239 }
240 }
241
242 fn written(&self) -> String {
251 let has_time = DatePart::ALL[3..]
252 .iter()
253 .any(|part| self.value(*part).is_some());
254 let parts = if has_time { 6 } else { 3 };
255 let mut written: Vec<String> = Vec::with_capacity(parts);
256 for part in &DatePart::ALL[..parts] {
257 match self.value(*part) {
258 None => written.push("--".to_string()),
259 Some(value) => {
260 let mut text = if *part == DatePart::Year {
261 format!("{value:04}")
262 } else {
263 format!("{value:02}")
264 };
265 if let Some(symbol) = self.certainty(*part).and_then(Certainty::symbol) {
266 text.push(symbol);
267 }
268 written.push(text);
269 }
270 }
271 }
272 let mut out = written[..written.len().min(4)].join("/");
275 if written.len() > 4 {
276 out.push(':');
277 out.push_str(&written[4..].join(":"));
278 }
279 out
280 }
281
282 pub fn has_time(&self) -> bool {
284 DatePart::ALL[3..]
285 .iter()
286 .any(|part| self.value(*part).is_some())
287 }
288
289 pub fn has_certainty(&self) -> bool {
291 self.certainties.iter().any(Option::is_some)
292 }
293
294 pub fn from_written(written: &str) -> Result<Self> {
303 let date = Self::from_written_unchecked(written)?;
304 date.check()?;
305 Ok(date)
306 }
307
308 pub fn from_written_unchecked(written: &str) -> Result<Self> {
317 let mut date = Self::new();
318 let written = written.replace(':', "/").replace(' ', "");
319 for (part, chunk) in DatePart::ALL.iter().zip(written.split('/')) {
320 if chunk == "--" {
321 continue;
322 }
323 let (value, certainty) = strip_certainty(chunk)?;
324 date.values[part.at()] = Some(value);
325 if certainty.is_some() {
326 date.certainties[part.at()] = certainty;
327 }
328 }
329 Ok(date)
330 }
331
332 pub fn from_parts(
338 year: Option<IntegerType>,
339 month: Option<IntegerType>,
340 day: Option<IntegerType>,
341 ) -> Result<Self> {
342 let mut date = Self::new();
343 date.values[0] = year;
344 date.values[1] = month;
345 date.values[2] = day;
346 date.check()?;
347 Ok(date)
348 }
349
350 pub fn take_from(&mut self, other: &Self) -> Result<()> {
357 for part in DatePart::ALL {
358 if let Some(value) = other.value(part) {
359 self.values[part.at()] = Some(value);
360 if let Some(certainty) = other.certainty(part) {
361 self.certainties[part.at()] = Some(certainty);
362 }
363 }
364 }
365 self.check()
366 }
367
368 pub fn check(&self) -> Result<()> {
376 let (year, month, day) = (self.year(), self.month(), self.day());
377 if let Some(month) = month
378 && !(1..=12).contains(&month)
379 {
380 return Err(Error::Value(format!(
381 "Month must be between 1 and 12, not {month}."
382 )));
383 }
384 if let Some(day) = day {
385 let impossible = match month {
386 Some(4 | 6 | 9 | 11) => day == 31,
387 Some(2) => day > 29 || (day == 29 && year.is_some_and(|year| year % 4 != 0)),
388 _ => false,
389 };
390 if !(1..=31).contains(&day) || impossible {
391 let month = month.map_or_else(|| "None".to_string(), |month| month.to_string());
392 return Err(Error::Value(format!(
393 "Day {day} is not possible with month {month}."
394 )));
395 }
396 }
397 if self.value(DatePart::Hour).is_some_and(|hour| hour > 23)
398 || self.value(DatePart::Hour).is_some_and(|hour| hour < 0)
399 {
400 return Err(Error::Value("Hour must be between 0 and 23".to_string()));
401 }
402 if self
403 .value(DatePart::Minute)
404 .is_some_and(|minute| !(0..=59).contains(&minute))
405 {
406 return Err(Error::Value("Minute must be between 0 and 59".to_string()));
407 }
408 if self
409 .value(DatePart::Second)
410 .is_some_and(|second| !(0..=59).contains(&second))
411 {
412 return Err(Error::Value("Second must be between 0 and 59".to_string()));
413 }
414 Ok(())
415 }
416}
417
418impl fmt::Display for Date {
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420 f.write_str(&self.written())
421 }
422}
423
424pub fn strip_certainty(written: &str) -> Result<(IntegerType, Option<Certainty>)> {
433 let mut certainty = None;
434 let mut cleaned = written.to_string();
435 for character in written.chars() {
436 if let Some(found) = Certainty::of_symbol(character) {
437 certainty = Some(found);
438 cleaned = written.replace(character, "");
439 break;
440 }
441 }
442 let read: f64 = cleaned
443 .parse()
444 .map_err(|_| Error::Value(format!("{written} is not a date")))?;
445 Ok((read as IntegerType, certainty))
446}
447
448#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
451#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
452pub enum Relevance {
453 Certain,
455 Approximate,
457 Uncertain,
459 Prior,
462 After,
464 OnOrBefore,
466 OnOrAfter,
468 Between,
470 Or,
473 And,
475}
476
477impl Relevance {
478 #[must_use]
480 pub fn name(self) -> &'static str {
481 match self {
482 Self::Certain => "certain",
483 Self::Approximate => "approximate",
484 Self::Uncertain => "uncertain",
485 Self::Prior => "prior",
486 Self::After => "after",
487 Self::OnOrBefore => "onorbefore",
488 Self::OnOrAfter => "onorafter",
489 Self::Between => "between",
490 Self::Or => "or",
491 Self::And => "and",
492 }
493 }
494
495 #[must_use]
498 pub fn named(name: &str) -> Option<Self> {
499 match name.to_lowercase().as_str() {
500 "certain" => Some(Self::Certain),
501 "approximate" => Some(Self::Approximate),
502 "uncertain" => Some(Self::Uncertain),
503 "prior" | "before" => Some(Self::Prior),
504 "after" => Some(Self::After),
505 "onorbefore" => Some(Self::OnOrBefore),
506 "onorafter" => Some(Self::OnOrAfter),
507 "between" => Some(Self::Between),
508 "or" => Some(Self::Or),
509 "and" => Some(Self::And),
510 _ => None,
511 }
512 }
513
514 #[must_use]
517 pub fn kind(self) -> DateKind {
518 match self {
519 Self::Certain | Self::Approximate | Self::Uncertain => DateKind::Single,
520 Self::Prior | Self::After | Self::OnOrBefore | Self::OnOrAfter => DateKind::Relative,
521 Self::Between => DateKind::Between,
522 Self::Or | Self::And => DateKind::Selection,
523 }
524 }
525}
526
527#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
529#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
530pub enum DateKind {
531 Single,
533 Relative,
535 Between,
537 Selection,
539}
540
541impl DateKind {
542 #[must_use]
544 pub fn name(self) -> &'static str {
545 match self {
546 Self::Single => "DateSingle",
547 Self::Relative => "DateRelative",
548 Self::Between => "DateBetween",
549 Self::Selection => "DateSelection",
550 }
551 }
552
553 #[must_use]
555 pub fn default_relevance(self) -> Relevance {
556 match self {
557 Self::Single => Relevance::Certain,
558 Self::Relative => Relevance::After,
559 Self::Between => Relevance::Between,
560 Self::Selection => Relevance::Or,
561 }
562 }
563}
564
565#[derive(Clone, Debug, PartialEq, Eq, Hash)]
581#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
582#[must_use]
583pub struct DatePrimitive {
584 dates: Vec<Date>,
585 relevance: Relevance,
586}
587
588impl DatePrimitive {
589 pub fn dates(&self) -> &[Date] {
591 &self.dates
592 }
593
594 pub fn relevance(&self) -> Relevance {
596 self.relevance
597 }
598
599 pub fn kind(&self) -> DateKind {
601 self.relevance.kind()
602 }
603
604 pub fn set_relevance(&mut self, relevance: Relevance) -> Result<()> {
612 if relevance.kind() != self.kind() {
613 return Err(Error::Metadata(format!(
614 "Relevance value is not supported by this object: '{}'",
615 relevance.name()
616 )));
617 }
618 self.relevance = relevance;
619 Ok(())
620 }
621
622 pub fn single(written: &str, relevance: Relevance) -> Result<Self> {
629 Self::of(
630 vec![Date::from_written(written)?],
631 relevance,
632 DateKind::Single,
633 )
634 }
635
636 pub fn relative(written: &str, relevance: Relevance) -> Result<Self> {
644 Self::of(
645 vec![Date::from_written(written)?],
646 relevance,
647 DateKind::Relative,
648 )
649 }
650
651 pub fn between<I, S>(written: I) -> Result<Self>
657 where
658 I: IntoIterator<Item = S>,
659 S: AsRef<str>,
660 {
661 Self::of(read_dates(written)?, Relevance::Between, DateKind::Between)
662 }
663
664 pub fn selection<I, S>(written: I, relevance: Relevance) -> Result<Self>
671 where
672 I: IntoIterator<Item = S>,
673 S: AsRef<str>,
674 {
675 Self::of(read_dates(written)?, relevance, DateKind::Selection)
676 }
677
678 pub fn of_dates(dates: Vec<Date>, relevance: Relevance, kind: DateKind) -> Result<Self> {
684 Self::of(dates, relevance, kind)
685 }
686
687 fn of(dates: Vec<Date>, relevance: Relevance, kind: DateKind) -> Result<Self> {
688 if relevance.kind() != kind {
689 return Err(Error::Metadata(format!(
690 "Relevance value is not supported by this object: '{}'",
691 relevance.name()
692 )));
693 }
694 Ok(Self { dates, relevance })
695 }
696}
697
698fn read_dates<I, S>(written: I) -> Result<Vec<Date>>
700where
701 I: IntoIterator<Item = S>,
702 S: AsRef<str>,
703{
704 written
705 .into_iter()
706 .map(|one| Date::from_written(one.as_ref()))
707 .collect()
708}
709
710impl fmt::Display for DatePrimitive {
711 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
712 let written: Vec<String> = self.dates.iter().map(Date::to_string).collect();
713 match self.relevance {
714 Relevance::Certain | Relevance::Approximate | Relevance::Uncertain => {
715 f.write_str(written.first().map_or("", String::as_str))
716 }
717 Relevance::Prior => write!(f, "prior to {}", written.join(" ")),
718 Relevance::After => write!(f, "after {}", written.join(" ")),
719 Relevance::OnOrBefore => write!(f, "{} or earlier", written.join(" ")),
720 Relevance::OnOrAfter => write!(f, "{} or later", written.join(" ")),
721 Relevance::Between => f.write_str(&written.join(" to ")),
722 Relevance::Or => f.write_str(&written.join(" or ")),
723 Relevance::And => f.write_str(&written.join(" and ")),
724 }
725 }
726}
727
728#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
740#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
741#[must_use]
742pub struct Text {
743 data: String,
744 language: Option<String>,
745 is_translated: Option<bool>,
746 encoding_scheme: Option<String>,
747 role: Option<String>,
749}
750
751impl Text {
752 pub fn new(data: impl Into<String>) -> Self {
754 Self {
755 data: data.into(),
756 ..Self::default()
757 }
758 }
759
760 pub fn data(&self) -> &str {
762 &self.data
763 }
764
765 pub fn language(&self) -> Option<&str> {
767 self.language.as_deref()
768 }
769
770 pub fn is_translated(&self) -> Option<bool> {
773 self.is_translated
774 }
775
776 pub fn encoding_scheme(&self) -> Option<&str> {
778 self.encoding_scheme.as_deref()
779 }
780
781 pub fn role(&self) -> Option<&str> {
783 self.role.as_deref()
784 }
785
786 pub fn in_language(mut self, language: impl Into<String>) -> Self {
788 self.language = Some(language.into());
789 self
790 }
791
792 pub fn translated(mut self, is_translated: bool) -> Self {
794 self.is_translated = Some(is_translated);
795 self
796 }
797
798 pub fn encoded_by(mut self, scheme: impl Into<String>) -> Self {
800 self.encoding_scheme = Some(scheme.into());
801 self
802 }
803
804 pub fn in_role(mut self, role: impl Into<String>) -> Self {
806 self.role = Some(role.into());
807 self
808 }
809
810 pub fn set_language(&mut self, language: Option<String>) {
812 self.language = language;
813 }
814
815 pub fn set_translated(&mut self, is_translated: Option<bool>) {
817 self.is_translated = is_translated;
818 }
819
820 pub fn set_encoding_scheme(&mut self, scheme: Option<String>) {
822 self.encoding_scheme = scheme;
823 }
824
825 pub fn set_role(&mut self, role: Option<String>) {
827 self.role = role;
828 }
829
830 pub fn normalized_article(&self) -> Result<String> {
838 crate::text::prepend_article(&self.data, self.language.as_deref())
839 }
840}
841
842impl fmt::Display for Text {
843 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
844 f.write_str(&self.data)
845 }
846}
847
848#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
860#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
861#[must_use]
862pub struct Contributor {
863 role: Option<String>,
864 names: Vec<Text>,
865 birth: Option<DatePrimitive>,
866 death: Option<DatePrimitive>,
867}
868
869pub const ROLE_ABBREVIATIONS: [(&str, &str); 10] = [
872 ("com", "composer"),
873 ("coa", "attributedComposer"),
874 ("cos", "suspectedComposer"),
875 ("col", "composerAlias"),
876 ("coc", "composerCorporate"),
877 ("lyr", "lyricist"),
878 ("lib", "librettist"),
879 ("lar", "arranger"),
880 ("lor", "orchestrator"),
881 ("trn", "translator"),
882];
883
884impl Contributor {
885 pub fn role_of_abbreviation(abbreviation: &str) -> Result<&'static str> {
892 let abbreviation = abbreviation.to_lowercase();
893 ROLE_ABBREVIATIONS
894 .iter()
895 .find(|(written, _)| *written == abbreviation)
896 .map(|(_, role)| *role)
897 .ok_or_else(|| Error::Metadata(format!("no such role: '{abbreviation}'")))
898 }
899
900 pub fn abbreviation_of_role(role: &str) -> Result<&'static str> {
907 ROLE_ABBREVIATIONS
908 .iter()
909 .find(|(_, known)| known.eq_ignore_ascii_case(role))
910 .map(|(written, _)| *written)
911 .ok_or_else(|| Error::Metadata(format!("No such role: {role}")))
912 }
913
914 pub fn new<I, T>(role: impl Into<String>, names: I) -> Self
917 where
918 I: IntoIterator<Item = T>,
919 T: Into<Text>,
920 {
921 let mut made = Self {
922 role: None,
923 names: names.into_iter().map(Into::into).collect(),
924 birth: None,
925 death: None,
926 };
927 made.set_role(Some(role.into()));
928 made
929 }
930
931 pub fn unnamed() -> Self {
934 Self::default()
935 }
936
937 pub fn role(&self) -> Option<&str> {
939 self.role.as_deref()
940 }
941
942 pub fn names(&self) -> &[Text] {
945 &self.names
946 }
947
948 pub fn name(&self) -> Option<&Text> {
950 self.names.first()
951 }
952
953 pub fn birth(&self) -> Option<&DatePrimitive> {
955 self.birth.as_ref()
956 }
957
958 pub fn death(&self) -> Option<&DatePrimitive> {
960 self.death.as_ref()
961 }
962
963 pub fn set_role(&mut self, role: Option<String>) {
967 self.role = role
968 .map(|role| Self::role_of_abbreviation(&role).map_or(role, |found| found.to_string()));
969 }
970
971 pub fn set_name(&mut self, name: Text) {
974 self.names = vec![name];
975 }
976
977 pub fn set_names(&mut self, names: Vec<Text>) {
979 self.names = names;
980 }
981
982 pub fn set_birth(&mut self, birth: Option<DatePrimitive>) {
984 self.birth = birth;
985 }
986
987 pub fn set_death(&mut self, death: Option<DatePrimitive>) {
989 self.death = death;
990 }
991}
992
993impl fmt::Display for Contributor {
996 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
997 let names: Vec<String> = self.names.iter().map(Text::to_string).collect();
998 write!(
999 f,
1000 "{}:{}",
1001 self.role.as_deref().unwrap_or(""),
1002 names.join(",")
1003 )
1004 }
1005}
1006
1007impl From<&str> for Text {
1008 fn from(data: &str) -> Self {
1009 Self::new(data)
1010 }
1011}
1012
1013impl From<String> for Text {
1014 fn from(data: String) -> Self {
1015 Self::new(data)
1016 }
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021 use super::*;
1022
1023 #[test]
1025 fn a_date_is_written_as_music21_writes_one() {
1026 let mut date = Date::from_written("1030?/12~/?4").expect("a date");
1027 assert_eq!(date.to_string(), "1030?/12~/04?");
1028 assert_eq!(date.value(DatePart::Month), Some(12));
1029 assert_eq!(
1030 date.certainty(DatePart::Month),
1031 Some(Certainty::Approximate)
1032 );
1033 assert_eq!(date.value(DatePart::Day), Some(4));
1034 assert_eq!(date.certainty(DatePart::Day), Some(Certainty::Uncertain));
1035
1036 let timed = Date::from_written("1834/12/4/4:50:32").expect("a date");
1037 assert_eq!(timed.to_string(), "1834/12/04/04:50:32");
1038 assert_eq!(timed.value(DatePart::Minute), Some(50));
1039 assert_eq!(timed.value(DatePart::Second), Some(32));
1040 assert!(timed.has_time());
1041 assert!(!timed.has_certainty());
1042
1043 let mut approximate = Date::from_parts(Some(1805), Some(3), None).expect("a date");
1044 approximate
1045 .set(DatePart::Month, Some(3), Some(Certainty::Approximate))
1046 .expect("a month");
1047 assert_eq!(approximate.to_string(), "1805/03~/--");
1048
1049 assert_eq!(
1050 Date::from_parts(Some(1843), Some(3), Some(3))
1051 .expect("a date")
1052 .to_string(),
1053 "1843/03/03"
1054 );
1055 date.take_from(&Date::from_parts(Some(1843), None, None).expect("a date"))
1056 .expect("taking a year");
1057 assert_eq!(date.year(), Some(1843));
1058 }
1059
1060 #[test]
1062 fn a_written_number_gives_up_its_certainty() {
1063 assert_eq!(
1064 strip_certainty("1247~").expect("a number"),
1065 (1247, Some(Certainty::Approximate))
1066 );
1067 assert_eq!(
1068 strip_certainty("63?").expect("a number"),
1069 (63, Some(Certainty::Uncertain))
1070 );
1071 assert_eq!(strip_certainty("4.43").expect("a number"), (4, None));
1072 }
1073
1074 #[test]
1076 fn a_date_no_calendar_has_is_refused() {
1077 assert_eq!(
1078 Date::from_written("1999/14/32")
1079 .expect_err("a fourteenth month")
1080 .to_string(),
1081 "Value error: Month must be between 1 and 12, not 14."
1082 );
1083 assert!(Date::from_parts(Some(1999), Some(4), Some(31)).is_err());
1084 assert!(Date::from_parts(Some(1999), Some(2), Some(29)).is_err());
1085 assert!(Date::from_parts(Some(2000), Some(2), Some(29)).is_ok());
1086 }
1087
1088 #[test]
1090 fn the_four_date_classes_are_written_as_music21_writes_them() {
1091 let single = DatePrimitive::single("2009/12/31", Relevance::Approximate).expect("a date");
1092 assert_eq!(single.to_string(), "2009/12/31");
1093 assert_eq!(single.relevance(), Relevance::Approximate);
1094 assert_eq!(single.kind(), DateKind::Single);
1095 assert_eq!(
1096 DatePrimitive::single("1805/3/12", Relevance::Uncertain)
1097 .expect("a date")
1098 .to_string(),
1099 "1805/03/12"
1100 );
1101
1102 let mut relative = DatePrimitive::relative("2009/12/31", Relevance::Prior).expect("a date");
1103 assert_eq!(relative.to_string(), "prior to 2009/12/31");
1104 relative
1105 .set_relevance(Relevance::After)
1106 .expect("after a date");
1107 assert_eq!(relative.to_string(), "after 2009/12/31");
1108 assert!(DatePrimitive::relative("2009/12/31", Relevance::Certain).is_err());
1109
1110 assert_eq!(
1111 DatePrimitive::between(["2009/12/31", "2010/1/28"])
1112 .expect("a span")
1113 .to_string(),
1114 "2009/12/31 to 2010/01/28"
1115 );
1116 assert_eq!(
1117 DatePrimitive::of_dates(
1118 vec![
1119 Date::from_parts(Some(1605), None, None).expect("a year"),
1120 Date::from_written("1608/11?").expect("a date"),
1121 ],
1122 Relevance::Between,
1123 DateKind::Between,
1124 )
1125 .expect("a span")
1126 .to_string(),
1127 "1605/--/-- to 1608/11?/--"
1128 );
1129
1130 assert_eq!(
1131 DatePrimitive::selection(["2009/12/31", "2010/1/28", "1894/1/28"], Relevance::Or)
1132 .expect("a choice")
1133 .to_string(),
1134 "2009/12/31 or 2010/01/28 or 1894/01/28"
1135 );
1136 assert_eq!(
1137 DatePrimitive::selection(["1750", "1775/03?"], Relevance::And)
1138 .expect("a choice")
1139 .to_string(),
1140 "1750/--/-- and 1775/03?/--"
1141 );
1142 assert!(DatePrimitive::selection(["2009/12/31"], Relevance::Certain).is_err());
1143 }
1144
1145 #[test]
1147 fn a_text_keeps_what_it_says_of_itself() {
1148 let title = Text::new("concerto in d").in_language("en");
1149 assert_eq!(title.to_string(), "concerto in d");
1150 assert_eq!(title.language(), Some("en"));
1151 assert_ne!(title, Text::new("concerto in d"));
1152 assert_eq!(Text::new("some text"), Text::new("some text"));
1153 assert_ne!(
1154 Text::new("some text"),
1155 Text::new("some text").translated(true)
1156 );
1157
1158 let article = Text::new("Ale is Dear, The").in_language("en");
1159 assert_eq!(
1160 article.normalized_article().expect("an article"),
1161 "The Ale is Dear"
1162 );
1163 let german = Text::new("Ale is Dear, The").in_language("de");
1164 assert_eq!(
1165 german.normalized_article().expect("an article"),
1166 "Ale is Dear, The"
1167 );
1168
1169 let composer = Contributor::new("composer", [Text::new("Beethoven, Ludwig van")]);
1170 assert_eq!(composer.to_string(), "composer:Beethoven, Ludwig van");
1171 assert_eq!(
1172 composer.name().map(Text::data),
1173 Some("Beethoven, Ludwig van")
1174 );
1175 assert_eq!(Contributor::unnamed().role(), None);
1176 assert_eq!(
1178 Contributor::new("com", [Text::new("Chopin")]).role(),
1179 Some("composer")
1180 );
1181 assert_eq!(
1182 Contributor::role_of_abbreviation("lib").expect("a role"),
1183 "librettist"
1184 );
1185 assert_eq!(
1186 Contributor::abbreviation_of_role("composer").expect("an abbreviation"),
1187 "com"
1188 );
1189 assert!(Contributor::role_of_abbreviation("zzz").is_err());
1190 assert!(Contributor::abbreviation_of_role("conductor").is_err());
1191 }
1192}