3030import static com .oracle .truffle .r .runtime .builtins .RBuiltinKind .INTERNAL ;
3131
3232import java .text .ParsePosition ;
33+ import java .time .Clock ;
3334import java .time .DateTimeException ;
3435import java .time .Instant ;
3536import java .time .LocalDate ;
3637import java .time .LocalDateTime ;
38+ import java .time .LocalTime ;
3739import java .time .ZoneId ;
3840import java .time .ZoneOffset ;
3941import java .time .ZonedDateTime ;
42+ import java .time .chrono .Chronology ;
43+ import java .time .chrono .ChronoLocalDate ;
44+ import java .time .chrono .ChronoLocalDateTime ;
45+ import java .time .chrono .ChronoPeriod ;
46+ import java .time .chrono .ChronoZonedDateTime ;
47+ import java .time .chrono .Era ;
48+ import java .time .chrono .IsoChronology ;
4049import java .time .format .DateTimeFormatter ;
4150import java .time .format .DateTimeFormatterBuilder ;
4251import java .time .format .DateTimeParseException ;
4352import java .time .format .FormatStyle ;
53+ import java .time .format .ResolverStyle ;
54+ import java .time .format .SignStyle ;
4455import java .time .format .TextStyle ;
4556import java .time .temporal .ChronoField ;
4657import java .time .temporal .TemporalAccessor ;
58+ import java .time .temporal .TemporalField ;
59+ import java .time .temporal .ValueRange ;
4760import java .util .HashMap ;
61+ import java .util .List ;
4862import java .util .Locale ;
63+ import java .util .Map ;
4964import java .util .TimeZone ;
5065
5166import com .oracle .truffle .api .CompilerDirectives .TruffleBoundary ;
7085import com .oracle .truffle .r .runtime .data .model .RAbstractListVector ;
7186import com .oracle .truffle .r .runtime .data .model .RAbstractStringVector ;
7287import com .oracle .truffle .r .runtime .data .model .RAbstractVector ;
73- import java .time .LocalTime ;
88+ import java .time .Month ;
7489
7590// from GnuR datatime.c
7691
@@ -435,7 +450,7 @@ protected RList strptime(RAbstractStringVector x, RAbstractStringVector format,
435450 DateTimeFormatterBuilder [] builders = createFormatters (format , true );
436451 DateTimeFormatter [] formatters = new DateTimeFormatter [builders .length ];
437452 for (int i = 0 ; i < builders .length ; i ++) {
438- formatters [i ] = builders [i ].toFormatter ();
453+ formatters [i ] = builders [i ].toFormatter (). withChronology ( LeapYearChronology . INSTANCE ) ;
439454 }
440455
441456 for (int i = 0 ; i < length ; i ++) {
@@ -485,7 +500,7 @@ private static DateTimeFormatterBuilder[] createFormatters(RAbstractStringVector
485500 private static DateTimeFormatterBuilder createFormatter (String format , boolean forInput ) {
486501 DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder ();
487502 if (forInput ) {
488- // Opposite of STRICT mode; allows to parse single-digit hour and minute like '0:3:22'.
503+ // Lenient parsing required to parse datetimes like "2002-6-24-0-0-10"
489504 builder .parseLenient ();
490505 }
491506 boolean escaped = false ;
@@ -564,7 +579,11 @@ private static DateTimeFormatterBuilder createFormatter(String format, boolean f
564579 * 24:00:00 are accepted for input, since ISO 8601 allows these. For output
565580 * 00-23 is required thus using HOUR_OF_DAY.
566581 */
567- builder .appendValue (forInput ? ChronoField .CLOCK_HOUR_OF_DAY : ChronoField .HOUR_OF_DAY , 2 );
582+ if (forInput ) {
583+ builder .appendValue (ChronoField .CLOCK_HOUR_OF_DAY , 1 , 2 , SignStyle .NOT_NEGATIVE );
584+ } else {
585+ builder .appendValue (ChronoField .HOUR_OF_DAY , 2 );
586+ }
568587 break ;
569588 case 'I' :
570589 // Hours as decimal number (01–12).
@@ -580,7 +599,11 @@ private static DateTimeFormatterBuilder createFormatter(String format, boolean f
580599 break ;
581600 case 'M' :
582601 // Minute as decimal number (00–59).
583- builder .appendValue (ChronoField .MINUTE_OF_HOUR , 2 );
602+ if (forInput ) {
603+ builder .appendValue (ChronoField .MINUTE_OF_HOUR , 1 , 2 , SignStyle .NOT_NEGATIVE );
604+ } else {
605+ builder .appendValue (ChronoField .MINUTE_OF_HOUR , 2 );
606+ }
584607 break ;
585608 case 'n' :
586609 // Newline on output, arbitrary whitespace on input.
@@ -625,16 +648,21 @@ private static DateTimeFormatterBuilder createFormatter(String format, boolean f
625648 * Second as decimal number (00–61), allowing for up to two leap-seconds
626649 * (but POSIX-compliant implementations will ignore leap seconds).
627650 */
628- builder .appendValue (ChronoField .SECOND_OF_MINUTE , 2 );
651+ if (forInput ) {
652+ builder .appendValue (ChronoField .SECOND_OF_MINUTE , 1 , 2 , SignStyle .NOT_NEGATIVE );
653+ } else {
654+ builder .appendValue (ChronoField .SECOND_OF_MINUTE , 2 );
655+ }
629656 break ;
630657 case 't' :
631658 // Tab on output, arbitrary whitespace on input.
632659 builder .appendLiteral ('\t' );
633660 break ;
634661 case 'T' :
635662 // Equivalent to %H:%M:%S.
636- builder .appendValue (ChronoField .CLOCK_HOUR_OF_DAY , 2 ).appendLiteral (':' ).appendValue (ChronoField .MINUTE_OF_HOUR , 2 ).appendLiteral (':' ).appendValue (
637- ChronoField .SECOND_OF_MINUTE , 2 );
663+ builder .appendValue (ChronoField .CLOCK_HOUR_OF_DAY , forInput ? 1 : 2 ).appendLiteral (':' ).appendValue (ChronoField .MINUTE_OF_HOUR , forInput ? 1 : 2 ).appendLiteral (
664+ ':' ).appendValue (
665+ ChronoField .SECOND_OF_MINUTE , forInput ? 1 : 2 );
638666 break ;
639667 case 'u' :
640668 // Weekday as a decimal number (1–7, Monday is 1).
@@ -786,4 +814,165 @@ private static String getTimeZomeFromAttribute(RAbstractListVector x) {
786814 }
787815 return zone ;
788816 }
817+
818+ private static final class LeapYearChronology implements Chronology {
819+
820+ static LeapYearChronology INSTANCE = new LeapYearChronology (IsoChronology .INSTANCE );
821+
822+ private static final int [] maxDayOfMonths = new int []{31 , 29 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 };
823+
824+ private final Chronology delegate ;
825+
826+ LeapYearChronology (Chronology delegate ) {
827+ this .delegate = delegate ;
828+ }
829+
830+ @ Override
831+ public String getId () {
832+ return delegate .getId ();
833+ }
834+
835+ @ Override
836+ public String getCalendarType () {
837+ return delegate .getCalendarType ();
838+ }
839+
840+ @ Override
841+ public ChronoLocalDate date (Era era , int yearOfEra , int month , int dayOfMonth ) {
842+ return delegate .date (era , yearOfEra , month , dayOfMonth );
843+ }
844+
845+ @ Override
846+ public ChronoLocalDate date (int prolepticYear , int month , int dayOfMonth ) {
847+ return delegate .date (prolepticYear , month , dayOfMonth );
848+ }
849+
850+ @ Override
851+ public ChronoLocalDate dateYearDay (Era era , int yearOfEra , int dayOfYear ) {
852+ return delegate .dateYearDay (era , yearOfEra , dayOfYear );
853+ }
854+
855+ @ Override
856+ public ChronoLocalDate dateYearDay (int prolepticYear , int dayOfYear ) {
857+ return delegate .dateYearDay (prolepticYear , dayOfYear );
858+ }
859+
860+ @ Override
861+ public ChronoLocalDate dateEpochDay (long epochDay ) {
862+ return delegate .dateEpochDay (epochDay );
863+ }
864+
865+ @ Override
866+ public ChronoLocalDate dateNow () {
867+ return delegate .dateNow ();
868+ }
869+
870+ @ Override
871+ public ChronoLocalDate dateNow (ZoneId zone ) {
872+ return delegate .dateNow (zone );
873+ }
874+
875+ @ Override
876+ public ChronoLocalDate dateNow (Clock clock ) {
877+ return delegate .dateNow (clock );
878+ }
879+
880+ @ Override
881+ public ChronoLocalDate date (TemporalAccessor temporal ) {
882+ return delegate .date (temporal );
883+ }
884+
885+ @ Override
886+ public ChronoLocalDateTime <? extends ChronoLocalDate > localDateTime (TemporalAccessor temporal ) {
887+ return delegate .localDateTime (temporal );
888+ }
889+
890+ @ Override
891+ public ChronoZonedDateTime <? extends ChronoLocalDate > zonedDateTime (TemporalAccessor temporal ) {
892+ return delegate .zonedDateTime (temporal );
893+ }
894+
895+ @ Override
896+ public ChronoZonedDateTime <? extends ChronoLocalDate > zonedDateTime (Instant instant , ZoneId zone ) {
897+ return delegate .zonedDateTime (instant , zone );
898+ }
899+
900+ @ Override
901+ public boolean isLeapYear (long prolepticYear ) {
902+ return delegate .isLeapYear (prolepticYear );
903+ }
904+
905+ @ Override
906+ public int prolepticYear (Era era , int yearOfEra ) {
907+ return delegate .prolepticYear (era , yearOfEra );
908+ }
909+
910+ @ Override
911+ public Era eraOf (int eraValue ) {
912+ return delegate .eraOf (eraValue );
913+ }
914+
915+ @ Override
916+ public List <Era > eras () {
917+ return delegate .eras ();
918+ }
919+
920+ @ Override
921+ public ValueRange range (ChronoField field ) {
922+ return delegate .range (field );
923+ }
924+
925+ @ Override
926+ public String getDisplayName (TextStyle style , Locale locale ) {
927+ return delegate .getDisplayName (style , locale );
928+ }
929+
930+ @ Override
931+ public ChronoLocalDate resolveDate (Map <TemporalField , Long > fieldValues , ResolverStyle resolverStyle ) {
932+ // date(int prolepticYear, int month, int dayOfMonth) not called -> handle here
933+ Long day = fieldValues .get (ChronoField .DAY_OF_MONTH );
934+ if (day != null && day >= 29 ) {
935+ Long month = fieldValues .get (ChronoField .MONTH_OF_YEAR );
936+ if (month != null && month <= 12 ) {
937+ if (month == 2 && day == 29 ) {
938+ Long year = fieldValues .get (ChronoField .YEAR );
939+ if (year != null && !isLeapYear (year )) {
940+ throw new DateTimeException ("Invalid date 'February 29' as '" + year + "' is not a leap year" );
941+ }
942+ } else {
943+ int monthInt = (int ) (long ) month ;
944+ if (day > maxDayOfMonths [(monthInt ) - 1 ]) {
945+ throw new DateTimeException ("Invalid date '" + Month .of (monthInt ).name () + " " + day + "'" );
946+ }
947+ }
948+ }
949+ }
950+ return delegate .resolveDate (fieldValues , resolverStyle );
951+ }
952+
953+ @ Override
954+ public ChronoPeriod period (int years , int months , int days ) {
955+ return delegate .period (years , months , days );
956+ }
957+
958+ @ Override
959+ public int compareTo (Chronology other ) {
960+ return delegate .compareTo (other );
961+ }
962+
963+ @ Override
964+ public boolean equals (Object obj ) {
965+ return delegate .equals (obj );
966+ }
967+
968+ @ Override
969+ public int hashCode () {
970+ return delegate .hashCode ();
971+ }
972+
973+ @ Override
974+ public String toString () {
975+ return delegate .toString ();
976+ }
977+ }
789978}
0 commit comments