Skip to content

Commit

Permalink
Adjust date format for "When" parameters to correspond to system locale
Browse files Browse the repository at this point in the history
  • Loading branch information
dbarnett committed Sep 17, 2024
1 parent da09b72 commit 95c2f5e
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 8 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ v4.5.0
w/ or w/o --config-folder
- POSSIBLE ACTION REQUIRED: Use `@path/to/gcalclirc` explicitly if it stops
reading an rc file you needed
* Determine date format to use based on system locale's in "When" inputs
* Respect locally-installed certificates (ajkessel)
* Re-add a `--noauth_local_server` to provide instructions for authenticating
from a remote system using port forwarding
Expand Down
5 changes: 3 additions & 2 deletions gcalcli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from .gcal import GoogleCalendarInterface
from .printer import Printer, valid_color_name
from .validators import (
DATETIME_INPUT_DESCRIPTION,
get_date_input_description,
get_input,
PARSABLE_DATE,
PARSABLE_DURATION,
Expand Down Expand Up @@ -115,11 +115,12 @@ def run_add_prompt(parsed_args, printer):
if parsed_args.where is None:
parsed_args.where = get_input(printer, 'Location: ', STR_ALLOW_EMPTY)
if parsed_args.when is None:
date_format_desc = get_date_input_description()
parsed_args.when = get_input(
printer,
'When (? for help): ',
PARSABLE_DATE,
help=f'Expected format: {DATETIME_INPUT_DESCRIPTION}',
help=f'Expected format: {date_format_desc}',
)
if parsed_args.duration is None and parsed_args.end is None:
if parsed_args.allday:
Expand Down
25 changes: 24 additions & 1 deletion gcalcli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from datetime import datetime, timedelta
from typing import Tuple

import babel
from dateutil.parser import parse as dateutil_parse
from dateutil.tz import tzlocal
from parsedatetime.parsedatetime import Calendar
Expand Down Expand Up @@ -85,6 +86,26 @@ def get_times_from_duration(
return start.isoformat(), stop.isoformat()


def is_dayfirst_locale():
"""Detect whether system locale date format has day first.
Examples:
- M/d/yy -> False
- dd/MM/yy -> True
- (UnknownLocaleError) -> False
Pattern syntax is documented at
https://babel.pocoo.org/en/latest/dates.html#pattern-syntax.
"""
try:
locale = babel.Locale(babel.default_locale('LC_TIME'))
except babel.UnknownLocaleError:
# Couldn't detect locale, assume non-dayfirst.
return False
m = re.search(r'M|d|$', locale.date_formats['short'].pattern)
return m and m.group(0) == 'd'


def get_time_from_str(when):
"""Convert a string to a time: first uses the dateutil parser, falls back
on fuzzy matching with parsedatetime
Expand All @@ -93,7 +114,9 @@ def get_time_from_str(when):
hour=0, minute=0, second=0, microsecond=0)

try:
event_time = dateutil_parse(when, default=zero_oclock_today)
event_time = dateutil_parse(
when, default=zero_oclock_today, dayfirst=is_dayfirst_locale()
)
except ValueError:
struct, result = fuzzy_date_parse(when)
if not result:
Expand Down
20 changes: 15 additions & 5 deletions gcalcli/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
from typing import Optional

from .exceptions import ValidationError
from .utils import get_time_from_str, get_timedelta_from_str, REMINDER_REGEX
from .utils import (
get_time_from_str,
get_timedelta_from_str,
REMINDER_REGEX,
is_dayfirst_locale,
)

# TODO: in the future, pull these from the API
# https://developers.google.com/calendar/v3/reference/colors
VALID_OVERRIDE_COLORS = ['lavender', 'sage', 'grape', 'flamingo',
'banana', 'tangerine', 'peacock', 'graphite',
'blueberry', 'basil', 'tomato']

DATETIME_INPUT_DESCRIPTION = 'a date (e.g. 2019-12-31, tomorrow 10am, 2nd Jan, \
Jan 4th, etc) or valid time if today'


def get_override_color_id(color):
return str(VALID_OVERRIDE_COLORS.index(color) + 1)
Expand Down Expand Up @@ -66,6 +68,13 @@ def str_to_int_validator(input_str):
)


def get_date_input_description():
dayfirst = is_dayfirst_locale()
sample_date = '2019-31-12' if dayfirst else '2019-12-31'
return f'a date (e.g. {sample_date}, tomorrow 10am, 2nd Jan, Jan 4th, etc) \
or valid time if today'


def parsable_date_validator(input_str):
"""
A filter allowing any string which can be parsed
Expand All @@ -76,8 +85,9 @@ def parsable_date_validator(input_str):
get_time_from_str(input_str)
return input_str
except ValueError:
format_desc = get_date_input_description()
raise ValidationError(
f'Expected format: {DATETIME_INPUT_DESCRIPTION}. '
f'Expected format: {format_desc}. '
'(Ctrl-C to exit)\n'
)

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ classifiers = [
]
dependencies = [
"argcomplete",
"babel",
"google-api-python-client>=1.4",
"google_auth_oauthlib",
"httplib2",
Expand Down

0 comments on commit 95c2f5e

Please sign in to comment.