Skip to content

Commit

Permalink
Added support for parsing AudioMoth V1 dates
Browse files Browse the repository at this point in the history
  • Loading branch information
atruskie committed Sep 30, 2019
1 parent d5f0523 commit 3657e16
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 57 deletions.
148 changes: 95 additions & 53 deletions src/Acoustics.Shared/FileDateHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ namespace Acoustics.Shared

public class FileDateHelpers
{
public static readonly DateTimeOffset UnixEpoch =
new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);

private const string AudioMothKey = "AudioMoth";

private static readonly string[] AcceptedFormatsNoTimeZone =
{
"yyyyMMdd[-|T|_]HHmmss (if timezone offset hint provided)",
Expand Down Expand Up @@ -63,6 +68,14 @@ public class FileDateHelpers
"HHmm-ddMMyyyy",
parseTimeZone: false,
Array.Empty<string>()),

// AudioMoth V1 format
// 5BFA3A06.WAV
new DateVariants(
"^(?<date>[0-9A-F]{8}).WAV$",
parseFormat: AudioMothKey,
parseTimeZone: true,
acceptedFormats: Array.Empty<string>()),
};

private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
Expand Down Expand Up @@ -155,11 +168,15 @@ public static SortedDictionary<DateTimeOffset, T> FilterObjectsForDates<T>(IEnum
/// <returns>A sorted dictionary FileInfo objects mapped to parsed dates.</returns>
public static SortedDictionary<DateTimeOffset, DirectoryInfo> FilterDirectoriesForDates(IEnumerable<DirectoryInfo> directories, TimeSpan? offsetHint = null)
{
if (directories == null)
{
throw new ArgumentNullException(nameof(directories));
}

var datesAndDirs = new SortedDictionary<DateTimeOffset, DirectoryInfo>();
foreach (var dir in directories)
{
DateTimeOffset parsedDate;
if (FileNameContainsDateTime(dir.Name, out parsedDate, offsetHint))
if (FileNameContainsDateTime(dir.Name, out var parsedDate, offsetHint))
{
datesAndDirs.Add(parsedDate, dir);
}
Expand Down Expand Up @@ -189,7 +206,7 @@ public static bool FileNameContainsDateTime(string fileName, out DateTimeOffset
}
}

parsedDate = new DateTimeOffset();
parsedDate = default;
return false;
}

Expand All @@ -200,7 +217,7 @@ private static bool ParseFileDateTimeBase(
TimeSpan? offsetHint)
{
var match = Regex.Match(filename, format.Regex);
fileDate = new DateTimeOffset();
fileDate = default;
var successful = match.Success;

if (!successful)
Expand All @@ -210,73 +227,98 @@ private static bool ParseFileDateTimeBase(

var stringDate = match.Groups["date"].Value;

var separator = match.Groups["separator"].Value;

// Normalize the separator
stringDate = stringDate.Replace(separator, "-");

if (format.ParseTimeZone)
if (format.ParseFormat == AudioMothKey)
{
successful = ParseAudioMothV1Date(stringDate, out fileDate);
}
else
{
var offsetText = match.Groups["offset"].Value;
var separator = match.Groups["separator"].Value;

if (offsetText.Equals("Z", StringComparison.InvariantCultureIgnoreCase))
{
var parseFormat = format.ParseFormat.Replace("zzz", "Z");
// Normalize the separator
stringDate = stringDate.Replace(separator, "-");

successful = DateTimeOffset.TryParseExact(
stringDate,
parseFormat,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal,
out fileDate);
}
else if (offsetText.Length == 5)
if (format.ParseTimeZone)
{
// e.g. +1000
successful = DateTimeOffset.TryParseExact(
stringDate,
format.ParseFormat,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out fileDate);
var offsetText = match.Groups["offset"].Value;

if (offsetText.Equals("Z", StringComparison.InvariantCultureIgnoreCase))
{
var parseFormat = format.ParseFormat.Replace("zzz", "Z");

successful = DateTimeOffset.TryParseExact(
stringDate,
parseFormat,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal,
out fileDate);
}
else if (offsetText.Length == 5)
{
// e.g. +1000
successful = DateTimeOffset.TryParseExact(
stringDate,
format.ParseFormat,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out fileDate);
}
else
{
successful = DateTimeOffset.TryParseExact(
stringDate,
format.ParseFormat.Replace("zzz", "zz"),
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out fileDate);
}
}
else
{
successful = DateTimeOffset.TryParseExact(
successful = DateTime.TryParseExact(
stringDate,
format.ParseFormat.Replace("zzz", "zz"),
format.ParseFormat,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out fileDate);
}
}
else
{
DateTime dateWithoutTimeZone;
successful = DateTime.TryParseExact(
stringDate,
format.ParseFormat,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out dateWithoutTimeZone);

if (successful)
{
if (offsetHint == null)
{
Log.Warn($"File date `{stringDate}` is amibiguous. The date is understood but no timezone offset could be found and a timezone offset hint was not provided.");
return false;
}
else
out var dateWithoutTimeZone);

if (successful)
{
fileDate = new DateTimeOffset(dateWithoutTimeZone, offsetHint.Value);
if (offsetHint == null)
{
Log.Warn(
$"File date `{stringDate}` is ambiguous. The date is understood but no timezone offset could be found and a timezone offset hint was not provided.");
return false;
}
else
{
fileDate = new DateTimeOffset(dateWithoutTimeZone, offsetHint.Value);
}
}
}
}

return successful;
}

private static bool ParseAudioMothV1Date(string text, out DateTimeOffset date)
{
var successful = long.TryParse(
text,
NumberStyles.AllowHexSpecifier,
CultureInfo.InvariantCulture,
out var secondsSinceEpoch);

if (successful)
{
date = UnixEpoch.AddSeconds(secondsSinceEpoch);
return true;
}

date = default;
return false;
}

internal class DateVariants
{
public DateVariants(string regex, string parseFormat, bool parseTimeZone, string[] acceptedFormats)
Expand Down
9 changes: 5 additions & 4 deletions tests/Acoustics.Test/Shared/FileDateHelpersTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// --------------------------------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="FileDateHelpersTests.cs" company="QutEcoacoustics">
// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group).
// </copyright>
Expand Down Expand Up @@ -92,7 +92,7 @@ public void TestInvalidDateFormats()
}
}

private Dictionary<string, DateTimeOffset> validFormats = new Dictionary<string, DateTimeOffset>
private readonly Dictionary<string, DateTimeOffset> validFormats = new Dictionary<string, DateTimeOffset>
{
["sdncv*_-T&^%34jd_20140301_085031+0630blah_T-suffix.mp3"] = Parse("2014-03-01T08:50:31.000+06:30"),
["sdncv*_-T&^%34jd_20140301_085031-0630blah_T-suffix.mp3"] = Parse("2014-03-01T08:50:31.000-06:30"),
Expand All @@ -101,6 +101,7 @@ public void TestInvalidDateFormats()
["blah_T-suffix20140301-085031Z:dncv*_-T&^%34jd.ext"] = Parse("2014-03-01T08:50:31.000+00:00"),
["SERF_20130314_000021Z_000.wav"] = Parse("2013-03-14T00:00:21.000+00:00"),
["20150727T133138Z.wav"] = Parse("2015-07-27T13:31:38.000+00:00"),
["5BFA3A06.WAV"] = Parse("2018-11-25T05:58:30.000+00:00"),
};

[TestMethod]
Expand All @@ -118,7 +119,7 @@ public void TestValidDateFormats()
}
}

