Calendar events ported not passing

This commit is contained in:
Henry
2022-09-12 20:08:25 -05:00
parent 56b13b16fb
commit e7fc4a50d3
10 changed files with 757 additions and 629 deletions

View File

@@ -1,259 +0,0 @@
/*
* 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;
/**
* Represents a parsed result that encodes a calendar event at a certain time, optionally
* with attendees and a location.
*
* @author Sean Owen
*/
public final class CalendarParsedRXingResult extends ParsedRXingResult {
private static final Pattern RFC2445_DURATION =
Pattern.compile("P(?:(\\d+)W)?(?:(\\d+)D)?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?)?");
private static final long[] RFC2445_DURATION_FIELD_UNITS = {
7 * 24 * 60 * 60 * 1000L, // 1 week
24 * 60 * 60 * 1000L, // 1 day
60 * 60 * 1000L, // 1 hour
60 * 1000L, // 1 minute
1000L, // 1 second
};
private static final Pattern DATE_TIME = Pattern.compile("[0-9]{8}(T[0-9]{6}Z?)?");
private final String summary;
private final long start;
private final boolean startAllDay;
private final long end;
private final boolean endAllDay;
private final String location;
private final String organizer;
private final String[] attendees;
private final String description;
private final double latitude;
private final double longitude;
public CalendarParsedRXingResult(String summary,
String startString,
String endString,
String durationString,
String location,
String organizer,
String[] attendees,
String description,
double latitude,
double longitude) {
super(ParsedRXingResultType.CALENDAR);
this.summary = summary;
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());
}
}
this.startAllDay = startString.length() == 8;
this.endAllDay = endString != null && endString.length() == 8;
this.location = location;
this.organizer = organizer;
this.attendees = attendees;
this.description = description;
this.latitude = latitude;
this.longitude = longitude;
}
public String getSummary() {
return summary;
}
/**
* @return start time
* @deprecated use {@link #getStartTimestamp()}
*/
@Deprecated
public Date getStart() {
return new Date(start);
}
/**
* @return start time
* @see #getEndTimestamp()
*/
public long getStartTimestamp() {
return start;
}
/**
* @return true if start time was specified as a whole day
*/
public boolean isStartAllDay() {
return startAllDay;
}
/**
* @return event end {@link Date}, or {@code null} if event has no duration
* @deprecated use {@link #getEndTimestamp()}
*/
@Deprecated
public Date getEnd() {
return end < 0L ? null : new Date(end);
}
/**
* @return event end {@link Date}, or -1 if event has no duration
* @see #getStartTimestamp()
*/
public long getEndTimestamp() {
return end;
}
/**
* @return true if end time was specified as a whole day
*/
public boolean isEndAllDay() {
return endAllDay;
}
public String getLocation() {
return location;
}
public String getOrganizer() {
return organizer;
}
public String[] getAttendees() {
return attendees;
}
public String getDescription() {
return description;
}
public double getLatitude() {
return latitude;
}
public double getLongitude() {
return longitude;
}
@Override
public String getDisplayRXingResult() {
StringBuilder result = new StringBuilder(100);
maybeAppend(summary, result);
maybeAppend(format(startAllDay, start), result);
maybeAppend(format(endAllDay, end), result);
maybeAppend(location, result);
maybeAppend(organizer, result);
maybeAppend(attendees, result);
maybeAppend(description, result);
return result.toString();
}
/**
* 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
*/
private static long parseDate(String when) throws ParseException {
if (!DATE_TIME.matcher(when).matches()) {
throw new ParseException(when, 0);
}
if (when.length() == 8) {
// Show only year/month/day
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
format.setTimeZone(TimeZone.getTimeZone("GMT"));
return format.parse(when).getTime();
}
// The when string can be local time, or UTC if it ends with a Z
if (when.length() == 16 && when.charAt(15) == 'Z') {
long milliseconds = parseDateTimeString(when.substring(0, 15));
Calendar calendar = new GregorianCalendar();
// Account for time zone difference
milliseconds += calendar.get(Calendar.ZONE_OFFSET);
// Might need to correct for daylight savings time, but use target time since
// now might be in DST but not then, or vice versa
calendar.setTime(new Date(milliseconds));
return milliseconds + calendar.get(Calendar.DST_OFFSET);
}
return parseDateTimeString(when);
}
private static String format(boolean allDay, long date) {
if (date < 0L) {
return null;
}
DateFormat format = allDay
? DateFormat.getDateInstance(DateFormat.MEDIUM)
: DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
return format.format(date);
}
private static long parseDurationMS(CharSequence durationString) {
if (durationString == null) {
return -1L;
}
Matcher m = RFC2445_DURATION.matcher(durationString);
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;
}
private static long parseDateTimeString(String dateTimeString) throws ParseException {
DateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH);
return format.parse(dateTimeString).getTime();
}
}

