@ -3,8 +3,8 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import datetime
import os
import re
import sys
from typing import Union
@ -16,10 +16,56 @@ _ROOT_DIR = os.path.abspath(os.path.join(_THIS_DIR, "..", "..", ".."))
sys . path . insert ( 0 , _ROOT_DIR )
import metadata . fields . field_types as field_types
import metadata . fields . util as util
import metadata . validation_result as vr
_PATTERN_DATE = re . compile ( r " ^ \ d {4} -(0|1) \ d-[0-3] \ d$ " )
# The preferred date format for the start of date values.
_PREFERRED_PREFIX_FORMAT = " % Y- % m- %d "
# Formats for the start of date values that are recognized as
# alternative date formats.
_RECOGNIZED_PREFIX_FORMATS = (
" %d - % m- % Y " ,
" % m- %d - % Y " ,
" %d - % m- % y " ,
" % m- %d - % y " ,
" %d / % m/ % Y " ,
" % m/ %d / % Y " ,
" %d / % m/ % y " ,
" % m/ %d / % y " ,
" %d . % m. % Y " ,
" % m. %d . % Y " ,
" %d . % m. % y " ,
" % m. %d . % y " ,
" % Y/ % m/ %d " ,
" % Y. % m. %d " ,
" % Y % m %d " ,
)
# Formats recognized as alternative date formats (entire value must
# match).
_RECOGNIZED_DATE_FORMATS = (
" %d % b % Y " ,
" %d % b, % Y " ,
" % b %d % Y " ,
" % b %d , % Y " ,
" % Y % b %d " ,
" %d % B % Y " ,
" %d % B, % Y " ,
" % B %d % Y " ,
" % B %d , % Y " ,
" % Y % B %d " ,
" %a % b %d % H: % M: % S % Y " ,
" %a % b %d % H: % M: % S % Y % z " ,
)
def format_matches ( value : str , date_format : str ) :
""" Returns whether the given value matches the date format. """
try :
datetime . datetime . strptime ( value , date_format )
except ValueError :
return False
return True
class DateField ( field_types . MetadataField ) :
@ -29,11 +75,38 @@ class DateField(field_types.MetadataField):
def validate ( self , value : str ) - > Union [ vr . ValidationResult , None ] :
""" Checks the given value is a YYYY-MM-DD date. """
if util . matches ( _PATTERN_DATE , value ) :
value = value . strip ( )
if not value :
return vr . ValidationError (
reason = f " { self . _name } is empty. " ,
additional = [ " Provide date in format YYYY-MM-DD. " ] )
# Check if the first part (to ignore timezone info) uses the
# preferred format.
parts = value . split ( )
if format_matches ( parts [ 0 ] , _PREFERRED_PREFIX_FORMAT ) :
return None
return vr . ValidationError ( reason = f " { self . _name } is invalid. " ,
additional = [
" The correct format is YYYY-MM-DD. " ,
f " Current value is ' { value } ' . " ,
] )
# Check if the first part (to ignore timezone info) uses a
# recognized format.
for prefix_format in _RECOGNIZED_PREFIX_FORMATS :
if format_matches ( parts [ 0 ] , prefix_format ) :
return vr . ValidationWarning (
reason = f " { self . _name } is not in the preferred format. " ,
additional = [
" Use YYYY-MM-DD. " , f " Current value is ' { value } ' . "
] )
# Check the entire value for recognized date formats.
for date_format in _RECOGNIZED_DATE_FORMATS :
if format_matches ( value , date_format ) :
return vr . ValidationWarning (
reason = f " { self . _name } is not in the preferred format. " ,
additional = [
" Use YYYY-MM-DD. " , f " Current value is ' { value } ' . "
] )
# Return an error as the value's format was not recognized.
return vr . ValidationError (
reason = f " { self . _name } is invalid. " ,
additional = [ " Use YYYY-MM-DD. " , f " Current value is ' { value } ' . " ] )