RegEx untuk mencocokkan Corak Tarikh di Java

1. Pengenalan

Ungkapan biasa adalah alat yang ampuh untuk memadankan pelbagai jenis corak apabila digunakan dengan tepat.

Dalam artikel ini, kami akan menggunakan pakej java.util.regex untuk menentukan sama ada String yang diberikan mengandungi tarikh yang sah atau tidak.

Untuk pengenalan ungkapan biasa, lihat Panduan kami untuk API Ekspresi Biasa Java.

2. Gambaran Keseluruhan Format Tarikh

Kami akan menentukan tarikh yang sah berkaitan dengan kalendar Gregorian antarabangsa. Format kami akan mengikut corak umum: YYYY-MM-DD.

Mari kita sertakan juga konsep tahun lompat iaitu tahun yang mengandungi hari 29 Februari. Menurut kalendar Gregorian, kita akan memanggil lompatan tahun jika nombor tahun dapat dibahagi sama dengan 4 kecuali bagi yang dapat dibahagi dengan 100 tetapi termasuk yang dapat dibahagi dengan 400 .

Dalam semua kes-kes lain , kami akan memanggil tahun biasa .

Contoh tarikh yang sah:

  • 2017-12-31
  • 2020-02-29
  • 2400-02-29

Contoh tarikh yang tidak sah:

  • 2017/12/31 : pembatas token yang salah
  • 2018-1-1 : kehilangan sifar terkemuka
  • 2018-04-31 : penghitungan hari yang salah untuk bulan April
  • 2100-02-29 : tahun ini tidak melonjak ketika nilainya dibahagi dengan 100 , jadi Februari dibatasi pada 28 hari

3. Melaksanakan Penyelesaian

Oleh kerana kita akan mencocokkan tarikh menggunakan ungkapan biasa, pertama-tama mari kita lakar antara muka DateMatcher , yang menyediakan kaedah padanan tunggal :

public interface DateMatcher { boolean matches(String date); }

Kami akan membentangkan pelaksanaannya langkah demi langkah di bawah ini, menuju penyelesaian yang lengkap pada akhirnya.

3.1. Memadankan Format Luas

Kami akan mulakan dengan membuat prototaip yang sangat mudah menangani batasan format pemadan kami:

class FormattedDateMatcher implements DateMatcher { private static Pattern DATE_PATTERN = Pattern.compile( "^\\d{4}-\\d{2}-\\d{2}$"); @Override public boolean matches(String date) { return DATE_PATTERN.matcher(date).matches(); } }

Di sini kami menyatakan bahawa tarikh yang sah mesti terdiri daripada tiga kumpulan bilangan bulat yang dipisahkan oleh tanda sempang. Kumpulan pertama terdiri daripada empat bilangan bulat, dengan dua kumpulan selebihnya masing-masing mempunyai dua bilangan bulat.

Tarikh perlawanan : 2017-12-31 , 2018-01-31 , 0000-00-00 , 1029-99-72

Tarikh tidak sepadan: 2018-01 , 2018-01-XX , 2020/02/29

3.2. Memadankan Format Tarikh Khusus

Contoh kedua kami menerima julat token tarikh dan juga batasan pemformatan kami. Untuk kesederhanaan, kami telah membataskan minat kami pada tahun 1900 - 2999.

Setelah berjaya memadankan format tarikh umum kami, kami perlu mengatasinya lebih jauh - untuk memastikan tarikhnya betul:

^((19|2[0-9])[0-9]{2})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$

Di sini kami telah memperkenalkan tiga kumpulan julat bilangan bulat yang perlu dipadankan:

  • (19|2[0-9])[0-9]{2}merangkumi jangka masa terhad tahun dengan memadankan nombor yang bermula dengan 19 atau 2X diikuti oleh beberapa digit.
  • 0[1-9]|1[012]sepadan dengan nombor bulan dalam lingkungan 01-12
  • 0[1-9]|[12][0-9]|3[01]sepadan dengan nombor hari dalam julat 01-31

Tarikh perlawanan : 1900-01-01 , 2205-02-31 , 2999-12-31

Tarikh tidak sepadan: 1899-12-31 , 2018-05-35 , 2018-13-05 , 3000-01-01 , 2018-01-XX

3.3. Sepadan dengan 29 Februari