View File

@@ -0,0 +1,313 @@
/*
* 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, Utc};
use regex::Regex;
use crate::exceptions::Exceptions;
use super::{
maybe_append_multiple, maybe_append_string, ParsedRXingResult, ParsedRXingResultType,
ResultParser,
};
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 * 1000i64, // 1 week
24 * 60 * 60 * 1000i64, // 1 day
60 * 60 * 1000i64, // 1 hour
60 * 1000i64, // 1 minute
1000i64, // 1 second
];
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
*/
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 {
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, Exceptions> {
let start = Self::parseDate(startString.clone())?;
let end = if endString.is_empty() {
let durationMS = Self::parseDurationMS(&durationString);
if durationMS < 0i64 {
-1i64
} else {
start + durationMS
}
} else {
Self::parseDate(endString.clone())?
};
let startAllDay;
let endAllDay;
// 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());
// }
// }
startAllDay = startString.len() == 8;
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, Exceptions> {
let date_time_regex = Regex::new(DATE_TIME).unwrap();
if !date_time_regex.is_match(&when) {
return Err(Exceptions::ParseException(when));
}
if when.len() == 8 {
// Show only year/month/day
let date_format_string = "%Y%m%d";
// 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
let dtm = DateTime::parse_from_str(&when, date_format_string).unwrap();
let dtm = dtm.with_timezone(&Utc);
// format.setTimeZone(TimeZone.getTimeZone("GMT"));
// return format.parse(when).getTime();
return Ok(dtm.timestamp());
}
// The when string can be local time, or UTC if it ends with a Z
if when.len() == 16 && when.chars().nth(15).unwrap() == 'Z' {
let milliseconds = Self::parseDateTimeString(&when[0..15]);
// Calendar calendar = new GregorianCalendar();
// // Account for time zone difference
// milliseconds += calendar.get(Calendar.ZONE_OFFSET);
// // Might need to correct for daylight savings time, but use target time since
// // now might be in DST but not then, or vice versa
// calendar.setTime(new Date(milliseconds));
// return milliseconds + calendar.get(Calendar.DST_OFFSET);
return milliseconds;
}
Self::parseDateTimeString(&when)
}
fn format_event(allDay: bool, date: i64) -> String {
if date < 0 {
return "".to_owned();
}
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);
NaiveDateTime::from_timestamp(date, 0)
.format(format_string)
.to_string()
}
fn parseDurationMS(durationString: &str) -> i64 {
if durationString.is_empty() {
return -1;
}
let regex = Regex::new(RFC2445_DURATION).unwrap();
if let Some(m) = regex.captures(durationString) {
let mut durationMS = 0i64;
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 fieldValue.is_some() {
let z = fieldValue.unwrap().as_str().parse::<i64>().unwrap();
durationMS += RFC2445_DURATION_FIELD_UNITS[i] * z;
}
}
durationMS
} else {
-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, Exceptions> {
if let Ok(dtm) = DateTime::parse_from_str(dateTimeString, "%Y%m%dT%H%M%S") {
Ok(dtm.timestamp())
} else {
Err(Exceptions::ParseException(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
}
}

View File

@@ -1,250 +0,0 @@
/*
* Copyright 2007 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 com.google.zxing.BarcodeFormat;
import com.google.zxing.RXingResult;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.TimeZone;
/**
* Tests {@link CalendarParsedRXingResult}.
*
* @author Sean Owen
*/
public final class CalendarParsedRXingResultTestCase extends Assert {
private static final double EPSILON = 1.0E-10;
private static DateFormat makeGMTFormat() {
DateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
return format;
}
@Before
public void setUp() {
Locale.setDefault(Locale.ENGLISH);
TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
}
@Test
public void testStartEnd() {
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\n" +
"DTSTART:20080504T123456Z\r\n" +
"DTEND:20080505T234555Z\r\n" +
"END:VEVENT\r\nEND:VCALENDAR",
null, null, null, "20080504T123456Z", "20080505T234555Z");
}
@Test
public void testNoVCalendar() {
doTest(
"BEGIN:VEVENT\r\n" +
"DTSTART:20080504T123456Z\r\n" +
"DTEND:20080505T234555Z\r\n" +
"END:VEVENT",
null, null, null, "20080504T123456Z", "20080505T234555Z");
}
@Test
public void testStart() {
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\n" +
"DTSTART:20080504T123456Z\r\n" +
"END:VEVENT\r\nEND:VCALENDAR",
null, null, null, "20080504T123456Z", null);
}
@Test
public void testDuration() {
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\n" +
"DTSTART:20080504T123456Z\r\n" +
"DURATION:P1D\r\n" +
"END:VEVENT\r\nEND:VCALENDAR",
null, null, null, "20080504T123456Z", "20080505T123456Z");
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\n" +
"DTSTART:20080504T123456Z\r\n" +
"DURATION:P1DT2H3M4S\r\n" +
"END:VEVENT\r\nEND:VCALENDAR",
null, null, null, "20080504T123456Z", "20080505T143800Z");
}
@Test
public void testSummary() {
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\n" +
"SUMMARY:foo\r\n" +
"DTSTART:20080504T123456Z\r\n" +
"END:VEVENT\r\nEND:VCALENDAR",
null, "foo", null, "20080504T123456Z", null);
}
@Test
public void testLocation() {
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\n" +
"LOCATION:Miami\r\n" +
"DTSTART:20080504T123456Z\r\n" +
"END:VEVENT\r\nEND:VCALENDAR",
null, null, "Miami", "20080504T123456Z", null);
}
@Test
public void testDescription() {
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\n" +
"DTSTART:20080504T123456Z\r\n" +
"DESCRIPTION:This is a test\r\n" +
"END:VEVENT\r\nEND:VCALENDAR",
"This is a test", null, null, "20080504T123456Z", null);
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\n" +
"DTSTART:20080504T123456Z\r\n" +
"DESCRIPTION:This is a test\r\n\t with a continuation\r\n" +
"END:VEVENT\r\nEND:VCALENDAR",
"This is a test with a continuation", null, null, "20080504T123456Z", null);
}
@Test
public void testGeo() {
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\n" +
"DTSTART:20080504T123456Z\r\n" +
"GEO:-12.345;-45.678\r\n" +
"END:VEVENT\r\nEND:VCALENDAR",
null, null, null, "20080504T123456Z", null, null, null, -12.345, -45.678);
}
@Test
public void testBadGeo() {
// Not parsed as VEVENT
RXingResult fakeRXingResult = new RXingResult("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\n" +
"GEO:-12.345\r\n" +
"END:VEVENT\r\nEND:VCALENDAR", null, null, BarcodeFormat.QR_CODE);
ParsedRXingResult result = RXingResultParser.parseRXingResult(fakeRXingResult);
assertSame(ParsedRXingResultType.TEXT, result.getType());
}
@Test
public void testOrganizer() {
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\n" +
"DTSTART:20080504T123456Z\r\n" +
"ORGANIZER:mailto:bob@example.org\r\n" +
"END:VEVENT\r\nEND:VCALENDAR",
null, null, null, "20080504T123456Z", null, "bob@example.org", null, Double.NaN, Double.NaN);
}
@Test
public void testAttendees() {
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\n" +
"DTSTART:20080504T123456Z\r\n" +
"ATTENDEE:mailto:bob@example.org\r\n" +
"ATTENDEE:mailto:alice@example.org\r\n" +
"END:VEVENT\r\nEND:VCALENDAR",
null, null, null, "20080504T123456Z", null, null,
new String[] {"bob@example.org", "alice@example.org"}, Double.NaN, Double.NaN);
}
@Test
public void testVEventEscapes() {
doTest("BEGIN:VEVENT\n" +
"CREATED:20111109T110351Z\n" +
"LAST-MODIFIED:20111109T170034Z\n" +
"DTSTAMP:20111109T170034Z\n" +
"UID:0f6d14ef-6cb7-4484-9080-61447ccdf9c2\n" +
"SUMMARY:Summary line\n" +
"CATEGORIES:Private\n" +
"DTSTART;TZID=Europe/Vienna:20111110T110000\n" +
"DTEND;TZID=Europe/Vienna:20111110T120000\n" +
"LOCATION:Location\\, with\\, escaped\\, commas\n" +
"DESCRIPTION:Meeting with a friend\\nlook at homepage first\\n\\n\n" +
" \\n\n" +
"SEQUENCE:1\n" +
"X-MOZ-GENERATION:1\n" +
"END:VEVENT",
"Meeting with a friend\nlook at homepage first\n\n\n \n",
"Summary line",
"Location, with, escaped, commas",
"20111110T110000Z",
"20111110T120000Z");
}
@Test
public void testAllDayValueDate() {
doTest("BEGIN:VEVENT\n" +
"DTSTART;VALUE=DATE:20111110\n" +
"DTEND;VALUE=DATE:20111110\n" +
"END:VEVENT",
null, null, null, "20111110T000000Z", "20111110T000000Z");
}
private static void doTest(String contents,
String description,
String summary,
String location,
String startString,
String endString) {
doTest(contents, description, summary, location, startString, endString, null, null, Double.NaN, Double.NaN);
}
private static void doTest(String contents,
String description,
String summary,
String location,
String startString,
String endString,
String organizer,
String[] attendees,
double latitude,
double longitude) {
RXingResult fakeRXingResult = new RXingResult(contents, null, null, BarcodeFormat.QR_CODE);
ParsedRXingResult result = RXingResultParser.parseRXingResult(fakeRXingResult);
assertSame(ParsedRXingResultType.CALENDAR, result.getType());
CalendarParsedRXingResult calRXingResult = (CalendarParsedRXingResult) result;
assertEquals(description, calRXingResult.getDescription());
assertEquals(summary, calRXingResult.getSummary());
assertEquals(location, calRXingResult.getLocation());
DateFormat dateFormat = makeGMTFormat();
assertEquals(startString, dateFormat.format(calRXingResult.getStartTimestamp()));
assertEquals(endString, calRXingResult.getEndTimestamp() < 0L ? null : dateFormat.format(calRXingResult.getEndTimestamp()));
assertEquals(organizer, calRXingResult.getOrganizer());
assertArrayEquals(attendees, calRXingResult.getAttendees());
assertEqualOrNaN(latitude, calRXingResult.getLatitude());
assertEqualOrNaN(longitude, calRXingResult.getLongitude());
}
private static void assertEqualOrNaN(double expected, double actual) {
if (Double.isNaN(expected)) {
assertTrue(Double.isNaN(actual));
} else {
assertEquals(expected, actual, EPSILON);
}
}
}

