Month-name errors in High Sierra?

I don’t know if this is a known issue, or if it is unique to High Sierra, but on dates prior to 1884 –
parses for the month name of the first day of the month are consistently misinterpreted.


set someDates to {"January 1, 1885", "January 1, 1884", "January 1, 1883", "January 1, 1882", "January 1, 1881"}

set rprt to return
repeat with eachDate in someDates
	set rprt to rprt & "The month of " & eachDate & " is " & month of (date eachDate) & return
end repeat
return rprt

Result:
"
The month of January 1, 1885 is January
The month of January 1, 1884 is January
The month of January 1, 1883 is December
The month of January 1, 1882 is December
The month of January 1, 1881 is December
"
Notes:
Error does not occur if day number is greater than 1.
Error occurs with each month name.

Model: MacBook Pro
AppleScript: 2.7
Browser: Safari 537.36
Operating System: Mac OS X (10.13 Developer Beta 3)

Yeah. From around 1880 and back, AppleScript date strings are several seconds out with the dates’ other properties. So if you have a date before then which is very close to midnight on December 31/January 1, the date and its string representation could be in different years. It’s the strings that are wrong. The exact amount by which they’re out and the year before which the problem starts apparently depend on which time zone you’re in. It’s been that way for a while now. :frowning:

If you want an early AS date that’s correct, you have to “build” it, although the string representation may still be wrong:

tell (current date) to set {earlyDate, its day, its year, its month, its day, its time} to {it, 1, 1880, January, 1, 0}

The second setting of the day is obviously superfluous here, but I’m following the rigour for this sort of construction, which is to set the day to a low value first and then to set the year, the month, and the day to the required values. This prevents any possible overflow into the following month as the date’s properties are changed.

Nigel,

I wonder if the issue you mention is the same as that the OP is seeing. At least, when I run his script here under 10.12.6, I get January each time – even if I wind the dates back a further 1000 years. The date descriptions all include “at 12:00:00 am”. I even made a virtual trip to London, and still got the same result.

Hi Shane.

I think that’s because we’re seeing a shift in the other direction in our time zones. If I try Mr. Science’s script with the time property instead of the month, I only have to go back to 1847 to see a sudden shift of 75 seconds. Try it with the last day of a month at 23:59:59 instead.

FWIW, I did test after changing to your time zone. Nearly froze to death :wink: I’ll see if I can try under 10.13 here later.

Thanks all. I’m playing with Nigel’s suggested coercion and tried it like this (with odd results):


set y to 1883
set someDates to {{y, 1, 1}, {y, 2, 2}, {y, 3, 3}, {y, 4, 4}, {y, 5, 5}, {y, 6, 6}, {y, 7, 7}, {y, 8, 8}, {y, 9, 9}, {y, 10, 10}, {y, 11, 11}, {y, 12, 12}}

set rprt to return
repeat with eachDate in someDates
	set {yr, mo, dy} to eachDate
	tell (current date) to set {earlyDate, its year, its month, its day, its time} to {it, yr, mo, dy, 0}
	set rprt to rprt & "The month number of " & date string of earlyDate & " is " & mo & return
end repeat
return rprt

The result incorrectly shows February as being March:
"
The month number of Monday, January 1, 1883 is 1
The month number of Friday, March 2, 1883 is 2
The month number of Saturday, March 3, 1883 is 3
The month number of Wednesday, April 4, 1883 is 4
The month number of Saturday, May 5, 1883 is 5
The month number of Wednesday, June 6, 1883 is 6
The month number of Saturday, July 7, 1883 is 7
The month number of Wednesday, August 8, 1883 is 8
The month number of Sunday, September 9, 1883 is 9
The month number of Wednesday, October 10, 1883 is 10
The month number of Sunday, November 11, 1883 is 11
The month number of Wednesday, December 12, 1883 is 12
"

BTW, I think I may have also found another ‘glitch-zone’ up around the year 2101. It’s via a similar script that gets the 12 weekdays from a given year, whose day-of-month is also the month-of-year, then counts weekdays for that year which were omitted. Despite 1-1-1883 being a Monday, It erringly found 1883’s matches to never include a Monday. It also sees several millennia after 2101 as only having 1 omitted weekday per year. I haven’t verified those dates, but the pattern seems statistically improbable.

As I am running the system in French, I had to test :

