Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/Objects/Item.vala
Original file line number Diff line number Diff line change
Expand Up @@ -1172,7 +1172,7 @@ public class Objects.Item : Objects.BaseObject {
Array<short> values = new Array<short> ();
short val;

if (due.recurrency_weeks.split (",").length > 0) {
if (due.recurrency_weeks != null && due.recurrency_weeks.split (",").length > 0) {
if (due.recurrency_weeks.contains ("1")) {
val = (short) 2;
values.append_val (val);
Expand Down
48 changes: 11 additions & 37 deletions core/Services/Chrono/Chrono.vala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Alain M. (https://github.com/alainm23/planify)
* Copyright © 2025 Alain M. (https://github.com/alainm23/planify)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
Expand All @@ -18,43 +18,17 @@
*
* Authored by: Alain M. <[email protected]>
*/

public class Chrono.Parse : GLib.Object {
public string lang { get; construct; }

private Gee.HashMap<string, Chrono.Configuration> configurations;
private Chrono.Configuration configuration;

public Parse (string lang = "en") {
Object (
lang: lang
);
}

construct {
configurations = new Gee.HashMap<string, Chrono.Configuration> ();
configurations.set ("en", new Chrono.En.Parse ());

if (configurations.has_key (lang)) {
configuration = configurations.get (lang);
} else {
configuration = new Chrono.En.Parse ();
}
}

public void parse (string text) {
Gee.ArrayList<Chrono.ParsingResult> results = new Gee.ArrayList<Chrono.ParsingResult> ();

GLib.MatchInfo match;
foreach (Chrono.AbstractParser parser in configuration.parsers) {
if (parser.inner_pattern ().match_all (text, 0, out match)) {
Chrono.ParsingResult result = parser.inner_extract (match);
results.add (result);
}

namespace Chrono {
public class Chrono : Object {
private Parser parser;

public Chrono () {
parser = new Parser ();
}

foreach (Chrono.ParsingResult result in results) {
print ("Date: %s\n", result.datetime.to_string ());
public ParseResult? parse (string text, string language = "en", bool parse_recurrence = false) {
return parser.parse (text, language, parse_recurrence);
}
}
}
86 changes: 86 additions & 0 deletions core/Services/Chrono/Common/ISOParser.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright © 2025 Alain M. (https://github.com/alainm23/planify)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* Authored by: Alain M. <[email protected]>
*/

namespace Chrono {
public class ISOParser : Object {
private Regex iso_regex;

// ISO 8601
// http://www.w3.org/TR/NOTE-datetime
// - YYYY-MM-DD
// - YYYY-MM-DDThh:mmTZD
// - YYYY-MM-DDThh:mm:ssTZD
// - YYYY-MM-DDThh:mm:ss.sTZD
// - TZD = (Z or +hh:mm or -hh:mm)

public ISOParser () {
try {
iso_regex = new Regex (
"(\\d{4})-(\\d{2})-(\\d{2})" +
"(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d+))?)?" +
"(Z|[+-]\\d{2}:\\d{2})?)?",
RegexCompileFlags.CASELESS
);
} catch (Error e) {
warning ("Error creating ISO regex: %s", e.message);
}
}

public ParseResult? parse (string text) {
try {
MatchInfo match;
if (!iso_regex.match (text, 0, out match)) {
return null;
}

int year = int.parse (match.fetch (1));
int month = int.parse (match.fetch (2));
int day = int.parse (match.fetch (3));

int hour = 0, minute = 0, second = 0;
string? time_part = match.fetch (4);

if (time_part != null && time_part.length > 0) {
hour = int.parse (time_part);
minute = int.parse (match.fetch (5));

string? sec = match.fetch (6);
if (sec != null && sec.length > 0) {
second = int.parse (sec);
}
}

var result = new ParseResult ();
result.date = new DateTime.local (year, month, day, hour, minute, second);

int start_pos, end_pos;
match.fetch_pos (0, out start_pos, out end_pos);
result.start_index = start_pos;
result.end_index = end_pos;
result.matched_text = match.fetch (0);

return result;
} catch (Error e) {
return null;
}
}
}
}
80 changes: 80 additions & 0 deletions core/Services/Chrono/Common/SlashParser.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright © 2025 Alain M. (https://github.com/alainm23/planify)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* Authored by: Alain M. <[email protected]>
*/

namespace Chrono {
public class SlashParser : Object {
private Regex slash_regex;

// Date format with slash "/" (or dot ".") between numbers.
// For examples:
// - 7/10
// - 7/12/2020
// - 7.12.2020

public SlashParser () {
try {
// Matches: d/m, d/m/y, d.m, d.m.y
slash_regex = new Regex (
"(\\d{1,2})[/\\.](\\d{1,2})(?:[/\\.](\\d{2,4}))?",
RegexCompileFlags.CASELESS
);
} catch (Error e) {
warning ("Error creating slash regex: %s", e.message);
}
}

public ParseResult? parse (string text) {
try {
MatchInfo match;
if (!slash_regex.match (text, 0, out match)) {
return null;
}

int day = int.parse (match.fetch (1));
int month = int.parse (match.fetch (2));

var now = new DateTime.now_local ();
int year = now.get_year ();

string? year_str = match.fetch (3);
if (year_str != null && year_str.length > 0) {
year = int.parse (year_str);
if (year < 100) {
year += 2000;
}
}

var result = new ParseResult ();
result.date = new DateTime.local (year, month, day, 0, 0, 0);

int start_pos, end_pos;
match.fetch_pos (0, out start_pos, out end_pos);
result.start_index = start_pos;
result.end_index = end_pos;
result.matched_text = match.fetch (0);

return result;
} catch (Error e) {
return null;
}
}
}
}
146 changes: 146 additions & 0 deletions core/Services/Chrono/Common/TimeParser.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright © 2025 Alain M. (https://github.com/alainm23/planify)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* Authored by: Alain M. <[email protected]>
*/

namespace Chrono {
/**
* Time format parser (language independent)
*
* For examples:
* - 14:30
* - 23:45
* - 2:30pm
* - 3pm
* - 15:00
*/
public class TimeParser : Object {
private Regex time_24h_regex;
private Regex time_12h_regex;

public TimeParser () {
try {
// 24-hour format: 14:30, 23:45
time_24h_regex = new Regex (
"(\\d{1,2}):(\\d{2})",
RegexCompileFlags.CASELESS
);

// 12-hour format: 2:30pm, 3pm, 11:45am
time_12h_regex = new Regex (
"(\\d{1,2})(?::(\\d{2}))?\\s*(am|pm)",
RegexCompileFlags.CASELESS
);
} catch (Error e) {
warning ("Error creating time regex: %s", e.message);
}
}

public ParseResult? parse (string text) {
var result = parse_12h (text);
if (result != null) {
return result;
}

return parse_24h (text);
}

private ParseResult? parse_12h (string text) {
try {
MatchInfo match;
if (!time_12h_regex.match (text, 0, out match)) {
return null;
}

int hour = int.parse (match.fetch (1));
int minute = 0;

string? min_str = match.fetch (2);
if (min_str != null && min_str.length > 0) {
minute = int.parse (min_str);
}

string period = match.fetch (3).down ();
if (period == "pm" && hour < 12) {
hour += 12;
} else if (period == "am" && hour == 12) {
hour = 0;
}

var now = new DateTime.now_local ();
var result = new ParseResult ();
result.date = new DateTime.local (
now.get_year (),
now.get_month (),
now.get_day_of_month (),
hour,
minute,
0
);

int start_pos, end_pos;
match.fetch_pos (0, out start_pos, out end_pos);
result.start_index = start_pos;
result.end_index = end_pos;
result.matched_text = match.fetch (0);

return result;
} catch (Error e) {
return null;
}
}

private ParseResult? parse_24h (string text) {
try {
MatchInfo match;
if (!time_24h_regex.match (text, 0, out match)) {
return null;
}

int hour = int.parse (match.fetch (1));
int minute = int.parse (match.fetch (2));

if (hour > 23 || minute > 59) {
return null;
}

var now = new DateTime.now_local ();
var result = new ParseResult ();
result.date = new DateTime.local (
now.get_year (),
now.get_month (),
now.get_day_of_month (),
hour,
minute,
0
);

int start_pos, end_pos;
match.fetch_pos (0, out start_pos, out end_pos);
result.start_index = start_pos;
result.end_index = end_pos;
result.matched_text = match.fetch (0);

return result;
} catch (Error e) {
return null;
}
}
}
}
Empty file removed core/Services/Chrono/Enum.vala
Empty file.
Loading