Untuk menyamai tahun lompat dengan betul, kita mesti terlebih dahulu mengenal pasti kapan kita mengalami tahun lompatan , dan kemudian memastikan bahawa kita menerima 29 Februari sebagai tarikh yang sah untuk tahun-tahun tersebut.

Oleh kerana bilangan tahun lompatan dalam jarak terhad kami cukup besar, kami harus menggunakan peraturan pembahagi yang sesuai untuk menyaringnya:

  • Sekiranya nombor yang dibentuk oleh dua digit terakhir dalam nombor boleh dibahagi dengan 4, nombor asal dapat dibahagi dengan 4
  • Sekiranya dua digit terakhir nombor tersebut adalah 00, nombor tersebut dapat dibahagi dengan 100

Inilah penyelesaiannya:

^((2000|2400|2800|(19|2[0-9](0[48]|[2468][048]|[13579][26])))-02-29)$

Corak terdiri daripada bahagian-bahagian berikut:

  • 2000|2400|2800sepadan dengan satu tahun lompat dengan pembahagi 400 dalam jarak terhad 1900-2999
  • 19|2[0-9](0[48]|[2468][048]|[13579][26]))sepadan dengan semua kombinasi senarai putih tahun yang mempunyai pembahagi 4 dan tidak mempunyai pembahagi 100
  • -02-29perlawanan pada 2 Februari

Tarikh perlawanan : 2020-02-29 , 2024-02-29 , 2400-02-29

Tarikh tidak sepadan: 2019-02-29 , 2100-02-29 , 3200-02-29 , 2020/02/29

3.4. Perlawanan Hari Umum Februari

Serta sepadan dengan 29 Februari pada tahun lompat, kami juga perlu menyamai semua hari Februari (1 - 28) yang lain sepanjang tahun :

^(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))$

Tarikh perlawanan : 2018-02-01 , 2019-02-13 , 2020-02-25

Non-matching dates: 2000-02-30, 2400-02-62, 2018/02/28

3.5. Matching 31-Day Months

The months January, March, May, July, August, October, and December should match for between 1 and 31 days:

^(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))$

Matching dates: 2018-01-31, 2021-07-31, 2022-08-31

Non-matching dates: 2018-01-32, 2019-03-64, 2018/01/31

3.6. Matching 30-Day Months

The months April, June, September, and November should match for between 1 and 30 days:

^(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30))$

Matching dates: 2018-04-30, 2019-06-30, 2020-09-30

Non-matching dates: 2018-04-31, 2019-06-31, 2018/04/30

3.7. Gregorian Date Matcher

Now we can combine all of the patterns above into a single matcher to have a complete GregorianDateMatcher satisfying all of the constraints:

class GregorianDateMatcher implements DateMatcher { private static Pattern DATE_PATTERN = Pattern.compile( "^((2000|2400|2800|(19|2[0-9](0[48]|[2468][048]|[13579][26])))-02-29)$" + "|^(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))$" + "|^(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))$" + "|^(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30))$"); @Override public boolean matches(String date) { return DATE_PATTERN.matcher(date).matches(); } }

We've used an alternation character “|” to match at least one of the four branches. Thus, the valid date of February either matches the first branch of February 29th of a leap year either the second branch of any day from 1 to 28. The dates of remaining months match third and fourth branches.

Since we haven't optimized this pattern in favor of a better readability, feel free to experiment with a length of it.

At this moment we have satisfied all the constraints, we introduced in the beginning.

3.8. Note on Performance

Menghuraikan ungkapan biasa yang kompleks dapat mempengaruhi prestasi aliran pelaksanaan dengan ketara. Tujuan utama artikel ini bukanlah untuk mempelajari cara yang efisien untuk menguji rentetan keahliannya dalam satu set semua tarikh yang mungkin.

Pertimbangkan untuk menggunakan LocalDate.parse () yang disediakan oleh Java8 jika diperlukan pendekatan yang pantas dan pantas untuk mengesahkan tarikh.

4. Kesimpulan

Dalam artikel ini, kami telah belajar bagaimana menggunakan ungkapan biasa untuk mencocokkan tarikh kalendar Gregorian yang diformat ketat dengan memberikan peraturan format, rentang dan panjang bulan juga.

Semua kod yang disajikan dalam artikel ini terdapat di Github. Ini adalah projek berasaskan Maven, jadi mudah diimport dan dijalankan sebagaimana adanya.