set someDates to {"1 Janvier 1885", "1 Janvier 1884", "1 Janvier 1883", "1 Janvier 1882", "1 Janvier 1881"}
set rprt to return
repeat with eachDate in someDates
	set rprt to rprt & "The month of " & eachDate & " is " & month of (date eachDate) & return
end repeat
return rprt

I got the correct result : "
The month of 1 Janvier 1885 is January
The month of 1 Janvier 1884 is January
The month of 1 Janvier 1883 is January
The month of 1 Janvier 1882 is January
The month of 1 Janvier 1881 is January
"

Yvan KOENIG running High Sierra 10.13.2 in French (VALLAURIS, France) samedi 30 décembre 2017 11:08:25

To clarify what I understand the situation to be:

AppleScript date objects are a hang-over from simpler days. They operate a strictly proleptic Gregorian calendar with no regard for time zone, daylight savings, leap seconds, or other adjustments. But within these limits, they’re mathematically very reliable:

set fourHundredYears to (365 * 400 + 97) * days
set twoThousandYears to fourHundredYears * 5

set now to (current date)
set twoThousandYearsAgo to now - twoThousandYears
return {year, month, day, hours, minutes, seconds} of now & linefeed & {year, month, day, hours, minutes, seconds} of twoThousandYearsAgo

The only things to remember with very early dates is that the ‘year’ of 1 BC is 0, that of 2 BC is -1, etc. and that negative years can’t be set directly.

Applications and macOS itself use another date system which does take into account time zones and daylight savings regimes. It’s believed that AppleScript dates will eventually be changed to use this, but it would undoubtedly break many existing scripts.

There’s also a formatting system which converts dates expressed as strings into AppleScript dates and vice versa using the date and time format preferences set on the user’s own computer. (System Preferences → Language & Region → Advanced… → ‘Dates’ and ‘Times’.) It’s this which seems not to be delivering the goods properly with dates some distance removed from the present. Previous discussions with Shane and Yvan about this have revealed that the degree of error and the cut-in dates for it occurring depend on whereabouts in the world you are. This suggests that time zone plays a part in the error, although I suspect that the date and time formats being converted may also have an effect. I haven’t looked into it that deeply.

Bringing the string formatter into play in the script above:

set fourHundredYears to (365 * 400 + 97) * days
set twoThousandYears to fourHundredYears * 5

set now to (current date)
set twoThousandYearsAgo to now - twoThousandYears
return {year, month, day, hours, minutes, seconds, date string, time string} of now & linefeed & {year, month, day, hours, minutes, seconds, date string, time string} of twoThousandYearsAgo

The (date eachDate) in Mr. Science’s first script should produce an AppleScript date whose date is the one suggested by the string and whose time is 0 (since the time isn’t specified in the string). But when the year in the string is 1883 or earlier, the AppleScript date actually produced on his machine is a few seconds before that — ie. some during the previous day: December 31 1882.

