/* * 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 = Lazy::new(|| Regex::new("[0-9]{8}(T[0-9]{6}Z?)?").unwrap()); static RFC2445_DURATION: Lazy = 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, 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, description: String, latitude: f64, longitude: f64, ) -> Result { 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 { 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 { 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::() .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 { 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 { &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 {}