Files
rxing/src/client/result/CalendarParsedResult.rs
Steve Cook 51fcc98b34 Merge branch 'main' into thiserror
# Conflicts:
#	src/aztec/decoder.rs
#	src/datamatrix/decoder/decoded_bit_stream_parser.rs
2023-03-01 13:39:30 -05:00

361 lines
12 KiB
Rust

/*
* Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// package com.google.zxing.client.result;
// import java.text.DateFormat;
// import java.text.ParseException;
// import java.text.SimpleDateFormat;
// import java.util.Calendar;
// import java.util.Date;
// import java.util.GregorianCalendar;
// import java.util.Locale;
// import java.util.TimeZone;
// import java.util.regex.Matcher;
// import java.util.regex.Pattern;
use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
use chrono_tz::Tz;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::common::Result;
use crate::exceptions::Exceptions;
use super::{maybe_append_multiple, maybe_append_string, ParsedRXingResult, ParsedRXingResultType};
// const RFC2445_DURATION: &'static str =
// "P(?:(\\d+)W)?(?:(\\d+)D)?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?)?";
const RFC2445_DURATION_FIELD_UNITS: [i64; 5] = [
7 * 24 * 60 * 60 * 1000, // 1 week
24 * 60 * 60 * 1000, // 1 day
60 * 60 * 1000, // 1 hour
60 * 1000, // 1 minute
1000, // 1 second
];
static DATE_TIME: Lazy<Regex> = Lazy::new(|| Regex::new("[0-9]{8}(T[0-9]{6}Z?)?").unwrap());
static RFC2445_DURATION: Lazy<Regex> = Lazy::new(|| {
Regex::new("P(?:(\\d+)W)?(?:(\\d+)D)?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?)?").unwrap()
});
// const DATE_TIME: &'static str = "[0-9]{8}(T[0-9]{6}Z?)?";
/**
* Represents a parsed result that encodes a calendar event at a certain time, optionally
* with attendees and a location.
*
* @author Sean Owen
*/
#[derive(Debug)]
pub struct CalendarParsedRXingResult {
summary: String,
start: i64,
startAllDay: bool,
end: i64,
endAllDay: bool,
location: String,
organizer: String,
attendees: Vec<String>,
description: String,
latitude: f64,
longitude: f64,
}
impl ParsedRXingResult for CalendarParsedRXingResult {
fn getType(&self) -> super::ParsedRXingResultType {
ParsedRXingResultType::CALENDAR
}
fn getDisplayRXingResult(&self) -> String {
let mut result = String::with_capacity(100);
maybe_append_string(&self.summary, &mut result);
maybe_append_string(
&Self::format_event(self.startAllDay, self.start),
&mut result,
);
maybe_append_string(&Self::format_event(self.endAllDay, self.end), &mut result);
maybe_append_string(&self.location, &mut result);
maybe_append_string(&self.organizer, &mut result);
maybe_append_multiple(&self.attendees, &mut result);
maybe_append_string(&self.description, &mut result);
result
}
}
impl CalendarParsedRXingResult {
#[allow(clippy::too_many_arguments)]
pub fn new(
summary: String,
startString: String,
endString: String,
durationString: String,
location: String,
organizer: String,
attendees: Vec<String>,
description: String,
latitude: f64,
longitude: f64,
) -> Result<Self> {
let start = Self::parseDate(startString.clone())?;
let end = if endString.is_empty() {
let durationMS = Self::parseDurationMS(&durationString)?;
if durationMS < 0 {
-1
} else {
start + (durationMS / 1000)
}
} else {
Self::parseDate(endString.clone())?
};
// try {
// this.start = parseDate(startString);
// } catch (ParseException pe) {
// throw new IllegalArgumentException(pe.toString());
// }
// if (endString == null) {
// long durationMS = parseDurationMS(durationString);
// end = durationMS < 0L ? -1L : start + durationMS;
// } else {
// try {
// this.end = parseDate(endString);
// } catch (ParseException pe) {
// throw new IllegalArgumentException(pe.toString());
// }
// }
let startAllDay = startString.len() == 8;
let endAllDay = !endString.is_empty() && endString.len() == 8;
Ok(Self {
summary,
start,
startAllDay,
end,
endAllDay,
location,
organizer,
attendees,
description,
latitude,
longitude,
})
}
/**
* Parses a string as a date. RFC 2445 allows the start and end fields to be of type DATE (e.g. 20081021)
* or DATE-TIME (e.g. 20081021T123000 for local time, or 20081021T123000Z for UTC).
*
* @param when The string to parse
* @throws ParseException if not able to parse as a date
*/
fn parseDate(when: String) -> Result<i64> {
if !DATE_TIME.is_match(&when) {
return Err(Exceptions::parse_with(when));
}
if when.len() == 8 {
// Show only year/month/day
let date_format_string = "%Y%m%dT%H%M%SZ";
// DateFormat format = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
// For dates without a time, for purposes of interacting with Android, the resulting timestamp
// needs to be midnight of that day in GMT. See:
// http://code.google.com/p/android/issues/detail?id=8330
return match Utc.datetime_from_str(&format!("{}T000000Z", &when,), date_format_string) {
Ok(dtm) => Ok(dtm.timestamp()),
Err(e) => Err(Exceptions::parse_with(e.to_string())),
};
}
// The when string can be local time, or UTC if it ends with a Z
if when.len() == 16 && when.chars().nth(15).ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)? == 'Z' {
return match Utc.datetime_from_str(&when, "%Y%m%dT%H%M%SZ") {
Ok(dtm) => Ok(dtm.with_timezone(&Utc).timestamp()),
Err(e) => Err(Exceptions::parse_with(format!("couldn't parse string: {e}"))),
};
}
// Try once more, with weird tz formatting
if when.len() > 16 {
let time_part = &when[..15];
let tz_part = &when[15..];
let tz_parsed: Tz = match tz_part.parse() {
Ok(time_zone) => time_zone,
Err(e) => {
return Err(Exceptions::parse_with(format!(
"couldn't parse timezone '{tz_part}': {e}"
)))
}
};
return match Utc.datetime_from_str(time_part, "%Y%m%dT%H%M%S") {
Ok(dtm) => Ok(dtm.with_timezone(&tz_parsed).timestamp()),
Err(e) => Err(Exceptions::parse_with(format!("couldn't parse string: {e}"))),
};
}
// Try a final time with an exact length
if when.len() == 15 {
return match Utc.datetime_from_str(&when, "%Y%m%dT%H%M%S") {
Ok(dtm) => Ok(dtm.timestamp()),
Err(e) => Err(Exceptions::parse_with(format!(
"couldn't parse local time: {e}"
))),
};
}
Self::parseDateTimeString(&when)
}
fn format_event(allDay: bool, date: i64) -> String {
if date < 0 {
return String::default();
}
let format_string = if allDay { "%F" } else { "%c" };
// DateFormat format = allDay
// ? DateFormat.getDateInstance(DateFormat.MEDIUM)
// : DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
// return format.format(date);
if let Some(dtm) = NaiveDateTime::from_timestamp_opt(date, 0) {
dtm.format(format_string).to_string()
} else {
String::default()
}
}
fn parseDurationMS(durationString: &str) -> Result<i64> {
if durationString.is_empty() {
return Ok(-1);
}
// let regex = Regex::new(RFC2445_DURATION).unwrap();
if let Some(m) = RFC2445_DURATION.captures(durationString) {
let mut durationMS: i64 = 0;
for (i, unit) in RFC2445_DURATION_FIELD_UNITS.iter().enumerate() {
// for i in 0..RFC2445_DURATION_FIELD_UNITS.len() {
// for (int i = 0; i < RFC2445_DURATION_FIELD_UNITS.length; i++) {
let fieldValue = m.get(i + 1);
if let Some(parseable) = fieldValue {
let z = parseable
.as_str()
.parse::<i64>()
.map_err(|e| Exceptions::parse_with(e.to_string()))?;
durationMS += unit * z;
}
}
Ok(durationMS)
} else {
Ok(-1)
}
// if (!m.matches()) {
// return -1L;
// }
// long durationMS = 0L;
// for (int i = 0; i < RFC2445_DURATION_FIELD_UNITS.length; i++) {
// String fieldValue = m.group(i + 1);
// if (fieldValue != null) {
// durationMS += RFC2445_DURATION_FIELD_UNITS[i] * Integer.parseInt(fieldValue);
// }
// }
// return durationMS;
}
fn parseDateTimeString(dateTimeString: &str) -> Result<i64> {
if let Ok(dtm) = DateTime::parse_from_str(dateTimeString, "%Y%m%dT%H%M%S") {
Ok(dtm.timestamp())
} else {
Err(Exceptions::parse_with(format!(
"Couldn't parse {dateTimeString}"
)))
}
// DateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH);
// return format.parse(dateTimeString).getTime();
}
pub fn getSummary(&self) -> &String {
&self.summary
}
/**
* @return start time
* @see #getEndTimestamp()
*/
pub fn getStartTimestamp(&self) -> i64 {
self.start
}
/**
* @return true if start time was specified as a whole day
*/
pub fn isStartAllDay(&self) -> bool {
self.startAllDay
}
/**
* @return event end {@link Date}, or -1 if event has no duration
* @see #getStartTimestamp()
*/
pub fn getEndTimestamp(&self) -> i64 {
self.end
}
/**
* @return true if end time was specified as a whole day
*/
pub fn isEndAllDay(&self) -> bool {
self.endAllDay
}
pub fn getLocation(&self) -> &str {
&self.location
}
pub fn getOrganizer(&self) -> &str {
&self.organizer
}
pub fn getAttendees(&self) -> &Vec<String> {
&self.attendees
}
pub fn getDescription(&self) -> &str {
&self.description
}
pub fn getLatitude(&self) -> f64 {
self.latitude
}
pub fn getLongitude(&self) -> f64 {
self.longitude
}
}
impl PartialEq for CalendarParsedRXingResult {
fn eq(&self, other: &Self) -> bool {
self.summary == other.summary
&& self.start == other.start
&& self.startAllDay == other.startAllDay
&& self.end == other.end
&& self.endAllDay == other.endAllDay
&& self.location == other.location
&& self.organizer == other.organizer
&& self.attendees == other.attendees
&& self.description == other.description
&& self.latitude == other.latitude
&& self.longitude == other.longitude
}
}
impl Eq for CalendarParsedRXingResult {}