The reason Mr. Science is getting the wrong month for February with his second script (post #6) is that he’s missed my earlier point about setting the day to 1 before setting anything else. Running the script today (30th December 2017) gets a current date whose year, month, and day are respectively 2017, December, and 30. Setting the year first to 1883 simply changes the date to represent December 30 1883. But then setting the month to 2 (or February) changes the date to February 30 1883. Since there were only 28 days in February that year, the date overflows and becomes March 2 1883. Setting the day to 2 after that simply keeps the date as March 2. When using current date as a basis for building dates, you should set the day to 1 first (or to any day which exists in every month) so that such overflows can’t occur.


set y to 1883
set someDates to {{y, 1, 1}, {y, 2, 2}, {y, 3, 3}, {y, 4, 4}, {y, 5, 5}, {y, 6, 6}, {y, 7, 7}, {y, 8, 8}, {y, 9, 9}, {y, 10, 10}, {y, 11, 11}, {y, 12, 12}}

set rprt to return
repeat with eachDate in someDates
	set {yr, mo, dy} to eachDate
	tell (current date) to set {earlyDate, its day, its year, its month, its day, its time} to {it, 1, yr, mo, dy, 0}
	set rprt to rprt & "The month number of " & date string of earlyDate & " is " & mo & return
end repeat
return rprt

This is so far giving me two omitted weekdays in every year except for leap years, when there are three:

set y to 9996

set weekdayCounts to {0, 0, 0, 0, 0, 0, 0}
-- Only one date object's needed and its day won't be set higher than 12 by the script, so get it and initialise its day to 1 now, before the repeat.
tell (current date) to set {asDate, its day} to {it, 1}
repeat with i from 1 to 12
	tell asDate
		set {its year, its month, its day} to {y, i, i}
		-- The weekday must be read _after_ the mass setting above.
		set w to its weekday as integer
	end tell
	set item w of weekdayCounts to (item w of weekdayCounts) + 1
end repeat

-- To count the omitted weekdays:
set numberOfOmittedWeekdays to 0
repeat with i from 1 to 7
	if (item i of weekdayCounts is 0) then set numberOfOmittedWeekdays to numberOfOmittedWeekdays + 1
end repeat
return numberOfOmittedWeekdays

(*
-- Or to return the omitted weekdays.
set allWeekdays to {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
set omittedWeekdays to {}
repeat with i from 1 to 7
	if (item i of weekdayCounts is 0) then set end of omittedWeekdays to item i of allWeekdays
end repeat
return omittedWeekdays
*)

Indeed. Mr. Science is in Florida, and GMT was adopted by the USA in November 1883.

The date string represents the time that would have been showing on a theoretical clock at however many seconds from the reference date the time represents, ignoring DST changes. So the date of introduction of GMT matters, as does the before-and-after difference in GMT offset.

Of course, once the script’s been run a few times and it’s noticed that in non-leap years, the omitted weekdays are always those of January 2 and 4, and that in leap years they’re always those of January 2, 3, and 6, the code can be reduced to this:

set y to 9996

-- Get the weekday number of January 2 in the given year.
tell (current date)
	set {its day, its year, its month} to {2, y, 1}
	set Jan2Weekday to its weekday as integer
end tell

-- Get the weekday names corresponding to that number and the appropriate number of days later, using modulo arithmetic to index names of days possibly in the following week.
set allWeekdays to {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
if ((y mod 4 > 0) or ((y mod 100 is 0) and (y mod 400 > 0))) then
	-- Non-leap year. Weekdays of January 2 and January 4.
	set omittedWeekdays to {item Jan2Weekday of allWeekdays, item ((Jan2Weekday + 1) mod 7 + 1) of allWeekdays}
else
	-- Leap year. Weekdays of January 2, January 3 (or January 10), and January 6.
	set omittedWeekdays to {item Jan2Weekday of allWeekdays, item ((Jan2Weekday + 7) mod 7 + 1) of allWeekdays, item ((Jan2Weekday + 3) mod 7 + 1) of allWeekdays}
end if
return omittedWeekdays

Awesome! Thank you, everyone. Had this been navigation, I’d be lost at sea. And Nigel, you are correct that I missed your original point! – I am to understand that my machine needs an appropriate reference point for it’s cascade of measurements or it will skip & drift thru the timeline based on several factors including location. I’d imagined timing to be simple arithmetic for any date after English Parliaments switch from the Julian to the Gregorian Calendar in 1752.

It does sound complicated. :slight_smile: But basically, AppleScript dates are absolutely fine provided you want a proleptic Gregorian system without time zones or daylight savings and avoid the use of strings. It’s deriving dates from strings and vice versa using the inbuilt methods which can be a problem with distant dates. Anything like these:

date someString

someDate's date string
someDate's short date string
someDate's time string
someDate as text

In practice, you’d want to avoid them anyway if you’re writing scripts for general distribution because the date and time string formats depend on the individual users’ preference settings. Shane, Yvan, and I can’t run your script in post #1 as it is because our date format preferences are set for a day-month-year order. Similarly, it’s probably that you use a 12-hour time format, which would be incompatible with our preferred 24-hour formats if time strings were involved.

Nigel, I truly appreciate your attention to detail. I certainly shouldn’t expect such scripts to function universally. It began as a completely different scripting session, but several rabbit-holes later I thought it curious enough to post.

You may find the initial goal interesting; after reading about lava-lamps being used as a source of randomized-info for encryption purposes, I became curious as to how much random data I could generate from just a date string. For example, “Tuesday” has seven characters which gives me an easily harvested 7 to work with, “October” also yields a 7 so their sameness can provide a true (7=7) which converts to a binary 1 or 0, etc…

I wondered how many “set this to that” variables I could arrive at before weaving them together with “if thens” or “set their_interaction to some item of {P,E,M,D,A,S}”.

Of course, it’s very easy to achieve beachballs & stack overflows that way, and not nearly as visual as lava-lamps.

I suppose a random geographic variable could be had via …

set FloridaMac to (December1883 = January1884)

:lol: