-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
See details in the `SasDateFormat.sasLeapDaysFix`
- Loading branch information
1 parent
70f5877
commit e059517
Showing
9 changed files
with
244 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package com.epam.parso.impl; | ||
|
||
import static com.epam.parso.impl.SasFileConstants.SECONDS_IN_DAY; | ||
|
||
/** | ||
* SAS supports wide family of date formats. | ||
* It is reasonable to keep all SAS date related features separately. | ||
* See more about SAS dates: | ||
* - https://v8doc.sas.com/sashtml/lrcon/zenid-63.htm | ||
* - https://v8doc.sas.com/sashtml/lgref/z0197923.htm | ||
* - https://v8doc.sas.com/sashtml/ets/chap2/sect7.htm | ||
*/ | ||
final class SasDateFormat { | ||
/** | ||
* Private constructor for utility class. | ||
*/ | ||
private SasDateFormat() { | ||
} | ||
|
||
/** | ||
* First time when a leap day is removed from the SAS calendar. | ||
* In seconds since 1960-01-01 | ||
*/ | ||
private static final double SAS_SECONDS_29FEB4000 = 64381305600D; | ||
|
||
/** | ||
* Second time when a leap day is removed from the SAS calendar. | ||
* In seconds since 1960-01-01 | ||
*/ | ||
private static final double SAS_SECONDS_29FEB8000 = 190609027200D; | ||
|
||
/** | ||
* SAS removes leap day every 4000 year. | ||
* It removes these days: | ||
* - 29FEB4000 | ||
* - 29FEB8000 | ||
* This guy proposed such approach many years ago: https://en.wikipedia.org/wiki/John_Herschel | ||
* <p> | ||
* Sometimes people discussed why SAS dates are so strange: | ||
* - https://blogs.sas.com/content/sasdummy/2010/04/05/in-the-year-9999/ | ||
* - https://communities.sas.com/t5/SAS-Programming/Leap-Years-divisible-by-4000/td-p/663467 | ||
* <p> | ||
* See the SAS program and its output: | ||
* ```shell | ||
* data test; | ||
* dtime = '28FEB4000:00:00:00'dt; | ||
* put dtime; *out: 64381219200 | ||
* <p> | ||
* dtime = '29FEB4000:00:00:00'dt; | ||
* put dtime; *err: ERROR: Invalid date/time/datetime constant '29FEB4000:00:00:00'dt. | ||
* <p> | ||
* dtime = '01MAR4000:00:00:00'dt; | ||
* put dtime; *out: 64381305600 | ||
* <p> | ||
* dtime = '31DEC4000:00:00:00'dt; | ||
* put dtime; *out: 64407657600 | ||
* <p> | ||
* dtime = '28FEB8000:00:00:00'dt; | ||
* put dtime; *out: 190608940800 | ||
* <p> | ||
* dtime = '29FEB8000:00:00:00'dt; | ||
* put dtime; *err: ERROR: Invalid date/time/datetime constant '29FEB8000:00:00:00'dt. | ||
* <p> | ||
* dtime = '01MAR8000:00:00:00'dt; | ||
* put dtime; * out: 190609027200 | ||
* <p> | ||
* dtime = '31DEC8000:00:00:00'dt; | ||
* put dtime; *out: 190635379200 | ||
* <p> | ||
* dtime = '31DEC9999:00:00:00'dt; | ||
* put dtime; *out: 253717660800 | ||
* run; | ||
* ``` | ||
* As you can see SAS doesn't accept leap days for 4000 and 8000 years | ||
* and removes these days at all from the SAS calendar. | ||
* <p> | ||
* At the same time these leap days are ok for: | ||
* - Java: `LocalDateTime.of(4000, 2, 29, 0, 0).toEpochSecond(ZoneOffset.UTC)` | ||
* outputs 64065686400 | ||
* - JavaScript: `Date.parse('4000-02-29')` | ||
* outputs 64065686400000 | ||
* - GNU/date: `date --utc --date '4000-02-29' +%s` | ||
* outputs 64065686400 | ||
* and so on. | ||
* <p> | ||
* So, in order to parse SAS dates correctly, | ||
* we need to restore removed leap days | ||
* | ||
* @param sasSeconds SAS date representation in seconds since 1960-01-01 | ||
* @return seconds with restored leap days | ||
*/ | ||
public static double sasLeapDaysFix(double sasSeconds) { | ||
if (sasSeconds >= SAS_SECONDS_29FEB4000) { | ||
if (sasSeconds >= SAS_SECONDS_29FEB8000) { | ||
sasSeconds += SECONDS_IN_DAY; //restore Y8K leap day | ||
} | ||
sasSeconds += SECONDS_IN_DAY; //restore Y4K leap day | ||
} | ||
return sasSeconds; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
d, dt | ||
28Feb2000,28Feb2000:00:00:00.00 | ||
29Feb2000,29Feb2000:00:00:00.00 | ||
01Mar2000,01Mar2000:00:00:00.00 | ||
31Dec2000,31Dec2000:00:00:00.00 | ||
28Feb4000,28Feb4000:00:00:00.00 | ||
01Mar4000,01Mar4000:00:00:00.00 | ||
31Dec4000,31Dec4000:00:00:00.00 | ||
28Feb6000,28Feb6000:00:00:00.00 | ||
29Feb6000,29Feb6000:00:00:00.00 | ||
01Mar6000,01Mar6000:00:00:00.00 | ||
31Dec6000,31Dec6000:00:00:00.00 | ||
28Feb8000,28Feb8000:00:00:00.00 | ||
01Mar8000,01Mar8000:00:00:00.00 | ||
31Dec8000,31Dec8000:00:00:00.00 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
Number,Name,Type,Data length,Format,Label | ||
1,d,Numeric,8,DATE9., | ||
2,dt,Numeric,8,DATETIME20., | ||
Bitness: x64 | ||
Compressed: null | ||
Endianness: LITTLE_ENDIANNESS | ||
Encoding: ISO-8859-1 | ||
Name: DATES_LEAP_DAYS | ||
File type: DATA | ||
File label: Leap days dataset | ||
Date created: Fri Jan 01 13:53:59 MSK 2021 | ||
Date modified: Fri Jan 01 13:53:59 MSK 2021 | ||
SAS release: 9.0401M5 | ||
SAS server type: Linux | ||
OS name: x86_64 | ||
OS type: 3.10.0-1160.2.1. | ||
Header Length: 4096 | ||
Page Length: 4096 | ||
Page Count: 1 | ||
Row Length: 16 | ||
Row Count: 14 | ||
Mix Page Row Count: 124 | ||
Columns Count: 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* | ||
SAS program to generate sas7bdat file with two types of columns: date and datetime. | ||
Both columns contain data around leap days. | ||
Years 4000 and 8000 don't have leap days in terms of SAS. | ||
Years 2000 and 6000 have it. | ||
All of them necessary for unit tests. | ||
*/ | ||
|
||
options bufsize=4096 pagesize=15; | ||
|
||
data dev.dates_leap_days(label='Leap days dataset'); | ||
format d date9.; | ||
format dt datetime20.; | ||
|
||
d='28FEB2000'd; | ||
dt='28FEB2000:00:00:00'dt; | ||
output; | ||
d='29FEB2000'd; | ||
dt='29FEB2000:00:00:00'dt; | ||
output; | ||
d='01MAR2000'd; | ||
dt='01MAR2000:00:00:00'dt; | ||
output; | ||
d='31DEC2000'd; | ||
dt='31DEC2000:00:00:00'dt; | ||
output; | ||
d='28FEB4000'd; | ||
dt='28FEB4000:00:00:00'dt; | ||
output; | ||
d='01MAR4000'd; | ||
dt='01MAR4000:00:00:00'dt; | ||
output; | ||
d='31DEC4000'd; | ||
dt='31DEC4000:00:00:00'dt; | ||
output; | ||
d='28FEB6000'd; | ||
dt='28FEB6000:00:00:00'dt; | ||
output; | ||
d='29FEB6000'd; | ||
dt='29FEB6000:00:00:00'dt; | ||
output; | ||
d='01MAR6000'd; | ||
dt='01MAR6000:00:00:00'dt; | ||
output; | ||
d='31DEC6000'd; | ||
dt='31DEC6000:00:00:00'dt; | ||
output; | ||
d='28FEB8000'd; | ||
dt='28FEB8000:00:00:00'dt; | ||
output; | ||
d='01MAR8000'd; | ||
dt='01MAR8000:00:00:00'dt; | ||
output; | ||
d='31DEC8000'd; | ||
dt='31DEC8000:00:00:00'dt; | ||
output; | ||
run; |
Binary file not shown.