private Dictionary<string, DateTimeOffset> validFormatsWithOffsetHint = new Dictionary<string, DateTimeOffset>
private readonly Dictionary<string, DateTimeOffset> validFormatsWithOffsetHint = new Dictionary<string, DateTimeOffset>
{
["sdncv*_-T&^%34jd_20140301_085031blah_T-suffix.mp3"] = Parse("2014-03-01T08:50:31.000+06:30"),
["sdncv*_-T&^%34jd_20140301T085031blah_T-suffix.mp3"] = Parse("2014-03-01T08:50:31.000+06:30"),
Expand Down Expand Up @@ -159,7 +160,7 @@ public void TestValidDateFormatsWithOffsetHint()
@"Y:\2015Sept20\Woondum3\20150917-064553Z.wav", @"Y:\2015Sept20\Woondum3\20150917_133143+1000.wav",
@"Y:\2015Sept20\Woondum3\20150917_201733+1000.wav", @"Y:\2015Aug2\GympieNP\20150801_000004+1000.wav",

@"Y:\2015Aug2\GympieNP\20150801-064555.wav", @"Y:\2015Aug2\GympieNP\20150801_133148+1000.wav", @"Y:\2015Aug2\GympieNP\20150801-064555+1000.wav",
@"Y:\2015Aug2\GympieNP\20150801-064555.wav", @"Y:\2015Aug2\GympieNP\20150801_133148+1000.wav", @"Y:\2015Aug2\GympieNP\20150801-064555+1000.wav",
@"Y:\2015Aug2\GympieNP\20150801-201742+1000.wav", @"Y:\2015Aug2\GympieNP\20150802-000006Z.wav",
@"Y:\2015Aug2\GympieNP\20150802-064559+1000.wav", @"Y:\2015Sept20\Woondum3\20150919_000006+1000.wav",
@"Y:\2015Sept20\Woondum3\20150919_064557+1000.wav", @"Y:\2015Sept20\Woondum3\20150919-133149+1000.wav",
Expand Down

0 comments on commit 3657e16

Please sign in to comment.