View File

@@ -0,0 +1,257 @@
/*
* Copyright 2007 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 com.google.zxing.BarcodeFormat;
// import com.google.zxing.RXingResult;
// import org.junit.Assert;
// import org.junit.Before;
// import org.junit.Test;
// import java.text.DateFormat;
// import java.text.SimpleDateFormat;
// import java.util.Locale;
// import java.util.TimeZone;
// /**
// * Tests {@link CalendarParsedRXingResult}.
// *
// * @author Sean Owen
// */
// public final class CalendarParsedRXingResultTestCase extends Assert {
use chrono::{DateTime, NaiveDateTime, Utc};
use crate::{
client::result::{ParsedClientResult, ParsedRXingResultType},
BarcodeFormat, RXingResult,
};
use super::{ParsedRXingResult, ResultParser};
const EPSILON: f64 = 1.0E-10;
const DATE_FORMAT_STRING: &'static str = "yyyyMMdd'T'HHmmss'Z'";
// private static DateFormat makeGMTFormat() {
// DateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH);
// format.setTimeZone(TimeZone.getTimeZone("GMT"));
// return format;
// }
// @Before
// public void setUp() {
// Locale.setDefault(Locale.ENGLISH);
// TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
// }
#[test]
fn testStartEnd() {
doTestShort(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20080504T123456Z\r\nDTEND:20080505T234555Z\r\nEND:VEVENT\r\nEND:VCALENDAR",
"", "", "", "20080504T123456Z", "20080505T234555Z");
}
#[test]
fn testNoVCalendar() {
doTestShort(
"BEGIN:VEVENT\r\nDTSTART:20080504T123456Z\r\nDTEND:20080505T234555Z\r\nEND:VEVENT",
"",
"",
"",
"20080504T123456Z",
"20080505T234555Z",
);
}
#[test]
fn testStart() {
doTestShort(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20080504T123456Z\r\nEND:VEVENT\r\nEND:VCALENDAR",
"", "", "", "20080504T123456Z", "");
}
#[test]
fn testDuration() {
doTestShort(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20080504T123456Z\r\nDURATION:P1D\r\nEND:VEVENT\r\nEND:VCALENDAR",
"", "", "", "20080504T123456Z", "20080505T123456Z");
doTestShort(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20080504T123456Z\r\nDURATION:P1DT2H3M4S\r\nEND:VEVENT\r\nEND:VCALENDAR",
"", "", "", "20080504T123456Z", "20080505T143800Z");
}
#[test]
fn testSummary() {
doTestShort(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nSUMMARY:foo\r\nDTSTART:20080504T123456Z\r\nEND:VEVENT\r\nEND:VCALENDAR",
"", "foo", "", "20080504T123456Z", "");
}
#[test]
fn testLocation() {
doTestShort(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nLOCATION:Miami\r\nDTSTART:20080504T123456Z\r\nEND:VEVENT\r\nEND:VCALENDAR",
"", "", "Miami", "20080504T123456Z", "");
}
#[test]
fn testDescription() {
doTestShort(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20080504T123456Z\r\nDESCRIPTION:This is a test\r\nEND:VEVENT\r\nEND:VCALENDAR",
"This is a test", "", "", "20080504T123456Z", "");
doTestShort(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20080504T123456Z\r\nDESCRIPTION:This is a test\r\n\t with a continuation\r\nEND:VEVENT\r\nEND:VCALENDAR",
"This is a test with a continuation", "", "", "20080504T123456Z", "");
}
#[test]
fn testGeo() {
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20080504T123456Z\r\nGEO:-12.345;-45.678\r\nEND:VEVENT\r\nEND:VCALENDAR",
"", "", "", "20080504T123456Z", "", "", &Vec::new(), -12.345, -45.678);
}
#[test]
fn testBadGeo() {
// Not parsed as VEVENT
let fakeRXingResult = RXingResult::new(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nGEO:-12.345\r\nEND:VEVENT\r\nEND:VCALENDAR",
Vec::new(),
Vec::new(),
BarcodeFormat::QR_CODE,
);
let result = ResultParser::parseRXingResult(&fakeRXingResult);
assert_eq!(ParsedRXingResultType::TEXT, result.getType());
}
#[test]
fn testOrganizer() {
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20080504T123456Z\r\nORGANIZER:mailto:bob@example.org\r\nEND:VEVENT\r\nEND:VCALENDAR",
"", "", "", "20080504T123456Z", "", "bob@example.org", &Vec::new(), f64::NAN, f64::NAN);
}
#[test]
fn testAttendees() {
doTest(
"BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20080504T123456Z\r\nATTENDEE:mailto:bob@example.org\r\nATTENDEE:mailto:alice@example.org\r\nEND:VEVENT\r\nEND:VCALENDAR",
"", "", "", "20080504T123456Z", "", "",
&vec!["bob@example.org", "alice@example.org"], f64::NAN, f64::NAN);
}
#[test]
fn testVEventEscapes() {
doTestShort("BEGIN:VEVENT\nCREATED:20111109T110351Z\nLAST-MODIFIED:20111109T170034Z\nDTSTAMP:20111109T170034Z\nUID:0f6d14ef-6cb7-4484-9080-61447ccdf9c2\nSUMMARY:Summary line\nCATEGORIES:Private\nDTSTART;TZID=Europe/Vienna:20111110T110000\nDTEND;TZID=Europe/Vienna:20111110T120000\nLOCATION:Location\\, with\\, escaped\\, commas\nDESCRIPTION:Meeting with a friend\\nlook at homepage first\\n\\n\n \\n\nSEQUENCE:1\nX-MOZ-GENERATION:1\nEND:VEVENT",
"Meeting with a friend\nlook at homepage first\n\n\n \n",
"Summary line",
"Location, with, escaped, commas",
"20111110T110000Z",
"20111110T120000Z");
}
#[test]
fn testAllDayValueDate() {
doTestShort(
"BEGIN:VEVENT\nDTSTART;VALUE=DATE:20111110\nDTEND;VALUE=DATE:20111110\nEND:VEVENT",
"",
"",
"",
"20111110T000000Z",
"20111110T000000Z",
);
}
fn doTestShort(
contents: &str,
description: &str,
summary: &str,
location: &str,
startString: &str,
endString: &str,
) {
doTest(
contents,
description,
summary,
location,
startString,
endString,
"",
&Vec::new(),
f64::NAN,
f64::NAN,
);
}
fn doTest(
contents: &str,
description: &str,
summary: &str,
location: &str,
startString: &str,
endString: &str,
organizer: &str,
attendees: &[&str],
latitude: f64,
longitude: f64,
) {
let fakeRXingResult =
RXingResult::new(contents, Vec::new(), Vec::new(), BarcodeFormat::QR_CODE);
let result = ResultParser::parseRXingResult(&fakeRXingResult);
assert_eq!(ParsedRXingResultType::CALENDAR, result.getType());
if let ParsedClientResult::CalendarEventResult(calRXingResult) = result {
assert_eq!(description, calRXingResult.getDescription());
assert_eq!(summary, calRXingResult.getSummary());
assert_eq!(location, calRXingResult.getLocation());
let dateFormat = "%c"; //makeGMTFormat();
assert_eq!(
startString,
format_date_string(calRXingResult.getStartTimestamp(), dateFormat) // dateFormat.format(calRXingResult.getStartTimestamp())
);
assert_eq!(
endString,
if calRXingResult.getEndTimestamp() < 0i64 {
"".to_owned()
} else {
format_date_string(calRXingResult.getEndTimestamp(), dateFormat)
// dateFormat.format(calRXingResult.getEndTimestamp())
}
);
assert_eq!(organizer, calRXingResult.getOrganizer());
assert_eq!(attendees, calRXingResult.getAttendees());
assertEqualOrNaN(latitude, calRXingResult.getLatitude());
assertEqualOrNaN(longitude, calRXingResult.getLongitude());
} else {
panic!("Expected Calendar");
}
}
fn assertEqualOrNaN(expected: f64, actual: f64) {
if expected.is_nan() {
assert!(actual.is_nan());
} else {
assert!(expected - actual < EPSILON && actual - expected < EPSILON);
// assert_eq!(expected, actual, EPSILON);
}
}
fn format_date_string(timestamp: i64, format_string: &str) -> String {
NaiveDateTime::from_timestamp(timestamp, 0)
.format(format_string)
.to_string()
// DateTime::from(timestamp,0).with_timezone(Utc).format(format_string).to_string()
}

