pbot/lib/PBot/Core/Utils/ParseDate.pm

154 lines
4.9 KiB
Perl
Raw Normal View History

# File: ParseDate.pm
#
# Purpose: Intelligently parses strings like "1h30m", "5 minutes", "next week",
# "3:30 am pdt", "11 pm utc", etc, into seconds.
2023-02-21 06:31:52 +01:00
# SPDX-FileCopyrightText: 2015-2023 Pragmatic Software <pragma78@gmail.com>
2021-07-11 00:00:22 +02:00
# SPDX-License-Identifier: MIT
License project under MPL2 This patch adds the file LICENSE which is the verbatim copy of the Mozilla Public License Version 2.0 as retreived from https://www.mozilla.org/media/MPL/2.0/index.815ca599c9df.txt on 2017-03-05. This patch also places license headers for the MPL2 type A variant of the license header in the following files: PBot/AntiFlood.pm PBot/BanTracker.pm PBot/BlackList.pm PBot/BotAdminCommands.pm PBot/BotAdmins.pm PBot/ChanOpCommands.pm PBot/ChanOps.pm PBot/Channels.pm PBot/Commands.pm PBot/DualIndexHashObject.pm PBot/EventDispatcher.pm PBot/FactoidCommands.pm PBot/FactoidModuleLauncher.pm PBot/Factoids.pm PBot/HashObject.pm PBot/IRCHandlers.pm PBot/IgnoreList.pm PBot/IgnoreListCommands.pm PBot/Interpreter.pm PBot/LagChecker.pm PBot/Logger.pm PBot/MessageHistory.pm PBot/MessageHistory_SQLite.pm PBot/NickList.pm PBot/PBot.pm PBot/Plugins.pm PBot/Plugins/AntiAway.pm PBot/Plugins/AntiKickAutoRejoin.pm PBot/Plugins/AntiRepeat.pm PBot/Plugins/AntiTwitter.pm PBot/Plugins/AutoRejoin.pm PBot/Plugins/Counter.pm PBot/Plugins/Quotegrabs.pm PBot/Plugins/Quotegrabs/Quotegrabs_Hashtable.pm PBot/Plugins/Quotegrabs/Quotegrabs_SQLite.pm PBot/Plugins/UrlTitles.pm PBot/Plugins/_Example.pm PBot/Refresher.pm PBot/Registerable.pm PBot/Registry.pm PBot/RegistryCommands.pm PBot/SQLiteLogger.pm PBot/SQLiteLoggerLayer.pm PBot/SelectHandler.pm PBot/StdinReader.pm PBot/Timer.pm PBot/Utils/ParseDate.pm PBot/VERSION.pm build/update-version.pl modules/acronym.pl modules/ago.pl modules/c11std.pl modules/c2english.pl modules/c2english/CGrammar.pm modules/c2english/c2eng.pl modules/c99std.pl modules/cdecl.pl modules/cfaq.pl modules/cjeopardy/IRCColors.pm modules/cjeopardy/QStatskeeper.pm modules/cjeopardy/Scorekeeper.pm modules/cjeopardy/cjeopardy.pl modules/cjeopardy/cjeopardy_answer.pl modules/cjeopardy/cjeopardy_filter.pl modules/cjeopardy/cjeopardy_hint.pl modules/cjeopardy/cjeopardy_qstats.pl modules/cjeopardy/cjeopardy_scores.pl modules/cjeopardy/cjeopardy_show.pl modules/codepad.pl modules/compiler_block.pl modules/compiler_client.pl modules/compiler_vm/Diff.pm modules/compiler_vm/cc modules/compiler_vm/compiler_client.pl modules/compiler_vm/compiler_server.pl modules/compiler_vm/compiler_server_vbox_win32.pl modules/compiler_vm/compiler_server_watchdog.pl modules/compiler_vm/compiler_vm_client.pl modules/compiler_vm/compiler_vm_server.pl modules/compiler_vm/compiler_watchdog.pl modules/compiler_vm/languages/_c_base.pm modules/compiler_vm/languages/_default.pm modules/compiler_vm/languages/bash.pm modules/compiler_vm/languages/bc.pm modules/compiler_vm/languages/bf.pm modules/compiler_vm/languages/c11.pm modules/compiler_vm/languages/c89.pm modules/compiler_vm/languages/c99.pm modules/compiler_vm/languages/clang.pm modules/compiler_vm/languages/clang11.pm modules/compiler_vm/languages/clang89.pm modules/compiler_vm/languages/clang99.pm modules/compiler_vm/languages/clangpp.pm modules/compiler_vm/languages/clisp.pm modules/compiler_vm/languages/cpp.pm modules/compiler_vm/languages/freebasic.pm modules/compiler_vm/languages/go.pm modules/compiler_vm/languages/haskell.pm modules/compiler_vm/languages/java.pm modules/compiler_vm/languages/javascript.pm modules/compiler_vm/languages/ksh.pm modules/compiler_vm/languages/lua.pm modules/compiler_vm/languages/perl.pm modules/compiler_vm/languages/python.pm modules/compiler_vm/languages/python3.pm modules/compiler_vm/languages/qbasic.pm modules/compiler_vm/languages/scheme.pm modules/compiler_vm/languages/server/_c_base.pm modules/compiler_vm/languages/server/_default.pm modules/compiler_vm/languages/server/c11.pm modules/compiler_vm/languages/server/c89.pm modules/compiler_vm/languages/server/c99.pm modules/compiler_vm/languages/server/clang.pm modules/compiler_vm/languages/server/clang11.pm modules/compiler_vm/languages/server/clang89.pm modules/compiler_vm/languages/server/clang99.pm modules/compiler_vm/languages/server/cpp.pm modules/compiler_vm/languages/server/freebasic.pm modules/compiler_vm/languages/server/haskell.pm modules/compiler_vm/languages/server/java.pm modules/compiler_vm/languages/server/qbasic.pm modules/compiler_vm/languages/server/tendra.pm modules/compiler_vm/languages/sh.pm modules/compiler_vm/languages/tendra.pm modules/compliment modules/cstd.pl modules/define.pl modules/dice_roll.pl modules/excuse.sh modules/expand_macros.pl modules/fnord.pl modules/funnyish_quote.pl modules/g.pl modules/gdefine.pl modules/gen_cfacts.pl modules/gencstd.pl modules/get_title.pl modules/getcfact.pl modules/google.pl modules/gspy.pl modules/gtop10.pl modules/gtop15.pl modules/headlines.pl modules/horoscope modules/horrorscope modules/ideone.pl modules/insult.pl modules/love_quote.pl modules/man.pl modules/map.pl modules/math.pl modules/prototype.pl modules/qalc.pl modules/random_quote.pl modules/seen.pl modules/urban modules/weather.pl modules/wikipedia.pl pbot.pl pbot.sh It is highly recommended that this list of files is reviewed to ensure that all files are the copyright of the sole maintainer of the repository. If any files with license headers contain the intellectual property of anyone else, it is recommended that a request is made to revise this patch or that the explicit permission of the co-author is gained to allow for the license of the work to be changed. I (Tomasz Kramkowski), the contributor, take no responsibility for any legal action taken against the maintainer of this repository for incorrectly claiming copyright to any work not owned by the maintainer of this repository.
2017-03-05 22:33:31 +01:00
2021-07-24 04:22:25 +02:00
package PBot::Core::Utils::ParseDate;
use PBot::Imports;
use DateTime;
use DateTime::Format::Flexible;
use DateTime::Format::Duration;
sub new {
2020-02-15 23:38:32 +01:00
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref $_[1] eq 'HASH';
my ($class, %args) = @_;
2020-02-15 23:38:32 +01:00
my $self = bless {}, $class;
$self->initialize(%args);
2020-02-15 23:38:32 +01:00
return $self;
}
sub initialize {
2020-02-15 23:38:32 +01:00
my ($self, %conf) = @_;
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
}
# expands stuff like "7d3h" to "7 days and 3 hours"
sub unconcise {
2020-02-15 23:38:32 +01:00
my ($input) = @_;
my %word = (y => 'years', w => 'weeks', d => 'days', h => 'hours', m => 'minutes', s => 'seconds');
$input =~ s/(\d+)([ywdhms])(?![a-z])/"$1 " . $word{lc $2} . ' and '/ige;
$input =~ s/ and $//;
return $input;
}
# parses English natural language date strings into seconds
# does not accept times or dates in the past
sub parsedate {
2020-02-15 23:38:32 +01:00
my ($self, $input) = @_;
my $examples = "Try `30s`, `1h30m`, `tomorrow`, `next monday`, `9:30am pdt`, `11pm utc`, etc.";
my $attempts = 0;
my $original_input = $input;
2020-02-15 23:38:32 +01:00
my $override = "";
TRY_AGAIN:
2020-02-15 23:38:32 +01:00
$input = "$override$input" if length $override;
return (0, "Could not parse `$original_input`. $examples") if ++$attempts > 10;
2020-02-15 23:38:32 +01:00
# expand stuff like 7d3h
$input = unconcise($input);
# some aliases
$input =~ s/\bsecs?\b/seconds/g;
$input =~ s/\bmins?\b/minutes/g;
$input =~ s/\bhrs?\b/hours/g;
$input =~ s/\bwks?\b/weeks/g;
$input =~ s/\byrs?\b/years/g;
$input =~ s/\butc\b/gmt/g;
2020-02-15 23:38:32 +01:00
# sanitizers
$input =~ s/\b(\d+)\s+(am?|pm?)\b/$1$2/; # remove leading spaces from am/pm
$input =~ s/ (\d+)(am?|pm?)\b/ $1:00:00$2/; # convert 3pm to 3:00:00pm
$input =~ s/ (\d+:\d+)(am?|pm?)\b/ $1:00$2/; # convert 4:20pm to 4:20:00pm
$input =~
s/next (jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|june?|july?|aug(?:ust)?|sept(?:ember)?|oct(?:ober)?|nov(?:ember)|dec(?:ember)?) (\d+)(?:st|nd|rd|th)?(.*)/"next $1 and " . ($2 - 1) . " days" . (length $3 ? " and $3" : "")/ie;
# split input on "and" or comma, then we'll add up the results
# this allows us to parse things like "1 hour and 30 minutes"
my @inputs = split /(?:,?\s+and\s+|\s*,\s*|\s+at\s+)/, $input;
# adjust timezone to user-override if user provides a timezone
# we won't know if a timezone was provided until it is parsed
my $timezone;
my $tz_override = 'UTC';
ADJUST_TIMEZONE:
2020-02-15 23:38:32 +01:00
$timezone = $tz_override;
my $now = DateTime->now(time_zone => $timezone);
my $seconds = 0;
my ($to, $base);
foreach my $input (@inputs) {
return -1 if $input =~ m/forever/i;
$input .= ' seconds' if $input =~ m/^\s*\d+\s*$/;
# DateTime::Format::Flexible doesn't support seconds, but that's okay;
# we can take care of that easily here!
if ($input =~ m/^\s*(\d+)\s+seconds$/) {
$seconds += $1;
next;
}
# adjust base
if (defined $to) {
$base = $to->clone;
$base->set_time_zone($timezone);
} else {
$base = $now;
}
# First, attempt to parse as-is...
$to = eval { return DateTime::Format::Flexible->parse_datetime($input, lang => ['en'], base => $base); };
# If there was an error, then append "from now" and attempt to parse as a relative time...
if ($@) {
$input .= ' from now';
$to = eval { return DateTime::Format::Flexible->parse_datetime($input, lang => ['en'], base => $base); };
# If there's still an error, it's bad input
if (my $error = $@) {
$error =~ s/ ${override}from now at .*$//;
$error =~ s/\s*$/. $examples/;
return (0, $error);
2020-02-15 23:38:32 +01:00
}
}
# there was a timezone parsed, set the tz override and try again
if ($to->time_zone_short_name ne 'floating' and $to->time_zone_short_name ne 'UTC' and $tz_override eq 'UTC') {
$tz_override = $to->time_zone_long_name;
$to = undef;
2020-02-15 23:38:32 +01:00
goto ADJUST_TIMEZONE;
}
$to->set_time_zone('UTC');
$base->set_time_zone('UTC');
my $duration = $to->subtract_datetime_absolute($base);
# If the time is in the past, prepend "tomorrow" or "next" and reparse
if ($duration->is_negative) {
if ($input =~ m/^\d/) {
$override = "tomorrow ";
} else {
$override = "next ";
}
2020-02-15 23:38:32 +01:00
$to = undef;
goto TRY_AGAIN;
}
# add the seconds from this input chunk
$seconds += $duration->seconds;
}
2020-02-15 23:38:32 +01:00
return $seconds;
}
1;