View File

@@ -153,7 +153,7 @@ pub fn parse(result: &RXingResult) -> Option<ParsedClientResult> {
}
}
fn matchVCardPrefixedField(
pub fn matchVCardPrefixedField(
prefix: &str,
rawText: &str,
trim: bool,
@@ -427,7 +427,7 @@ fn maybeAppendFragment(fragmentBuffer: &mut Vec<u8>, charset: &str, result: &mut
}
}
fn matchSingleVCardPrefixedField(
pub fn matchSingleVCardPrefixedField(
prefix: &str,
rawText: &str,
trim: bool,

View File

@@ -1,118 +0,0 @@
/*
* 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 com.google.zxing.RXingResult;
import java.util.List;
/**
* Partially implements the iCalendar format's "VEVENT" format for specifying a
* calendar event. See RFC 2445. This supports SUMMARY, LOCATION, GEO, DTSTART and DTEND fields.
*
* @author Sean Owen
*/
public final class VEventRXingResultParser extends RXingResultParser {
@Override
public CalendarParsedRXingResult parse(RXingResult result) {
String rawText = getMassagedText(result);
int vEventStart = rawText.indexOf("BEGIN:VEVENT");
if (vEventStart < 0) {
return null;
}
String summary = matchSingleVCardPrefixedField("SUMMARY", rawText);
String start = matchSingleVCardPrefixedField("DTSTART", rawText);
if (start == null) {
return null;
}
String end = matchSingleVCardPrefixedField("DTEND", rawText);
String duration = matchSingleVCardPrefixedField("DURATION", rawText);
String location = matchSingleVCardPrefixedField("LOCATION", rawText);
String organizer = stripMailto(matchSingleVCardPrefixedField("ORGANIZER", rawText));
String[] attendees = matchVCardPrefixedField("ATTENDEE", rawText);
if (attendees != null) {
for (int i = 0; i < attendees.length; i++) {
attendees[i] = stripMailto(attendees[i]);
}
}
String description = matchSingleVCardPrefixedField("DESCRIPTION", rawText);
String geoString = matchSingleVCardPrefixedField("GEO", rawText);
double latitude;
double longitude;
if (geoString == null) {
latitude = Double.NaN;
longitude = Double.NaN;
} else {
int semicolon = geoString.indexOf(';');
if (semicolon < 0) {
return null;
}
try {
latitude = Double.parseDouble(geoString.substring(0, semicolon));
longitude = Double.parseDouble(geoString.substring(semicolon + 1));
} catch (NumberFormatException ignored) {
return null;
}
}
try {
return new CalendarParsedRXingResult(summary,
start,
end,
duration,
location,
organizer,
attendees,
description,
latitude,
longitude);
} catch (IllegalArgumentException ignored) {
return null;
}
}
private static String matchSingleVCardPrefixedField(CharSequence prefix,
String rawText) {
List<String> values = VCardRXingResultParser.matchSingleVCardPrefixedField(prefix, rawText, true, false);
return values == null || values.isEmpty() ? null : values.get(0);
}
private static String[] matchVCardPrefixedField(CharSequence prefix, String rawText) {
List<List<String>> values = VCardRXingResultParser.matchVCardPrefixedField(prefix, rawText, true, false);
if (values == null || values.isEmpty()) {
return null;
}
int size = values.size();
String[] result = new String[size];
for (int i = 0; i < size; i++) {
result[i] = values.get(i).get(0);
}
return result;
}
private static String stripMailto(String s) {
if (s != null && (s.startsWith("mailto:") || s.startsWith("MAILTO:"))) {
s = s.substring(7);
}
return s;
}
}

View File

@@ -0,0 +1,174 @@
/*
* 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 com.google.zxing.RXingResult;
// import java.util.List;
use crate::RXingResult;
use super::{CalendarParsedRXingResult, ParsedClientResult, ResultParser, VCardResultParser};
/**
* Partially implements the iCalendar format's "VEVENT" format for specifying a
* calendar event. See RFC 2445. This supports SUMMARY, LOCATION, GEO, DTSTART and DTEND fields.
*
* @author Sean Owen
*/
pub fn parse(result: &RXingResult) -> Option<ParsedClientResult> {
let rawText = ResultParser::getMassagedText(result);
if let None = rawText.find("BEGIN:VEVENT") {
return None;
}
// if (vEventStart < 0) {
// return null;
// }
let summary = matchSingleVCardPrefixedField("SUMMARY", &rawText);
let start = matchSingleVCardPrefixedField("DTSTART", &rawText);
if start.is_empty() {
return None;
}
let end = matchSingleVCardPrefixedField("DTEND", &rawText);
let duration = matchSingleVCardPrefixedField("DURATION", &rawText);
let location = matchSingleVCardPrefixedField("LOCATION", &rawText);
let organizer = stripMailto(&matchSingleVCardPrefixedField("ORGANIZER", &rawText));
let mut attendees = matchVCardPrefixedField("ATTENDEE", &rawText);
if !attendees.is_empty() {
for i in 0..attendees.len() {
// for (int i = 0; i < attendees.length; i++) {
attendees[i] = stripMailto(&attendees[i]);
}
}
let description = matchSingleVCardPrefixedField("DESCRIPTION", &rawText);
let geoString = matchSingleVCardPrefixedField("GEO", &rawText);
let latitude;
let longitude;
if geoString.is_empty() {
latitude = f64::NAN;
longitude = f64::NAN;
} else {
if let Some(semicolon) = geoString.find(';') {
latitude = if let Ok(l) = geoString[..semicolon].parse() {
l
} else {
return None;
};
longitude = if let Ok(l) = geoString[semicolon + 1..].parse() {
l
} else {
return None;
}
} else {
return None;
}
// if (semicolon < 0) {
// return null;
// }
// try {
// latitude = Double.parseDouble(geoString.substring(0, semicolon));
// longitude = Double.parseDouble(geoString.substring(semicolon + 1));
// } catch (NumberFormatException ignored) {
// return null;
// }
}
if let Ok(cpr) = CalendarParsedRXingResult::new(
summary,
start,
end,
duration,
location,
organizer,
attendees,
description,
latitude,
longitude,
) {
Some(ParsedClientResult::CalendarEventResult(cpr))
} else {
None
}
// try {
// return new CalendarParsedRXingResult(summary,
// start,
// end,
// duration,
// location,
// organizer,
// attendees,
// description,
// latitude,
// longitude);
// } catch (IllegalArgumentException ignored) {
// return null;
// }
}
fn matchSingleVCardPrefixedField(prefix: &str, rawText: &str) -> String {
if let Some(values) =
VCardResultParser::matchSingleVCardPrefixedField(prefix, rawText, true, false)
{
if values.is_empty() {
"".to_owned()
} else {
values.get(0).unwrap().clone()
}
} else {
"".to_owned()
}
// return values == null || values.isEmpty() ? null : values.get(0);
}
fn matchVCardPrefixedField(prefix: &str, rawText: &str) -> Vec<String> {
if let Some(values) = VCardResultParser::matchVCardPrefixedField(prefix, rawText, true, false) {
if values.is_empty() {
Vec::new()
} else {
let size = values.len();
let mut result = vec!["".to_owned(); size]; //new String[size];
for i in 0..size {
// for (int i = 0; i < size; i++) {
result[i] = values.get(i).unwrap().get(0).unwrap().clone();
}
result
}
} else {
Vec::new()
}
// if values == null || values.isEmpty() {
// return null;
// }
// int size = values.size();
// String[] result = new String[size];
// for (int i = 0; i < size; i++) {
// result[i] = values.get(i).get(0);
// }
// return result;
}
fn stripMailto(s: &str) -> String {
let mut s = s;
if !s.is_empty() && (s.starts_with("mailto:") || s.starts_with("MAILTO:")) {
s = &s[7..];
}
s.to_owned()
}

View File

@@ -31,6 +31,8 @@ mod AddressBookDoCoMoResultParser;
mod AddressBookAUResultParser;
mod VCardResultParser;
mod BizcardResultParser;
mod CalendarParsedResult;
mod VEventResultParser;
use std::fmt;
@@ -52,6 +54,8 @@ pub use URIParsedResult::*;
pub use EmailAddressParsedResult::*;
pub use VINParsedResult::*;
pub use AddressBookParsedResult::*;
pub use CalendarParsedResult::*;
pub use CalendarParsedResult::*;
#[cfg(test)]
mod TelParsedResultTestCase;
@@ -73,6 +77,8 @@ mod EmailAddressParsedResultTestCase;
mod VINParsedResultTestCase;
#[cfg(test)]
mod AddressBookParsedResultTestCase;
#[cfg(test)]
mod CalendarParsedResultTestCase;
pub enum ParsedClientResult {
TextResult(TextParsedRXingResult),
@@ -86,6 +92,7 @@ pub enum ParsedClientResult {
EmailResult(EmailAddressParsedRXingResult),
VINResult(VINParsedRXingResult),
AddressBookResult(AddressBookParsedRXingResult),
CalendarEventResult(CalendarParsedRXingResult),
}
impl ParsedRXingResult for ParsedClientResult {
@@ -102,6 +109,7 @@ impl ParsedRXingResult for ParsedClientResult {
ParsedClientResult::EmailResult(a) => a.getType(),
ParsedClientResult::VINResult(a) => a.getType(),
ParsedClientResult::AddressBookResult(a) => a.getType(),
ParsedClientResult::CalendarEventResult(a) => a.getType(),
}
}
@@ -118,6 +126,7 @@ impl ParsedRXingResult for ParsedClientResult {
ParsedClientResult::EmailResult(a) => a.getDisplayRXingResult(),
ParsedClientResult::VINResult(a) => a.getDisplayRXingResult(),
ParsedClientResult::AddressBookResult(a) => a.getDisplayRXingResult(),
ParsedClientResult::CalendarEventResult(a) => a.getDisplayRXingResult(),
}
}
}